Compare commits

..

No commits in common. "892db62df208a0a4a6dc98ad4e0d39d92c3e98c4" and "c081358a95d95bf586cafa5c4181a01d30f58d33" have entirely different histories.

96 changed files with 3987 additions and 3920 deletions

View File

@ -1,271 +0,0 @@
# Augment Code Review Guidelines for BMAD-METHOD
# https://docs.augmentcode.com/codereview/overview
# Focus: Workflow validation and quality
file_paths_to_ignore:
# --- Shared baseline: tool configs ---
- ".coderabbit.yaml"
- ".augment/**"
- "eslint.config.mjs"
# --- Shared baseline: build output ---
- "dist/**"
- "build/**"
- "coverage/**"
# --- Shared baseline: vendored/generated ---
- "node_modules/**"
- "**/*.min.js"
- "**/*.generated.*"
- "**/*.bundle.md"
# --- Shared baseline: package metadata ---
- "package-lock.json"
# --- Shared baseline: binary/media ---
- "*.png"
- "*.jpg"
- "*.svg"
# --- Shared baseline: test fixtures ---
- "test/fixtures/**"
- "test/template-test-generator/**"
- "tools/template-test-generator/test-scenarios/**"
# --- Shared baseline: non-project dirs ---
- "_bmad*/**"
- "website/**"
- "z*/**"
- "sample-project/**"
- "test-project-install/**"
# --- Shared baseline: AI assistant dirs ---
- ".claude/**"
- ".codex/**"
- ".agent/**"
- ".agentvibes/**"
- ".kiro/**"
- ".roo/**"
- ".github/chatmodes/**"
# --- Shared baseline: build temp ---
- ".bundler-temp/**"
# --- Shared baseline: generated reports ---
- "**/validation-report-*.md"
- "CHANGELOG.md"
areas:
# ============================================
# WORKFLOW STRUCTURE RULES
# ============================================
workflow_structure:
description: "Workflow folder organization and required components"
globs:
- "src/**/workflows/**"
rules:
- id: "workflow_entry_point_required"
description: "Every workflow folder must have workflow.yaml, workflow.md, or workflow.xml as entry point"
severity: "high"
- id: "sharded_workflow_steps_folder"
description: "Sharded workflows (using workflow.md) must have steps/ folder with numbered files (step-01-*.md, step-02-*.md)"
severity: "high"
- id: "standard_workflow_instructions"
description: "Standard workflows using workflow.yaml must include instructions.md for execution guidance"
severity: "medium"
- id: "workflow_step_limit"
description: "Workflows should have 5-10 steps maximum to prevent context loss in LLM execution"
severity: "medium"
# ============================================
# WORKFLOW ENTRY FILE RULES
# ============================================
workflow_definitions:
description: "Workflow entry files (workflow.yaml, workflow.md, workflow.xml)"
globs:
- "src/**/workflows/**/workflow.yaml"
- "src/**/workflows/**/workflow.md"
- "src/**/workflows/**/workflow.xml"
rules:
- id: "workflow_name_required"
description: "Workflow entry files must define 'name' field in frontmatter or root element"
severity: "high"
- id: "workflow_description_required"
description: "Workflow entry files must include 'description' explaining the workflow's purpose"
severity: "high"
- id: "workflow_config_source"
description: "Workflows should reference config_source for variable resolution (e.g., {project-root}/_bmad/module/config.yaml)"
severity: "medium"
- id: "workflow_installed_path"
description: "Workflows should define installed_path for relative file references within the workflow"
severity: "medium"
- id: "valid_step_references"
description: "Step file references in workflow entry must point to existing files"
severity: "high"
# ============================================
# SHARDED WORKFLOW STEP RULES
# ============================================
workflow_steps:
description: "Individual step files in sharded workflows"
globs:
- "src/**/workflows/**/steps/step-*.md"
rules:
- id: "step_goal_required"
description: "Each step must clearly state its goal (## STEP GOAL, ## YOUR TASK, or step n='X' goal='...')"
severity: "high"
- id: "step_mandatory_rules"
description: "Step files should include MANDATORY EXECUTION RULES section with universal agent behavior rules"
severity: "medium"
- id: "step_context_boundaries"
description: "Step files should define CONTEXT BOUNDARIES explaining available context and limits"
severity: "medium"
- id: "step_success_metrics"
description: "Step files should include SUCCESS METRICS section with ✅ checkmarks for validation criteria"
severity: "medium"
- id: "step_failure_modes"
description: "Step files should include FAILURE MODES section with ❌ marks for anti-patterns to avoid"
severity: "medium"
- id: "step_next_step_reference"
description: "Step files should reference the next step file path for sequential execution"
severity: "medium"
- id: "step_no_forward_loading"
description: "Steps must NOT load future step files until current step completes - just-in-time loading only"
severity: "high"
- id: "valid_file_references"
description: "File path references using {variable}/filename.md must point to existing files"
severity: "high"
- id: "step_naming"
description: "Step files must be named step-NN-description.md (e.g., step-01-init.md, step-02-context.md)"
severity: "medium"
- id: "halt_before_menu"
description: "Steps presenting user menus ([C] Continue, [a] Advanced, etc.) must HALT and wait for response"
severity: "high"
# ============================================
# XML WORKFLOW/TASK RULES
# ============================================
xml_workflows:
description: "XML-based workflows and tasks"
globs:
- "src/**/workflows/**/*.xml"
- "src/**/tasks/**/*.xml"
rules:
- id: "xml_task_id_required"
description: "XML tasks must have unique 'id' attribute on root task element"
severity: "high"
- id: "xml_llm_instructions"
description: "XML workflows should include <llm> section with critical execution instructions for the agent"
severity: "medium"
- id: "xml_step_numbering"
description: "XML steps should use n='X' attribute for sequential numbering"
severity: "medium"
- id: "xml_action_tags"
description: "Use <action> for required actions, <ask> for user input (must HALT), <goto> for jumps, <check if='...'> for conditionals"
severity: "medium"
- id: "xml_ask_must_halt"
description: "<ask> tags require agent to HALT and wait for user response before continuing"
severity: "high"
# ============================================
# WORKFLOW CONTENT QUALITY
# ============================================
workflow_content:
description: "Content quality and consistency rules for all workflow files"
globs:
- "src/**/workflows/**/*.md"
- "src/**/workflows/**/*.yaml"
rules:
- id: "communication_language_variable"
description: "Workflows should use {communication_language} variable for agent output language consistency"
severity: "low"
- id: "path_placeholders_required"
description: "Use path placeholders (e.g. {project-root}, {installed_path}, {output_folder}) instead of hardcoded paths"
severity: "medium"
- id: "no_time_estimates"
description: "Workflows should NOT include time estimates - AI development speed varies significantly"
severity: "low"
- id: "facilitator_not_generator"
description: "Workflow agents should act as facilitators (guide user input) not content generators (create without input)"
severity: "medium"
- id: "no_skip_optimization"
description: "Workflows must execute steps sequentially - no skipping or 'optimizing' step order"
severity: "high"
# ============================================
# AGENT DEFINITIONS
# ============================================
agent_definitions:
description: "Agent YAML configuration files"
globs:
- "src/**/*.agent.yaml"
rules:
- id: "agent_metadata_required"
description: "Agent files must have metadata section with id, name, title, icon, and module"
severity: "high"
- id: "agent_persona_required"
description: "Agent files must define persona with role, identity, communication_style, and principles"
severity: "high"
- id: "agent_menu_valid_workflows"
description: "Menu triggers must reference valid workflow paths that exist"
severity: "high"
# ============================================
# TEMPLATES
# ============================================
templates:
description: "Template files for workflow outputs"
globs:
- "src/**/template*.md"
- "src/**/templates/**/*.md"
rules:
- id: "placeholder_syntax"
description: "Use {variable_name} or {{variable_name}} syntax consistently for placeholders"
severity: "medium"
- id: "template_sections_marked"
description: "Template sections that need generation should be clearly marked (e.g., <!-- GENERATE: section_name -->)"
severity: "low"
# ============================================
# DOCUMENTATION
# ============================================
documentation:
description: "Documentation files"
globs:
- "docs/**/*.md"
- "README.md"
- "CONTRIBUTING.md"
rules:
- id: "valid_internal_links"
description: "Internal markdown links must point to existing files"
severity: "medium"
# ============================================
# BUILD TOOLS
# ============================================
build_tools:
description: "Build scripts and tooling"
globs:
- "tools/**"
rules:
- id: "script_error_handling"
description: "Scripts should handle errors gracefully with proper exit codes"
severity: "medium"

Binary file not shown.

View File

@ -17,66 +17,21 @@ reviews:
base_branches:
- main
path_filters:
# --- Shared baseline: tool configs ---
- "!.coderabbit.yaml"
- "!.augment/**"
- "!eslint.config.mjs"
# --- Shared baseline: build output ---
- "!dist/**"
- "!build/**"
- "!coverage/**"
# --- Shared baseline: vendored/generated ---
- "!**/node_modules/**"
- "!**/*.min.js"
- "!**/*.generated.*"
- "!**/*.bundle.md"
# --- Shared baseline: package metadata ---
- "!package-lock.json"
# --- Shared baseline: binary/media ---
- "!*.png"
- "!*.jpg"
- "!*.svg"
# --- Shared baseline: test fixtures ---
- "!test/fixtures/**"
- "!test/template-test-generator/**"
- "!tools/template-test-generator/test-scenarios/**"
# --- Shared baseline: non-project dirs ---
- "!_bmad*/**"
- "!website/**"
- "!z*/**"
- "!sample-project/**"
- "!test-project-install/**"
# --- Shared baseline: AI assistant dirs ---
- "!.claude/**"
- "!.codex/**"
- "!.agent/**"
- "!.agentvibes/**"
- "!.kiro/**"
- "!.roo/**"
- "!.github/chatmodes/**"
# --- Shared baseline: build temp ---
- "!.bundler-temp/**"
# --- Shared baseline: generated reports ---
- "!**/validation-report-*.md"
- "!CHANGELOG.md"
path_instructions:
- path: "**/*"
instructions: |
You are a cynical, jaded reviewer with zero patience for sloppy work.
This PR was submitted by a clueless weasel and you expect to find problems.
Be skeptical of everything.
Look for what's missing, not just what's wrong.
Use a precise, professional tone — no profanity or personal attacks.
Review with extreme skepticism — assume problems exist.
Find at least 10 issues to fix or improve.
Do NOT:
- Comment on formatting, linting, or style
- Give "looks good" passes
- Anchor on any specific ruleset — reason freely
If you find zero issues, re-analyze — this is suspicious.
Focus on inconsistencies, contradictions, edge cases and serious issues.
Avoid commenting on minor issues such as linting, formatting and style issues.
When providing code suggestions, use GitHub's suggestion format:
```suggestion
<code changes>
```
- path: "**/*.js"
instructions: |
CLI tooling code. Check for: missing error handling on fs operations,
path.join vs string concatenation, proper cleanup in error paths.
Flag any process.exit() without error message.
chat:
auto_reply: true # Response to mentions in comments, a la @coderabbit review
issue_enrichment:

View File

@ -1,7 +1,7 @@
name: Trigger CodeRabbit on Ready for Review
on:
pull_request_target:
pull_request:
types: [ready_for_review]
jobs:

View File

@ -6,6 +6,7 @@ on:
- main
paths:
- "docs/**"
- "src/modules/*/docs/**"
- "website/**"
- "tools/build-docs.mjs"
- ".github/workflows/docs.yaml"
@ -18,7 +19,6 @@ permissions:
concurrency:
group: "pages"
# No big win in setting this to true — risk of cancelling a deploy mid-flight.
cancel-in-progress: false
jobs:
@ -28,13 +28,12 @@ jobs:
- name: Checkout repository
uses: actions/checkout@v4
with:
# Full history needed for Starlight's lastUpdated timestamps (git log)
fetch-depth: 0
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version-file: ".nvmrc"
node-version: "20"
cache: "npm"
- name: Install dependencies

View File

@ -84,8 +84,10 @@ jobs:
- name: Install dependencies
run: npm ci
- name: Validate documentation links
run: npm run docs:validate-links
- name: Build documentation
# Note: build-docs.mjs runs link validation internally before building
run: npm run docs:build
validate:

4
.gitignore vendored
View File

@ -42,9 +42,7 @@ z*/
_bmad
_bmad-output
.clinerules
# .augment/ is gitignored except tracked config files — add exceptions explicitly
.augment/*
!.augment/code_review_guidelines.yaml
.augment
.crush
.cursor
.iflow

View File

@ -1,58 +1,5 @@
# Changelog
## [6.0.0-Beta.8]
**Release: February 8, 2026**
### 🌟 Key Highlights
1. **Non-Interactive Installation** — Full CI/CD support with 10 new CLI flags for automated deployments
2. **Complete @clack/prompts Migration** — Unified CLI experience with consolidated installer output
3. **CSV File Reference Validation** — Extended Layer 1 validator to catch broken workflow references in CSV files
4. **Kiro IDE Support** — Standardized config-driven installation, replacing custom installer
### 🎁 Features
* **Non-Interactive Installation** — Added `--directory`, `--modules`, `--tools`, `--custom-content`, `--user-name`, `--communication-language`, `--document-output-language`, `--output-folder`, and `-y/--yes` flags for CI/CD automation (#1520)
* **CSV File Reference Validation** — Extended validator to scan `.csv` files for broken workflow references, checking 501 references across 212 files (#1573)
* **Kiro IDE Support** — Replaced broken custom installer with config-driven templates using `#[[file:...]]` syntax and `inclusion: manual` frontmatter (#1589)
* **OpenCode Template Consolidation** — Combined split templates with `mode: primary` frontmatter for Tab-switching support, fixing agent discovery (#1556)
* **Modules Reference Page** — Added official external modules reference documentation (#1540)
### 🐛 Bug Fixes
* **Installer Streamlining** — Removed "None - Skip module installation" option, eliminated ~100 lines of dead code, and added ESM/.cjs support for module installers (#1590)
* **CodeRabbit Workflow** — Changed `pull_request` to `pull_request_target` to fix 403 errors and enable reviews on fork PRs (#1583)
* **Party Mode Return Protocol** — Added RETURN PROTOCOL to prevent lost-in-the-middle failures after Party Mode completes (#1569)
* **Spacebar Toggle** — Fixed SPACE key not working in autocomplete multiselect prompts for tool/IDE selection (#1557)
* **OpenCode Agent Routing** — Fixed agents installing to wrong directory by adding `targets` array for routing `.opencode/agent/` vs `.opencode/command/` (#1549)
* **Technical Research Workflow** — Fixed step-05 routing to step-06 and corrected `stepsCompleted` values (#1547)
* **Forbidden Variable Removal** — Removed `workflow_path` variable from 16 workflow step files (#1546)
* **Kilo Installer** — Fixed YAML formatting issues by trimming activation header and converting to yaml.parse/stringify (#1537)
* **bmad-help** — Now reads project-specific docs and respects `communication_language` setting (#1535)
* **Cache Errors** — Removed `--prefer-offline` npm flag to prevent stale cache errors during installation (#1531)
### ♻️ Refactoring
* **Complete @clack/prompts Migration** — Migrated 24 files from legacy libraries (ora, chalk, boxen, figlet, etc.), replaced ~100 console.log+chalk calls, consolidated installer output to single spinner, and removed 5 dependencies (#1586)
* **Downloads Page Removal** — Removed downloads page, bundle generation, and archiver dependency in favor of GitHub's native archives (#1577)
* **Workflow Verb Standardization** — Replaced "invoke/run" with "load and follow/load" in review workflow prompts (#1570)
* **Documentation Language** — Renamed "brownfield" to "established projects" and flattened directory structure for accessibility (#1539)
### 📚 Documentation
* **Comprehensive Site Review** — Fixed broken directory tree diagram, corrected grammar/capitalization, added SEO descriptions, and reordered how-to guides (#1578)
* **SEO Metadata** — Added description front matter to 9 documentation pages for search engine optimization (#1566)
* **PR Template** — Added pull request template for consistent PR descriptions (#1554)
* **Manual Release Cleanup** — Removed broken manual-release workflow and related scripts (#1576)
### 🔧 Maintenance
* **Dual-Mode AI Code Review** — Configured Augment Code (audit mode) and CodeRabbit (adversarial mode) for improved code quality (#1511)
* **Package-Lock Sync** — Cleaned up 471 lines of orphaned dependencies after archiver removal (#1580)
---
## [6.0.0-Beta.7]
**Release: February 4, 2026**

View File

@ -147,15 +147,6 @@ Keep messages under 72 characters. Each commit = one logical change.
- Everything is natural language (markdown) — no code in core framework
- Use BMad modules for domain-specific features
- Validate YAML schemas: `npm run validate:schemas`
- Validate file references: `npm run validate:refs`
### File-Pattern-to-Validator Mapping
| File Pattern | Validator | Extraction Function |
| ------------ | --------- | ------------------- |
| `*.yaml`, `*.yml` | `validate-file-refs.js` | `extractYamlRefs` |
| `*.md`, `*.xml` | `validate-file-refs.js` | `extractMarkdownRefs` |
| `*.csv` | `validate-file-refs.js` | `extractCsvRefs` |
---

View File

@ -36,7 +36,7 @@ Follow the installer prompts, then open your AI IDE (Claude Code, Cursor, Windsu
npx bmad-method install --directory /path/to/project --modules bmm --tools claude-code --yes
```
See [Non-Interactive Installation Guide](http://docs.bmad-method.org/how-to/non-interactive-installation/) for all available options.
See [Non-Interactive Installation Guide](docs/non-interactive-installation.md) for all available options.
> **Not sure what to do?** Run `/bmad-help` — it tells you exactly what's next and what's optional. You can also ask it questions like:

View File

@ -6,4 +6,4 @@ template: splash
The page you're looking for doesn't exist or has been moved.
[Return to Home](./index.md)
[Return to Home](/docs/index.md)

View File

@ -1,6 +1,5 @@
---
title: "Documentation Style Guide"
description: Project-specific documentation conventions based on Google style and Diataxis structure
---
This project adheres to the [Google Developer Documentation Style Guide](https://developers.google.com/style) and uses [Diataxis](https://diataxis.fr/) to structure content. Only project-specific conventions follow.

BIN
docs/bmgd/bmgd-logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 126 KiB

375
docs/bmgd/game-types.md Normal file
View File

@ -0,0 +1,375 @@
---
title: "Game Types Reference"
draft: true
---
BMGD supports 24 game type templates. Each adds genre-specific sections to your GDD.
## Game Types
### Action & Combat
#### Action Platformer
Side-scrolling or 3D platforming with combat mechanics.
**Examples:** Hollow Knight, Mega Man, Celeste
**GDD sections:**
- Movement systems (jumps, dashes, wall mechanics)
- Combat mechanics (melee/ranged, combos)
- Level design patterns
- Boss design
#### Shooter
Projectile combat with aiming mechanics.
**Examples:** Doom, Call of Duty, Splatoon
**GDD sections:**
- Weapon systems
- Aiming and accuracy
- Enemy AI patterns
- Level/arena design
- Multiplayer considerations
#### Fighting
1v1 combat with combos and frame data.
**Examples:** Street Fighter, Tekken, Super Smash Bros.
**GDD sections:**
- Frame data systems
- Combo mechanics
- Character movesets
- Competitive balance
- Netcode requirements
### Strategy & Tactics
#### Strategy
Resource management with tactical decisions.
**Examples:** StarCraft, Civilization, Europa Universalis
**GDD sections:**
- Resource systems
- Unit/building design
- AI opponent behavior
- Map/scenario design
- Victory conditions
#### Turn-Based Tactics
Grid-based movement with turn order.
**Examples:** XCOM, Fire Emblem, Into the Breach
**GDD sections:**
- Grid and movement systems
- Turn order mechanics
- Cover and positioning
- Unit progression
- Procedural mission generation
#### Tower Defense
Wave-based defense with tower placement.
**Examples:** Bloons TD, Kingdom Rush, Plants vs. Zombies
**GDD sections:**
- Tower types and upgrades
- Wave design and pacing
- Economy systems
- Map design patterns
- Meta-progression
### RPG & Progression
#### RPG
Character progression with stats, inventory, and quests.
**Examples:** Final Fantasy, The Witcher, Baldur's Gate
**GDD sections:**
- Character stats and leveling
- Inventory and equipment
- Quest system design
- Combat system (action/turn-based)
- Skill trees and builds
#### Roguelike
Procedural generation with permadeath and run-based progression.
**Examples:** Hades, Dead Cells, Spelunky
**GDD sections:**
- Procedural generation rules
- Permadeath and persistence
- Run structure and pacing
- Item/ability synergies
- Meta-progression systems
#### Metroidvania
Interconnected world with ability gating.
**Examples:** Metroid, Castlevania: Symphony of the Night, Ori
**GDD sections:**
- World map connectivity
- Ability gating design
- Backtracking flow
- Secret and collectible placement
- Power-up progression
### Narrative & Story
#### Adventure
Story-driven exploration with puzzle elements.
**Examples:** Monkey Island, Myst, Life is Strange
**GDD sections:**
- Puzzle design
- Narrative delivery
- Exploration mechanics
- Dialogue systems
- Story branching
#### Visual Novel
Narrative choices with branching story.
**Examples:** Doki Doki Literature Club, Phoenix Wright, Steins;Gate
**GDD sections:**
- Branching narrative structure
- Choice and consequence
- Character routes
- UI/presentation
- Save/load states
#### Text-Based
Text input/output games with parser or choice mechanics.
**Examples:** Zork, 80 Days, Dwarf Fortress (adventure mode)
**GDD sections:**
- Parser or choice systems
- World model
- Narrative structure
- Text presentation
- Save state management
### Simulation & Management
#### Simulation
Realistic systems with management and building.
**Examples:** SimCity, RollerCoaster Tycoon, The Sims
**GDD sections:**
- Core simulation loops
- Economy modeling
- AI agents/citizens
- Building/construction
- Failure states
#### Sandbox
Creative freedom with building and minimal objectives.
**Examples:** Minecraft, Terraria, Garry's Mod
**GDD sections:**
- Creation tools
- Physics/interaction systems
- Persistence and saving
- Sharing/community features
- Optional objectives
### Sports & Racing
#### Racing
Vehicle control with tracks and lap times.
**Examples:** Mario Kart, Forza, Need for Speed
**GDD sections:**
- Vehicle physics model
- Track design
- AI opponents
- Progression/career mode
- Multiplayer racing
#### Sports
Team-based or individual sports simulation.
**Examples:** FIFA, NBA 2K, Tony Hawk's Pro Skater
**GDD sections:**
- Sport-specific rules
- Player/team management
- AI opponent behavior
- Season/career modes
- Multiplayer modes
### Multiplayer
#### MOBA
Multiplayer team battles with hero selection.
**Examples:** League of Legends, Dota 2, Smite
**GDD sections:**
- Hero/champion design
- Lane and map design
- Team composition
- Matchmaking
- Economy (gold/items)
#### Party Game
Local multiplayer with minigames.
**Examples:** Mario Party, Jackbox, Overcooked
**GDD sections:**
- Minigame design patterns
- Controller support
- Round/game structure
- Scoring systems
- Player count flexibility
### Horror & Survival
#### Survival
Resource gathering with crafting and persistent threats.
**Examples:** Don't Starve, Subnautica, The Forest
**GDD sections:**
- Resource gathering
- Crafting systems
- Hunger/health/needs
- Threat systems
- Base building
#### Horror
Atmosphere and tension with limited resources.
**Examples:** Resident Evil, Silent Hill, Amnesia
**GDD sections:**
- Fear mechanics
- Resource scarcity
- Sound design
- Lighting and visibility
- Enemy/threat design
### Casual & Progression
#### Puzzle
Logic-based challenges and problem-solving.
**Examples:** Tetris, Portal, The Witness
**GDD sections:**
- Puzzle mechanics
- Difficulty progression
- Hint systems
- Level structure
- Scoring/rating
#### Idle/Incremental
Passive progression with upgrades and automation.
**Examples:** Cookie Clicker, Adventure Capitalist, Clicker Heroes
**GDD sections:**
- Core loop design
- Prestige systems
- Automation unlocks
- Number scaling
- Offline progress
#### Card Game
Deck building with card mechanics.
**Examples:** Slay the Spire, Hearthstone, Magic: The Gathering Arena
**GDD sections:**
- Card design framework
- Deck building rules
- Mana/resource systems
- Rarity and collection
- Competitive balance
### Rhythm
#### Rhythm
Music synchronization with timing-based gameplay.
**Examples:** Guitar Hero, Beat Saber, Crypt of the NecroDancer
**GDD sections:**
- Note/beat mapping
- Scoring systems
- Difficulty levels
- Music licensing
- Input methods
## Hybrid Types
Multiple game types can be combined. GDD sections from all selected types are included.
| Hybrid | Components | Combined Sections |
|--------|------------|-------------------|
| Action RPG | Action Platformer + RPG | Movement, combat, stats, inventory |
| Survival Horror | Survival + Horror | Resources, crafting, atmosphere, fear |
| Roguelike Deckbuilder | Roguelike + Card Game | Run structure, procedural gen, cards |
| Tactical RPG | Turn-Based Tactics + RPG | Grid movement, stats, progression |
| Open World Survival | Sandbox + Survival | Building, crafting, exploration |

113
docs/bmgd/index.md Normal file
View File

@ -0,0 +1,113 @@
---
title: "BMGD Quick Guide"
description: Quick reference for BMad Game Dev Studio
draft: true
---
![BMGD Logo](bmgd-logo.png)
# BMGD Quick Guide
BMad Game Dev Studio (BMGD) extends BMM with game-specific capabilities. Developed by game industry veterans, it guides you through product research, technical design, narrative design, and a full epic-driven production cycle.
## Under Construction
Documentation is under heavy construction catching up with the new beta release. We'll have complete documentation up as soon as possible. For now, please ask in the BMGD section of the Discord if you have any questions.
![BMGD Workflow](workflow.jpg)
## Quick Start
**Install → Game Brief → GDD → (Narrative) → Architecture → Build**
BMGD is an optional module installed via BMAD Method: `npx bmad-method install`
See [How-To Reference](#how-to-reference) for commands.
## Development Phases
| Phase | Name | Key Activities |
|-------|------|----------------|
| 1 | **Preproduction** | Brainstorm Game, Game Brief, market research |
| 2 | **Design** | GDD creation, Narrative Design (for story-driven games) |
| 3 | **Technical** | Game Architecture (engine, systems, patterns) |
| 4 | **Production** | Sprint planning, story development, code review, testing |
## BMGD Agents
| Agent | Purpose |
|-------|---------|
| Game Designer | Game mechanics, balance, player psychology |
| Game Developer | Implementation with engine-specific patterns |
| Game Architect | Engine selection, systems design, technical structure |
| Game Scrum Master | Sprint planning and epic management |
| Game QA | Playtesting, engine-specific testing, performance profiling |
| Game Solo Dev | Full-stack game development for solo projects |
## Key Documents
| Document | Purpose |
|----------|---------|
| **Game Brief** | Vision, market positioning, fundamentals |
| **GDD** | Core loop, mechanics, progression, art/audio direction |
| **Narrative Design** | Story structure, characters, world-building, dialogue |
| **Architecture** | Engine, systems, patterns, project structure |
## Game Type Templates
BMGD includes 24 game type templates that auto-configure GDD sections:
Action, Adventure, Puzzle, RPG, Strategy, Simulation, Sports, Racing, Fighting, Horror, Platformer, Shooter, and more.
Each template provides genre-specific GDD sections, mechanics patterns, testing considerations, and common pitfalls to avoid.
## Explanation: BMGD vs BMM
### When to Use Each
| Use BMGD for | Use BMM for |
|--------------|-------------|
| Video games | Web applications |
| Interactive experiences | APIs and services |
| Game prototyping | Mobile apps (non-game) |
| Game jams | General software projects |
### Phase Mapping
| BMM Phase | BMGD Phase | Key Difference |
|-----------|------------|----------------|
| Analysis | Preproduction | Game concepts, Game Brief instead of Product Brief |
| Planning | Design | GDD instead of PRD; optional Narrative Design |
| Solutioning | Technical | Focus on engine selection, game-specific patterns |
| Implementation | Production | Game QA replaces TEA; engine-specific testing |
### Document Differences
| BMM | BMGD | Notes |
|-----|------|-------|
| Product Brief | Game Brief | Captures vision, market, fundamentals |
| PRD | GDD | Includes mechanics, balance, player experience |
| N/A | Narrative Design | Story, characters, world (story-driven games) |
| Architecture | Architecture | BMGD version includes engine-specific patterns and considerations |
### Testing Differences
**BMM (TEA):** Web-focused testing with Playwright, Cypress, API testing, E2E for web apps.
**BMGD (Game QA):** Engine-specific frameworks (Unity, Unreal, Godot), gameplay testing, performance profiling, playtest planning, balance validation.
## How-To Reference
| I need to... | Action |
|--------------|--------------------------------------------------------------------------------------------------------|
| Install BMGD | Run `npx bmad-method install` and select BMGD during module installation |
| Start a new game | Run `/bmad-gds-brainstorm-game`, then `/bmad:gds:create-game-brief` |
| Design my game | Run `/bmad-gds-create-gdd`; add `/bmad:gds:narrative` if story-heavy |
| Plan architecture | Run `/bmad-gds-game-architecture` with Game Architect |
| Build my game | Use Phase 4 production workflows - Run `/bmad-help` to see what's next |
| Test an idea quickly | Use [Quick-Flow](quick-flow-workflows.md) for rapid prototyping |
## Further Reading
- [Game Types Guide](game-types.md)
- [Quick-Flow Guide](quick-flow-workflows.md)

View File

@ -0,0 +1,161 @@
---
title: "Quick Flow Workflows"
draft: true
---
How to create tech specs and execute implementations with Quick Flow.
## Choosing a Workflow
| Situation | Workflow | Command |
|-----------|----------|---------|
| Need to document before implementing | Quick-Spec | `/bmad-gds-quick-spec` |
| Multiple approaches to evaluate | Quick-Spec | `/bmad-gds-quick-spec` |
| Have a completed tech-spec | Quick-Dev | `/bmad-gds-quick-dev path/to/spec.md` |
| Have clear, direct instructions | Quick-Dev | `/bmad-gds-quick-dev` |
| Building complete game system | Full GDS | `/bmad-gds-workflow-init` |
| Epic-level features | Full GDS | `/bmad-gds-workflow-init` |
---
## How to Create a Tech Spec (Quick-Spec)
### Step 1: Start the workflow
```bash
/bmad-gds-quick-spec
```
### Step 2: Describe your requirement
Provide your feature request. The agent scans the codebase and asks clarifying questions.
**Checkpoint options:**
- `[a]` Advanced Elicitation - explore requirements deeper
- `[c]` Continue to investigation
- `[p]` Party Mode - consult expert agents
### Step 3: Review investigation findings
The agent analyzes the codebase for patterns, constraints, and similar implementations. Review the findings.
**Checkpoint options:**
- `[c]` Continue to spec generation
- `[p]` Party Mode - get technical review
### Step 4: Review generated spec
The agent creates an ordered task list with file paths and acceptance criteria. Verify completeness.
**Checkpoint options:**
- `[c]` Continue to final review
- `[p]` Party Mode - technical review
### Step 5: Finalize
Confirm the spec meets these standards:
- Every task has a file path and specific action
- Tasks ordered by dependency
- Acceptance criteria in Given/When/Then format
- No placeholders or TBD sections
**Options:**
- `[d]` Start Quick-Dev immediately
- `[done]` Save spec and exit
**Output:** `{planning_artifacts}/tech-spec-{slug}.md`
---
## How to Execute Implementation (Quick-Dev)
### With a Tech-Spec
```bash
/bmad-gds-quick-dev path/to/tech-spec-feature.md
```
The agent:
1. Captures baseline git commit
2. Loads and validates the spec
3. Executes tasks in order
4. Runs self-check
5. Performs adversarial review
6. Resolves findings
7. Validates against acceptance criteria
### With Direct Instructions
```bash
/bmad-gds-quick-dev
```
Then describe what you want implemented:
1. Captures baseline git commit
2. Evaluates complexity (may suggest planning)
3. Gathers context from codebase
4. Executes implementation
5. Runs self-check and adversarial review
6. Resolves findings
**Escalation:** If the agent detects complexity (multiple components, system-level scope, uncertainty), it offers:
- `[t]` Create tech-spec first
- `[w]` Use full GDS workflow
- `[e]` Execute anyway
---
## Troubleshooting
### Spec has placeholders or TBD sections
Return to investigation step. Complete missing research, inline all findings, re-run review.
### Workflow lost context mid-step
Check frontmatter for `stepsCompleted`. Resume from last completed step.
### Agent suggested planning but you want to execute
You can override with `[e]`, but document your assumptions. Escalation heuristics exist because planning saves time on complex tasks.
### Tests failing after implementation
Return to the resolve-findings step. Review failures, fix issues, ensure test expectations are correct, re-run full suite.
### Need help
```bash
/bmad-help
```
---
## Reference
### File Locations
| File | Location |
|------|----------|
| Work in progress | `{implementation_artifacts}/tech-spec-wip.md` |
| Completed specs | `{planning_artifacts}/tech-spec-{slug}.md` |
| Archived specs | `{implementation_artifacts}/tech-spec-{slug}-archived-{date}.md` |
| Workflow files | `_bmad/gds/workflows/gds-quick-flow/` |
### Validation Criteria
**Self-check (before adversarial review):**
- All tasks/instructions completed
- Tests written and passing
- Follows existing patterns
- No obvious bugs
- Acceptance criteria met
- Code is readable
**Adversarial review:**
- Correctness
- Security
- Performance
- Maintainability
- Test coverage
- Error handling

BIN
docs/bmgd/workflow.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 118 KiB

View File

@ -1,17 +1,11 @@
---
title: "Advanced Elicitation"
description: Push the LLM to rethink its work using structured reasoning methods
sidebar:
order: 6
---
Make the LLM reconsider what it just generated. You pick a reasoning method, it applies that method to its own output, you decide whether to keep the improvements.
## What is Advanced Elicitation?
A structured second pass. Instead of asking the AI to "try again" or "make it better," you select a specific reasoning method and the AI re-examines its own output through that lens.
The difference matters. Vague requests produce vague revisions. A named method forces a particular angle of attack, surfacing insights that a generic retry would miss.
Dozens of methods are built in - things like First Principles, Red Team vs Blue Team, Pre-mortem Analysis, Socratic Questioning, and more.
## When to Use It
@ -28,22 +22,3 @@ Workflows offer advanced elicitation at decision points - after the LLM has gene
2. You pick one (or reshuffle for different options)
3. Method is applied, improvements shown
4. Accept or discard, repeat or continue
## Built-in Methods
Dozens of reasoning methods are available. A few examples:
- **Pre-mortem Analysis** - Assume the project already failed, work backward to find why
- **First Principles Thinking** - Strip away assumptions, rebuild from ground truth
- **Inversion** - Ask how to guarantee failure, then avoid those things
- **Red Team vs Blue Team** - Attack your own work, then defend it
- **Socratic Questioning** - Challenge every claim with "why?" and "how do you know?"
- **Constraint Removal** - Drop all constraints, see what changes, add them back selectively
- **Stakeholder Mapping** - Re-evaluate from each stakeholder's perspective
- **Analogical Reasoning** - Find parallels in other domains and apply their lessons
And many more. The AI picks the most relevant options for your content - you choose which to run.
:::tip[Start Here]
Pre-mortem Analysis is a good first pick for any spec or plan. It consistently finds gaps that a standard review misses.
:::

View File

@ -1,8 +1,6 @@
---
title: "Adversarial Review"
description: Forced reasoning technique that prevents lazy "looks good" reviews
sidebar:
order: 5
---
Force deeper analysis by requiring problems to be found.
@ -26,7 +24,7 @@ Normal reviews suffer from confirmation bias. You skim the work, nothing jumps o
## Where It's Used
Adversarial review appears throughout BMad workflows - code review, implementation readiness checks, spec validation, and others. Sometimes it's a required step, sometimes optional (like advanced elicitation or party mode). The pattern adapts to whatever artifact needs scrutiny.
Adversarial review appears throughout BMAD workflows - code review, implementation readiness checks, spec validation, and others. Sometimes it's a required step, sometimes optional (like advanced elicitation or party mode). The pattern adapts to whatever artifact needs scrutiny.
## Human Filtering Required

View File

@ -1,8 +1,6 @@
---
title: "Brainstorming"
description: Interactive creative sessions using 60+ proven ideation techniques
sidebar:
order: 2
---
Unlock your creativity through guided exploration.

View File

@ -1,8 +1,6 @@
---
title: "Established Projects FAQ"
description: Common questions about using BMad Method on established projects
sidebar:
order: 8
---
Quick answers to common questions about working on established projects with the BMad Method (BMM).

View File

@ -1,8 +1,6 @@
---
title: "Party Mode"
description: Multi-agent collaboration - get all your AI agents in one conversation
sidebar:
order: 7
---
Get all your AI agents in one conversation.

View File

@ -1,8 +1,6 @@
---
title: "Preventing Agent Conflicts"
description: How architecture prevents conflicts when multiple agents implement a system
sidebar:
order: 4
---
When multiple AI agents implement different parts of a system, they can make conflicting technical decisions. Architecture documentation prevents this by establishing shared standards.
@ -71,7 +69,7 @@ Explicit documentation of:
Think of architecture as the shared context that all agents read before implementing:
```text
```
PRD: "What to build"
Architecture: "How to build it"

View File

@ -1,73 +1,27 @@
---
title: "Quick Flow"
description: Fast-track for small changes - skip the full methodology
sidebar:
order: 1
---
Skip the ceremony. Quick Flow takes you from idea to working code in two commands - no Product Brief, no PRD, no Architecture doc.
## When to Use It
- Bug fixes and patches
- Refactoring existing code
- Small, well-understood features
- Prototyping and spikes
- Single-agent work where one developer can hold the full scope
## When NOT to Use It
- New products or platforms that need stakeholder alignment
- Major features spanning multiple components or teams
- Work that requires architectural decisions (database schema, API contracts, service boundaries)
- Anything where requirements are unclear or contested
:::caution[Scope Creep]
If you start a Quick Flow and realize the scope is bigger than expected, `quick-dev` will detect this and offer to escalate. You can switch to a full PRD workflow at any point without losing your work.
:::
Quick Flow is for when you don't need the full BMad Method. Skip Product Brief, PRD, and Architecture - go straight to implementation.
## How It Works
Quick Flow has two commands, each backed by a structured workflow. You can run them together or independently.
1. **Run `quick-spec`** — generates a focused tech-spec
2. **Run `quick-dev`** — implements it
### quick-spec: Plan
That's it.
Run `quick-spec` and Barry (the Quick Flow agent) walks you through a conversational discovery process:
## When to Use It
1. **Understand** - You describe what you want to build. Barry scans the codebase to ask informed questions, then captures a problem statement, solution approach, and scope boundaries.
2. **Investigate** - Barry reads relevant files, maps code patterns, identifies files to modify, and documents the technical context.
3. **Generate** - Produces a complete tech-spec with ordered implementation tasks (specific file paths and actions), acceptance criteria in Given/When/Then format, testing strategy, and dependencies.
4. **Review** - Presents the full spec for your sign-off. You can edit, ask questions, run adversarial review, or refine with advanced elicitation before finalizing.
- Bug fixes
- Refactoring
- Small features
- Prototyping
The output is a `tech-spec-{slug}.md` file saved to your project's implementation artifacts folder. It contains everything a fresh agent needs to implement the feature - no conversation history required.
## When to Use Full BMad Method Instead
### quick-dev: Build
Run `quick-dev` and Barry implements the work. It operates in two modes:
- **Tech-spec mode** - Point it at a spec file (`quick-dev tech-spec-auth.md`) and it executes every task in order, writes tests, and verifies acceptance criteria.
- **Direct mode** - Give it instructions directly (`quick-dev "refactor the auth middleware"`) and it gathers context, builds a mental plan, and executes.
After implementation, `quick-dev` runs a self-check audit against all tasks and acceptance criteria, then triggers an adversarial code review of the diff. Findings are presented for you to resolve before wrapping up.
:::tip[Fresh Context]
For best results, run `quick-dev` in a new conversation after finishing `quick-spec`. This gives the implementation agent clean context focused solely on building.
:::
## What Quick Flow Skips
The full BMad Method produces a Product Brief, PRD, Architecture doc, and Epic/Story breakdown before any code is written. Quick Flow replaces all of that with a single tech-spec. This works because Quick Flow targets changes where:
- The product direction is already established
- Architecture decisions are already made
- A single developer can reason about the full scope
- Requirements fit in one conversation
## Escalating to Full BMad Method
Quick Flow includes built-in guardrails for scope detection. When you run `quick-dev` with a direct request, it evaluates signals like multi-component mentions, system-level language, and uncertainty about approach. If it detects the work is bigger than a quick flow:
- **Light escalation** - Recommends running `quick-spec` first to create a plan
- **Heavy escalation** - Recommends switching to the full BMad Method PRD process
You can also escalate manually at any time. Your tech-spec work carries forward - it becomes input for the broader planning process rather than being discarded.
- New products
- Major features
- Multiple teams involved
- Stakeholder alignment needed

View File

@ -1,8 +1,6 @@
---
title: "Why Solutioning Matters"
description: Understanding why the solutioning phase is critical for multi-epic projects
sidebar:
order: 3
---
@ -10,7 +8,7 @@ Phase 3 (Solutioning) translates **what** to build (from Planning) into **how**
## The Problem Without Solutioning
```text
```
Agent 1 implements Epic 1 using REST API
Agent 2 implements Epic 2 using GraphQL
Result: Inconsistent API design, integration nightmare
@ -20,7 +18,7 @@ When multiple agents implement different parts of a system without shared archit
## The Solution With Solutioning
```text
```
architecture workflow decides: "Use GraphQL for all APIs"
All agents follow architecture decisions
Result: Consistent implementation, no conflicts

View File

@ -1,35 +1,33 @@
---
title: "How to Customize BMad"
description: Customize agents, workflows, and modules while preserving update compatibility
sidebar:
order: 7
title: "BMad Method Customization Guide"
---
Use the `.customize.yaml` files to tailor agent behavior, personas, and menus while preserving your changes across updates.
The ability to customize the BMad Method and its core to your needs, while still being able to get updates and enhancements is a critical idea within the BMad Ecosystem.
## When to Use This
The Customization Guidance outlined here, while targeted at understanding BMad Method customization, applies to any other module use within the BMad Method.
- You want to change an agent's name, personality, or communication style
- You need agents to remember project-specific context
- You want to add custom menu items that trigger your own workflows or prompts
- You want agents to perform specific actions every time they start up
## Types of Customization
:::note[Prerequisites]
- BMad installed in your project (see [How to Install BMad](./install-bmad.md))
- A text editor for YAML files
:::
Customization includes Agent Customization, Workflow/Skill customization, the addition of new MCPs or Skills to be used by existing agents. Aside from all of this, a whole other realm of customization involves creating / adding your own relevant BMad Builder workflows, skills, agents and maybe even your own net new modules to compliment the BMad Method Module.
:::caution[Keep Your Customizations Safe]
Always use the `.customize.yaml` files described here rather than editing agent files directly. The installer overwrites agent files during updates, but preserves your `.customize.yaml` changes.
:::
Warning: The reason for customizing as this guide will prescribe will allow you to continue getting updates without worrying about losing your customization changes. And by continuing to get updates as BMad modules advance, you will be able to continue to evolve as the system improves.
## Steps
## Agent Customization
### 1. Locate Customization Files
### Agent Customization Areas
After installation, find one `.customize.yaml` file per agent in:
- Change agent names, personas or manner of speech
- Add project-specific memories or context
- Add custom menu items to custom or inline prompts, skills or custom BMad workflows
- Define critical actions that occur agent startup for consistent behavior
```text
## How to customize an agent.
**1. Locate Customization Files**
After installation, find agent customization files in:
```
_bmad/_config/agents/
├── core-bmad-master.customize.yaml
├── bmm-dev.customize.yaml
@ -37,22 +35,28 @@ _bmad/_config/agents/
└── ... (one file per installed agent)
```
### 2. Edit the Customization File
**2. Edit Any Agent**
Open the `.customize.yaml` file for the agent you want to modify. Every section is optional -- customize only what you need.
Open the `.customize.yaml` file for the agent you want to modify. All sections are optional - customize only what you need.
| Section | Behavior | Purpose |
| ------------------- | ------------ | ---------------------------------------------- |
| `agent.metadata` | Replaces | Override the agent's display name |
| `persona` | Replaces | Set role, identity, style, and principles |
| `memories` | Appends | Add persistent context the agent always recalls |
| `menu` | Appends | Add custom menu items for workflows or prompts |
| `critical_actions` | Appends | Define startup instructions for the agent |
| `prompts` | Appends | Create reusable prompts for menu actions |
**3. Rebuild the Agent**
Sections marked **Replaces** overwrite the agent's defaults entirely. Sections marked **Appends** add to the existing configuration.
After editing, IT IS CRITICAL to rebuild the agent to apply changes:
**Agent Name**
```bash
npx bmad-method install
```
You can either then:
- Select `Quick Update` - This will also ensure all packages are up to date AND compile all agents to include any updates or customizations
- Select `Rebuild Agents` - This will only rebuild and apply customizations to agents, without pulling the latest
There will be additional tools shortly after beta launch to allow install of individual agents, workflows, skills and modules without the need for using the full bmad installer.
### What Agent Properties Can Be Customized?
#### Agent Name
Change how the agent introduces itself:
@ -62,7 +66,7 @@ agent:
name: 'Spongebob' # Default: "Amelia"
```
**Persona**
#### Persona
Replace the agent's personality, role, and communication style:
@ -76,9 +80,9 @@ persona:
- 'Favor composition over inheritance'
```
The `persona` section replaces the entire default persona, so include all four fields if you set it.
**Note:** The persona section replaces the entire default persona (not merged).
**Memories**
#### Memories
Add persistent context the agent will always remember:
@ -86,12 +90,12 @@ Add persistent context the agent will always remember:
memories:
- 'Works at Krusty Krab'
- 'Favorite Celebrity: David Hasslehoff'
- 'Learned in Epic 1 that it is not cool to just pretend that tests have passed'
- 'Learned in Epic 1 that its not cool to just pretend that tests have passed'
```
**Menu Items**
### Custom Menu Items
Add custom entries to the agent's display menu. Each item needs a `trigger`, a target (`workflow` path or `action` reference), and a `description`:
Any custom items you add here will be included in the agents display menu.
```yaml
menu:
@ -103,18 +107,18 @@ menu:
description: Deploy to production
```
**Critical Actions**
### Critical Actions
Define instructions that run when the agent starts up:
Add instructions that execute before the agent starts:
```yaml
critical_actions:
- 'Check the CI Pipelines with the XYZ Skill and alert user on wake if anything is urgently needing attention'
```
**Custom Prompts**
### Custom Prompts
Create reusable prompts that menu items can reference with `action="#id"`:
Define reusable prompts for `action="#id"` menu handlers:
```yaml
prompts:
@ -126,47 +130,29 @@ prompts:
3. Execute deployment script
```
### 3. Apply Your Changes
After editing, recompile the agent to apply changes:
```bash
npx bmad-method install
```
The installer detects the existing installation and offers these options:
| Option | What It Does |
| --------------------- | ------------------------------------------------------------------- |
| **Quick Update** | Updates all modules to the latest version and recompiles all agents |
| **Recompile Agents** | Applies customizations only, without updating module files |
| **Modify BMad Installation** | Full installation flow for adding or removing modules |
For customization-only changes, **Recompile Agents** is the fastest option.
## Troubleshooting
**Changes not appearing?**
- Run `npx bmad-method install` and select **Recompile Agents** to apply changes
- Check that your YAML syntax is valid (indentation matters)
- Verify you edited the correct `.customize.yaml` file for the agent
- Make sure you ran `npx bmad-method build <agent-name>` after editing
- Check YAML syntax is valid (indentation matters!)
- Verify the agent name matches the file name pattern
**Agent not loading?**
- Check for YAML syntax errors using an online YAML validator
- Ensure you did not leave fields empty after uncommenting them
- Try reverting to the original template and rebuilding
- Check for YAML syntax errors
- Ensure required fields aren't left empty if you uncommented them
- Try reverting to the template and rebuilding
**Need to reset an agent?**
**Need to reset?**
- Clear or delete the agent's `.customize.yaml` file
- Run `npx bmad-method install` and select **Recompile Agents** to restore defaults
- Remove content from the `.customize.yaml` file (or delete the file)
- Run `npx bmad-method build <agent-name>` to regenerate defaults
## Workflow Customization
Customization of existing BMad Method workflows and skills is coming soon.
Information about customizing existing BMad Method workflows and skills are coming soon.
## Module Customization
Guidance on building expansion modules and customizing existing modules is coming soon.
Information on how to build expansion modules that augment BMad, or make other existing module customizations are coming soon.

View File

@ -1,8 +1,6 @@
---
title: "Established Projects"
description: How to use BMad Method on existing codebases
sidebar:
order: 6
---
Use BMad Method effectively when working on existing projects and legacy codebases, sometimes also referred to as brownfield projects.
@ -46,8 +44,8 @@ You have two primary options depending on the scope of changes:
| Scope | Recommended Approach |
| ------------------------------ | ----------------------------------------------------------------------------------------------------------------------------- |
| **Small updates or additions** | Use `quick-flow-solo-dev` to create a tech-spec and implement the change. The full four-phase BMad Method is likely overkill. |
| **Major changes or additions** | Start with the BMad Method, applying as much or as little rigor as needed. |
| **Small updates or additions** | Use `quick-flow-solo-dev` to create a tech-spec and implement the change. The full four-phase BMad method is likely overkill. |
| **Major changes or additions** | Start with the BMad method, applying as much or as little rigor as needed. |
### During PRD Creation
@ -78,5 +76,5 @@ Pay close attention here to prevent reinventing the wheel or making decisions th
## More Information
- **[Quick Fixes](./quick-fixes.md)** - Bug fixes and ad-hoc changes
- **[Established Projects FAQ](../explanation/established-projects-faq.md)** - Common questions about working on established projects
- **[Quick Fixes](/docs/how-to/quick-fixes.md)** - Bug fixes and ad-hoc changes
- **[Established Projects FAQ](/docs/explanation/established-projects-faq.md)** - Common questions about working on established projects

View File

@ -1,8 +1,6 @@
---
title: "How to Get Answers About BMad"
description: Use an LLM to quickly answer your own BMad questions
sidebar:
order: 4
---
If you have successfully installed BMad and the BMad Method (+ other modules as needed) - the first step in getting answers is `/bmad-help`. This will answer upwards of 80% of all questions and is available to you in the IDE as you are working.
@ -40,11 +38,10 @@ The `_bmad` folder is created when you install BMad. If you don't have it yet, c
Fetch `llms-full.txt` into your session:
```text
```
https://bmad-code-org.github.io/BMAD-METHOD/llms-full.txt
```
### 3. Ask Your Question
:::note[Example]

View File

@ -1,13 +1,11 @@
---
title: "How to Install BMad"
description: Step-by-step guide to installing BMad in your project
sidebar:
order: 1
---
Use the `npx bmad-method install` command to set up BMad in your project with your choice of modules and AI tools.
If you want to use a non interactive installer and provide all install options on the command line, see [this guide](./non-interactive-installation.md).
If you want to use a non interactive installer and provide all install options on the command line, [this guide](/docs/non-interactive-installation.md).
## When to Use This
@ -29,13 +27,6 @@ If you want to use a non interactive installer and provide all install options o
npx bmad-method install
```
:::tip[Bleeding edge]
To install the latest from the main branch (may be unstable):
```bash
npx github:bmad-code-org/BMAD-METHOD install
```
:::
### 2. Choose Installation Location
The installer will ask where to install BMad files:
@ -50,7 +41,6 @@ Pick which AI tools you use:
- Claude Code
- Cursor
- Windsurf
- Kiro
- Others
Each tool has its own way of integrating commands. The installer creates tiny prompt files to activate workflows and agents — it just puts them where your tool expects to find them.
@ -65,7 +55,7 @@ The installer guides you through the rest — custom content, settings, etc.
## What You Get
```text
```
your-project/
├── _bmad/
│ ├── bmm/ # Your selected modules
@ -73,16 +63,22 @@ your-project/
│ ├── core/ # Required core module
│ └── ...
├── _bmad-output/ # Generated artifacts
├── .claude/ # Claude Code commands (if using Claude Code)
└── .kiro/ # Kiro steering files (if using Kiro)
└── .claude/ # Claude Code commands (if using Claude Code)
```
## Verify Installation
Run the `help` workflow (`/bmad-help` on most platforms) to verify everything works and see what to do next.
**Latest from main branch:**
```bash
npx github:bmad-code-org/BMAD-METHOD install
```
Use these if you want the newest features before they're officially released. Things might break.
## Troubleshooting
**Installer throws an error** — Copy-paste the output into your AI assistant and let it figure it out.
**Installer worked but something doesn't work later** — Your AI needs BMad context to help. See [How to Get Answers About BMad](./get-answers-about-bmad.md) for how to point your AI at the right sources.
**Installer worked but something doesn't work later** — Your AI needs BMad context to help. See [How to Get Answers About BMad](/docs/how-to/get-answers-about-bmad.md) for how to point your AI at the right sources.

View File

@ -1,171 +0,0 @@
---
title: Non-Interactive Installation
description: Install BMad using command-line flags for CI/CD pipelines and automated deployments
sidebar:
order: 2
---
Use command-line flags to install BMad non-interactively. This is useful for:
## When to Use This
- Automated deployments and CI/CD pipelines
- Scripted installations
- Batch installations across multiple projects
- Quick installations with known configurations
:::note[Prerequisites]
Requires [Node.js](https://nodejs.org) v20+ and `npx` (included with npm).
:::
## Available Flags
### Installation Options
| Flag | Description | Example |
|------|-------------|---------|
| `--directory <path>` | Installation directory | `--directory ~/projects/myapp` |
| `--modules <modules>` | Comma-separated module IDs | `--modules bmm,bmb` |
| `--tools <tools>` | Comma-separated tool/IDE IDs (use `none` to skip) | `--tools claude-code,cursor` or `--tools none` |
| `--custom-content <paths>` | Comma-separated paths to custom modules | `--custom-content ~/my-module,~/another-module` |
| `--action <type>` | Action for existing installations: `install` (default), `update`, `quick-update`, or `compile-agents` | `--action quick-update` |
### Core Configuration
| Flag | Description | Default |
|------|-------------|---------|
| `--user-name <name>` | Name for agents to use | System username |
| `--communication-language <lang>` | Agent communication language | English |
| `--document-output-language <lang>` | Document output language | English |
| `--output-folder <path>` | Output folder path | _bmad-output |
### Other Options
| Flag | Description |
|------|-------------|
| `-y, --yes` | Accept all defaults and skip prompts |
| `-d, --debug` | Enable debug output for manifest generation |
## Module IDs
Available module IDs for the `--modules` flag:
- `bmm` — BMad Method Master
- `bmb` — BMad Builder
Check the [BMad registry](https://github.com/bmad-code-org) for available external modules.
## Tool/IDE IDs
Available tool IDs for the `--tools` flag:
**Preferred:** `claude-code`, `cursor`, `windsurf`
Run `npx bmad-method install` interactively once to see the full current list of supported tools, or check the [platform codes configuration](https://github.com/bmad-code-org/BMAD-METHOD/blob/main/tools/cli/installers/lib/ide/platform-codes.yaml).
## Installation Modes
| Mode | Description | Example |
|------|-------------|---------|
| Fully non-interactive | Provide all flags to skip all prompts | `npx bmad-method install --directory . --modules bmm --tools claude-code --yes` |
| Semi-interactive | Provide some flags; BMad prompts for the rest | `npx bmad-method install --directory . --modules bmm` |
| Defaults only | Accept all defaults with `-y` | `npx bmad-method install --yes` |
| Without tools | Skip tool/IDE configuration | `npx bmad-method install --modules bmm --tools none` |
## Examples
### CI/CD Pipeline Installation
```bash
#!/bin/bash
# install-bmad.sh
npx bmad-method install \
--directory "${GITHUB_WORKSPACE}" \
--modules bmm \
--tools claude-code \
--user-name "CI Bot" \
--communication-language English \
--document-output-language English \
--output-folder _bmad-output \
--yes
```
### Update Existing Installation
```bash
npx bmad-method install \
--directory ~/projects/myapp \
--action update \
--modules bmm,bmb,custom-module
```
### Quick Update (Preserve Settings)
```bash
npx bmad-method install \
--directory ~/projects/myapp \
--action quick-update
```
### Installation with Custom Content
```bash
npx bmad-method install \
--directory ~/projects/myapp \
--modules bmm \
--custom-content ~/my-custom-module,~/another-module \
--tools claude-code
```
## What You Get
- A fully configured `_bmad/` directory in your project
- Compiled agents and workflows for your selected modules and tools
- A `_bmad-output/` folder for generated artifacts
## Validation and Error Handling
BMad validates all provided flags:
- **Directory** — Must be a valid path with write permissions
- **Modules** — Warns about invalid module IDs (but won't fail)
- **Tools** — Warns about invalid tool IDs (but won't fail)
- **Custom Content** — Each path must contain a valid `module.yaml` file
- **Action** — Must be one of: `install`, `update`, `quick-update`, `compile-agents`
Invalid values will either:
1. Show an error and exit (for critical options like directory)
2. Show a warning and skip (for optional items like custom content)
3. Fall back to interactive prompts (for missing required values)
:::tip[Best Practices]
- Use absolute paths for `--directory` to avoid ambiguity
- Test flags locally before using in CI/CD pipelines
- Combine with `-y` for truly unattended installations
- Use `--debug` if you encounter issues during installation
:::
## Troubleshooting
### Installation fails with "Invalid directory"
- The directory path must exist (or its parent must exist)
- You need write permissions
- The path must be absolute or correctly relative to the current directory
### Module not found
- Verify the module ID is correct
- External modules must be available in the registry
### Custom content path invalid
Ensure each custom content path:
- Points to a directory
- Contains a `module.yaml` file in the root
- Has a `code` field in the `module.yaml`
:::note[Still stuck?]
Run with `--debug` for detailed output, try interactive mode to isolate the issue, or report at <https://github.com/bmad-code-org/BMAD-METHOD/issues>.
:::

View File

@ -1,123 +1,76 @@
---
title: "Quick Fixes"
description: How to make quick fixes and ad-hoc changes
sidebar:
order: 5
---
Use the **DEV agent** directly for bug fixes, refactorings, or small targeted changes that don't require the full BMad Method or Quick Flow.
Use the **DEV agent** directly for bug fixes, refactorings, or small targeted changes that don't require the full BMad method or Quick Flow.
## When to Use This
- Bug fixes with a clear, known cause
- Small refactorings (rename, extract, restructure) contained within a few files
- Minor feature tweaks or configuration changes
- Exploratory work to understand an unfamiliar codebase
:::note[Prerequisites]
- BMad Method installed (`npx bmad-method install`)
- An AI-powered IDE (Claude Code, Cursor, Windsurf, or similar)
:::
## Choose Your Approach
| Situation | Agent | Why |
| --- | --- | --- |
| Fix a specific bug or make a small, scoped change | **DEV agent** | Jumps straight into implementation without planning overhead |
| Change touches several files or you want a written plan first | **Quick Flow Solo Dev** | Creates a quick-spec before implementation so the agent stays aligned to your standards |
If you are unsure, start with the DEV agent. You can always escalate to Quick Flow if the change grows.
- Simple bug fixes
- Small refactorings and changes that don't need extensive ideation, planning, or architectural shifts
- Larger refactorings or improvement with built in tool planning and execution mode combination, or better yet use quick flow
- Learning about your codebase
## Steps
### 1. Load the DEV Agent
### 1. Load an Agent
Start a **fresh chat** in your AI IDE and load the DEV agent with its slash command:
For quick fixes, you can use:
```text
/bmad-agent-bmm-dev
```
This loads the agent's persona and capabilities into the session. If you decide you need Quick Flow instead, load the **Quick Flow Solo Dev** agent in a fresh chat:
```text
/bmad-agent-bmm-quick-flow-solo-dev
```
Once the Solo Dev agent is loaded, describe your change and ask it to create a **quick-spec**. The agent drafts a lightweight spec capturing what you want to change and how. After you approve the quick-spec, tell the agent to start the **Quick Flow dev cycle** -- it will implement the change, run tests, and perform a self-review, all guided by the spec you just approved.
:::tip[Fresh Chats]
Always start a new chat session when loading an agent. Reusing a session from a previous workflow can cause context conflicts.
:::
- **DEV agent** - For implementation-focused work
- **Quick Flow Solo Dev** - For slightly larger changes that still need a quick-spec to keep the agent aligned to planning and standards
### 2. Describe the Change
Tell the agent what you need in plain language. Be specific about the problem and, if you know it, where the relevant code lives.
Simply tell the agent what you need:
:::note[Example Prompts]
**Bug fix** -- "Fix the login validation bug that allows empty passwords. The validation logic is in `src/auth/validate.ts`."
```
Fix the login validation bug that allows empty passwords
```
**Refactoring** -- "Refactor the UserService to use async/await instead of callbacks."
or
**Configuration change** -- "Update the CI pipeline to cache node_modules between runs."
**Dependency update** -- "Upgrade the express dependency to the latest v5 release and fix any breaking changes."
:::
You don't need to provide every detail. The agent will read the relevant source files and ask clarifying questions when needed.
```
Refactor the UserService to use async/await instead of callbacks
```
### 3. Let the Agent Work
The agent will:
- Read and analyze the relevant source files
- Propose a solution and explain its reasoning
- Implement the change across the affected files
- Run your project's test suite if one exists
- Analyze the relevant code
- Propose a solution
- Implement the change
- Run tests (if available)
If your project has tests, the agent runs them automatically after making changes and iterates until tests pass. For projects without a test suite, verify the change manually (run the app, hit the endpoint, check the output).
### 4. Review and Commit
### 4. Review and Verify
Before committing, review what changed:
- Read through the diff to confirm the change matches your intent
- Run the application or tests yourself to double-check
- If something looks wrong, tell the agent what to fix -- it can iterate in the same session
Once satisfied, commit the changes with a clear message describing the fix.
:::caution[If Something Breaks]
If a committed change causes unexpected issues, use `git revert HEAD` to undo the last commit cleanly. Then start a fresh chat with the DEV agent to try a different approach.
:::
Review the changes made and commit when satisfied.
## Learning Your Codebase
The DEV agent is also useful for exploring unfamiliar code. Load it in a fresh chat and ask questions:
This approach is also excellent for exploring unfamiliar code:
:::note[Example Prompts]
"Explain how the authentication system works in this codebase."
```
Explain how the authentication system works in this codebase
```
"Show me where error handling happens in the API layer."
```
Show me where error handling happens in the API layer
```
"What does the `ProcessOrder` function do and what calls it?"
:::
LLMs are excellent at interpreting and analyzing code, whether it was AI-generated or not. Use the agent to:
Use the agent to learn about your project, understand how components connect, and explore unfamiliar areas before making changes.
## What You Get
- Modified source files with the fix or refactoring applied
- Passing tests (if your project has a test suite)
- A clean commit describing the change
No planning artifacts are produced -- that's the point of this approach.
- Learn about your project
- Understand how things are built
- Explore unfamiliar parts of the codebase
## When to Upgrade to Formal Planning
Consider using [Quick Flow](../explanation/quick-flow.md) or the full BMad Method when:
Consider using Quick Flow or full BMad Method when:
- The change affects multiple systems or requires coordinated updates across many files
- You are unsure about the scope and need a spec to think it through
- The fix keeps growing in complexity as you work on it
- You need documentation or architectural decisions recorded for the team
- The change affects multiple files or systems
- You're unsure about the scope
- The fix keeps growing in complexity
- You need documentation for the change

View File

@ -1,19 +1,14 @@
---
title: "Document Sharding Guide"
description: Split large markdown files into smaller organized files for better context management
sidebar:
order: 8
---
Use the `shard-doc` tool if you need to split large markdown files into smaller, organized files for better context management.
:::caution[Deprecated]
This is no longer recommended, and soon with updated workflows and most major LLMs and tools supporting subprocesses this will be unnecessary.
:::
This is no longer recommended, and soon with updated workflows and most major llms and tools supporting sub processes this will be unnecessary.
## When to Use This
Only use this if you notice your chosen tool / model combination is failing to load and read all the documents as input when needed.
Only use this if you notice your chosen tool / model combination are failing to load and read all the documents as input when needed.
## What is Document Sharding?
@ -21,7 +16,7 @@ Document sharding splits large markdown files into smaller, organized files base
### Architecture
```text
```
Before Sharding:
docs/
└── PRD.md (large 50k token file)
@ -41,12 +36,12 @@ docs/
### 1. Run the Shard-Doc Tool
```bash
/bmad-shard-doc
/bmad:core:tools:shard-doc
```
### 2. Follow the Interactive Process
```text
```
Agent: Which document would you like to shard?
User: docs/PRD.md

View File

@ -1,8 +1,6 @@
---
title: "How to Upgrade to v6"
description: Migrate from BMad v4 to v6
sidebar:
order: 3
---
Use the BMad installer to upgrade from v4 to v6, which includes automatic detection of legacy installations and migration assistance.
@ -22,7 +20,7 @@ Use the BMad installer to upgrade from v4 to v6, which includes automatic detect
### 1. Run the Installer
Follow the [Installer Instructions](./install-bmad.md).
Follow the [Installer Instructions](/docs/how-to/install-bmad.md).
### 2. Handle Legacy Installation
@ -31,7 +29,7 @@ When v4 is detected, you can:
- Allow the installer to back up and remove `.bmad-method`
- Exit and handle cleanup manually
If you named your bmad method folder something else - you will need to manually remove the folder yourself.
If you named your bmad method folder something else - you will need to manual remove the folder yourself.
### 3. Clean Up IDE Commands
@ -65,16 +63,16 @@ If you have stories created or implemented:
**v6 unified structure:**
```text
```
your-project/
── _bmad/ # Single installation folder
├── _config/ # Your customizations
│ └── agents/ # Agent customization files
├── core/ # Universal core framework
├── bmm/ # BMad Method module
├── bmb/ # BMad Builder
└── cis/ # Creative Intelligence Suite
── _bmad-output/ # Output folder (was doc folder in v4)
── _bmad/ # Single installation folder
├── _config/ # Your customizations
│ └── agents/ # Agent customization files
├── core/ # Universal core framework
├── bmm/ # BMad Method module
├── bmb/ # BMad Builder
└── cis/ # Creative Intelligence Suite
── _bmad-output/ # Output folder (was doc folder in v4)
```
## Module Migration

View File

@ -1,18 +1,19 @@
---
title: Welcome to the BMad Method
description: AI-driven development framework with specialized agents, guided workflows, and intelligent planning
---
The BMad Method (**B**reakthrough **M**ethod of **A**gile AI **D**riven Development) is an AI-driven development framework that helps you build software through the whole process from ideation and planning all the way through agentic implementation. It provides specialized AI agents, guided workflows, and intelligent planning that adapts to your project's complexity, whether you're fixing a bug or building an enterprise platform.
If you're comfortable working with AI coding assistants like Claude, Cursor, or GitHub Copilot, you're ready to get started.
---
## New Here? Start with a Tutorial
The fastest way to understand BMad is to try it.
- **[Get Started with BMad](./tutorials/getting-started.md)** — Install and understand how BMad works
- **[Workflow Map](./reference/workflow-map.md)** — Visual overview of BMM phases, workflows, and context management.
- **[Get Started with BMad](/docs/tutorials/getting-started.md)** — Install and understand how BMad works
- **[Workflow Map](/docs/reference/workflow-map.md)** — Visual overview of BMM phases, workflows, and context management.
## How to Use These Docs
@ -25,6 +26,8 @@ These docs are organized into four sections based on what you're trying to do:
| **Explanation** | Understanding-oriented. Deep dives into concepts and architecture. Read when you want to know *why*. |
| **Reference** | Information-oriented. Technical specifications for agents, workflows, and configuration. |
---
## What You'll Need
BMad works with any AI coding assistant that supports custom system prompts or project context. Popular options include:
@ -32,11 +35,12 @@ BMad works with any AI coding assistant that supports custom system prompts or p
- **[Claude Code](https://code.claude.com)** — Anthropic's CLI tool (recommended)
- **[Cursor](https://cursor.sh)** — AI-first code editor
- **[Windsurf](https://codeium.com/windsurf)** — Codeium's AI IDE
- **[Kiro](https://kiro.dev)** — Amazon's AI-powered IDE
- **[Roo Code](https://roocode.com)** — VS Code extension
You should be comfortable with basic software development concepts like version control, project structure, and agile workflows. No prior experience with BMad-style agent systems is required—that's what these docs are for.
---
## Join the Community
Get help, share what you're building, or contribute to BMad:
@ -45,6 +49,8 @@ Get help, share what you're building, or contribute to BMad:
- **[GitHub](https://github.com/bmad-code-org/BMAD-METHOD)** — Source code, issues, and contributions
- **[YouTube](https://www.youtube.com/@BMadCode)** — Video tutorials and walkthroughs
---
## Next Step
Ready to dive in? **[Get Started with BMad](./tutorials/getting-started.md)** and build your first project.
Ready to dive in? **[Get Started with BMad](/docs/tutorials/getting-started.md)** and build your first project.

View File

@ -0,0 +1,314 @@
---
title: Non-Interactive Installation
description: Install BMAD using command-line flags for CI/CD pipelines and automated deployments
---
# Non-Interactive Installation
BMAD now supports non-interactive installation through command-line flags. This is particularly useful for:
- Automated deployments and CI/CD pipelines
- Scripted installations
- Batch installations across multiple projects
- Quick installations with known configurations
## Installation Modes
### 1. Fully Interactive (Default)
Run without any flags to use the traditional interactive prompts:
```bash
npx bmad-method install
```
### 2. Fully Non-Interactive
Provide all required flags to skip all prompts:
```bash
npx bmad-method install \
--directory /path/to/project \
--modules bmm,bmb \
--tools claude-code,cursor \
--user-name "John Doe" \
--communication-language English \
--document-output-language English \
--output-folder _bmad-output
```
### 3. Semi-Interactive (Graceful Fallback)
Provide some flags and let BMAD prompt for the rest:
```bash
npx bmad-method install \
--directory /path/to/project \
--modules bmm
```
In this case, BMAD will:
- Use the provided directory and modules
- Prompt for tool selection
- Prompt for core configuration
### 4. Quick Install with Defaults
Use the `-y` or `--yes` flag to accept all defaults:
```bash
npx bmad-method install --yes
```
This will:
- Install to the current directory
- Skip custom content prompts
- Use default values for all configuration
- Use previously configured tools (or skip tool configuration if none exist)
### 5. Install Without Tools
To skip tool/IDE configuration entirely:
**Option 1: Use --tools none**
```bash
npx bmad-method install --directory ~/myapp --modules bmm --tools none
```
**Option 2: Use --yes flag (if no tools were previously configured)**
```bash
npx bmad-method install --yes
```
**Option 3: Omit --tools and select "None" in the interactive prompt**
```bash
npx bmad-method install --directory ~/myapp --modules bmm
# Then select "⚠ None - I am not installing any tools" when prompted
```
## Available Flags
### Installation Options
| Flag | Description | Example |
|------|-------------|---------|
| `--directory <path>` | Installation directory | `--directory ~/projects/myapp` |
| `--modules <modules>` | Comma-separated module IDs | `--modules bmm,bmb` |
| `--tools <tools>` | Comma-separated tool/IDE IDs (use "none" to skip) | `--tools claude-code,cursor` or `--tools none` |
| `--custom-content <paths>` | Comma-separated paths to custom modules | `--custom-content ~/my-module,~/another-module` |
| `--action <type>` | Action for existing installations | `--action quick-update` |
### Core Configuration
| Flag | Description | Default |
|------|-------------|---------|
| `--user-name <name>` | Name for agents to use | System username |
| `--communication-language <lang>` | Agent communication language | English |
| `--document-output-language <lang>` | Document output language | English |
| `--output-folder <path>` | Output folder path | _bmad-output |
### Other Options
| Flag | Description |
|------|-------------|
| `-y, --yes` | Accept all defaults and skip prompts |
| `-d, --debug` | Enable debug output for manifest generation |
## Action Types
When working with existing installations, use the `--action` flag:
- `install` - Fresh installation (default for new directories)
- `update` - Modify existing installation (change modules/config)
- `quick-update` - Refresh installation without changing configuration
- `compile-agents` - Recompile agents with customizations only
Example:
```bash
npx bmad-method install --action quick-update
```
## Module IDs
Available module IDs for the `--modules` flag:
### Core Modules
- `bmm` - BMad Method Master
- `bmb` - BMad Builder
### External Modules
Check the [BMad registry](https://github.com/bmad-code-org) for available external modules.
## Tool/IDE IDs
Available tool IDs for the `--tools` flag:
- `claude-code` - Claude Code CLI
- `cursor` - Cursor IDE
- `windsurf` - Windsurf IDE
- `vscode` - Visual Studio Code
- `jetbrains` - JetBrains IDEs
- And more...
Run the interactive installer once to see all available tools.
## Examples
### Basic Installation
Install BMM module with Claude Code:
```bash
npx bmad-method install \
--directory ~/projects/myapp \
--modules bmm \
--tools claude-code \
--user-name "Development Team"
```
### Installation Without Tools
Install without configuring any tools/IDEs:
```bash
npx bmad-method install \
--directory ~/projects/myapp \
--modules bmm \
--tools none \
--user-name "Development Team"
```
### Full Installation with Multiple Modules
```bash
npx bmad-method install \
--directory ~/projects/myapp \
--modules bmm,bmb \
--tools claude-code,cursor \
--user-name "John Doe" \
--communication-language English \
--document-output-language English \
--output-folder _output
```
### Update Existing Installation
```bash
npx bmad-method install \
--directory ~/projects/myapp \
--action update \
--modules bmm,bmb,custom-module
```
### Quick Update (Preserve Settings)
```bash
npx bmad-method install \
--directory ~/projects/myapp \
--action quick-update
```
### Installation with Custom Content
```bash
npx bmad-method install \
--directory ~/projects/myapp \
--modules bmm \
--custom-content ~/my-custom-module,~/another-module \
--tools claude-code
```
### CI/CD Pipeline Installation
```bash
#!/bin/bash
# install-bmad.sh
npx bmad-method install \
--directory "${GITHUB_WORKSPACE}" \
--modules bmm \
--tools claude-code \
--user-name "CI Bot" \
--communication-language English \
--document-output-language English \
--output-folder _bmad-output \
--yes
```
## Environment-Specific Installations
### Development Environment
```bash
npx bmad-method install \
--directory . \
--modules bmm,bmb \
--tools claude-code,cursor \
--user-name "${USER}"
```
### Production Environment
```bash
npx bmad-method install \
--directory /opt/app \
--modules bmm \
--tools claude-code \
--user-name "Production Team" \
--output-folder /var/bmad-output
```
## Validation and Error Handling
BMAD validates all provided flags:
- **Directory**: Must be a valid path with write permissions
- **Modules**: Will warn about invalid module IDs (but won't fail)
- **Tools**: Will warn about invalid tool IDs (but won't fail)
- **Custom Content**: Each path must contain a valid `module.yaml` file
- **Action**: Must be one of: install, update, quick-update, compile-agents
Invalid values will either:
1. Show an error and exit (for critical options like directory)
2. Show a warning and skip (for optional items like custom content)
3. Fall back to interactive prompts (for missing required values)
## Tips and Best Practices
1. **Use absolute paths** for `--directory` to avoid ambiguity
2. **Test flags locally** before using in CI/CD pipelines
3. **Combine with `-y`** for truly unattended installations
4. **Check module availability** by running the interactive installer once
5. **Use `--debug`** flag if you encounter issues during installation
6. **Skip tool configuration** with `--tools none` for server/CI environments where IDEs aren't needed
7. **Partial flags are OK** - Omit flags and let BMAD prompt for missing values interactively
## Troubleshooting
### Installation fails with "Invalid directory"
Check that:
- The directory path exists or its parent exists
- You have write permissions
- The path is absolute or correctly relative to current directory
### Module not found
- Verify the module ID is correct (check available modules in interactive mode)
- External modules may need to be available in the registry
### Custom content path invalid
Ensure each custom content path:
- Points to a directory
- Contains a `module.yaml` file in the root
- Has a `code` field in the `module.yaml`
## Feedback and Issues
If you encounter any issues with non-interactive installation:
1. Run with `--debug` flag for detailed output
2. Try the interactive mode to verify the issue
3. Report issues on GitHub: <https://github.com/bmad-code-org/BMAD-METHOD/issues>

View File

@ -1,18 +1,12 @@
---
title: Agents
description: Default BMM agents with their menu triggers and primary workflows
sidebar:
order: 2
---
## Default Agents
This page lists the default BMM (Agile suite) agents that install with BMad Method, along with their menu triggers and primary workflows.
## Notes
This page lists the default BMM (Agile suite) agents that install with BMAD Method, along with their menu triggers and primary workflows.
Notes:
- Triggers are the short menu codes (e.g., `CP`) and fuzzy matches shown in each agent menu.
- Slash commands are generated separately. See [Commands](./commands.md) for the slash command list and where they are defined.
- Slash commands are generated separately. See `docs/reference/commands.md` for the slash command list and where they are defined.
- QA (Quinn) is the lightweight test automation agent in BMM. The full Test Architect (TEA) lives in its own module.
| Agent | Triggers | Primary workflows |

View File

@ -1,131 +1,34 @@
---
title: Commands
description: Reference for BMad slash commands — what they are, how they work, and where to find them.
sidebar:
order: 3
description: How BMAD commands are generated and where to find them.
---
Slash commands are pre-built prompts that load agents, run workflows, or execute tasks inside your IDE. The BMad installer generates them from your installed modules at install time. If you later add, remove, or change modules, re-run the installer to keep commands in sync (see [Troubleshooting](#troubleshooting)).
# Commands
## Commands vs. Agent Menu Triggers
BMAD slash commands are generated by the installer for your IDE and **reflect the modules you have installed**.
That means the authoritative list lives **in your project**, not in a static docs page.
BMad offers two ways to start work, and they serve different purposes.
## How to Discover Commands (Recommended)
| Mechanism | How you invoke it | What happens |
| --- | --- | --- |
| **Slash command** | Type `/bmad-...` in your IDE | Directly loads an agent, runs a workflow, or executes a task |
| **Agent menu trigger** | Load an agent first, then type a short code (e.g. `DS`) | The agent interprets the code and starts the matching workflow while staying in character |
- Type `/bmad` in your IDE and use autocomplete to browse agents/workflows.
- Run `/bmad-help` to get guided next steps and context-aware recommendations.
Agent menu triggers require an active agent session. Use slash commands when you know which workflow you want. Use triggers when you are already working with an agent and want to switch tasks without leaving the conversation.
## Where Commands Are Generated
## How Commands Are Generated
The installer writes command files into your project (example paths for Claude Code):
When you run `npx bmad-method install`, the installer reads the manifests for every selected module and writes one command file per agent, workflow, task, and tool. Each file is a short markdown prompt that instructs the AI to load the corresponding source file and follow its instructions.
- `.claude/commands/bmad/<module>/agents/`
- `.claude/commands/bmad/<module>/workflows/`
The installer uses templates for each command type:
These folders are the **canonical, project-specific command list**.
| Command type | What the generated file does |
| --- | --- |
| **Agent launcher** | Loads the agent persona file, activates its menu, and stays in character |
| **Workflow command** | Loads the workflow engine (`workflow.xml`) and passes the workflow config |
| **Task command** | Loads a standalone task file and follows its instructions |
| **Tool command** | Loads a standalone tool file and follows its instructions |
## Common Commands
:::note[Re-running the installer]
If you add or remove modules, run the installer again. It regenerates all command files to match your current module selection.
:::
- `/bmad-help` - Interactive help and next-step guidance
- `/bmad:<module>:agents:<agent>` - Load an agent (e.g. `/bmad:bmm:agents:dev`)
- `/bmad:<module>:workflows:<workflow>` - Run a workflow (e.g. `/bmad:bmm:workflows:create-prd`)
## Where Command Files Live
## Why This Page Is Short
The installer writes command files into an IDE-specific directory inside your project. The exact path depends on which IDE you selected during installation.
| IDE / CLI | Command directory |
| --- | --- |
| Claude Code | `.claude/commands/` |
| Cursor | `.cursor/commands/` |
| Windsurf | `.windsurf/workflows/` |
| Other IDEs | See the installer output for the target path |
All IDEs receive a flat set of command files in their command directory. For example, a Claude Code installation looks like:
```text
.claude/commands/
├── bmad-agent-bmm-dev.md
├── bmad-agent-bmm-pm.md
├── bmad-bmm-create-prd.md
├── bmad-editorial-review-prose.md
├── bmad-help.md
└── ...
```
The filename determines the slash command name in your IDE. For example, the file `bmad-agent-bmm-dev.md` registers the command `/bmad-agent-bmm-dev`.
## How to Discover Your Commands
Type `/bmad` in your IDE and use autocomplete to browse available commands.
Run `/bmad-help` for context-aware guidance on your next step.
:::tip[Quick discovery]
The generated command folders in your project are the canonical list. Open them in your file explorer to see every command with its description.
:::
## Command Categories
### Agent Commands
Agent commands load a specialized AI persona with a defined role, communication style, and menu of workflows. Once loaded, the agent stays in character and responds to menu triggers.
| Example command | Agent | Role |
| --- | --- | --- |
| `/bmad-agent-bmm-dev` | Amelia (Developer) | Implements stories with strict adherence to specs |
| `/bmad-agent-bmm-pm` | John (Product Manager) | Creates and validates PRDs |
| `/bmad-agent-bmm-architect` | Winston (Architect) | Designs system architecture |
| `/bmad-agent-bmm-sm` | Bob (Scrum Master) | Manages sprints and stories |
See [Agents](./agents.md) for the full list of default agents and their triggers.
### Workflow Commands
Workflow commands run a structured, multi-step process without loading an agent persona first. They load the workflow engine and pass a specific workflow configuration.
| Example command | Purpose |
| --- | --- |
| `/bmad-bmm-create-prd` | Create a Product Requirements Document |
| `/bmad-bmm-create-architecture` | Design system architecture |
| `/bmad-bmm-dev-story` | Implement a story |
| `/bmad-bmm-code-review` | Run a code review |
| `/bmad-bmm-quick-spec` | Define an ad-hoc change (Quick Flow) |
See [Workflow Map](./workflow-map.md) for the complete workflow reference organized by phase.
### Task and Tool Commands
Tasks and tools are standalone operations that do not require an agent or workflow context.
| Example command | Purpose |
| --- | --- |
| `/bmad-help` | Context-aware guidance and next-step recommendations |
| `/bmad-shard-doc` | Split a large markdown file into smaller sections |
| `/bmad-index-docs` | Index project documentation |
| `/bmad-editorial-review-prose` | Review document prose quality |
## Naming Convention
Command names follow a predictable pattern.
| Pattern | Meaning | Example |
| --- | --- | --- |
| `bmad-agent-<module>-<name>` | Agent launcher | `bmad-agent-bmm-dev` |
| `bmad-<module>-<workflow>` | Workflow command | `bmad-bmm-create-prd` |
| `bmad-<name>` | Core task or tool | `bmad-help` |
Module codes: `bmm` (Agile suite), `bmb` (Builder), `tea` (Test Architect), `cis` (Creative Intelligence), `gds` (Game Dev Studio). See [Modules](./modules.md) for descriptions.
## Troubleshooting
**Commands not appearing after install.** Restart your IDE or reload the window. Some IDEs cache the command list and require a refresh to pick up new files.
**Expected commands are missing.** The installer only generates commands for modules you selected. Run `npx bmad-method install` again and verify your module selection. Check that the command files exist in the expected directory.
**Commands from a removed module still appear.** The installer does not delete old command files automatically. Remove the stale files from your IDE's command directory, or delete the entire command directory and re-run the installer for a clean set.
BMAD is modular, so the exact commands vary by install.
Use your IDE's autocomplete or the generated command folders above to see *everything* available.

View File

@ -1,8 +1,5 @@
---
title: Official Modules
description: Add-on modules for building custom agents, creative intelligence, game development, and testing
sidebar:
order: 4
---
BMad extends through official modules that you select during installation. These add-on modules provide specialized agents, workflows, and tasks for specific domains beyond the built-in core and BMM (Agile suite).

View File

@ -1,106 +1,21 @@
---
title: Testing Options
description: Comparing the built-in QA agent (Quinn) with the Test Architect (TEA) module for test automation.
sidebar:
order: 5
---
BMad provides two testing paths: a built-in QA agent for fast test generation and an installable Test Architect module for enterprise-grade test strategy.
# Testing Options
## Which Should You Use?
BMad provides a built-in QA agent for quick test automation and a separate Test Architect (TEA) module for advanced testing.
| Factor | Quinn (Built-in QA) | TEA Module |
| --- | --- | --- |
| **Best for** | Small-medium projects, quick coverage | Large projects, regulated or complex domains |
| **Setup** | Nothing to install -- included in BMM | Install separately via `npx bmad-method install` |
| **Approach** | Generate tests fast, iterate later | Plan first, then generate with traceability |
| **Test types** | API and E2E tests | API, E2E, ATDD, NFR, and more |
| **Strategy** | Happy path + critical edge cases | Risk-based prioritization (P0-P3) |
| **Workflow count** | 1 (Automate) | 9 (design, ATDD, automate, review, trace, and others) |
## Built-in QA (Quinn)
:::tip[Start with Quinn]
Most projects should start with Quinn. If you later need test strategy, quality gates, or requirements traceability, install TEA alongside it.
:::
Use the built-in QA agent for fast, straightforward test coverage:
## Built-in QA Agent (Quinn)
Quinn is the built-in QA agent in the BMM (Agile suite) module. It generates working tests quickly using your project's existing test framework -- no configuration or additional installation required.
**Trigger:** `QA` or `bmad-bmm-qa-automate`
### What Quinn Does
Quinn runs a single workflow (Automate) that walks through five steps:
1. **Detect test framework** -- scans `package.json` and existing test files for your framework (Jest, Vitest, Playwright, Cypress, or any standard runner). If none exists, analyzes the project stack and suggests one.
2. **Identify features** -- asks what to test or auto-discovers features in the codebase.
3. **Generate API tests** -- covers status codes, response structure, happy path, and 1-2 error cases.
4. **Generate E2E tests** -- covers user workflows with semantic locators and visible-outcome assertions.
5. **Run and verify** -- executes the generated tests and fixes failures immediately.
Quinn produces a test summary saved to your project's implementation artifacts folder.
### Test Patterns
Generated tests follow a "simple and maintainable" philosophy:
- **Standard framework APIs only** -- no external utilities or custom abstractions
- **Semantic locators** for UI tests (roles, labels, text rather than CSS selectors)
- **Independent tests** with no order dependencies
- **No hardcoded waits or sleeps**
- **Clear descriptions** that read as feature documentation
:::note[Scope]
Quinn generates tests only. For code review and story validation, use the Code Review workflow (`CR`) instead.
:::
### When to Use Quinn
- Quick test coverage for a new or existing feature
- Beginner-friendly test automation without advanced setup
- Standard test patterns that any developer can read and maintain
- Small-medium projects where comprehensive test strategy is unnecessary
- Trigger: `QA` or `bmad-bmm-qa-automate`
- Best for: small projects, quick coverage, standard patterns
## Test Architect (TEA) Module
TEA is a standalone module that provides an expert agent (Murat) and nine structured workflows for enterprise-grade testing. It goes beyond test generation into test strategy, risk-based planning, quality gates, and requirements traceability.
TEA is a standalone module with advanced testing workflows (test design, ATDD, automate, review, trace, NFR assessment).
- **Documentation:** [TEA Module Docs](https://bmad-code-org.github.io/bmad-method-test-architecture-enterprise/)
- **Install:** `npx bmad-method install` and select the TEA module
- **npm:** [`bmad-method-test-architecture-enterprise`](https://www.npmjs.com/package/bmad-method-test-architecture-enterprise)
### What TEA Provides
| Workflow | Purpose |
| --- | --- |
| Test Design | Create a comprehensive test strategy tied to requirements |
| ATDD | Acceptance-test-driven development with stakeholder criteria |
| Automate | Generate tests with advanced patterns and utilities |
| Test Review | Validate test quality and coverage against strategy |
| Traceability | Map tests back to requirements for audit and compliance |
| NFR Assessment | Evaluate non-functional requirements (performance, security) |
| CI Setup | Configure test execution in continuous integration pipelines |
| Framework Scaffolding | Set up test infrastructure and project structure |
| Release Gate | Make data-driven go/no-go release decisions |
TEA also supports P0-P3 risk-based prioritization and optional integrations with Playwright Utils and MCP tooling.
### When to Use TEA
- Projects that require requirements traceability or compliance documentation
- Teams that need risk-based test prioritization across many features
- Enterprise environments with formal quality gates before release
- Complex domains where test strategy must be planned before tests are written
- Projects that have outgrown Quinn's single-workflow approach
## How Testing Fits into Workflows
Quinn's Automate workflow appears in Phase 4 (Implementation) of the BMad Method workflow map. A typical sequence:
1. Implement a story with the Dev workflow (`DS`)
2. Generate tests with Quinn (`QA`) or TEA's Automate workflow
3. Validate implementation with Code Review (`CR`)
Quinn works directly from source code without loading planning documents (PRD, architecture). TEA workflows can integrate with upstream planning artifacts for traceability.
For more on where testing fits in the overall process, see the [Workflow Map](./workflow-map.md).
- Documentation: <https://bmad-code-org.github.io/bmad-method-test-architecture-enterprise/>
- Install: `npx bmad-method@alpha install` and select the TEA module

View File

@ -1,23 +1,19 @@
---
title: "Workflow Map"
description: Visual reference for BMad Method workflow phases and outputs
sidebar:
order: 1
---
The BMad Method (BMM) is a module in the BMad Ecosystem, targeted at following the best practices of context engineering and planning. AI agents work best with clear, structured context. The BMM system builds that context progressively across 4 distinct phases - each phase, and multiple workflows optionally within each phase, produce documents that inform the next, so agents always know what to build and why.
The rationale and concepts come from agile methodologies that have been used across the industry with great success as a mental framework.
If at any time you are unsure what to do, the `/bmad-help` command will help you stay on track or know what to do next. You can always refer to this for reference also - but /bmad-help is fully interactive and much quicker if you have already installed the BMad Method. Additionally, if you are using different modules that have extended the BMad Method or added other complementary non-extension modules - the /bmad-help evolves to know all that is available to give you the best in-the-moment advice.
If at anytime you are unsure what to do, the `/bmad-help` command will help you stay on track or know what to do next. You can always refer to this for reference also - but /bmad-help is fully interactive and much quicker if you have already installed the BMadMethod. Additionally, if you are using different modules that have extended the BMad Method or added other complimentary non extension modules - the /bmad-help evolves to know all that is available to give you the best in the moment advice.
Final important note: Every workflow below can be run directly with your tool of choice via slash command or by loading an agent first and using the entry from the agents menu.
<iframe src="/workflow-map-diagram.html" title="BMad Method Workflow Map Diagram" width="100%" height="100%" style="border-radius: 8px; border: 1px solid #334155; min-height: 900px;"></iframe>
<iframe src="/workflow-map-diagram.html" width="100%" height="100%" frameborder="0" style="border-radius: 8px; border: 1px solid #334155; min-height: 900px;"></iframe>
<p style="font-size: 0.8rem; text-align: right; margin-top: -0.5rem; margin-bottom: 1rem;">
<a href="/workflow-map-diagram.html" target="_blank" rel="noopener noreferrer">Open diagram in new tab ↗</a>
</p>
*[Interactive diagram - hover over outputs to see artifact flows]*
## Phase 1: Analysis (Optional)
@ -25,7 +21,7 @@ Explore the problem space and validate ideas before committing to planning.
| Workflow | Purpose | Produces |
| ---------------------- | -------------------------------------------------------------------------- | ------------------------- |
| `brainstorming` | Brainstorm Project Ideas with guided facilitation of a brainstorming coach | `brainstorming-report.md` |
| `brainstorm` | Brainstorm Project Ideas with guided facilitation of a brainstorming coach | `brainstorming-report.md` |
| `research` | Validate market, technical, or domain assumptions | Research findings |
| `create-product-brief` | Capture strategic vision | `product-brief.md` |

View File

@ -37,7 +37,7 @@ BMad helps you build software through guided workflows with specialized AI agent
| 3 | Solutioning | Design architecture *(BMad Method/Enterprise only)* |
| 4 | Implementation | Build epic by epic, story by story |
**[Open the Workflow Map](../reference/workflow-map.md)** to explore phases, workflows, and context management.
**[Open the Workflow Map](/docs/reference/workflow-map.md)** to explore phases, workflows, and context management.
Based on your project's complexity, BMad offers three planning tracks:
@ -153,7 +153,7 @@ You've learned the foundation of building with BMad:
Your project now has:
```text
```
your-project/
├── _bmad/ # BMad configuration
├── _bmad-output/

View File

@ -12,7 +12,11 @@ export default [
'coverage/**',
'**/*.min.js',
'test/template-test-generator/**',
'test/template-test-generator/**/*.js',
'test/template-test-generator/**/*.md',
'test/fixtures/**',
'test/fixtures/**/*.yaml',
'_bmad/**',
'_bmad*/**',
// Build output
'build/**',
@ -32,10 +36,6 @@ export default [
'tools/template-test-generator/test-scenarios/**',
'src/modules/*/sub-modules/**',
'.bundler-temp/**',
// Augment vendor config — not project code, naming conventions
// are dictated by Augment and can't be changed, so exclude
// the entire directory from linting
'.augment/**',
],
},
@ -83,7 +83,7 @@ export default [
// CLI scripts under tools/** and test/**
{
files: ['tools/**/*.js', 'tools/**/*.mjs', 'test/**/*.js', 'test/**/*.mjs'],
files: ['tools/**/*.js', 'tools/**/*.mjs', 'test/**/*.js'],
rules: {
// Allow CommonJS patterns for Node CLI scripts
'unicorn/prefer-module': 'off',
@ -114,6 +114,17 @@ export default [
},
},
// Module installer scripts use CommonJS for compatibility
{
files: ['**/_module-installer/**/*.js'],
rules: {
// Allow CommonJS patterns for installer scripts
'unicorn/prefer-module': 'off',
'n/no-missing-require': 'off',
'n/no-unpublished-require': 'off',
},
},
// ESLint config file should not be checked for publish-related Node rules
{
files: ['eslint.config.mjs'],

856
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/package.json",
"name": "bmad-method",
"version": "6.0.0-Beta.8",
"version": "6.0.0-Beta.7",
"description": "Breakthrough Method of Agile AI-driven Development",
"keywords": [
"agile",
@ -25,11 +25,13 @@
},
"scripts": {
"bmad:install": "node tools/cli/bmad-cli.js install",
"bundle": "node tools/cli/bundlers/bundle-web.js all",
"docs:build": "node tools/build-docs.mjs",
"docs:dev": "astro dev --root website",
"docs:fix-links": "node tools/fix-doc-links.js",
"docs:preview": "astro preview --root website",
"docs:validate-links": "node tools/validate-doc-links.js",
"flatten": "node tools/flattener/main.js",
"format:check": "prettier --check \"**/*.{js,cjs,mjs,json,yaml}\"",
"format:fix": "prettier --write \"**/*.{js,cjs,mjs,json,yaml}\"",
"format:fix:staged": "prettier --write",
@ -39,10 +41,9 @@
"lint:md": "markdownlint-cli2 \"**/*.md\"",
"prepare": "command -v husky >/dev/null 2>&1 && husky || exit 0",
"rebundle": "node tools/cli/bundlers/bundle-web.js rebundle",
"test": "npm run test:schemas && npm run test:refs && npm run test:install && npm run validate:schemas && npm run lint && npm run lint:md && npm run format:check",
"test": "npm run test:schemas && npm run test:install && npm run validate:schemas && npm run lint && npm run lint:md && npm run format:check",
"test:coverage": "c8 --reporter=text --reporter=html npm run test:schemas",
"test:install": "node test/test-installation-components.js",
"test:refs": "node test/test-file-refs-csv.js",
"test:schemas": "node test/test-agent-schema.js",
"validate:refs": "node tools/validate-file-refs.js",
"validate:schemas": "node tools/validate-agent-schema.js"
@ -67,15 +68,20 @@
"@clack/core": "^1.0.0",
"@clack/prompts": "^1.0.0",
"@kayvan/markdown-tree-parser": "^1.6.1",
"boxen": "^5.1.2",
"chalk": "^4.1.2",
"cli-table3": "^0.6.5",
"commander": "^14.0.0",
"csv-parse": "^6.1.0",
"figlet": "^1.8.0",
"fs-extra": "^11.3.0",
"glob": "^11.0.3",
"ignore": "^7.0.5",
"js-yaml": "^4.1.0",
"ora": "^5.4.1",
"picocolors": "^1.1.1",
"semver": "^7.6.3",
"wrap-ansi": "^7.0.0",
"xml2js": "^0.6.2",
"yaml": "^2.7.0"
},

View File

@ -0,0 +1,48 @@
const fs = require('fs-extra');
const path = require('node:path');
const chalk = require('chalk');
// Directories to create from config
const DIRECTORIES = ['output_folder', 'planning_artifacts', 'implementation_artifacts'];
/**
* BMM Module Installer
* Creates output directories configured in module config
*
* @param {Object} options - Installation options
* @param {string} options.projectRoot - The root directory of the target project
* @param {Object} options.config - Module configuration from module.yaml
* @param {Array<string>} options.installedIDEs - Array of IDE codes that were installed
* @param {Object} options.logger - Logger instance for output
* @returns {Promise<boolean>} - Success status
*/
async function install(options) {
const { projectRoot, config, logger } = options;
try {
logger.log(chalk.blue('🚀 Installing BMM Module...'));
// Create configured directories
for (const configKey of DIRECTORIES) {
const configValue = config[configKey];
if (!configValue) continue;
const dirPath = configValue.replace('{project-root}/', '');
const fullPath = path.join(projectRoot, dirPath);
if (!(await fs.pathExists(fullPath))) {
const dirName = configKey.replace('_', ' ');
logger.log(chalk.yellow(`Creating ${dirName} directory: ${dirPath}`));
await fs.ensureDir(fullPath);
}
}
logger.log(chalk.green('✓ BMM Module installation complete'));
return true;
} catch (error) {
logger.error(chalk.red(`Error installing BMM module: ${error.message}`));
return false;
}
}
module.exports = { install };

View File

@ -42,9 +42,3 @@ project_knowledge: # Artifacts from research, document-project output, other lon
prompt: "Where should long-term project knowledge be stored? (docs, research, references)"
default: "docs"
result: "{project-root}/{value}"
# Directories to create during installation (declarative, no code execution)
directories:
- "{planning_artifacts}"
- "{implementation_artifacts}"
- "{project_knowledge}"

View File

@ -0,0 +1,60 @@
const chalk = require('chalk');
/**
* Core Module Installer
* Standard module installer function that executes after IDE installations
*
* @param {Object} options - Installation options
* @param {string} options.projectRoot - The root directory of the target project
* @param {Object} options.config - Module configuration from module.yaml
* @param {Array<string>} options.installedIDEs - Array of IDE codes that were installed
* @param {Object} options.logger - Logger instance for output
* @returns {Promise<boolean>} - Success status
*/
async function install(options) {
const { projectRoot, config, installedIDEs, logger } = options;
try {
logger.log(chalk.blue('🏗️ Installing Core Module...'));
// Core agent configs are created by the main installer's createAgentConfigs method
// No need to create them here - they'll be handled along with all other agents
// Handle IDE-specific configurations if needed
if (installedIDEs && installedIDEs.length > 0) {
logger.log(chalk.cyan(`Configuring Core for IDEs: ${installedIDEs.join(', ')}`));
// Add any IDE-specific Core configurations here
for (const ide of installedIDEs) {
await configureForIDE(ide, projectRoot, config, logger);
}
}
logger.log(chalk.green('✓ Core Module installation complete'));
return true;
} catch (error) {
logger.error(chalk.red(`Error installing Core module: ${error.message}`));
return false;
}
}
/**
* Configure Core module for specific IDE
* @private
*/
async function configureForIDE(ide) {
// Add IDE-specific configurations here
switch (ide) {
case 'claude-code': {
// Claude Code specific Core configurations
break;
}
// Add more IDEs as needed
default: {
// No specific configuration needed
break;
}
}
}
module.exports = { install };

View File

@ -1,3 +0,0 @@
module,phase,name,workflow-file,description
bmm,anytime,Document,,Analyze project
bmm,1-analysis,Brainstorm,,Brainstorm ideas
1 module phase name workflow-file description
2 bmm anytime Document Analyze project
3 bmm 1-analysis Brainstorm Brainstorm ideas

View File

@ -1 +0,0 @@
module,phase,name,workflow-file,description
1 module phase name workflow-file description

View File

@ -1,3 +0,0 @@
name,code,description,agent
brainstorm,BSP,"Generate ideas",analyst
party,PM,"Multi-agent",facilitator
1 name code description agent
2 brainstorm BSP Generate ideas analyst
3 party PM Multi-agent facilitator

View File

@ -1,3 +0,0 @@
module,phase,name,workflow-file,description
bmm,anytime,Template Var,{output_folder}/something.md,Has unresolvable template var
bmm,anytime,Normal Ref,_bmad/core/tasks/help.md,Normal resolvable ref
1 module phase name workflow-file description
2 bmm anytime Template Var {output_folder}/something.md Has unresolvable template var
3 bmm anytime Normal Ref _bmad/core/tasks/help.md Normal resolvable ref

View File

@ -1,3 +0,0 @@
module,phase,name,code,sequence,workflow-file,command,required,agent,options,description,output-location,outputs,
bmm,anytime,Document Project,DP,,_bmad/bmm/workflows/document-project/workflow.yaml,bmad-bmm-document-project,false,analyst,Create Mode,"Analyze project",project-knowledge,*,
bmm,1-analysis,Brainstorm Project,BP,10,_bmad/core/workflows/brainstorming/workflow.md,bmad-brainstorming,false,analyst,data=template.md,"Brainstorming",planning_artifacts,"session",
1 module phase name code sequence workflow-file command required agent options description output-location outputs
2 bmm anytime Document Project DP _bmad/bmm/workflows/document-project/workflow.yaml bmad-bmm-document-project false analyst Create Mode Analyze project project-knowledge *
3 bmm 1-analysis Brainstorm Project BP 10 _bmad/core/workflows/brainstorming/workflow.md bmad-brainstorming false analyst data=template.md Brainstorming planning_artifacts session

View File

@ -1,3 +0,0 @@
module,phase,name,code,sequence,workflow-file,command,required,agent,options,description,output-location,outputs
core,anytime,Brainstorming,BSP,,_bmad/core/workflows/brainstorming/workflow.md,bmad-brainstorming,false,analyst,,"Generate ideas",{output_folder}/brainstorming.md,
core,anytime,Party Mode,PM,,_bmad/core/workflows/party-mode/workflow.md,bmad-party-mode,false,facilitator,,"Multi-agent discussion",,
1 module phase name code sequence workflow-file command required agent options description output-location outputs
2 core anytime Brainstorming BSP _bmad/core/workflows/brainstorming/workflow.md bmad-brainstorming false analyst Generate ideas {output_folder}/brainstorming.md
3 core anytime Party Mode PM _bmad/core/workflows/party-mode/workflow.md bmad-party-mode false facilitator Multi-agent discussion

View File

@ -1,2 +0,0 @@
name,workflow-file,description
test,_bmad/core/tasks/help.md,A test entry
1 name workflow-file description
2 test _bmad/core/tasks/help.md A test entry

View File

@ -1,133 +0,0 @@
/**
* CSV File Reference Extraction Test Runner
*
* Tests extractCsvRefs() from validate-file-refs.js against fixtures.
* Verifies correct extraction of workflow-file references from CSV files.
*
* Usage: node test/test-file-refs-csv.js
* Exit codes: 0 = all tests pass, 1 = test failures
*/
const fs = require('node:fs');
const path = require('node:path');
const { extractCsvRefs } = require('../tools/validate-file-refs.js');
// ANSI color codes
const colors = {
reset: '\u001B[0m',
green: '\u001B[32m',
red: '\u001B[31m',
cyan: '\u001B[36m',
dim: '\u001B[2m',
};
const FIXTURES = path.join(__dirname, 'fixtures/file-refs-csv');
let totalTests = 0;
let passedTests = 0;
const failures = [];
function test(name, fn) {
totalTests++;
try {
fn();
passedTests++;
console.log(` ${colors.green}\u2713${colors.reset} ${name}`);
} catch (error) {
console.log(` ${colors.red}\u2717${colors.reset} ${name} ${colors.red}${error.message}${colors.reset}`);
failures.push({ name, message: error.message });
}
}
function assert(condition, message) {
if (!condition) throw new Error(message);
}
function loadFixture(relativePath) {
const fullPath = path.join(FIXTURES, relativePath);
const content = fs.readFileSync(fullPath, 'utf-8');
return { fullPath, content };
}
// --- Valid fixtures ---
console.log(`\n${colors.cyan}CSV File Reference Extraction Tests${colors.reset}\n`);
console.log(`${colors.cyan}Valid fixtures${colors.reset}`);
test('bmm-style.csv: extracts workflow-file refs with trailing commas', () => {
const { fullPath, content } = loadFixture('valid/bmm-style.csv');
const refs = extractCsvRefs(fullPath, content);
assert(refs.length === 2, `Expected 2 refs, got ${refs.length}`);
assert(refs[0].raw === '_bmad/bmm/workflows/document-project/workflow.yaml', `Wrong raw[0]: ${refs[0].raw}`);
assert(refs[1].raw === '_bmad/core/workflows/brainstorming/workflow.md', `Wrong raw[1]: ${refs[1].raw}`);
assert(refs[0].type === 'project-root', `Wrong type: ${refs[0].type}`);
assert(refs[0].line === 2, `Wrong line for row 0: ${refs[0].line}`);
assert(refs[1].line === 3, `Wrong line for row 1: ${refs[1].line}`);
assert(refs[0].file === fullPath, 'Wrong file path');
});
test('core-style.csv: extracts refs from core module-help format', () => {
const { fullPath, content } = loadFixture('valid/core-style.csv');
const refs = extractCsvRefs(fullPath, content);
assert(refs.length === 2, `Expected 2 refs, got ${refs.length}`);
assert(refs[0].raw === '_bmad/core/workflows/brainstorming/workflow.md', `Wrong raw[0]: ${refs[0].raw}`);
assert(refs[1].raw === '_bmad/core/workflows/party-mode/workflow.md', `Wrong raw[1]: ${refs[1].raw}`);
});
test('minimal.csv: extracts refs from minimal 3-column CSV', () => {
const { fullPath, content } = loadFixture('valid/minimal.csv');
const refs = extractCsvRefs(fullPath, content);
assert(refs.length === 1, `Expected 1 ref, got ${refs.length}`);
assert(refs[0].raw === '_bmad/core/tasks/help.md', `Wrong raw: ${refs[0].raw}`);
assert(refs[0].line === 2, `Wrong line: ${refs[0].line}`);
});
// --- Invalid fixtures ---
console.log(`\n${colors.cyan}Invalid fixtures (expect 0 refs)${colors.reset}`);
test('no-workflow-column.csv: returns 0 refs when workflow-file column missing', () => {
const { fullPath, content } = loadFixture('invalid/no-workflow-column.csv');
const refs = extractCsvRefs(fullPath, content);
assert(refs.length === 0, `Expected 0 refs, got ${refs.length}`);
});
test('empty-data.csv: returns 0 refs when CSV has header only', () => {
const { fullPath, content } = loadFixture('invalid/empty-data.csv');
const refs = extractCsvRefs(fullPath, content);
assert(refs.length === 0, `Expected 0 refs, got ${refs.length}`);
});
test('all-empty-workflow.csv: returns 0 refs when all workflow-file cells empty', () => {
const { fullPath, content } = loadFixture('invalid/all-empty-workflow.csv');
const refs = extractCsvRefs(fullPath, content);
assert(refs.length === 0, `Expected 0 refs, got ${refs.length}`);
});
test('unresolvable-vars.csv: filters out template variables, keeps normal refs', () => {
const { fullPath, content } = loadFixture('invalid/unresolvable-vars.csv');
const refs = extractCsvRefs(fullPath, content);
assert(refs.length === 1, `Expected 1 ref, got ${refs.length}`);
assert(refs[0].raw === '_bmad/core/tasks/help.md', `Wrong raw: ${refs[0].raw}`);
});
// --- Summary ---
console.log(`\n${colors.cyan}${'═'.repeat(55)}${colors.reset}`);
console.log(`${colors.cyan}Test Results:${colors.reset}`);
console.log(` Total: ${totalTests}`);
console.log(` Passed: ${colors.green}${passedTests}${colors.reset}`);
console.log(` Failed: ${passedTests === totalTests ? colors.green : colors.red}${totalTests - passedTests}${colors.reset}`);
console.log(`${colors.cyan}${'═'.repeat(55)}${colors.reset}\n`);
if (failures.length > 0) {
console.log(`${colors.red}FAILED TESTS:${colors.reset}\n`);
for (const failure of failures) {
console.log(`${colors.red}\u2717${colors.reset} ${failure.name}`);
console.log(` ${failure.message}\n`);
}
process.exit(1);
}
console.log(`${colors.green}All tests passed!${colors.reset}\n`);
process.exit(0);

File diff suppressed because it is too large Load Diff

View File

@ -52,12 +52,6 @@ const LLM_EXCLUDE_PATTERNS = [
*/
async function main() {
if (process.platform === 'win32') {
console.error('Error: The docs build pipeline does not support Windows.');
console.error('Please build on Linux, macOS, or WSL.');
process.exit(1);
}
console.log();
printBanner('BMAD Documentation Build Pipeline');
console.log();
@ -124,6 +118,9 @@ function buildAstroSite() {
runAstroBuild();
copyArtifactsToSite(artifactsDir, siteDir);
// No longer needed: Inject AI agents banner into every HTML page
// injectAgentBanner(siteDir);
console.log();
console.log(` \u001B[32m✓\u001B[0m Astro build complete`);
@ -155,18 +152,20 @@ function generateLlmsTxt(outputDir) {
'',
'## Quick Start',
'',
`- **[Getting Started](${siteUrl}/tutorials/getting-started/)** - Tutorial: install and learn how BMad works`,
`- **[Installation](${siteUrl}/how-to/install-bmad/)** - How to install BMad Method`,
`- **[Quick Start](${siteUrl}/docs/modules/bmm/quick-start)** - Get started with BMAD Method`,
`- **[Installation](${siteUrl}/docs/getting-started/installation)** - Installation guide`,
'',
'## Core Concepts',
'',
`- **[Quick Flow](${siteUrl}/explanation/quick-flow/)** - Fast development workflow`,
`- **[Party Mode](${siteUrl}/explanation/party-mode/)** - Multi-agent collaboration`,
`- **[Workflow Map](${siteUrl}/reference/workflow-map/)** - Visual overview of phases and workflows`,
`- **[Scale Adaptive System](${siteUrl}/docs/modules/bmm/scale-adaptive-system)** - Understand BMAD scaling`,
`- **[Quick Flow](${siteUrl}/docs/modules/bmm/bmad-quick-flow)** - Fast development workflow`,
`- **[Party Mode](${siteUrl}/docs/modules/bmm/party-mode)** - Multi-agent collaboration`,
'',
'## Modules',
'',
`- **[Official Modules](${siteUrl}/reference/modules/)** - BMM, BMB, BMGD, and more`,
`- **[BMM - Method](${siteUrl}/docs/modules/bmm/quick-start)** - Core methodology module`,
`- **[BMB - Builder](${siteUrl}/docs/modules/bmb/)** - Agent and workflow builder`,
`- **[BMGD - Game Dev](${siteUrl}/docs/modules/bmgd/quick-start)** - Game development module`,
'',
'---',
'',
@ -402,6 +401,32 @@ function formatFileSize(bytes) {
return `${bytes}B`;
}
// =============================================================================
// Post-build Injection
/**
* Recursively collects all files with the given extension under a directory.
*
* @param {string} dir - Root directory to search.
* @param {string} ext - File extension to match (include the leading dot, e.g. ".md").
* @returns {string[]} An array of file paths for files ending with `ext` found under `dir`.
*/
function getAllFilesByExtension(dir, ext) {
const result = [];
const entries = fs.readdirSync(dir, { withFileTypes: true });
for (const entry of entries) {
const fullPath = path.join(dir, entry.name);
if (entry.isDirectory()) {
result.push(...getAllFilesByExtension(fullPath, ext));
} else if (entry.name.endsWith(ext)) {
result.push(fullPath);
}
}
return result;
}
// =============================================================================
// File System Utilities
/**
@ -419,6 +444,33 @@ function cleanBuildDirectory() {
fs.mkdirSync(BUILD_DIR, { recursive: true });
}
/**
* Recursively copies all files and subdirectories from one directory to another, creating the destination if needed.
*
* @param {string} src - Path to the source directory to copy from.
* @param {string} dest - Path to the destination directory to copy to.
* @param {string[]} [exclude=[]] - List of file or directory names (not paths) to skip while copying.
* @returns {boolean} `true` if the source existed and copying proceeded, `false` if the source did not exist.
*/
function copyDirectory(src, dest, exclude = []) {
if (!fs.existsSync(src)) return false;
fs.mkdirSync(dest, { recursive: true });
for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
if (exclude.includes(entry.name)) continue;
const srcPath = path.join(src, entry.name);
const destPath = path.join(dest, entry.name);
if (entry.isDirectory()) {
copyDirectory(srcPath, destPath, exclude);
} else {
fs.copyFileSync(srcPath, destPath);
}
}
return true;
}
// =============================================================================
// Console Output Formatting
// =============================================================================
@ -444,7 +496,7 @@ function printBanner(title) {
/**
* Verify internal documentation links by running the link-checking script.
*
* Executes the Node script tools/validate-doc-links.js from the project root and
* Executes the Node script tools/check-doc-links.js from the project root and
* exits the process with code 1 if the check fails.
*/

View File

@ -2,14 +2,6 @@ const { program } = require('commander');
const path = require('node:path');
const fs = require('node:fs');
const { execSync } = require('node:child_process');
const prompts = require('./lib/prompts');
// The installer flow uses many sequential @clack/prompts, each adding keypress
// listeners to stdin. Raise the limit to avoid spurious EventEmitter warnings.
if (process.stdin?.setMaxListeners) {
const currentLimit = process.stdin.getMaxListeners();
process.stdin.setMaxListeners(Math.max(currentLimit, 50));
}
// Check for updates - do this asynchronously so it doesn't block startup
const packageJson = require('../../package.json');
@ -35,17 +27,17 @@ async function checkForUpdate() {
}).trim();
if (result && result !== packageJson.version) {
const color = await prompts.getColor();
const updateMsg = [
`You are using version ${packageJson.version} but ${result} is available.`,
'',
'To update, exit and first run:',
` npm cache clean --force && npx bmad-method@${tag} install`,
].join('\n');
await prompts.box(updateMsg, 'Update Available', {
rounded: true,
formatBorder: color.yellow,
});
console.warn('');
console.warn(' ╔═══════════════════════════════════════════════════════════════════════════════╗');
console.warn(' ║ UPDATE AVAILABLE ║');
console.warn(' ║ ║');
console.warn(` ║ You are using version ${packageJson.version} but ${result} is available. ║`);
console.warn(' ║ ║');
console.warn(' ║ To update,exir and first run: ║');
console.warn(` ║ npm cache clean --force && npx bmad-method@${tag} install ║`);
console.warn(' ║ ║');
console.warn(' ╚═══════════════════════════════════════════════════════════════════════════════╝');
console.warn('');
}
} catch {
// Silently fail - network issues or npm not available

View File

@ -1,5 +1,5 @@
const chalk = require('chalk');
const path = require('node:path');
const prompts = require('../lib/prompts');
const { Installer } = require('../installers/lib/core/installer');
const { UI } = require('../lib/ui');
@ -30,14 +30,14 @@ module.exports = {
// Set debug flag as environment variable for all components
if (options.debug) {
process.env.BMAD_DEBUG_MANIFEST = 'true';
await prompts.log.info('Debug mode enabled');
console.log(chalk.cyan('Debug mode enabled\n'));
}
const config = await ui.promptInstall(options);
// Handle cancel
if (config.actionType === 'cancel') {
await prompts.log.warn('Installation cancelled.');
console.log(chalk.yellow('Installation cancelled.'));
process.exit(0);
return;
}
@ -45,13 +45,13 @@ module.exports = {
// Handle quick update separately
if (config.actionType === 'quick-update') {
const result = await installer.quickUpdate(config);
await prompts.log.success('Quick update complete!');
await prompts.log.info(`Updated ${result.moduleCount} modules with preserved settings (${result.modules.join(', ')})`);
console.log(chalk.green('\n✨ Quick update complete!'));
console.log(chalk.cyan(`Updated ${result.moduleCount} modules with preserved settings (${result.modules.join(', ')})`));
// Display version-specific end message
const { MessageLoader } = require('../installers/lib/message-loader');
const messageLoader = new MessageLoader();
await messageLoader.displayEndMessage();
messageLoader.displayEndMessage();
process.exit(0);
return;
@ -60,8 +60,8 @@ module.exports = {
// Handle compile agents separately
if (config.actionType === 'compile-agents') {
const result = await installer.compileAgents(config);
await prompts.log.success('Agent recompilation complete!');
await prompts.log.info(`Recompiled ${result.agentCount} agents with customizations applied`);
console.log(chalk.green('\n✨ Agent recompilation complete!'));
console.log(chalk.cyan(`Recompiled ${result.agentCount} agents with customizations applied`));
process.exit(0);
return;
}
@ -80,22 +80,21 @@ module.exports = {
// Display version-specific end message from install-messages.yaml
const { MessageLoader } = require('../installers/lib/message-loader');
const messageLoader = new MessageLoader();
await messageLoader.displayEndMessage();
messageLoader.displayEndMessage();
process.exit(0);
}
} catch (error) {
try {
// Check if error has a complete formatted message
if (error.fullMessage) {
await prompts.log.error(error.fullMessage);
} else {
await prompts.log.error(`Installation failed: ${error.message}`);
}
console.error(error.fullMessage);
if (error.stack) {
await prompts.log.message(error.stack);
console.error('\n' + chalk.dim(error.stack));
}
} catch {
console.error(error.fullMessage || error.message || error);
} else {
// Generic error handling for all other errors
console.error(chalk.red('Installation failed:'), error.message);
console.error(chalk.dim(error.stack));
}
process.exit(1);
}

View File

@ -1,5 +1,5 @@
const chalk = require('chalk');
const path = require('node:path');
const prompts = require('../lib/prompts');
const { Installer } = require('../installers/lib/core/installer');
const { Manifest } = require('../installers/lib/core/manifest');
const { UI } = require('../lib/ui');
@ -21,9 +21,9 @@ module.exports = {
// Check if bmad directory exists
const fs = require('fs-extra');
if (!(await fs.pathExists(bmadDir))) {
await prompts.log.warn('No BMAD installation found in the current directory.');
await prompts.log.message(`Expected location: ${bmadDir}`);
await prompts.log.message('Run "bmad install" to set up a new installation.');
console.log(chalk.yellow('No BMAD installation found in the current directory.'));
console.log(chalk.dim(`Expected location: ${bmadDir}`));
console.log(chalk.dim('\nRun "bmad install" to set up a new installation.'));
process.exit(0);
return;
}
@ -32,8 +32,8 @@ module.exports = {
const manifestData = await manifest._readRaw(bmadDir);
if (!manifestData) {
await prompts.log.warn('No BMAD installation manifest found.');
await prompts.log.message('Run "bmad install" to set up a new installation.');
console.log(chalk.yellow('No BMAD installation manifest found.'));
console.log(chalk.dim('\nRun "bmad install" to set up a new installation.'));
process.exit(0);
return;
}
@ -46,7 +46,7 @@ module.exports = {
const availableUpdates = await manifest.checkForUpdates(bmadDir);
// Display status
await ui.displayStatus({
ui.displayStatus({
installation,
modules,
availableUpdates,
@ -55,9 +55,9 @@ module.exports = {
process.exit(0);
} catch (error) {
await prompts.log.error(`Status check failed: ${error.message}`);
console.error(chalk.red('Status check failed:'), error.message);
if (process.env.BMAD_DEBUG) {
await prompts.log.message(error.stack);
console.error(chalk.dim(error.stack));
}
process.exit(1);
}

View File

@ -42,12 +42,13 @@ modules:
type: bmad-org
npmPackage: bmad-method-test-architecture-enterprise
# TODO: Enable once fixes applied:
# whiteport-design-system:
# url: https://github.com/bmad-code-org/bmad-method-wds-expansion
# module-definition: src/module.yaml
# code: wds
# code: WDS
# name: "Whiteport UX Design System"
# description: "UX design framework with Figma integration"
# defaultSelected: false
# type: community
# npmPackage: bmad-method-wds-expansion

View File

@ -1,6 +1,7 @@
const path = require('node:path');
const fs = require('fs-extra');
const yaml = require('yaml');
const chalk = require('chalk');
const { getProjectRoot, getModulePath } = require('../../../lib/project-root');
const { CLIUtils } = require('../../../lib/cli-utils');
const prompts = require('../../../lib/prompts');
@ -188,18 +189,20 @@ class ConfigCollector {
this.allAnswers = {};
}
// Load module's config schema from module.yaml
// Load module's install config schema
// First, try the standard src/modules location
let installerConfigPath = path.join(getModulePath(moduleName), '_module-installer', 'module.yaml');
let moduleConfigPath = path.join(getModulePath(moduleName), 'module.yaml');
// If not found in src/modules, we need to find it by searching the project
if (!(await fs.pathExists(moduleConfigPath))) {
if (!(await fs.pathExists(installerConfigPath)) && !(await fs.pathExists(moduleConfigPath))) {
// Use the module manager to find the module source
const { ModuleManager } = require('../modules/manager');
const moduleManager = new ModuleManager();
const moduleSourcePath = await moduleManager.findModuleSource(moduleName);
if (moduleSourcePath) {
installerConfigPath = path.join(moduleSourcePath, '_module-installer', 'module.yaml');
moduleConfigPath = path.join(moduleSourcePath, 'module.yaml');
}
}
@ -209,6 +212,8 @@ class ConfigCollector {
if (await fs.pathExists(moduleConfigPath)) {
configPath = moduleConfigPath;
} else if (await fs.pathExists(installerConfigPath)) {
configPath = installerConfigPath;
} else {
// Check if this is a custom module with custom.yaml
const { ModuleManager } = require('../modules/manager');
@ -217,8 +222,9 @@ class ConfigCollector {
if (moduleSourcePath) {
const rootCustomConfigPath = path.join(moduleSourcePath, 'custom.yaml');
const moduleInstallerCustomPath = path.join(moduleSourcePath, '_module-installer', 'custom.yaml');
if (await fs.pathExists(rootCustomConfigPath)) {
if ((await fs.pathExists(rootCustomConfigPath)) || (await fs.pathExists(moduleInstallerCustomPath))) {
isCustomModule = true;
// For custom modules, we don't have an install-config schema, so just use existing values
// The custom.yaml values will be loaded and merged during installation
@ -254,9 +260,15 @@ class ConfigCollector {
// If module has no config keys at all, handle it specially
if (hasNoConfig && moduleConfig.subheader) {
// Add blank line for better readability (matches other modules)
console.log();
const moduleDisplayName = moduleConfig.header || `${moduleName.toUpperCase()} Module`;
await prompts.log.step(moduleDisplayName);
await prompts.log.message(` \u2713 ${moduleConfig.subheader}`);
// Display the module name in color first (matches other modules)
console.log(chalk.cyan('?') + ' ' + chalk.magenta(moduleDisplayName));
// Show the subheader since there's no configuration to ask about
console.log(chalk.dim(`${moduleConfig.subheader}`));
return false; // No new fields
}
@ -310,7 +322,7 @@ class ConfigCollector {
}
// Show "no config" message for modules with no new questions (that have config keys)
await prompts.log.message(` \u2713 ${moduleName.toUpperCase()} module already up to date`);
console.log(chalk.dim(` ${moduleName.toUpperCase()} module already up to date`));
return false; // No new fields
}
@ -338,15 +350,15 @@ class ConfigCollector {
if (questions.length > 0) {
// Only show header if we actually have questions
await CLIUtils.displayModuleConfigHeader(moduleName, moduleConfig.header, moduleConfig.subheader);
await prompts.log.message('');
CLIUtils.displayModuleConfigHeader(moduleName, moduleConfig.header, moduleConfig.subheader);
console.log(); // Line break before questions
const promptedAnswers = await prompts.prompt(questions);
// Merge prompted answers with static answers
Object.assign(allAnswers, promptedAnswers);
} else if (newStaticKeys.length > 0) {
// Only static fields, no questions - show no config message
await prompts.log.message(` \u2713 ${moduleName.toUpperCase()} module configuration updated`);
console.log(chalk.dim(` ${moduleName.toUpperCase()} module configuration updated`));
}
// Store all answers for cross-referencing
@ -495,24 +507,28 @@ class ConfigCollector {
}
// Load module's config
// First, check if we have a custom module path for this module
let installerConfigPath = null;
let moduleConfigPath = null;
if (this.customModulePaths && this.customModulePaths.has(moduleName)) {
const customPath = this.customModulePaths.get(moduleName);
installerConfigPath = path.join(customPath, '_module-installer', 'module.yaml');
moduleConfigPath = path.join(customPath, 'module.yaml');
} else {
// Try the standard src/modules location
installerConfigPath = path.join(getModulePath(moduleName), '_module-installer', 'module.yaml');
moduleConfigPath = path.join(getModulePath(moduleName), 'module.yaml');
}
// If not found in src/modules or custom paths, search the project
if (!(await fs.pathExists(moduleConfigPath))) {
if (!(await fs.pathExists(installerConfigPath)) && !(await fs.pathExists(moduleConfigPath))) {
// Use the module manager to find the module source
const { ModuleManager } = require('../modules/manager');
const moduleManager = new ModuleManager();
const moduleSourcePath = await moduleManager.findModuleSource(moduleName);
if (moduleSourcePath) {
installerConfigPath = path.join(moduleSourcePath, '_module-installer', 'module.yaml');
moduleConfigPath = path.join(moduleSourcePath, 'module.yaml');
}
}
@ -520,6 +536,8 @@ class ConfigCollector {
let configPath = null;
if (await fs.pathExists(moduleConfigPath)) {
configPath = moduleConfigPath;
} else if (await fs.pathExists(installerConfigPath)) {
configPath = installerConfigPath;
} else {
// No config for this module
return;
@ -570,7 +588,7 @@ class ConfigCollector {
// Skip prompts mode: use all defaults without asking
if (this.skipPrompts) {
await prompts.log.info(`Using default configuration for ${moduleDisplayName}`);
console.log(chalk.cyan('Using default configuration for'), chalk.magenta(moduleDisplayName));
// Use defaults for all questions
for (const question of questions) {
const hasDefault = question.default !== undefined && question.default !== null && question.default !== '';
@ -579,10 +597,12 @@ class ConfigCollector {
}
}
} else {
await prompts.log.step(moduleDisplayName);
console.log();
console.log(chalk.cyan('?') + ' ' + chalk.magenta(moduleDisplayName));
let customize = true;
if (moduleName === 'core') {
// Core module: no confirm prompt, continues directly
// Core module: no confirm prompt, so add spacing manually to match visual style
console.log(chalk.gray('│'));
} else {
// Non-core modules: show "Accept Defaults?" confirm prompt (clack adds spacing)
const customizeAnswer = await prompts.prompt([
@ -601,7 +621,7 @@ class ConfigCollector {
const questionsWithoutDefaults = questions.filter((q) => q.default === undefined || q.default === null || q.default === '');
if (questionsWithoutDefaults.length > 0) {
await prompts.log.message(` Asking required questions for ${moduleName.toUpperCase()}...`);
console.log(chalk.dim(`\n Asking required questions for ${moduleName.toUpperCase()}...`));
const promptedAnswers = await prompts.prompt(questionsWithoutDefaults);
Object.assign(allAnswers, promptedAnswers);
}
@ -727,15 +747,32 @@ class ConfigCollector {
const hasNoConfig = actualConfigKeys.length === 0;
if (hasNoConfig && (moduleConfig.subheader || moduleConfig.header)) {
await prompts.log.step(moduleDisplayName);
// Module explicitly has no configuration - show with special styling
// Add blank line for better readability (matches other modules)
console.log();
// Display the module name in color first (matches other modules)
console.log(chalk.cyan('?') + ' ' + chalk.magenta(moduleDisplayName));
// Ask user if they want to accept defaults or customize on the next line
const { customize } = await prompts.prompt([
{
type: 'confirm',
name: 'customize',
message: 'Accept Defaults (no to customize)?',
default: true,
},
]);
// Show the subheader if available, otherwise show a default message
if (moduleConfig.subheader) {
await prompts.log.message(` \u2713 ${moduleConfig.subheader}`);
console.log(chalk.dim(` ${moduleConfig.subheader}`));
} else {
await prompts.log.message(` \u2713 No custom configuration required`);
console.log(chalk.dim(` ✓ No custom configuration required`));
}
} else {
// Module has config but just no questions to ask
await prompts.log.message(` \u2713 ${moduleName.toUpperCase()} module configured`);
console.log(chalk.dim(` ${moduleName.toUpperCase()} module configured`));
}
}
@ -944,15 +981,14 @@ class ConfigCollector {
}
// Add current value indicator for existing configs
const color = await prompts.getColor();
if (existingValue !== null && existingValue !== undefined) {
if (typeof existingValue === 'boolean') {
message += color.dim(` (current: ${existingValue ? 'true' : 'false'})`);
message += chalk.dim(` (current: ${existingValue ? 'true' : 'false'})`);
} else if (Array.isArray(existingValue)) {
message += color.dim(` (current: ${existingValue.join(', ')})`);
message += chalk.dim(` (current: ${existingValue.join(', ')})`);
} else if (questionType !== 'list') {
// Show the cleaned value (without {project-root}/) for display
message += color.dim(` (current: ${existingValue})`);
message += chalk.dim(` (current: ${existingValue})`);
}
} else if (item.example && questionType === 'input') {
// Show example for input fields
@ -962,7 +998,7 @@ class ConfigCollector {
exampleText = this.replacePlaceholders(exampleText, moduleName, moduleConfig);
exampleText = exampleText.replace('{project-root}/', '');
}
message += color.dim(` (e.g., ${exampleText})`);
message += chalk.dim(` (e.g., ${exampleText})`);
}
// Build the question object

View File

@ -1,8 +1,8 @@
const fs = require('fs-extra');
const path = require('node:path');
const glob = require('glob');
const chalk = require('chalk');
const yaml = require('yaml');
const prompts = require('../../../lib/prompts');
/**
* Dependency Resolver for BMAD modules
@ -24,7 +24,7 @@ class DependencyResolver {
*/
async resolve(bmadDir, selectedModules = [], options = {}) {
if (options.verbose) {
await prompts.log.info('Resolving module dependencies...');
console.log(chalk.cyan('Resolving module dependencies...'));
}
// Always include core as base
@ -50,7 +50,7 @@ class DependencyResolver {
// Report results (only in verbose mode)
if (options.verbose) {
await this.reportResults(organizedFiles, selectedModules);
this.reportResults(organizedFiles, selectedModules);
}
return {
@ -90,12 +90,8 @@ class DependencyResolver {
}
}
if (!moduleDir) {
continue;
}
if (!(await fs.pathExists(moduleDir))) {
await prompts.log.warn('Module directory not found: ' + moduleDir);
console.warn(chalk.yellow(`Module directory not found: ${moduleDir}`));
continue;
}
@ -183,7 +179,7 @@ class DependencyResolver {
}
}
} catch (error) {
await prompts.log.warn('Failed to parse frontmatter in ' + file.name + ': ' + error.message);
console.warn(chalk.yellow(`Failed to parse frontmatter in ${file.name}: ${error.message}`));
}
}
@ -662,8 +658,8 @@ class DependencyResolver {
/**
* Report resolution results
*/
async reportResults(organized, selectedModules) {
await prompts.log.success('Dependency resolution complete');
reportResults(organized, selectedModules) {
console.log(chalk.green('\n✓ Dependency resolution complete'));
for (const [module, files] of Object.entries(organized)) {
const isSelected = selectedModules.includes(module) || module === 'core';
@ -671,31 +667,31 @@ class DependencyResolver {
files.agents.length + files.tasks.length + files.tools.length + files.templates.length + files.data.length + files.other.length;
if (totalFiles > 0) {
await prompts.log.info(` ${module.toUpperCase()} module:`);
await prompts.log.message(` Status: ${isSelected ? 'Selected' : 'Dependencies only'}`);
console.log(chalk.cyan(`\n ${module.toUpperCase()} module:`));
console.log(chalk.dim(` Status: ${isSelected ? 'Selected' : 'Dependencies only'}`));
if (files.agents.length > 0) {
await prompts.log.message(` Agents: ${files.agents.length}`);
console.log(chalk.dim(` Agents: ${files.agents.length}`));
}
if (files.tasks.length > 0) {
await prompts.log.message(` Tasks: ${files.tasks.length}`);
console.log(chalk.dim(` Tasks: ${files.tasks.length}`));
}
if (files.templates.length > 0) {
await prompts.log.message(` Templates: ${files.templates.length}`);
console.log(chalk.dim(` Templates: ${files.templates.length}`));
}
if (files.data.length > 0) {
await prompts.log.message(` Data files: ${files.data.length}`);
console.log(chalk.dim(` Data files: ${files.data.length}`));
}
if (files.other.length > 0) {
await prompts.log.message(` Other files: ${files.other.length}`);
console.log(chalk.dim(` Other files: ${files.other.length}`));
}
}
}
if (this.missingDependencies.size > 0) {
await prompts.log.warn('Missing dependencies:');
console.log(chalk.yellow('\n ⚠ Missing dependencies:'));
for (const missing of this.missingDependencies) {
await prompts.log.warn(` - ${missing}`);
console.log(chalk.yellow(` - ${missing}`));
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +1,7 @@
const path = require('node:path');
const fs = require('fs-extra');
const chalk = require('chalk');
const yaml = require('yaml');
const prompts = require('../../../lib/prompts');
const { FileOps } = require('../../../lib/file-ops');
const { XmlHandler } = require('../../../lib/xml-handler');
@ -55,7 +55,7 @@ class CustomHandler {
// Found a custom.yaml file
customPaths.push(fullPath);
} else if (
entry.name === 'module.yaml' && // Check if this is a custom module (in root directory)
entry.name === 'module.yaml' && // Check if this is a custom module (either in _module-installer or in root directory)
// Skip if it's in src/modules (those are standard modules)
!fullPath.includes(path.join('src', 'modules'))
) {
@ -88,7 +88,7 @@ class CustomHandler {
try {
config = yaml.parse(configContent);
} catch (parseError) {
await prompts.log.warn('YAML parse error in ' + configPath + ': ' + parseError.message);
console.warn(chalk.yellow(`Warning: YAML parse error in ${configPath}:`, parseError.message));
return null;
}
@ -111,7 +111,7 @@ class CustomHandler {
isInstallConfig: isInstallConfig, // Track which type this is
};
} catch (error) {
await prompts.log.warn('Failed to read ' + configPath + ': ' + error.message);
console.warn(chalk.yellow(`Warning: Failed to read ${configPath}:`, error.message));
return null;
}
}
@ -268,13 +268,14 @@ class CustomHandler {
}
results.filesCopied++;
if (entry.name.endsWith('.md')) {
results.workflowsInstalled++;
}
if (fileTrackingCallback) {
fileTrackingCallback(targetPath);
}
}
if (entry.name.endsWith('.md')) {
results.workflowsInstalled++;
}
} catch (error) {
results.errors.push(`Failed to copy ${entry.name}: ${error.message}`);
}
@ -321,7 +322,7 @@ class CustomHandler {
await fs.writeFile(customizePath, templateContent, 'utf8');
// Only show customize creation in verbose mode
if (process.env.BMAD_VERBOSE_INSTALL === 'true') {
await prompts.log.message(' Created customize: custom-' + agentName + '.customize.yaml');
console.log(chalk.dim(` Created customize: custom-${agentName}.customize.yaml`));
}
}
}
@ -345,10 +346,14 @@ class CustomHandler {
// Only show compilation details in verbose mode
if (process.env.BMAD_VERBOSE_INSTALL === 'true') {
await prompts.log.message(' Compiled agent: ' + agentName + ' -> ' + path.relative(targetAgentsPath, targetMdPath));
console.log(
chalk.dim(
` Compiled agent: ${agentName} -> ${path.relative(targetAgentsPath, targetMdPath)}${hasSidecar ? ' (with sidecar)' : ''}`,
),
);
}
} catch (error) {
await prompts.log.warn(' Failed to compile agent ' + agentName + ': ' + error.message);
console.warn(chalk.yellow(` Failed to compile agent ${agentName}:`, error.message));
results.errors.push(`Failed to compile agent ${agentName}: ${error.message}`);
}
}

View File

@ -1,7 +1,7 @@
const path = require('node:path');
const fs = require('fs-extra');
const chalk = require('chalk');
const { XmlHandler } = require('../../../lib/xml-handler');
const prompts = require('../../../lib/prompts');
const { getSourcePath } = require('../../../lib/project-root');
const { BMAD_FOLDER_NAME } = require('./shared/path-utils');
@ -53,7 +53,7 @@ class BaseIdeSetup {
* Cleanup IDE configuration
* @param {string} projectDir - Project directory
*/
async cleanup(projectDir, options = {}) {
async cleanup(projectDir) {
// Default implementation - can be overridden
if (this.configDir) {
const configPath = path.join(projectDir, this.configDir);
@ -61,7 +61,7 @@ class BaseIdeSetup {
const bmadRulesPath = path.join(configPath, BMAD_FOLDER_NAME);
if (await fs.pathExists(bmadRulesPath)) {
await fs.remove(bmadRulesPath);
if (!options.silent) await prompts.log.message(`Removed ${this.name} BMAD configuration`);
console.log(chalk.dim(`Removed ${this.name} BMAD configuration`));
}
}
}

View File

@ -1,7 +1,7 @@
const path = require('node:path');
const fs = require('fs-extra');
const chalk = require('chalk');
const { BaseIdeSetup } = require('./_base-ide');
const prompts = require('../../../lib/prompts');
const { AgentCommandGenerator } = require('./shared/agent-command-generator');
const { WorkflowCommandGenerator } = require('./shared/workflow-command-generator');
const { TaskToolCommandGenerator } = require('./shared/task-tool-command-generator');
@ -34,10 +34,10 @@ class ConfigDrivenIdeSetup extends BaseIdeSetup {
* @returns {Promise<Object>} Setup result
*/
async setup(projectDir, bmadDir, options = {}) {
if (!options.silent) await prompts.log.info(`Setting up ${this.name}...`);
console.log(chalk.cyan(`Setting up ${this.name}...`));
// Clean up any old BMAD installation first
await this.cleanup(projectDir, options);
await this.cleanup(projectDir);
if (!this.installerConfig) {
return { success: false, reason: 'no-config' };
@ -102,7 +102,7 @@ class ConfigDrivenIdeSetup extends BaseIdeSetup {
results.tools = taskToolResult.tools || 0;
}
await this.printSummary(results, target_dir, options);
this.printSummary(results, target_dir);
return { success: true, results };
}
@ -439,28 +439,32 @@ LOAD and execute from: {project-root}/{{bmadFolderName}}/{{path}}
* @param {Object} results - Installation results
* @param {string} targetDir - Target directory (relative)
*/
async printSummary(results, targetDir, options = {}) {
if (options.silent) return;
const parts = [];
if (results.agents > 0) parts.push(`${results.agents} agents`);
if (results.workflows > 0) parts.push(`${results.workflows} workflows`);
if (results.tasks > 0) parts.push(`${results.tasks} tasks`);
if (results.tools > 0) parts.push(`${results.tools} tools`);
await prompts.log.success(`${this.name} configured: ${parts.join(', ')}${targetDir}`);
printSummary(results, targetDir) {
console.log(chalk.green(`\n${this.name} configured:`));
if (results.agents > 0) {
console.log(chalk.dim(` - ${results.agents} agents installed`));
}
if (results.workflows > 0) {
console.log(chalk.dim(` - ${results.workflows} workflow commands generated`));
}
if (results.tasks > 0 || results.tools > 0) {
console.log(chalk.dim(` - ${results.tasks + results.tools} task/tool commands generated`));
}
console.log(chalk.dim(` - Destination: ${targetDir}`));
}
/**
* Cleanup IDE configuration
* @param {string} projectDir - Project directory
*/
async cleanup(projectDir, options = {}) {
async cleanup(projectDir) {
// Clean all target directories
if (this.installerConfig?.targets) {
for (const target of this.installerConfig.targets) {
await this.cleanupTarget(projectDir, target.target_dir, options);
await this.cleanupTarget(projectDir, target.target_dir);
}
} else if (this.installerConfig?.target_dir) {
await this.cleanupTarget(projectDir, this.installerConfig.target_dir, options);
await this.cleanupTarget(projectDir, this.installerConfig.target_dir);
}
}
@ -469,7 +473,7 @@ LOAD and execute from: {project-root}/{{bmadFolderName}}/{{path}}
* @param {string} projectDir - Project directory
* @param {string} targetDir - Target directory to clean
*/
async cleanupTarget(projectDir, targetDir, options = {}) {
async cleanupTarget(projectDir, targetDir) {
const targetPath = path.join(projectDir, targetDir);
if (!(await fs.pathExists(targetPath))) {
@ -492,22 +496,25 @@ LOAD and execute from: {project-root}/{{bmadFolderName}}/{{path}}
let removedCount = 0;
for (const entry of entries) {
// Skip non-strings or undefined entries
if (!entry || typeof entry !== 'string') {
continue;
}
if (entry.startsWith('bmad')) {
const entryPath = path.join(targetPath, entry);
try {
const stat = await fs.stat(entryPath);
if (stat.isFile()) {
await fs.remove(entryPath);
removedCount++;
} else if (stat.isDirectory()) {
await fs.remove(entryPath);
removedCount++;
} catch {
// Skip entries that can't be removed (broken symlinks, permission errors)
}
}
}
if (removedCount > 0 && !options.silent) {
await prompts.log.message(` Cleaned ${removedCount} BMAD files from ${targetDir}`);
if (removedCount > 0) {
console.log(chalk.dim(` Cleaned ${removedCount} BMAD files from ${targetDir}`));
}
}
}

View File

@ -1,6 +1,7 @@
const path = require('node:path');
const fs = require('fs-extra');
const os = require('node:os');
const chalk = require('chalk');
const { BaseIdeSetup } = require('./_base-ide');
const { WorkflowCommandGenerator } = require('./shared/workflow-command-generator');
const { AgentCommandGenerator } = require('./shared/agent-command-generator');
@ -42,11 +43,12 @@ class CodexSetup extends BaseIdeSetup {
default: 'global',
});
// Show brief confirmation hint (detailed instructions available via verbose)
// Display detailed instructions for the chosen option
console.log('');
if (installLocation === 'project') {
await prompts.log.info('Prompts installed to: <project>/.codex/prompts (requires CODEX_HOME)');
console.log(this.getProjectSpecificInstructions());
} else {
await prompts.log.info('Prompts installed to: ~/.codex/prompts');
console.log(this.getGlobalInstructions());
}
// Confirm the choice
@ -56,7 +58,7 @@ class CodexSetup extends BaseIdeSetup {
});
if (!confirmed) {
await prompts.log.warn("Let's choose a different installation option.");
console.log(chalk.yellow("\n Let's choose a different installation option.\n"));
}
}
@ -70,7 +72,7 @@ class CodexSetup extends BaseIdeSetup {
* @param {Object} options - Setup options
*/
async setup(projectDir, bmadDir, options = {}) {
if (!options.silent) await prompts.log.info(`Setting up ${this.name}...`);
console.log(chalk.cyan(`Setting up ${this.name}...`));
// Always use CLI mode
const mode = 'cli';
@ -82,7 +84,7 @@ class CodexSetup extends BaseIdeSetup {
const destDir = this.getCodexPromptDir(projectDir, installLocation);
await fs.ensureDir(destDir);
await this.clearOldBmadFiles(destDir, options);
await this.clearOldBmadFiles(destDir);
// Collect artifacts and write using underscore format
const agentGen = new AgentCommandGenerator(this.bmadFolderName);
@ -122,11 +124,16 @@ class CodexSetup extends BaseIdeSetup {
const written = agentCount + workflowCount + tasksWritten;
if (!options.silent) {
await prompts.log.success(
`${this.name} configured: ${counts.agents} agents, ${counts.workflows} workflows, ${counts.tasks} tasks, ${written} files → ${destDir}`,
);
console.log(chalk.green(`${this.name} configured:`));
console.log(chalk.dim(` - Mode: CLI`));
console.log(chalk.dim(` - ${counts.agents} agents exported`));
console.log(chalk.dim(` - ${counts.tasks} tasks exported`));
console.log(chalk.dim(` - ${counts.workflows} workflow commands exported`));
if (counts.workflowLaunchers > 0) {
console.log(chalk.dim(` - ${counts.workflowLaunchers} workflow launchers exported`));
}
console.log(chalk.dim(` - ${written} Codex prompt files written`));
console.log(chalk.dim(` - Destination: ${destDir}`));
return {
success: true,
@ -255,7 +262,7 @@ class CodexSetup extends BaseIdeSetup {
return written;
}
async clearOldBmadFiles(destDir, options = {}) {
async clearOldBmadFiles(destDir) {
if (!(await fs.pathExists(destDir))) {
return;
}
@ -265,7 +272,7 @@ class CodexSetup extends BaseIdeSetup {
entries = await fs.readdir(destDir);
} catch (error) {
// Directory exists but can't be read - skip cleanup
if (!options.silent) await prompts.log.warn(`Warning: Could not read directory ${destDir}: ${error.message}`);
console.warn(chalk.yellow(`Warning: Could not read directory ${destDir}: ${error.message}`));
return;
}
@ -284,11 +291,15 @@ class CodexSetup extends BaseIdeSetup {
const entryPath = path.join(destDir, entry);
try {
const stat = await fs.stat(entryPath);
if (stat.isFile()) {
await fs.remove(entryPath);
} else if (stat.isDirectory()) {
await fs.remove(entryPath);
} catch (error) {
if (!options.silent) {
await prompts.log.message(` Skipping ${entry}: ${error.message}`);
}
} catch (error) {
// Skip files that can't be processed
console.warn(chalk.dim(` Skipping ${entry}: ${error.message}`));
}
}
}
@ -304,16 +315,22 @@ class CodexSetup extends BaseIdeSetup {
*/
getGlobalInstructions(destDir) {
const lines = [
'IMPORTANT: Codex Configuration',
'',
'/prompts installed globally to your HOME DIRECTORY.',
chalk.bold.cyan('═'.repeat(70)),
chalk.bold.yellow(' IMPORTANT: Codex Configuration'),
chalk.bold.cyan('═'.repeat(70)),
'',
'These prompts reference a specific _bmad path.',
"To use with other projects, you'd need to copy the _bmad dir.",
chalk.white(' /prompts installed globally to your HOME DIRECTORY.'),
'',
chalk.yellow(' ⚠️ These prompts reference a specific _bmad path'),
chalk.dim(" To use with other projects, you'd need to copy the _bmad dir"),
'',
chalk.green(' ✓ You can now use /commands in Codex CLI'),
chalk.dim(' Example: /bmad_bmm_pm'),
chalk.dim(' Type / to see all available commands'),
'',
chalk.bold.cyan('═'.repeat(70)),
'',
'You can now use /commands in Codex CLI',
' Example: /bmad_bmm_pm',
' Type / to see all available commands',
];
return lines.join('\n');
}
@ -328,34 +345,43 @@ class CodexSetup extends BaseIdeSetup {
const isWindows = os.platform() === 'win32';
const commonLines = [
'Project-Specific Codex Configuration',
'',
`Prompts will be installed to: ${destDir || '<project>/.codex/prompts'}`,
chalk.bold.cyan('═'.repeat(70)),
chalk.bold.yellow(' Project-Specific Codex Configuration'),
chalk.bold.cyan('═'.repeat(70)),
'',
'REQUIRED: You must set CODEX_HOME to use these prompts',
chalk.white(' Prompts will be installed to: ') + chalk.cyan(destDir || '<project>/.codex/prompts'),
'',
chalk.bold.yellow(' ⚠️ REQUIRED: You must set CODEX_HOME to use these prompts'),
'',
];
const windowsLines = [
'Create a codex.cmd file in your project root:',
chalk.bold(' Create a codex.cmd file in your project root:'),
'',
' @echo off',
' set CODEX_HOME=%~dp0.codex',
' codex %*',
chalk.green(' @echo off'),
chalk.green(' set CODEX_HOME=%~dp0.codex'),
chalk.green(' codex %*'),
'',
String.raw`Then run: .\codex instead of codex`,
'(The %~dp0 gets the directory of the .cmd file)',
chalk.dim(String.raw` Then run: .\codex instead of codex`),
chalk.dim(' (The %~dp0 gets the directory of the .cmd file)'),
];
const unixLines = [
'Add this alias to your ~/.bashrc or ~/.zshrc:',
chalk.bold(' Add this alias to your ~/.bashrc or ~/.zshrc:'),
'',
' alias codex=\'CODEX_HOME="$PWD/.codex" codex\'',
chalk.green(' alias codex=\'CODEX_HOME="$PWD/.codex" codex\''),
'',
chalk.dim(' After adding, run: source ~/.bashrc (or source ~/.zshrc)'),
chalk.dim(' (The $PWD uses your current working directory)'),
];
const closingLines = [
'',
chalk.dim(' This tells Codex CLI to use prompts from this project instead of ~/.codex'),
'',
chalk.bold.cyan('═'.repeat(70)),
'',
'After adding, run: source ~/.bashrc (or source ~/.zshrc)',
'(The $PWD uses your current working directory)',
];
const closingLines = ['', 'This tells Codex CLI to use prompts from this project instead of ~/.codex'];
const lines = [...commonLines, ...(isWindows ? windowsLines : unixLines), ...closingLines];

View File

@ -1,7 +1,7 @@
const path = require('node:path');
const { BaseIdeSetup } = require('./_base-ide');
const chalk = require('chalk');
const yaml = require('yaml');
const prompts = require('../../../lib/prompts');
const { AgentCommandGenerator } = require('./shared/agent-command-generator');
const { WorkflowCommandGenerator } = require('./shared/workflow-command-generator');
const { TaskToolCommandGenerator } = require('./shared/task-tool-command-generator');
@ -23,10 +23,10 @@ class KiloSetup extends BaseIdeSetup {
* @param {Object} options - Setup options
*/
async setup(projectDir, bmadDir, options = {}) {
if (!options.silent) await prompts.log.info(`Setting up ${this.name}...`);
console.log(chalk.cyan(`Setting up ${this.name}...`));
// Clean up any old BMAD installation first
await this.cleanup(projectDir, options);
await this.cleanup(projectDir);
// Load existing config (may contain non-BMAD modes and other settings)
const kiloModesPath = path.join(projectDir, this.configFile);
@ -38,7 +38,7 @@ class KiloSetup extends BaseIdeSetup {
config = yaml.parse(existingContent) || {};
} catch {
// If parsing fails, start fresh but warn user
await prompts.log.warn('Warning: Could not parse existing .kilocodemodes, starting fresh');
console.log(chalk.yellow('Warning: Could not parse existing .kilocodemodes, starting fresh'));
config = {};
}
}
@ -88,11 +88,14 @@ class KiloSetup extends BaseIdeSetup {
const taskCount = taskToolCounts.tasks || 0;
const toolCount = taskToolCounts.tools || 0;
if (!options.silent) {
await prompts.log.success(
`${this.name} configured: ${addedCount} modes, ${workflowCount} workflows, ${taskCount} tasks, ${toolCount} tools → ${this.configFile}`,
);
}
console.log(chalk.green(`${this.name} configured:`));
console.log(chalk.dim(` - ${addedCount} modes added`));
console.log(chalk.dim(` - ${workflowCount} workflows exported`));
console.log(chalk.dim(` - ${taskCount} tasks exported`));
console.log(chalk.dim(` - ${toolCount} tools exported`));
console.log(chalk.dim(` - Configuration file: ${this.configFile}`));
console.log(chalk.dim(` - Workflows directory: .kilocode/workflows/`));
console.log(chalk.dim('\n Modes will be available when you open this project in KiloCode'));
return {
success: true,
@ -171,7 +174,7 @@ class KiloSetup extends BaseIdeSetup {
/**
* Cleanup KiloCode configuration
*/
async cleanup(projectDir, options = {}) {
async cleanup(projectDir) {
const fs = require('fs-extra');
const kiloModesPath = path.join(projectDir, this.configFile);
@ -189,12 +192,12 @@ class KiloSetup extends BaseIdeSetup {
if (removedCount > 0) {
await fs.writeFile(kiloModesPath, yaml.stringify(config, { lineWidth: 0 }));
if (!options.silent) await prompts.log.message(`Removed ${removedCount} BMAD modes from .kilocodemodes`);
console.log(chalk.dim(`Removed ${removedCount} BMAD modes from .kilocodemodes`));
}
}
} catch {
// If parsing fails, leave file as-is
if (!options.silent) await prompts.log.warn('Warning: Could not parse .kilocodemodes for cleanup');
console.log(chalk.yellow('Warning: Could not parse .kilocodemodes for cleanup'));
}
}

View File

@ -0,0 +1,326 @@
const path = require('node:path');
const { BaseIdeSetup } = require('./_base-ide');
const chalk = require('chalk');
const fs = require('fs-extra');
const yaml = require('yaml');
/**
* Kiro CLI setup handler for BMad Method
*/
class KiroCliSetup extends BaseIdeSetup {
constructor() {
super('kiro-cli', 'Kiro CLI', false);
this.configDir = '.kiro';
this.agentsDir = 'agents';
}
/**
* Cleanup old BMAD installation before reinstalling
* @param {string} projectDir - Project directory
*/
async cleanup(projectDir) {
const bmadAgentsDir = path.join(projectDir, this.configDir, this.agentsDir);
if (await fs.pathExists(bmadAgentsDir)) {
// Remove existing BMad agents
const files = await fs.readdir(bmadAgentsDir);
for (const file of files) {
if (file.startsWith('bmad')) {
await fs.remove(path.join(bmadAgentsDir, file));
}
}
console.log(chalk.dim(` Cleaned old BMAD agents from ${this.name}`));
}
}
/**
* Setup Kiro CLI configuration with BMad agents
* @param {string} projectDir - Project directory
* @param {string} bmadDir - BMAD installation directory
* @param {Object} options - Setup options
*/
async setup(projectDir, bmadDir, options = {}) {
console.log(chalk.cyan(`Setting up ${this.name}...`));
await this.cleanup(projectDir);
const kiroDir = path.join(projectDir, this.configDir);
const agentsDir = path.join(kiroDir, this.agentsDir);
await this.ensureDir(agentsDir);
// Create BMad agents from source YAML files
await this.createBmadAgentsFromSource(agentsDir, projectDir);
console.log(chalk.green(`${this.name} configured with BMad agents`));
}
/**
* Create BMad agent definitions from source YAML files
* @param {string} agentsDir - Agents directory
* @param {string} projectDir - Project directory
*/
async createBmadAgentsFromSource(agentsDir, projectDir) {
const sourceDir = path.join(__dirname, '../../../../../src/modules');
// Find all agent YAML files
const agentFiles = await this.findAgentFiles(sourceDir);
for (const agentFile of agentFiles) {
try {
await this.processAgentFile(agentFile, agentsDir, projectDir);
} catch (error) {
console.warn(chalk.yellow(`⚠️ Failed to process ${agentFile}: ${error.message}`));
}
}
}
/**
* Find all agent YAML files in modules and core
* @param {string} sourceDir - Source modules directory
* @returns {Array} Array of agent file paths
*/
async findAgentFiles(sourceDir) {
const agentFiles = [];
// Check core agents
const coreAgentsDir = path.join(__dirname, '../../../../../src/core/agents');
if (await fs.pathExists(coreAgentsDir)) {
const files = await fs.readdir(coreAgentsDir);
for (const file of files) {
if (file.endsWith('.agent.yaml')) {
agentFiles.push(path.join(coreAgentsDir, file));
}
}
}
// Check module agents
if (!(await fs.pathExists(sourceDir))) {
return agentFiles;
}
const modules = await fs.readdir(sourceDir);
for (const module of modules) {
const moduleAgentsDir = path.join(sourceDir, module, 'agents');
if (await fs.pathExists(moduleAgentsDir)) {
const files = await fs.readdir(moduleAgentsDir);
for (const file of files) {
if (file.endsWith('.agent.yaml')) {
agentFiles.push(path.join(moduleAgentsDir, file));
}
}
}
}
return agentFiles;
}
/**
* Validate BMad Core compliance
* @param {Object} agentData - Agent YAML data
* @returns {boolean} True if compliant
*/
validateBmadCompliance(agentData) {
const requiredFields = ['agent.metadata.id', 'agent.persona.role', 'agent.persona.principles'];
for (const field of requiredFields) {
const keys = field.split('.');
let current = agentData;
for (const key of keys) {
if (!current || !current[key]) {
return false;
}
current = current[key];
}
}
return true;
}
/**
* Process individual agent YAML file
* @param {string} agentFile - Path to agent YAML file
* @param {string} agentsDir - Target agents directory
* @param {string} projectDir - Project directory
*/
async processAgentFile(agentFile, agentsDir, projectDir) {
const yamlContent = await fs.readFile(agentFile, 'utf8');
const agentData = yaml.parse(yamlContent);
if (!this.validateBmadCompliance(agentData)) {
return;
}
// Extract module from file path
const normalizedPath = path.normalize(agentFile);
const pathParts = normalizedPath.split(path.sep);
const basename = path.basename(agentFile, '.agent.yaml');
// Find the module name from path
let moduleName = 'unknown';
if (pathParts.includes('src')) {
const srcIndex = pathParts.indexOf('src');
if (srcIndex + 3 < pathParts.length) {
const folderAfterSrc = pathParts[srcIndex + 1];
if (folderAfterSrc === 'core') {
moduleName = 'core';
} else if (folderAfterSrc === 'bmm') {
moduleName = 'bmm';
}
}
}
// Extract the agent name from the ID path in YAML if available
let agentBaseName = basename;
if (agentData.agent && agentData.agent.metadata && agentData.agent.metadata.id) {
const idPath = agentData.agent.metadata.id;
agentBaseName = path.basename(idPath, '.md');
}
const agentName = `bmad-${moduleName}-${agentBaseName}`;
const sanitizedAgentName = this.sanitizeAgentName(agentName);
// Create JSON definition
await this.createAgentDefinitionFromYaml(agentsDir, sanitizedAgentName, agentData);
// Create prompt file
await this.createAgentPromptFromYaml(agentsDir, sanitizedAgentName, agentData, projectDir);
}
/**
* Sanitize agent name for file naming
* @param {string} name - Agent name
* @returns {string} Sanitized name
*/
sanitizeAgentName(name) {
return name
.toLowerCase()
.replaceAll(/\s+/g, '-')
.replaceAll(/[^a-z0-9-]/g, '');
}
/**
* Create agent JSON definition from YAML data
* @param {string} agentsDir - Agents directory
* @param {string} agentName - Agent name (role-based)
* @param {Object} agentData - Agent YAML data
*/
async createAgentDefinitionFromYaml(agentsDir, agentName, agentData) {
const personName = agentData.agent.metadata.name;
const role = agentData.agent.persona.role;
const agentConfig = {
name: agentName,
description: `${personName} - ${role}`,
prompt: `file://./${agentName}-prompt.md`,
tools: ['*'],
mcpServers: {},
useLegacyMcpJson: true,
resources: [],
};
const agentPath = path.join(agentsDir, `${agentName}.json`);
await fs.writeJson(agentPath, agentConfig, { spaces: 2 });
}
/**
* Create agent prompt from YAML data
* @param {string} agentsDir - Agents directory
* @param {string} agentName - Agent name (role-based)
* @param {Object} agentData - Agent YAML data
* @param {string} projectDir - Project directory
*/
async createAgentPromptFromYaml(agentsDir, agentName, agentData, projectDir) {
const promptPath = path.join(agentsDir, `${agentName}-prompt.md`);
// Generate prompt from YAML data
const prompt = this.generatePromptFromYaml(agentData);
await fs.writeFile(promptPath, prompt);
}
/**
* Generate prompt content from YAML data
* @param {Object} agentData - Agent YAML data
* @returns {string} Generated prompt
*/
generatePromptFromYaml(agentData) {
const agent = agentData.agent;
const name = agent.metadata.name;
const icon = agent.metadata.icon || '🤖';
const role = agent.persona.role;
const identity = agent.persona.identity;
const style = agent.persona.communication_style;
const principles = agent.persona.principles;
let prompt = `# ${name} ${icon}\n\n`;
prompt += `## Role\n${role}\n\n`;
if (identity) {
prompt += `## Identity\n${identity}\n\n`;
}
if (style) {
prompt += `## Communication Style\n${style}\n\n`;
}
if (principles) {
prompt += `## Principles\n`;
if (typeof principles === 'string') {
// Handle multi-line string principles
prompt += principles + '\n\n';
} else if (Array.isArray(principles)) {
// Handle array principles
for (const principle of principles) {
prompt += `- ${principle}\n`;
}
prompt += '\n';
}
}
// Add menu items if available
if (agent.menu && agent.menu.length > 0) {
prompt += `## Available Workflows\n`;
for (let i = 0; i < agent.menu.length; i++) {
const item = agent.menu[i];
prompt += `${i + 1}. **${item.trigger}**: ${item.description}\n`;
}
prompt += '\n';
}
prompt += `## Instructions\nYou are ${name}, part of the BMad Method. Follow your role and principles while assisting users with their development needs.\n`;
return prompt;
}
/**
* Check if Kiro CLI is available
* @returns {Promise<boolean>} True if available
*/
async isAvailable() {
try {
const { execSync } = require('node:child_process');
execSync('kiro-cli --version', { stdio: 'ignore' });
return true;
} catch {
return false;
}
}
/**
* Get installation instructions
* @returns {string} Installation instructions
*/
getInstallInstructions() {
return `Install Kiro CLI:
curl -fsSL https://github.com/aws/kiro-cli/releases/latest/download/install.sh | bash
Or visit: https://github.com/aws/kiro-cli`;
}
}
module.exports = { KiroCliSetup };

View File

@ -1,14 +1,14 @@
const fs = require('fs-extra');
const path = require('node:path');
const chalk = require('chalk');
const { BMAD_FOLDER_NAME } = require('./shared/path-utils');
const prompts = require('../../../lib/prompts');
/**
* IDE Manager - handles IDE-specific setup
* Dynamically discovers and loads IDE handlers
*
* Loading strategy:
* 1. Custom installer files (codex.js, kilo.js) - for platforms with unique installation logic
* 1. Custom installer files (codex.js, kilo.js, kiro-cli.js) - for platforms with unique installation logic
* 2. Config-driven handlers (from platform-codes.yaml) - for standard IDE installation patterns
*/
class IdeManager {
@ -44,12 +44,12 @@ class IdeManager {
/**
* Dynamically load all IDE handlers
* 1. Load custom installer files first (codex.js, kilo.js)
* 1. Load custom installer files first (codex.js, kilo.js, kiro-cli.js)
* 2. Load config-driven handlers from platform-codes.yaml
*/
async loadHandlers() {
// Load custom installer files
await this.loadCustomInstallerFiles();
this.loadCustomInstallerFiles();
// Load config-driven handlers from platform-codes.yaml
await this.loadConfigDrivenHandlers();
@ -59,9 +59,9 @@ class IdeManager {
* Load custom installer files (unique installation logic)
* These files have special installation patterns that don't fit the config-driven model
*/
async loadCustomInstallerFiles() {
loadCustomInstallerFiles() {
const ideDir = __dirname;
const customFiles = ['codex.js', 'kilo.js'];
const customFiles = ['codex.js', 'kilo.js', 'kiro-cli.js'];
for (const file of customFiles) {
const filePath = path.join(ideDir, file);
@ -81,7 +81,7 @@ class IdeManager {
}
}
} catch (error) {
await prompts.log.warn(`Warning: Could not load ${file}: ${error.message}`);
console.log(chalk.yellow(` Warning: Could not load ${file}: ${error.message}`));
}
}
}
@ -171,45 +171,17 @@ class IdeManager {
const handler = this.handlers.get(ideName.toLowerCase());
if (!handler) {
await prompts.log.warn(`IDE '${ideName}' is not yet supported`);
await prompts.log.message(`Supported IDEs: ${[...this.handlers.keys()].join(', ')}`);
return { success: false, ide: ideName, error: 'unsupported IDE' };
console.warn(chalk.yellow(`⚠️ IDE '${ideName}' is not yet supported`));
console.log(chalk.dim('Supported IDEs:', [...this.handlers.keys()].join(', ')));
return { success: false, reason: 'unsupported' };
}
try {
const handlerResult = await handler.setup(projectDir, bmadDir, options);
// Build detail string from handler-returned data
let detail = '';
if (handlerResult && handlerResult.results) {
// Config-driven handlers return { success, results: { agents, workflows, tasks, tools } }
const r = handlerResult.results;
const parts = [];
if (r.agents > 0) parts.push(`${r.agents} agents`);
if (r.workflows > 0) parts.push(`${r.workflows} workflows`);
if (r.tasks > 0) parts.push(`${r.tasks} tasks`);
if (r.tools > 0) parts.push(`${r.tools} tools`);
detail = parts.join(', ');
} else if (handlerResult && handlerResult.counts) {
// Codex handler returns { success, counts: { agents, workflows, tasks }, written }
const c = handlerResult.counts;
const parts = [];
if (c.agents > 0) parts.push(`${c.agents} agents`);
if (c.workflows > 0) parts.push(`${c.workflows} workflows`);
if (c.tasks > 0) parts.push(`${c.tasks} tasks`);
detail = parts.join(', ');
} else if (handlerResult && handlerResult.modes !== undefined) {
// Kilo handler returns { success, modes, workflows, tasks, tools }
const parts = [];
if (handlerResult.modes > 0) parts.push(`${handlerResult.modes} modes`);
if (handlerResult.workflows > 0) parts.push(`${handlerResult.workflows} workflows`);
if (handlerResult.tasks > 0) parts.push(`${handlerResult.tasks} tasks`);
if (handlerResult.tools > 0) parts.push(`${handlerResult.tools} tools`);
detail = parts.join(', ');
}
return { success: true, ide: ideName, detail, handlerResult };
await handler.setup(projectDir, bmadDir, options);
return { success: true, ide: ideName };
} catch (error) {
await prompts.log.error(`Failed to setup ${ideName}: ${error.message}`);
return { success: false, ide: ideName, error: error.message };
console.error(chalk.red(`Failed to setup ${ideName}:`), error.message);
return { success: false, error: error.message };
}
}
@ -282,7 +254,7 @@ class IdeManager {
const handler = this.handlers.get(ideName.toLowerCase());
if (!handler) {
await prompts.log.warn(`IDE '${ideName}' is not yet supported for custom agent installation`);
console.warn(chalk.yellow(`⚠️ IDE '${ideName}' is not yet supported for custom agent installation`));
continue;
}
@ -294,7 +266,7 @@ class IdeManager {
}
}
} catch (error) {
await prompts.log.warn(`Failed to install ${ideName} launcher: ${error.message}`);
console.warn(chalk.yellow(`⚠️ Failed to install ${ideName} launcher: ${error.message}`));
}
}

View File

@ -111,14 +111,12 @@ platforms:
description: "AI coding platform"
# No installer config - uses custom kilo.js (creates .kilocodemodes file)
kiro:
name: "Kiro"
kiro-cli:
name: "Kiro CLI"
preferred: false
category: ide
description: "Amazon's AI-powered IDE"
installer:
target_dir: .kiro/steering
template_type: kiro
category: cli
description: "Kiro command-line interface"
# No installer config - uses custom kiro-cli.js (YAML→JSON conversion)
opencode:
name: "OpenCode"

View File

@ -1,5 +1,6 @@
const path = require('node:path');
const fs = require('fs-extra');
const chalk = require('chalk');
const { toColonPath, toDashPath, customAgentColonName, customAgentDashName, BMAD_FOLDER_NAME } = require('./path-utils');
/**

View File

@ -1,6 +1,7 @@
const path = require('node:path');
const fs = require('fs-extra');
const csv = require('csv-parse/sync');
const chalk = require('chalk');
const { toColonName, toColonPath, toDashPath, BMAD_FOLDER_NAME } = require('./path-utils');
/**

View File

@ -1,7 +1,7 @@
const path = require('node:path');
const fs = require('fs-extra');
const csv = require('csv-parse/sync');
const prompts = require('../../../../lib/prompts');
const chalk = require('chalk');
const { toColonPath, toDashPath, customAgentColonName, customAgentDashName, BMAD_FOLDER_NAME } = require('./path-utils');
/**
@ -22,7 +22,7 @@ class WorkflowCommandGenerator {
const workflows = await this.loadWorkflowManifest(bmadDir);
if (!workflows) {
await prompts.log.warn('Workflow manifest not found. Skipping command generation.');
console.log(chalk.yellow('Workflow manifest not found. Skipping command generation.'));
return { generated: 0 };
}
@ -157,7 +157,8 @@ class WorkflowCommandGenerator {
.replaceAll('{{module}}', workflow.module)
.replaceAll('{{description}}', workflow.description)
.replaceAll('{{workflow_path}}', workflowPath)
.replaceAll('_bmad', this.bmadFolderName);
.replaceAll('_bmad', this.bmadFolderName)
.replaceAll('_bmad', '_bmad');
}
/**
@ -237,7 +238,6 @@ When running any workflow:
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) {
@ -247,6 +247,7 @@ When running any workflow:
return transformed;
}
}
async loadWorkflowManifest(bmadDir) {
const manifestPath = path.join(bmadDir, '_config', 'workflow-manifest.csv');

View File

@ -1,16 +0,0 @@
---
inclusion: manual
---
# {{name}}
You must fully embody this agent's persona and follow all activation instructions exactly as specified. NEVER break character until given an exit command.
<agent-activation CRITICAL="TRUE">
1. LOAD the FULL agent file from #[[file:{{bmadFolderName}}/{{path}}]]
2. READ its entire contents - this contains the complete agent persona, menu, and instructions
3. FOLLOW every step in the <activation> section precisely
4. DISPLAY the welcome/greeting as instructed
5. PRESENT the numbered menu
6. WAIT for user input before proceeding
</agent-activation>

View File

@ -1,9 +0,0 @@
---
inclusion: manual
---
# {{name}}
Read the entire task file at: #[[file:{{bmadFolderName}}/{{path}}]]
Follow all instructions in the task file exactly as written.

View File

@ -1,9 +0,0 @@
---
inclusion: manual
---
# {{name}}
Read the entire tool file at: #[[file:{{bmadFolderName}}/{{path}}]]
Follow all instructions in the tool file exactly as written.

View File

@ -1,15 +0,0 @@
---
inclusion: manual
---
# {{name}}
IT IS CRITICAL THAT YOU FOLLOW THESE STEPS - while staying in character as the current agent persona you may have loaded:
<steps CRITICAL="TRUE">
1. Always LOAD the FULL #[[file:{{bmadFolderName}}/core/tasks/workflow.xml]]
2. READ its entire contents - this is the CORE OS for EXECUTING the specific workflow-config #[[file:{{bmadFolderName}}/{{path}}]]
3. Pass the yaml path {{bmadFolderName}}/{{path}} as 'workflow-config' parameter to the workflow.xml instructions
4. Follow workflow.xml instructions EXACTLY as written to process and follow the specific workflow config and its instructions
5. Save outputs after EACH section when generating any documents from templates
</steps>

View File

@ -1,7 +0,0 @@
---
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!

View File

@ -1,7 +1,7 @@
const fs = require('fs-extra');
const path = require('node:path');
const yaml = require('yaml');
const prompts = require('../../lib/prompts');
const chalk = require('chalk');
/**
* Load and display installer messages from messages.yaml
@ -51,20 +51,22 @@ class MessageLoader {
/**
* Display the start message (after logo, before prompts)
*/
async displayStartMessage() {
displayStartMessage() {
const message = this.getStartMessage();
if (message) {
await prompts.log.info(message);
console.log(chalk.cyan(message));
console.log();
}
}
/**
* Display the end message (after installation completes)
*/
async displayEndMessage() {
displayEndMessage() {
const message = this.getEndMessage();
if (message) {
await prompts.log.info(message);
console.log();
console.log(chalk.cyan(message));
}
}

View File

@ -1,7 +1,8 @@
const path = require('node:path');
const fs = require('fs-extra');
const yaml = require('yaml');
const prompts = require('../../../lib/prompts');
const chalk = require('chalk');
const ora = require('ora');
const { XmlHandler } = require('../../../lib/xml-handler');
const { getProjectRoot, getSourcePath, getModulePath } = require('../../../lib/project-root');
const { filterCustomizationData } = require('../../../lib/agent/compiler');
@ -16,7 +17,7 @@ const { BMAD_FOLDER_NAME } = require('../ide/shared/path-utils');
* @class ModuleManager
* @requires fs-extra
* @requires yaml
* @requires prompts
* @requires chalk
* @requires XmlHandler
*
* @example
@ -151,26 +152,26 @@ class ModuleManager {
// File hasn't been modified by user, safe to update
await this.copyFileWithPlaceholderReplacement(sourceFilePath, targetFilePath, true);
if (process.env.BMAD_VERBOSE_INSTALL === 'true') {
await prompts.log.message(` Updated sidecar file: ${relativeToBmad}`);
console.log(chalk.dim(` Updated sidecar file: ${relativeToBmad}`));
}
} else {
// User has modified the file, preserve it
if (process.env.BMAD_VERBOSE_INSTALL === 'true') {
await prompts.log.message(` Preserving user-modified file: ${relativeToBmad}`);
console.log(chalk.dim(` Preserving user-modified file: ${relativeToBmad}`));
}
}
} else {
// First time seeing this file in manifest, copy it
await this.copyFileWithPlaceholderReplacement(sourceFilePath, targetFilePath, true);
if (process.env.BMAD_VERBOSE_INSTALL === 'true') {
await prompts.log.message(` Added new sidecar file: ${relativeToBmad}`);
console.log(chalk.dim(` Added new sidecar file: ${relativeToBmad}`));
}
}
} else {
// New installation
await this.copyFileWithPlaceholderReplacement(sourceFilePath, targetFilePath, true);
if (process.env.BMAD_VERBOSE_INSTALL === 'true') {
await prompts.log.message(` Copied sidecar file: ${relativeToBmad}`);
console.log(chalk.dim(` Copied sidecar file: ${relativeToBmad}`));
}
}
@ -236,11 +237,17 @@ class ModuleManager {
async getModuleInfo(modulePath, defaultName, sourceDescription) {
// Check for module structure (module.yaml OR custom.yaml)
const moduleConfigPath = path.join(modulePath, 'module.yaml');
const installerConfigPath = path.join(modulePath, '_module-installer', 'module.yaml');
const customConfigPath = path.join(modulePath, '_module-installer', 'custom.yaml');
const rootCustomConfigPath = path.join(modulePath, 'custom.yaml');
let configPath = null;
if (await fs.pathExists(moduleConfigPath)) {
configPath = moduleConfigPath;
} else if (await fs.pathExists(installerConfigPath)) {
configPath = installerConfigPath;
} else if (await fs.pathExists(customConfigPath)) {
configPath = customConfigPath;
} else if (await fs.pathExists(rootCustomConfigPath)) {
configPath = rootCustomConfigPath;
}
@ -262,7 +269,7 @@ class ModuleManager {
description: 'BMAD Module',
version: '5.0.0',
source: sourceDescription,
isCustom: configPath === rootCustomConfigPath || isCustomSource,
isCustom: configPath === customConfigPath || configPath === rootCustomConfigPath || isCustomSource,
};
// Read module config for metadata
@ -281,7 +288,7 @@ class ModuleManager {
moduleInfo.dependencies = config.dependencies || [];
moduleInfo.defaultSelected = config.default_selected === undefined ? false : config.default_selected;
} catch (error) {
await prompts.log.warn(`Failed to read config for ${defaultName}: ${error.message}`);
console.warn(`Failed to read config for ${defaultName}:`, error.message);
}
return moduleInfo;
@ -292,7 +299,7 @@ class ModuleManager {
* @param {string} moduleCode - Code of the module to find (from module.yaml)
* @returns {string|null} Path to the module source or null if not found
*/
async findModuleSource(moduleCode, options = {}) {
async findModuleSource(moduleCode) {
const projectRoot = getProjectRoot();
// First check custom module paths if they exist
@ -309,7 +316,7 @@ class ModuleManager {
}
// Check external official modules
const externalSource = await this.findExternalModuleSource(moduleCode, options);
const externalSource = await this.findExternalModuleSource(moduleCode);
if (externalSource) {
return externalSource;
}
@ -341,7 +348,7 @@ class ModuleManager {
* @param {string} moduleCode - Code of the external module
* @returns {string} Path to the cloned repository
*/
async cloneExternalModule(moduleCode, options = {}) {
async cloneExternalModule(moduleCode) {
const { execSync } = require('node:child_process');
const moduleInfo = await this.externalModuleManager.getModuleByCode(moduleCode);
@ -351,32 +358,10 @@ class ModuleManager {
const cacheDir = this.getExternalCacheDir();
const moduleCacheDir = path.join(cacheDir, moduleCode);
const silent = options.silent || false;
// Create cache directory if it doesn't exist
await fs.ensureDir(cacheDir);
// Helper to create a spinner or a no-op when silent
const createSpinner = async () => {
if (silent) {
return {
start() {},
stop() {},
error() {},
message() {},
cancel() {},
clear() {},
get isSpinning() {
return false;
},
get isCancelled() {
return false;
},
};
}
return await prompts.spinner();
};
// Track if we need to install dependencies
let needsDependencyInstall = false;
let wasNewClone = false;
@ -384,30 +369,21 @@ class ModuleManager {
// Check if already cloned
if (await fs.pathExists(moduleCacheDir)) {
// Try to update if it's a git repo
const fetchSpinner = await createSpinner();
fetchSpinner.start(`Fetching ${moduleInfo.name}...`);
const fetchSpinner = ora(`Fetching ${moduleInfo.name}...`).start();
try {
const currentRef = execSync('git rev-parse HEAD', { cwd: moduleCacheDir, stdio: 'pipe' }).toString().trim();
// Fetch and reset to remote - works better with shallow clones than pull
execSync('git fetch origin --depth 1', {
cwd: moduleCacheDir,
stdio: ['ignore', 'pipe', 'pipe'],
env: { ...process.env, GIT_TERMINAL_PROMPT: '0' },
});
execSync('git reset --hard origin/HEAD', {
cwd: moduleCacheDir,
stdio: ['ignore', 'pipe', 'pipe'],
env: { ...process.env, GIT_TERMINAL_PROMPT: '0' },
});
execSync('git fetch origin --depth 1', { cwd: moduleCacheDir, stdio: 'pipe' });
execSync('git reset --hard origin/HEAD', { cwd: moduleCacheDir, stdio: 'pipe' });
const newRef = execSync('git rev-parse HEAD', { cwd: moduleCacheDir, stdio: 'pipe' }).toString().trim();
fetchSpinner.stop(`Fetched ${moduleInfo.name}`);
fetchSpinner.succeed(`Fetched ${moduleInfo.name}`);
// Force dependency install if we got new code
if (currentRef !== newRef) {
needsDependencyInstall = true;
}
} catch {
fetchSpinner.error(`Fetch failed, re-downloading ${moduleInfo.name}`);
fetchSpinner.warn(`Fetch failed, re-downloading ${moduleInfo.name}`);
// If update fails, remove and re-clone
await fs.remove(moduleCacheDir);
wasNewClone = true;
@ -418,16 +394,14 @@ class ModuleManager {
// Clone if not exists or was removed
if (wasNewClone) {
const fetchSpinner = await createSpinner();
fetchSpinner.start(`Fetching ${moduleInfo.name}...`);
const fetchSpinner = ora(`Fetching ${moduleInfo.name}...`).start();
try {
execSync(`git clone --depth 1 "${moduleInfo.url}" "${moduleCacheDir}"`, {
stdio: ['ignore', 'pipe', 'pipe'],
env: { ...process.env, GIT_TERMINAL_PROMPT: '0' },
stdio: 'pipe',
});
fetchSpinner.stop(`Fetched ${moduleInfo.name}`);
fetchSpinner.succeed(`Fetched ${moduleInfo.name}`);
} catch (error) {
fetchSpinner.error(`Failed to fetch ${moduleInfo.name}`);
fetchSpinner.fail(`Failed to fetch ${moduleInfo.name}`);
throw new Error(`Failed to clone external module '${moduleCode}': ${error.message}`);
}
}
@ -441,18 +415,17 @@ class ModuleManager {
// Force install if we updated or cloned new
if (needsDependencyInstall || wasNewClone || nodeModulesMissing) {
const installSpinner = await createSpinner();
installSpinner.start(`Installing dependencies for ${moduleInfo.name}...`);
const installSpinner = ora(`Installing dependencies for ${moduleInfo.name}...`).start();
try {
execSync('npm install --omit=dev --no-audit --no-fund --no-progress --legacy-peer-deps', {
cwd: moduleCacheDir,
stdio: ['ignore', 'pipe', 'pipe'],
stdio: 'pipe',
timeout: 120_000, // 2 minute timeout
});
installSpinner.stop(`Installed dependencies for ${moduleInfo.name}`);
installSpinner.succeed(`Installed dependencies for ${moduleInfo.name}`);
} catch (error) {
installSpinner.error(`Failed to install dependencies for ${moduleInfo.name}`);
if (!silent) await prompts.log.warn(` Warning: ${error.message}`);
installSpinner.warn(`Failed to install dependencies for ${moduleInfo.name}`);
console.warn(chalk.yellow(` Warning: ${error.message}`));
}
} else {
// Check if package.json is newer than node_modules
@ -467,18 +440,17 @@ class ModuleManager {
}
if (packageJsonNewer) {
const installSpinner = await createSpinner();
installSpinner.start(`Installing dependencies for ${moduleInfo.name}...`);
const installSpinner = ora(`Installing dependencies for ${moduleInfo.name}...`).start();
try {
execSync('npm install --omit=dev --no-audit --no-fund --no-progress --legacy-peer-deps', {
cwd: moduleCacheDir,
stdio: ['ignore', 'pipe', 'pipe'],
stdio: 'pipe',
timeout: 120_000, // 2 minute timeout
});
installSpinner.stop(`Installed dependencies for ${moduleInfo.name}`);
installSpinner.succeed(`Installed dependencies for ${moduleInfo.name}`);
} catch (error) {
installSpinner.error(`Failed to install dependencies for ${moduleInfo.name}`);
if (!silent) await prompts.log.warn(` Warning: ${error.message}`);
installSpinner.warn(`Failed to install dependencies for ${moduleInfo.name}`);
console.warn(chalk.yellow(` Warning: ${error.message}`));
}
}
}
@ -492,7 +464,7 @@ class ModuleManager {
* @param {string} moduleCode - Code of the external module
* @returns {string|null} Path to the module source or null if not found
*/
async findExternalModuleSource(moduleCode, options = {}) {
async findExternalModuleSource(moduleCode) {
const moduleInfo = await this.externalModuleManager.getModuleByCode(moduleCode);
if (!moduleInfo) {
@ -500,7 +472,7 @@ class ModuleManager {
}
// Clone the external module repo
const cloneDir = await this.cloneExternalModule(moduleCode, options);
const cloneDir = await this.cloneExternalModule(moduleCode);
// The module-definition specifies the path to module.yaml relative to repo root
// We need to return the directory containing module.yaml
@ -521,7 +493,7 @@ class ModuleManager {
* @param {Object} options.logger - Logger instance for output
*/
async install(moduleName, bmadDir, fileTrackingCallback = null, options = {}) {
const sourcePath = await this.findModuleSource(moduleName, { silent: options.silent });
const sourcePath = await this.findModuleSource(moduleName);
const targetPath = path.join(bmadDir, moduleName);
// Check if source module exists
@ -535,13 +507,21 @@ class ModuleManager {
// Check if this is a custom module and read its custom.yaml values
let customConfig = null;
const rootCustomConfigPath = path.join(sourcePath, 'custom.yaml');
const moduleInstallerCustomPath = path.join(sourcePath, '_module-installer', 'custom.yaml');
if (await fs.pathExists(rootCustomConfigPath)) {
try {
const customContent = await fs.readFile(rootCustomConfigPath, 'utf8');
customConfig = yaml.parse(customContent);
} catch (error) {
await prompts.log.warn(`Warning: Failed to read custom.yaml for ${moduleName}: ${error.message}`);
console.warn(chalk.yellow(`Warning: Failed to read custom.yaml for ${moduleName}:`, error.message));
}
} else if (await fs.pathExists(moduleInstallerCustomPath)) {
try {
const customContent = await fs.readFile(moduleInstallerCustomPath, 'utf8');
customConfig = yaml.parse(customContent);
} catch (error) {
console.warn(chalk.yellow(`Warning: Failed to read custom.yaml for ${moduleName}:`, error.message));
}
}
@ -549,7 +529,7 @@ class ModuleManager {
if (customConfig) {
options.moduleConfig = { ...options.moduleConfig, ...customConfig };
if (options.logger) {
options.logger.log(` Merged custom configuration for ${moduleName}`);
options.logger.log(chalk.cyan(` Merged custom configuration for ${moduleName}`));
}
}
@ -571,9 +551,9 @@ class ModuleManager {
// Process agent files to inject activation block
await this.processAgentFiles(targetPath, moduleName);
// Create directories declared in module.yaml (unless explicitly skipped)
// Call module-specific installer if it exists (unless explicitly skipped)
if (!options.skipModuleInstaller) {
await this.createModuleDirectories(moduleName, bmadDir, options);
await this.runModuleInstaller(moduleName, bmadDir, options);
}
// Capture version info for manifest
@ -602,7 +582,7 @@ class ModuleManager {
* @param {string} bmadDir - Target bmad directory
* @param {boolean} force - Force update (overwrite modifications)
*/
async update(moduleName, bmadDir, force = false, options = {}) {
async update(moduleName, bmadDir, force = false) {
const sourcePath = await this.findModuleSource(moduleName);
const targetPath = path.join(bmadDir, moduleName);
@ -619,7 +599,7 @@ class ModuleManager {
if (force) {
// Force update - remove and reinstall
await fs.remove(targetPath);
return await this.install(moduleName, bmadDir, null, { installer: options.installer });
return await this.install(moduleName, bmadDir);
} else {
// Selective update - preserve user modifications
await this.syncModule(sourcePath, targetPath);
@ -693,7 +673,7 @@ class ModuleManager {
const config = yaml.parse(configContent);
Object.assign(moduleInfo, config);
} catch (error) {
await prompts.log.warn(`Failed to read installed module config: ${error.message}`);
console.warn(`Failed to read installed module config:`, error.message);
}
}
@ -729,8 +709,8 @@ class ModuleManager {
continue;
}
// Skip module.yaml at root - it's only needed at install time
if (file === 'module.yaml') {
// Skip _module-installer directory - it's only needed at install time
if (file.startsWith('_module-installer/') || file === 'module.yaml') {
continue;
}
@ -755,7 +735,7 @@ class ModuleManager {
// Check for localskip="true" in the agent tag
const agentMatch = content.match(/<agent[^>]*\slocalskip="true"[^>]*>/);
if (agentMatch) {
await prompts.log.message(` Skipping web-only agent: ${path.basename(file)}`);
console.log(chalk.dim(` Skipping web-only agent: ${path.basename(file)}`));
continue; // Skip this agent
}
}
@ -788,6 +768,7 @@ class ModuleManager {
// IMPORTANT: Replace escape sequence and placeholder BEFORE parsing YAML
// Otherwise parsing will fail on the placeholder
yamlContent = yamlContent.replaceAll('_bmad', '_bmad');
yamlContent = yamlContent.replaceAll('_bmad', this.bmadFolderName);
try {
@ -857,7 +838,7 @@ class ModuleManager {
await fs.writeFile(targetFile, strippedYaml, 'utf8');
} catch {
// If anything fails, just copy the file as-is
await prompts.log.warn(` Warning: Could not process ${path.basename(sourceFile)}, copying as-is`);
console.warn(chalk.yellow(` Warning: Could not process ${path.basename(sourceFile)}, copying as-is`));
await fs.copy(sourceFile, targetFile, { overwrite: true });
}
}
@ -909,7 +890,7 @@ class ModuleManager {
await this.copyFileWithPlaceholderReplacement(genericTemplatePath, customizePath);
// Only show customize creation in verbose mode
if (process.env.BMAD_VERBOSE_INSTALL === 'true') {
await prompts.log.message(` Created customize: ${moduleName}-${agentName}.customize.yaml`);
console.log(chalk.dim(` Created customize: ${moduleName}-${agentName}.customize.yaml`));
}
// Store original hash for modification detection
@ -1009,10 +990,10 @@ class ModuleManager {
const copiedFiles = await this.copySidecarToMemory(sourceSidecarPath, agentName, bmadMemoryPath, isUpdate, bmadDir, installer);
if (process.env.BMAD_VERBOSE_INSTALL === 'true' && copiedFiles.length > 0) {
await prompts.log.message(` Sidecar files processed: ${copiedFiles.length} files`);
console.log(chalk.dim(` Sidecar files processed: ${copiedFiles.length} files`));
}
} else if (process.env.BMAD_VERBOSE_INSTALL === 'true') {
await prompts.log.warn(` Warning: Agent marked as having sidecar but ${sidecarDirName} directory not found`);
console.log(chalk.yellow(` Warning: Agent marked as having sidecar but ${sidecarDirName} directory not found`));
}
}
@ -1031,12 +1012,14 @@ class ModuleManager {
// Only show compilation details in verbose mode
if (process.env.BMAD_VERBOSE_INSTALL === 'true') {
await prompts.log.message(
console.log(
chalk.dim(
` Compiled agent: ${agentName} -> ${path.relative(targetPath, targetMdPath)}${hasSidecar ? ' (with sidecar)' : ''}`,
),
);
}
} catch (error) {
await prompts.log.warn(` Failed to compile agent ${agentName}: ${error.message}`);
console.warn(chalk.yellow(` Failed to compile agent ${agentName}:`, error.message));
}
}
}
@ -1156,11 +1139,11 @@ class ModuleManager {
}
if (!workflowsVendored) {
await prompts.log.info(`\n Vendoring cross-module workflows for ${moduleName}...`);
console.log(chalk.cyan(`\n Vendoring cross-module workflows for ${moduleName}...`));
workflowsVendored = true;
}
await prompts.log.message(` Processing: ${agentFile}`);
console.log(chalk.dim(` Processing: ${agentFile}`));
for (const item of workflowInstallItems) {
const sourceWorkflowPath = item.workflow; // Where to copy FROM
@ -1172,7 +1155,7 @@ class ModuleManager {
// Or: {project-root}/bmad/bmm/workflows/4-implementation/create-story/workflow.yaml
const sourceMatch = sourceWorkflowPath.match(/\{project-root\}\/(?:_bmad)\/([^/]+)\/workflows\/(.+)/);
if (!sourceMatch) {
await prompts.log.warn(` Could not parse workflow path: ${sourceWorkflowPath}`);
console.warn(chalk.yellow(` Could not parse workflow path: ${sourceWorkflowPath}`));
continue;
}
@ -1183,7 +1166,7 @@ class ModuleManager {
// Example: {project-root}/_bmad/bmgd/workflows/4-production/create-story/workflow.yaml
const installMatch = installWorkflowPath.match(/\{project-root\}\/(_bmad)\/([^/]+)\/workflows\/(.+)/);
if (!installMatch) {
await prompts.log.warn(` Could not parse workflow-install path: ${installWorkflowPath}`);
console.warn(chalk.yellow(` Could not parse workflow-install path: ${installWorkflowPath}`));
continue;
}
@ -1196,13 +1179,15 @@ class ModuleManager {
// Check if source workflow exists
if (!(await fs.pathExists(actualSourceWorkflowPath))) {
await prompts.log.warn(` Source workflow not found: ${actualSourceWorkflowPath}`);
console.warn(chalk.yellow(` Source workflow not found: ${actualSourceWorkflowPath}`));
continue;
}
// Copy the entire workflow folder
await prompts.log.message(
console.log(
chalk.dim(
` Vendoring: ${sourceModule}/workflows/${sourceWorkflowSubPath.replace(/\/workflow\.yaml$/, '')}${moduleName}/workflows/${installWorkflowSubPath.replace(/\/workflow\.yaml$/, '')}`,
),
);
await fs.ensureDir(path.dirname(actualDestWorkflowPath));
@ -1218,7 +1203,7 @@ class ModuleManager {
}
if (workflowsVendored) {
await prompts.log.success(` Workflow vendoring complete\n`);
console.log(chalk.green(` ✓ Workflow vendoring complete\n`));
}
}
@ -1240,106 +1225,66 @@ class ModuleManager {
if (updatedYaml !== yamlContent) {
await fs.writeFile(workflowYamlPath, updatedYaml, 'utf8');
await prompts.log.message(` Updated config_source to: ${this.bmadFolderName}/${newModuleName}/config.yaml`);
console.log(chalk.dim(` Updated config_source to: ${this.bmadFolderName}/${newModuleName}/config.yaml`));
}
}
/**
* Create directories declared in module.yaml's `directories` key
* This replaces the security-risky module installer pattern with declarative config
* Run module-specific installer if it exists
* @param {string} moduleName - Name of the module
* @param {string} bmadDir - Target bmad directory
* @param {Object} options - Installation options
* @param {Object} options.moduleConfig - Module configuration from config collector
* @param {Object} options.coreConfig - Core configuration
*/
async createModuleDirectories(moduleName, bmadDir, options = {}) {
const moduleConfig = options.moduleConfig || {};
const projectRoot = path.dirname(bmadDir);
async runModuleInstaller(moduleName, bmadDir, options = {}) {
// Special handling for core module - it's in src/core not src/modules
let sourcePath;
if (moduleName === 'core') {
sourcePath = getSourcePath('core');
} else {
sourcePath = await this.findModuleSource(moduleName, { silent: true });
sourcePath = await this.findModuleSource(moduleName);
if (!sourcePath) {
return; // No source found, skip
// No source found, skip module installer
return;
}
}
// Read module.yaml to find the `directories` key
const moduleYamlPath = path.join(sourcePath, 'module.yaml');
if (!(await fs.pathExists(moduleYamlPath))) {
return; // No module.yaml, skip
const installerPath = path.join(sourcePath, '_module-installer', 'installer.js');
// Check if module has a custom installer
if (!(await fs.pathExists(installerPath))) {
return; // No custom installer
}
let moduleYaml;
try {
const yamlContent = await fs.readFile(moduleYamlPath, 'utf8');
moduleYaml = yaml.parse(yamlContent);
} catch {
return; // Invalid YAML, skip
}
// Load the module installer
const moduleInstaller = require(installerPath);
if (!moduleYaml || !moduleYaml.directories) {
return; // No directories declared, skip
}
if (typeof moduleInstaller.install === 'function') {
// Get project root (parent of bmad directory)
const projectRoot = path.dirname(bmadDir);
// Get color utility for styled output
const color = await prompts.getColor();
const directories = moduleYaml.directories;
const wdsFolders = moduleYaml.wds_folders || [];
// Prepare logger (use console if not provided)
const logger = options.logger || {
log: console.log,
error: console.error,
warn: console.warn,
};
for (const dirRef of directories) {
// Parse variable reference like "{design_artifacts}"
const varMatch = dirRef.match(/^\{([^}]+)\}$/);
if (!varMatch) {
// Not a variable reference, skip
continue;
}
// Call the module installer
const result = await moduleInstaller.install({
projectRoot,
config: options.moduleConfig || {},
coreConfig: options.coreConfig || {},
installedIDEs: options.installedIDEs || [],
logger,
});
const configKey = varMatch[1];
const dirValue = moduleConfig[configKey];
if (!dirValue || typeof dirValue !== 'string') {
continue; // No value or not a string, skip
}
// Strip {project-root}/ prefix if present
let dirPath = dirValue.replace(/^\{project-root\}\/?/, '');
// Handle remaining {project-root} anywhere in the path
dirPath = dirPath.replaceAll('{project-root}', '');
// Resolve to absolute path
const fullPath = path.join(projectRoot, dirPath);
// Validate path is within project root (prevent directory traversal)
const normalizedPath = path.normalize(fullPath);
const normalizedRoot = path.normalize(projectRoot);
if (!normalizedPath.startsWith(normalizedRoot + path.sep) && normalizedPath !== normalizedRoot) {
await prompts.log.warn(color.yellow(`Warning: ${configKey} path escapes project root, skipping: ${dirPath}`));
continue;
}
// Create directory if it doesn't exist
if (!(await fs.pathExists(fullPath))) {
const dirName = configKey.replaceAll('_', ' ');
await prompts.log.message(color.yellow(`Creating ${dirName} directory: ${dirPath}`));
await fs.ensureDir(fullPath);
}
// Create WDS subfolders if this is the design_artifacts directory
if (configKey === 'design_artifacts' && wdsFolders.length > 0) {
await prompts.log.message(color.cyan('Creating WDS folder structure...'));
for (const subfolder of wdsFolders) {
const subPath = path.join(fullPath, subfolder);
if (!(await fs.pathExists(subPath))) {
await fs.ensureDir(subPath);
await prompts.log.message(color.dim(`${subfolder}/`));
}
if (!result) {
console.warn(chalk.yellow(`Module installer for ${moduleName} returned false`));
}
}
} catch (error) {
console.error(chalk.red(`Error running module installer for ${moduleName}: ${error.message}`));
}
}
@ -1361,7 +1306,7 @@ class ModuleManager {
await fs.writeFile(configPath, configContent, 'utf8');
} catch (error) {
await prompts.log.warn(`Failed to process module config: ${error.message}`);
console.warn(`Failed to process module config:`, error.message);
}
}
}
@ -1409,6 +1354,10 @@ class ModuleManager {
const fullPath = path.join(dir, entry.name);
if (entry.isDirectory()) {
// Skip _module-installer directories
if (entry.name === '_module-installer') {
continue;
}
const subFiles = await this.getFileList(fullPath, baseDir);
files.push(...subFiles);
} else {

View File

@ -6,7 +6,7 @@
const fs = require('node:fs');
const path = require('node:path');
const yaml = require('yaml');
const prompts = require('../prompts');
const readline = require('node:readline');
const { compileAgent, compileAgentFile } = require('./compiler');
const { extractInstallConfig, getDefaultValues } = require('./template-engine');
@ -149,47 +149,83 @@ async function promptInstallQuestions(installConfig, defaults, presetAnswers = {
return { ...defaults, ...presetAnswers };
}
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});
const question = (prompt) =>
new Promise((resolve) => {
rl.question(prompt, resolve);
});
const answers = { ...defaults, ...presetAnswers };
await prompts.note(installConfig.description || '', 'Agent Configuration');
console.log('\n📝 Agent Configuration\n');
if (installConfig.description) {
console.log(` ${installConfig.description}\n`);
}
for (const q of installConfig.questions) {
// Skip questions for variables that are already set (e.g., custom_name set upfront)
if (answers[q.var] !== undefined && answers[q.var] !== defaults[q.var]) {
await prompts.log.message(` ${q.var}: ${answers[q.var]} (already set)`);
console.log(chalk.dim(` ${q.var}: ${answers[q.var]} (already set)`));
continue;
}
let response;
switch (q.type) {
case 'text': {
const response = await prompts.text({
message: q.prompt,
default: q.default ?? '',
});
answers[q.var] = response ?? q.default ?? '';
const defaultHint = q.default ? ` (default: ${q.default})` : '';
response = await question(` ${q.prompt}${defaultHint}: `);
answers[q.var] = response || q.default || '';
break;
}
case 'boolean': {
const response = await prompts.confirm({
message: q.prompt,
default: q.default,
});
answers[q.var] = response;
const defaultHint = q.default ? ' [Y/n]' : ' [y/N]';
response = await question(` ${q.prompt}${defaultHint}: `);
if (response === '') {
answers[q.var] = q.default;
} else {
answers[q.var] = response.toLowerCase().startsWith('y');
}
break;
}
case 'choice': {
const response = await prompts.select({
message: q.prompt,
options: q.options.map((o) => ({ value: o.value, label: o.label })),
initialValue: q.default,
});
answers[q.var] = response;
console.log(` ${q.prompt}`);
for (const [idx, opt] of q.options.entries()) {
const marker = opt.value === q.default ? '* ' : ' ';
console.log(` ${marker}${idx + 1}. ${opt.label}`);
}
const defaultIdx = q.options.findIndex((o) => o.value === q.default) + 1;
let validChoice = false;
let choiceIdx;
while (!validChoice) {
response = await question(` Choice (default: ${defaultIdx}): `);
if (response) {
choiceIdx = parseInt(response, 10) - 1;
if (isNaN(choiceIdx) || choiceIdx < 0 || choiceIdx >= q.options.length) {
console.log(` Invalid choice. Please enter 1-${q.options.length}`);
} else {
validChoice = true;
}
} else {
choiceIdx = defaultIdx - 1;
validChoice = true;
}
}
answers[q.var] = q.options[choiceIdx].value;
break;
}
// No default
}
}
rl.close();
return answers;
}

View File

@ -1,6 +1,9 @@
const chalk = require('chalk');
const boxen = require('boxen');
const wrapAnsi = require('wrap-ansi');
const figlet = require('figlet');
const path = require('node:path');
const os = require('node:os');
const prompts = require('./prompts');
const CLIUtils = {
/**
@ -16,32 +19,27 @@ const CLIUtils = {
},
/**
* Display BMAD logo using @clack intro + box
* @param {boolean} _clearScreen - Deprecated, ignored (no longer clears screen)
* Display BMAD logo
* @param {boolean} clearScreen - Whether to clear the screen first (default: true for initial display only)
*/
async displayLogo(_clearScreen = true) {
displayLogo(clearScreen = true) {
if (clearScreen) {
console.clear();
}
const version = this.getVersion();
const color = await prompts.getColor();
// ASCII art logo
const logo = [
' ██████╗ ███╗ ███╗ █████╗ ██████╗ ™',
' ██╔══██╗████╗ ████║██╔══██╗██╔══██╗',
' ██████╔╝██╔████╔██║███████║██║ ██║',
' ██╔══██╗██║╚██╔╝██║██╔══██║██║ ██║',
' ██████╔╝██║ ╚═╝ ██║██║ ██║██████╔╝',
' ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═════╝',
]
.map((line) => color.yellow(line))
.join('\n');
const logo = `
`;
const tagline = ' Build More, Architect Dreams';
await prompts.box(`${logo}\n${tagline}`, `v${version}`, {
contentAlign: 'center',
rounded: true,
formatBorder: color.blue,
});
console.log(chalk.cyan(logo));
console.log(chalk.dim(` Build More, Architect Dreams`) + chalk.cyan.bold(` v${version}`) + '\n');
},
/**
@ -49,8 +47,13 @@ const CLIUtils = {
* @param {string} title - Section title
* @param {string} subtitle - Optional subtitle
*/
async displaySection(title, subtitle = null) {
await prompts.note(subtitle || '', title);
displaySection(title, subtitle = null) {
console.log('\n' + chalk.cyan('═'.repeat(80)));
console.log(chalk.cyan.bold(` ${title}`));
if (subtitle) {
console.log(chalk.dim(` ${subtitle}`));
}
console.log(chalk.cyan('═'.repeat(80)) + '\n');
},
/**
@ -58,21 +61,25 @@ const CLIUtils = {
* @param {string|Array} content - Content to display
* @param {Object} options - Box options
*/
async displayBox(content, options = {}) {
displayBox(content, options = {}) {
const defaultOptions = {
padding: 1,
margin: 1,
borderStyle: 'round',
borderColor: 'cyan',
...options,
};
// Handle array content
let text = content;
if (Array.isArray(content)) {
text = content.join('\n\n');
}
const color = await prompts.getColor();
const borderColor = options.borderColor || 'cyan';
const colorMap = { green: color.green, red: color.red, yellow: color.yellow, cyan: color.cyan, blue: color.blue };
const formatBorder = colorMap[borderColor] || color.cyan;
// Wrap text to prevent overflow
const wrapped = wrapAnsi(text, 76, { hard: true, wordWrap: true });
await prompts.box(text, options.title, {
rounded: options.borderStyle === 'round' || options.borderStyle === undefined,
formatBorder,
});
console.log(boxen(wrapped, defaultOptions));
},
/**
@ -81,9 +88,14 @@ const CLIUtils = {
* @param {string} header - Custom header from module.yaml
* @param {string} subheader - Custom subheader from module.yaml
*/
async displayModuleConfigHeader(moduleName, header = null, subheader = null) {
const title = header || `Configuring ${moduleName.toUpperCase()} Module`;
await prompts.note(subheader || '', title);
displayModuleConfigHeader(moduleName, header = null, subheader = null) {
// Simple blue banner with custom header/subheader if provided
console.log('\n' + chalk.cyan('─'.repeat(80)));
console.log(chalk.cyan(header || `Configuring ${moduleName.toUpperCase()} Module`));
if (subheader) {
console.log(chalk.dim(`${subheader}`));
}
console.log(chalk.cyan('─'.repeat(80)) + '\n');
},
/**
@ -92,9 +104,14 @@ const CLIUtils = {
* @param {string} header - Custom header from module.yaml
* @param {string} subheader - Custom subheader from module.yaml
*/
async displayModuleNoConfig(moduleName, header = null, subheader = null) {
const title = header || `${moduleName.toUpperCase()} Module - No Custom Configuration`;
await prompts.note(subheader || '', title);
displayModuleNoConfig(moduleName, header = null, subheader = null) {
// Show full banner with header/subheader, just like modules with config
console.log('\n' + chalk.cyan('─'.repeat(80)));
console.log(chalk.cyan(header || `${moduleName.toUpperCase()} Module - No Custom Configuration`));
if (subheader) {
console.log(chalk.dim(`${subheader}`));
}
console.log(chalk.cyan('─'.repeat(80)) + '\n');
},
/**
@ -103,33 +120,42 @@ const CLIUtils = {
* @param {number} total - Total steps
* @param {string} description - Step description
*/
async displayStep(current, total, description) {
displayStep(current, total, description) {
const progress = `[${current}/${total}]`;
await prompts.log.step(`${progress} ${description}`);
console.log('\n' + chalk.cyan(progress) + ' ' + chalk.bold(description));
console.log(chalk.dim('─'.repeat(80 - progress.length - 1)) + '\n');
},
/**
* Display completion message
* @param {string} message - Completion message
*/
async displayComplete(message) {
const color = await prompts.getColor();
await prompts.box(`\u2728 ${message}`, 'Complete', {
rounded: true,
formatBorder: color.green,
});
displayComplete(message) {
console.log(
'\n' +
boxen(chalk.green('✨ ' + message), {
padding: 1,
margin: 1,
borderStyle: 'round',
borderColor: 'green',
}),
);
},
/**
* Display error message
* @param {string} message - Error message
*/
async displayError(message) {
const color = await prompts.getColor();
await prompts.box(`\u2717 ${message}`, 'Error', {
rounded: true,
formatBorder: color.red,
});
displayError(message) {
console.log(
'\n' +
boxen(chalk.red('✗ ' + message), {
padding: 1,
margin: 1,
borderStyle: 'round',
borderColor: 'red',
}),
);
},
/**
@ -137,7 +163,7 @@ const CLIUtils = {
* @param {Array} items - Items to display
* @param {string} prefix - Item prefix
*/
formatList(items, prefix = '\u2022') {
formatList(items, prefix = '') {
return items.map((item) => ` ${prefix} ${item}`).join('\n');
},
@ -152,6 +178,25 @@ const CLIUtils = {
}
},
/**
* Display table
* @param {Array} data - Table data
* @param {Object} options - Table options
*/
displayTable(data, options = {}) {
const Table = require('cli-table3');
const table = new Table({
style: {
head: ['cyan'],
border: ['dim'],
},
...options,
});
for (const row of data) table.push(row);
console.log(table.toString());
},
/**
* Display module completion message
* @param {string} moduleName - Name of the completed module

View File

@ -89,51 +89,11 @@ async function note(message, title) {
/**
* Display a spinner for async operations
* Wraps @clack/prompts spinner with isSpinning state tracking
* @returns {Object} Spinner controller with start, stop, message, error, cancel, clear, isSpinning
* @returns {Object} Spinner controller with start, stop, message methods
*/
async function spinner() {
const clack = await getClack();
const s = clack.spinner();
let spinning = false;
return {
start: (msg) => {
if (spinning) {
s.message(msg);
} else {
spinning = true;
s.start(msg);
}
},
stop: (msg) => {
if (spinning) {
spinning = false;
s.stop(msg);
}
},
message: (msg) => {
if (spinning) s.message(msg);
},
error: (msg) => {
spinning = false;
s.error(msg);
},
cancel: (msg) => {
spinning = false;
s.cancel(msg);
},
clear: () => {
spinning = false;
s.clear();
},
get isSpinning() {
return spinning;
},
get isCancelled() {
return s.isCancelled;
},
};
return clack.spinner();
}
/**
@ -230,6 +190,31 @@ async function multiselect(options) {
return result;
}
/**
* Grouped multi-select prompt for categorized options
* @param {Object} options - Prompt options
* @param {string} options.message - The question to ask
* @param {Object} options.options - Object mapping group names to arrays of choices
* @param {Array} [options.initialValues] - Array of initially selected values
* @param {boolean} [options.required=false] - Whether at least one must be selected
* @param {boolean} [options.selectableGroups=false] - Whether groups can be selected as a whole
* @returns {Promise<Array>} Array of selected values
*/
async function groupMultiselect(options) {
const clack = await getClack();
const result = await clack.groupMultiselect({
message: options.message,
options: options.options,
initialValues: options.initialValues,
required: options.required || false,
selectableGroups: options.selectableGroups || false,
});
await handleCancel(result);
return result;
}
/**
* Default filter function for autocomplete - case-insensitive label matching
* @param {string} search - Search string
@ -252,7 +237,6 @@ function defaultAutocompleteFilter(search, option) {
* @param {boolean} [options.required=false] - Whether at least one must be selected
* @param {number} [options.maxItems=5] - Maximum visible items in scrollable list
* @param {Function} [options.filter] - Custom filter function (search, option) => boolean
* @param {Array} [options.lockedValues] - Values that are always selected and cannot be toggled off
* @returns {Promise<Array>} Array of selected values
*/
async function autocompleteMultiselect(options) {
@ -261,7 +245,6 @@ async function autocompleteMultiselect(options) {
const color = await getPicocolors();
const filterFn = options.filter ?? defaultAutocompleteFilter;
const lockedSet = new Set(options.lockedValues || []);
const prompt = new core.AutocompletePrompt({
options: options.options,
@ -272,7 +255,7 @@ async function autocompleteMultiselect(options) {
return 'Please select at least one item';
}
},
initialValue: [...new Set([...(options.initialValues || []), ...(options.lockedValues || [])])],
initialValue: options.initialValues,
render() {
const barColor = this.state === 'error' ? color.yellow : color.cyan;
const bar = barColor(clack.S_BAR);
@ -297,17 +280,9 @@ async function autocompleteMultiselect(options) {
// Render option with checkbox
const renderOption = (opt, isHighlighted) => {
const isSelected = this.selectedValues.includes(opt.value);
const isLocked = lockedSet.has(opt.value);
const label = opt.label ?? String(opt.value ?? '');
const hintText = opt.hint && isHighlighted ? color.dim(` (${opt.hint})`) : '';
let checkbox;
if (isLocked) {
checkbox = color.green(clack.S_CHECKBOX_SELECTED);
const lockHint = color.dim(' (always installed)');
return isHighlighted ? `${checkbox} ${label}${lockHint}` : `${checkbox} ${color.dim(label)}${lockHint}`;
}
checkbox = isSelected ? color.green(clack.S_CHECKBOX_SELECTED) : color.dim(clack.S_CHECKBOX_INACTIVE);
const hintText = opt.hint && opt.value === this.focusedValue ? color.dim(` (${opt.hint})`) : '';
const checkbox = isSelected ? color.green(clack.S_CHECKBOX_SELECTED) : color.dim(clack.S_CHECKBOX_INACTIVE);
return isHighlighted ? `${checkbox} ${label}${hintText}` : `${checkbox} ${color.dim(label)}`;
};
@ -347,18 +322,6 @@ async function autocompleteMultiselect(options) {
},
});
// Prevent locked values from being toggled off
if (lockedSet.size > 0) {
const originalToggle = prompt.toggleSelected.bind(prompt);
prompt.toggleSelected = function (value) {
// If locked and already selected, skip the toggle (would deselect)
if (lockedSet.has(value) && this.selectedValues.includes(value)) {
return;
}
originalToggle(value);
};
}
// === FIX: Make SPACE always act as selection key (not search input) ===
// Override _isActionKey to treat SPACE like TAB - always an action key
// This prevents SPACE from being added to the search input
@ -372,9 +335,8 @@ async function autocompleteMultiselect(options) {
// Handle SPACE toggle when NOT navigating (internal code only handles it when isNavigating=true)
prompt.on('key', (char, key) => {
if (key && key.name === 'space' && !prompt.isNavigating) {
const focused = prompt.filteredOptions[prompt.cursor];
if (focused) prompt.toggleSelected(focused.value);
if (key && key.name === 'space' && !prompt.isNavigating && prompt.focusedValue !== undefined) {
prompt.toggleSelected(prompt.focusedValue);
}
});
// === END FIX ===
@ -558,131 +520,6 @@ const log = {
},
};
/**
* Display cancellation message
* @param {string} [message='Operation cancelled'] - The cancellation message
*/
async function cancel(message = 'Operation cancelled') {
const clack = await getClack();
clack.cancel(message);
}
/**
* Display content in a styled box
* @param {string} content - The box content
* @param {string} [title] - Optional title
* @param {Object} [options] - Box options (contentAlign, titleAlign, width, rounded, formatBorder, etc.)
*/
async function box(content, title, options) {
const clack = await getClack();
clack.box(content, title, options);
}
/**
* Create a progress bar for visualizing task completion
* @param {Object} [options] - Progress options (max, style, etc.)
* @returns {Promise<Object>} Progress controller with start, advance, stop methods
*/
async function progress(options) {
const clack = await getClack();
return clack.progress(options);
}
/**
* Create a task log for displaying scrolling subprocess output
* @param {Object} options - TaskLog options (title, limit, retainLog)
* @returns {Promise<Object>} TaskLog controller with message, success, error methods
*/
async function taskLog(options) {
const clack = await getClack();
return clack.taskLog(options);
}
/**
* File system path prompt with autocomplete
* @param {Object} options - Path options
* @param {string} options.message - The prompt message
* @param {string} [options.initialValue] - Initial path value
* @param {boolean} [options.directory=false] - Only allow directories
* @param {Function} [options.validate] - Validation function
* @returns {Promise<string>} Selected path
*/
async function pathPrompt(options) {
const clack = await getClack();
const result = await clack.path(options);
await handleCancel(result);
return result;
}
/**
* Autocomplete single-select prompt with type-ahead filtering
* @param {Object} options - Autocomplete options
* @param {string} options.message - The prompt message
* @param {Array} options.options - Array of choices [{value, label, hint?}]
* @param {string} [options.placeholder] - Placeholder text
* @param {number} [options.maxItems] - Maximum visible items
* @param {Function} [options.filter] - Custom filter function
* @returns {Promise<any>} Selected value
*/
async function autocomplete(options) {
const clack = await getClack();
const result = await clack.autocomplete(options);
await handleCancel(result);
return result;
}
/**
* Key-based instant selection prompt
* @param {Object} options - SelectKey options
* @param {string} options.message - The prompt message
* @param {Array} options.options - Array of choices [{value, label, hint?}]
* @returns {Promise<any>} Selected value
*/
async function selectKey(options) {
const clack = await getClack();
const result = await clack.selectKey(options);
await handleCancel(result);
return result;
}
/**
* Stream messages with dynamic content (for LLMs, generators, etc.)
*/
const stream = {
async info(generator) {
const clack = await getClack();
return clack.stream.info(generator);
},
async success(generator) {
const clack = await getClack();
return clack.stream.success(generator);
},
async step(generator) {
const clack = await getClack();
return clack.stream.step(generator);
},
async warn(generator) {
const clack = await getClack();
return clack.stream.warn(generator);
},
async error(generator) {
const clack = await getClack();
return clack.stream.error(generator);
},
async message(generator, options) {
const clack = await getClack();
return clack.stream.message(generator, options);
},
};
/**
* Get the color utility (picocolors instance from @clack/prompts)
* @returns {Promise<Object>} The color utility (picocolors)
*/
async function getColor() {
return await getPicocolors();
}
/**
* Execute an array of Inquirer-style questions using @clack/prompts
* This provides compatibility with dynamic question arrays
@ -782,28 +619,20 @@ async function prompt(questions) {
module.exports = {
getClack,
getColor,
handleCancel,
intro,
outro,
cancel,
note,
box,
spinner,
progress,
taskLog,
select,
multiselect,
groupMultiselect,
autocompleteMultiselect,
autocomplete,
selectKey,
confirm,
text,
path: pathPrompt,
password,
group,
tasks,
log,
stream,
prompt,
};

File diff suppressed because it is too large Load Diff

View File

@ -20,8 +20,11 @@ const path = require('node:path');
const DOCS_ROOT = path.resolve(__dirname, '../docs');
const DRY_RUN = !process.argv.includes('--write');
// Match all markdown links; filtering (external, anchors, assets) happens in convertToRepoRelative.
// This intentionally matches broadly so the handler can make context-aware decisions.
// Regex to match markdown links:
// - [text](path.md) or [text](path.md#anchor) - existing .md links
// - [text](/path/to/page/) or [text](/path/to/page/#anchor) - site-relative links to convert
const MARKDOWN_LINK_REGEX = /\[([^\]]*)\]\(([^)]+(?:\.md|\/))(?:#[^)]*)?(?:\?[^)]*)?\)/g;
// Simpler approach: match all markdown links and filter in the handler
const ALL_MARKDOWN_LINKS_REGEX = /\[([^\]]*)\]\(([^)]+)\)/g;
/**
@ -61,8 +64,8 @@ function getMarkdownFiles(dir) {
* @returns {string|null} - Repo-relative path (e.g., "/docs/path/to/file.md"), or null if shouldn't be converted
*/
function convertToRepoRelative(href, currentFilePath) {
// Skip external links (including protocol-relative URLs like //cdn.example.com)
if (href.includes('://') || href.startsWith('//') || href.startsWith('mailto:') || href.startsWith('tel:')) {
// Skip external links
if (href.includes('://') || href.startsWith('mailto:') || href.startsWith('tel:')) {
return null;
}

View File

@ -67,11 +67,11 @@ platforms:
category: ide
description: "Atlassian's Rovo development environment"
kiro:
name: "Kiro"
kiro-cli:
name: "Kiro CLI"
preferred: false
category: ide
description: "Amazon's AI-powered IDE"
category: cli
description: "Kiro command-line interface"
github-copilot:
name: "GitHub Copilot"

View File

@ -21,8 +21,8 @@ const path = require('node:path');
const DOCS_ROOT = path.resolve(__dirname, '../docs');
const DRY_RUN = !process.argv.includes('--write');
// Regex to match markdown links with site-relative paths or bare .md references
const LINK_REGEX = /\[([^\]]*)\]\(((?:\.{1,2}\/|\/)[^)]+|[\w][^)\s]*\.md(?:[?#][^)]*)?)\)/g;
// Regex to match markdown links with site-relative paths
const LINK_REGEX = /\[([^\]]*)\]\((\/[^)]+)\)/g;
// File extensions that are static assets, not markdown docs
const STATIC_ASSET_EXTENSIONS = ['.zip', '.txt', '.pdf', '.png', '.jpg', '.jpeg', '.gif', '.svg', '.webp', '.ico'];
@ -108,27 +108,11 @@ function extractAnchors(content) {
* /docs/how-to/installation/install-bmad.md -> docs/how-to/installation/install-bmad.md
* /how-to/installation/install-bmad/ -> docs/how-to/installation/install-bmad.md or .../index.md
*/
function resolveLink(siteRelativePath, sourceFile) {
function resolveLink(siteRelativePath) {
// Strip anchor and query
let checkPath = siteRelativePath.split('#')[0].split('?')[0];
// Handle relative paths (including bare .md): resolve from source file's directory
if (checkPath.startsWith('./') || checkPath.startsWith('../') || (!checkPath.startsWith('/') && checkPath.endsWith('.md'))) {
const sourceDir = path.dirname(sourceFile);
const resolved = path.resolve(sourceDir, checkPath);
// Ensure the resolved path stays within DOCS_ROOT
if (!resolved.startsWith(DOCS_ROOT + path.sep) && resolved !== DOCS_ROOT) return null;
if (fs.existsSync(resolved) && fs.statSync(resolved).isFile()) return resolved;
if (fs.existsSync(resolved + '.md')) return resolved + '.md';
// Directory: check for index.md
if (fs.existsSync(resolved) && fs.statSync(resolved).isDirectory()) {
const indexFile = path.join(resolved, 'index.md');
if (fs.existsSync(indexFile)) return indexFile;
}
return null;
}
// Strip /docs/ prefix if present (legacy absolute links)
// Strip /docs/ prefix if present (repo-relative links)
if (checkPath.startsWith('/docs/')) {
checkPath = checkPath.slice(5); // Remove '/docs' but keep leading '/'
}
@ -145,18 +129,12 @@ function resolveLink(siteRelativePath, sourceFile) {
// Direct path (e.g., /path/file.md)
const direct = path.join(DOCS_ROOT, checkPath);
if (fs.existsSync(direct) && fs.statSync(direct).isFile()) return direct;
if (fs.existsSync(direct)) return direct;
// Try with .md extension
const withMd = direct + '.md';
if (fs.existsSync(withMd)) return withMd;
// Directory without trailing slash: check for index.md
if (fs.existsSync(direct) && fs.statSync(direct).isDirectory()) {
const indexFile = path.join(direct, 'index.md');
if (fs.existsSync(indexFile)) return indexFile;
}
return null;
}
@ -166,7 +144,7 @@ function resolveLink(siteRelativePath, sourceFile) {
function findFileWithContext(brokenPath) {
// Extract filename and parent directory from the broken path
// e.g., /tutorials/getting-started/foo/ -> parent: getting-started, file: foo.md
const cleanPath = brokenPath.replace(/\/$/, '').replace(/^(\.\.\/|\.\/|\/)+/, '');
const cleanPath = brokenPath.replace(/\/$/, '').replace(/^\//, '');
const parts = cleanPath.split('/');
const fileName = parts.at(-1) + '.md';
const parentDir = parts.length > 1 ? parts.at(-2) : null;
@ -241,7 +219,7 @@ function processFile(filePath) {
}
// Validate the link target exists
const targetFile = resolveLink(linkPath, filePath);
const targetFile = resolveLink(linkPath);
if (!targetFile) {
// Link is broken - try to find the file

View File

@ -29,7 +29,6 @@
const fs = require('node:fs');
const path = require('node:path');
const yaml = require('yaml');
const { parse: parseCsv } = require('csv-parse/sync');
const PROJECT_ROOT = path.resolve(__dirname, '..');
const SRC_DIR = path.join(PROJECT_ROOT, 'src');
@ -39,10 +38,10 @@ const STRICT = process.argv.includes('--strict');
// --- Constants ---
// File extensions to scan
const SCAN_EXTENSIONS = new Set(['.yaml', '.yml', '.md', '.xml', '.csv']);
const SCAN_EXTENSIONS = new Set(['.yaml', '.yml', '.md', '.xml']);
// Skip directories
const SKIP_DIRS = new Set(['node_modules', '.git']);
const SKIP_DIRS = new Set(['node_modules', '_module-installer', '.git']);
// Pattern: {project-root}/_bmad/ references
const PROJECT_ROOT_REF = /\{project-root\}\/_bmad\/([^\s'"<>})\]`]+)/g;
@ -293,46 +292,6 @@ function extractMarkdownRefs(filePath, content) {
return refs;
}
function extractCsvRefs(filePath, content) {
const refs = [];
let records;
try {
records = parseCsv(content, {
columns: true,
skip_empty_lines: true,
relax_column_count: true,
});
} catch (error) {
// No CSV schema validator exists yet (planned as Layer 2c) — surface parse errors visibly.
// YAML equivalent (line ~198) defers to validate-agent-schema.js; CSV has no such fallback.
const rel = path.relative(PROJECT_ROOT, filePath);
console.error(` [CSV-PARSE-ERROR] ${rel}: ${error.message}`);
if (process.env.GITHUB_ACTIONS) {
console.log(`::warning file=${rel},line=1::${escapeAnnotation(`CSV parse error: ${error.message}`)}`);
}
return refs;
}
// Only process if workflow-file column exists
const firstRecord = records[0];
if (!firstRecord || !('workflow-file' in firstRecord)) {
return refs;
}
for (const [i, record] of records.entries()) {
const raw = record['workflow-file'];
if (!raw || raw.trim() === '') continue;
if (!isResolvable(raw)) continue;
// Line = header (1) + data row index (0-based) + 1
const line = i + 2;
refs.push({ file: filePath, raw, type: 'project-root', line });
}
return refs;
}
// --- Reference Resolution ---
function resolveRef(ref) {
@ -392,12 +351,8 @@ function checkAbsolutePathLeaks(filePath, content) {
return leaks;
}
// --- Exports (for testing) ---
module.exports = { extractCsvRefs };
// --- Main ---
if (require.main === module) {
console.log(`\nValidating file references in: ${SRC_DIR}`);
console.log(`Mode: ${STRICT ? 'STRICT (exit 1 on issues)' : 'WARNING (exit 0)'}${VERBOSE ? ' + VERBOSE' : ''}\n`);
@ -419,45 +374,35 @@ if (require.main === module) {
let refs;
if (ext === '.yaml' || ext === '.yml') {
refs = extractYamlRefs(filePath, content);
} else if (ext === '.csv') {
refs = extractCsvRefs(filePath, content);
} else {
refs = extractMarkdownRefs(filePath, content);
}
// Resolve and classify all refs before printing anything.
// This avoids the confusing pattern of printing headers at two different
// times depending on verbosity — collect first, then print once.
// Resolve and check
const broken = [];
const ok = [];
if (VERBOSE && refs.length > 0) {
console.log(`\n${relativePath}`);
}
for (const ref of refs) {
totalRefs++;
const resolved = resolveRef(ref);
if (resolved && !fs.existsSync(resolved)) {
// Extensionless paths may be directory references or partial templates.
// If the path has no extension, check whether it exists as a directory.
// Flag it if nothing exists at all — likely a real broken reference.
// For paths without extensions, also check if it's a directory
const hasExt = path.extname(resolved) !== '';
if (!hasExt) {
if (fs.existsSync(resolved)) {
ok.push({ ref, tag: 'OK-DIR' });
} else {
// No extension and nothing exists — not a file, not a directory.
// Flag as UNRESOLVED (distinct from BROKEN which means "file with extension not found").
broken.push({ ref, resolved: path.relative(PROJECT_ROOT, resolved), kind: 'unresolved' });
brokenRefs++;
}
// Could be a directory reference — skip if not clearly a file
continue;
}
broken.push({ ref, resolved: path.relative(PROJECT_ROOT, resolved), kind: 'broken' });
broken.push({ ref, resolved: path.relative(PROJECT_ROOT, resolved) });
brokenRefs++;
continue;
}
if (resolved) {
ok.push({ ref, tag: 'OK' });
if (VERBOSE && resolved) {
console.log(` [OK] ${ref.raw}`);
}
}
@ -465,33 +410,21 @@ if (require.main === module) {
const leaks = checkAbsolutePathLeaks(filePath, content);
totalLeaks += leaks.length;
// Print results — file header appears once, in one place
const hasFileIssues = broken.length > 0 || leaks.length > 0;
if (hasFileIssues) {
// Report issues for this file
if (broken.length > 0 || leaks.length > 0) {
filesWithIssues++;
if (!VERBOSE) {
console.log(`\n${relativePath}`);
if (VERBOSE) {
for (const { ref, tag, note } of ok) {
const suffix = note ? ` (${note})` : '';
console.log(` [${tag}] ${ref.raw}${suffix}`);
}
}
for (const { ref, resolved, kind } of broken) {
for (const { ref, resolved } of broken) {
const location = ref.line ? `line ${ref.line}` : ref.key ? `key: ${ref.key}` : '';
const tag = kind === 'unresolved' ? 'UNRESOLVED' : 'BROKEN';
const detail = kind === 'unresolved' ? 'Not found as file or directory' : 'Target not found';
const issueType = kind === 'unresolved' ? 'unresolved path' : 'broken ref';
console.log(` [${tag}] ${ref.raw}${location ? ` (${location})` : ''}`);
console.log(` ${detail}: ${resolved}`);
allIssues.push({ file: relativePath, line: ref.line || 1, ref: ref.raw, issue: issueType });
console.log(` [BROKEN] ${ref.raw}${location ? ` (${location})` : ''}`);
console.log(` Target not found: ${resolved}`);
allIssues.push({ file: relativePath, line: ref.line || 1, ref: ref.raw, issue: 'broken ref' });
if (process.env.GITHUB_ACTIONS) {
const line = ref.line || 1;
console.log(
`::warning file=${relativePath},line=${line}::${escapeAnnotation(`${tag === 'UNRESOLVED' ? 'Unresolved path' : 'Broken reference'}: ${ref.raw}${resolved}`)}`,
);
console.log(`::warning file=${relativePath},line=${line}::${escapeAnnotation(`Broken reference: ${ref.raw}${resolved}`)}`);
}
}
@ -502,12 +435,6 @@ if (require.main === module) {
console.log(`::warning file=${relativePath},line=${leak.line}::${escapeAnnotation(`Absolute path leak: ${leak.content}`)}`);
}
}
} else if (VERBOSE && refs.length > 0) {
console.log(`\n${relativePath}`);
for (const { ref, tag, note } of ok) {
const suffix = note ? ` (${note})` : '';
console.log(` [${tag}] ${ref.raw}${suffix}`);
}
}
}
@ -551,4 +478,3 @@ if (require.main === module) {
}
process.exit(hasIssues && STRICT ? 1 : 0);
}

View File

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" width="100" height="100">
<rect width="100" height="100" rx="20" fill="#0d9488"/>
<text x="50" y="65" font-family="Arial, sans-serif" font-size="40" font-weight="bold" fill="white" text-anchor="middle">B</text>
</svg>

After

Width:  |  Height:  |  Size: 284 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 MiB

View File

@ -4,16 +4,17 @@ import { getSiteUrl } from '../lib/site-url.mjs';
const llmsFullUrl = `${getSiteUrl()}/llms-full.txt`;
---
<div class="ai-banner" role="note" aria-label="AI documentation notice">
<div class="ai-banner">
<span>🤖 Consolidated, AI-optimized BMAD docs: <a href={llmsFullUrl}>llms-full.txt</a>. Fetch this plain text file for complete context.</span>
</div>
<style>
.ai-banner {
width: 100%;
width: 100vw; /* Full viewport width */
margin-left: calc(-50vw + 50%); /* Center and break out of container */
height: var(--ai-banner-height, 2.75rem);
background: #334155;
color: #cbd5e1;
color: rgb(148, 163, 184);
padding: 0.5rem 1rem;
font-size: 0.875rem;
border-bottom: 1px solid rgba(140, 140, 255, 0.15);
@ -32,18 +33,13 @@ const llmsFullUrl = `${getSiteUrl()}/llms-full.txt`;
max-width: 100%;
}
.ai-banner a {
color: #B9B9FF;
color: #8C8CFF;
text-decoration: none;
font-weight: 600;
}
.ai-banner a:hover {
text-decoration: underline;
}
.ai-banner a:focus-visible {
outline: 2px solid #B9B9FF;
outline-offset: 2px;
border-radius: 2px;
}
/* Match navbar padding at breakpoints */
@media (min-width: 50rem) {

View File

@ -5,10 +5,6 @@
* /img/foo.png /BMAD-METHOD/img/foo.png (when base is /BMAD-METHOD/)
* /llms.txt /BMAD-METHOD/llms.txt
*
* Supported elements:
* - img[src], iframe[src], video[src], source[src], audio[src]
* - a[href], link[href]
*
* Only affects absolute paths (/) - relative paths and external URLs are unchanged.
* Does NOT process .md links (those are handled by rehype-markdown-links).
*/
@ -25,57 +21,37 @@ import { visit } from 'unist-util-visit';
export default function rehypeBasePaths(options = {}) {
const base = options.base || '/';
// Normalize base: ensure trailing slash so concatenation with path.slice(1) (no leading /)
// produces correct paths like /BMAD-METHOD/img/foo.png.
// Note: rehype-markdown-links uses the opposite convention (strips trailing slash) because
// it concatenates with paths that start with /.
// Normalize base: ensure it ends with / and doesn't have double slashes
const normalizedBase = base === '/' ? '/' : base.endsWith('/') ? base : base + '/';
/**
* Prepend base path to an absolute URL attribute if needed.
* Skips protocol-relative URLs (//) and paths that already include the base.
*
* @param {object} node - HAST element node
* @param {string} attr - Attribute name ('src' or 'href')
*/
function prependBase(node, attr) {
const value = node.properties?.[attr];
if (typeof value !== 'string' || !value.startsWith('/') || value.startsWith('//')) {
return;
}
if (normalizedBase !== '/' && !value.startsWith(normalizedBase)) {
node.properties[attr] = normalizedBase + value.slice(1);
}
}
return (tree) => {
// Handle raw HTML blocks (inline HTML in markdown that isn't parsed into HAST elements)
if (normalizedBase !== '/') {
visit(tree, 'raw', (node) => {
// Replace absolute src="/..." and href="/..." attributes, skipping protocol-relative
// and paths that already have the base prefix
node.value = node.value.replace(/(?<attr>\b(?:src|href))="(?<path>\/(?!\/)[^"]*)"/g, (match, attr, pathValue) => {
if (pathValue.startsWith(normalizedBase)) return match;
return `${attr}="${normalizedBase}${pathValue.slice(1)}"`;
});
});
}
visit(tree, 'element', (node) => {
const tag = node.tagName;
// Process img tags with src attribute
if (node.tagName === 'img' && node.properties?.src) {
const src = node.properties.src;
// Tags with src attribute
if (['img', 'iframe', 'video', 'source', 'audio'].includes(tag)) {
prependBase(node, 'src');
if (typeof src === 'string' && src.startsWith('/') && !src.startsWith('//')) {
// Don't transform if already has the base path
if (normalizedBase !== '/' && !src.startsWith(normalizedBase)) {
node.properties.src = normalizedBase + src.slice(1);
}
}
}
// Link tags with href attribute (stylesheets, preloads, etc.)
if (tag === 'link') {
prependBase(node, 'href');
// Process iframe tags with src attribute
if (node.tagName === 'iframe' && node.properties?.src) {
const src = node.properties.src;
if (typeof src === 'string' && src.startsWith('/') && !src.startsWith('//')) {
// Don't transform if already has the base path
if (normalizedBase !== '/' && !src.startsWith(normalizedBase)) {
node.properties.src = normalizedBase + src.slice(1);
}
}
}
// Anchor tags need special handling - skip .md links
if (tag === 'a' && node.properties?.href) {
// Process anchor tags with href attribute
if (node.tagName === 'a' && node.properties?.href) {
const href = node.properties.href;
if (typeof href !== 'string') {

View File

@ -1,111 +1,117 @@
/**
* Rehype plugin to transform relative .md links into correct site URLs.
* Rehype plugin to transform markdown file links (.md) to page routes
*
* Uses the source file's disk path (via vfile) to resolve the link target,
* then computes the output URL relative to the content root directory.
* This correctly handles Starlight's directory-per-page URL structure
* where ./sibling.md from reference/testing.md must become /reference/sibling/
* (not ./sibling/ which would resolve to /reference/testing/sibling/).
* Transforms:
* ./path/to/file.md ./path/to/file/
* ./path/index.md ./path/ (index.md becomes directory root)
* ../path/file.md#anchor ../path/file/#anchor
* ./file.md?query=param ./file/?query=param
* /docs/absolute/path/file.md {base}/absolute/path/file/
*
* Supports: ./sibling.md, ../other/page.md, bare.md, /docs/absolute.md
* Preserves: query strings, hash anchors
* Skips: external URLs, non-.md links
* For absolute paths starting with /docs/, the /docs prefix is stripped
* since the Astro site serves content from the docs directory as the root.
* The base path is prepended to absolute paths for subdirectory deployments.
*
* Affects relative links (./, ../) and absolute paths (/) - external links are unchanged
*/
import { visit } from 'unist-util-visit';
import path from 'node:path';
/**
* @param {Object} options
* @param {string} options.base - Site base path (e.g., '/BMAD-METHOD/')
* @param {string} [options.contentDir] - Absolute path to content root; auto-detected if omitted
* Convert Markdown file links (.md) into equivalent page route-style links.
*
* The returned transformer walks the HTML tree and rewrites anchor `href` values that are relative paths (./, ../) or absolute paths (/) pointing to `.md` files. It preserves query strings and hash anchors, rewrites `.../index.md` to the directory root path (`.../`), and rewrites other `.md` file paths by removing the `.md` extension and ensuring a trailing slash. External links (http://, https://) and non-.md links are left unchanged.
*
* @param {Object} options - Plugin options
* @param {string} options.base - The base path to prepend to absolute URLs (e.g., '/BMAD-METHOD/')
* @returns {function} A HAST tree transformer that mutates `a` element `href` properties as described.
*/
export default function rehypeMarkdownLinks(options = {}) {
const base = options.base || '/';
const normalizedBase = base === '/' ? '' : base.replace(/\/$/, '');
return (tree, file) => {
// The current file's absolute path on disk, set by Astro's markdown pipeline
const currentFilePath = file.path;
if (!currentFilePath) return;
// Auto-detect content root: walk up from current file to find src/content/docs
const contentDir = options.contentDir || detectContentDir(currentFilePath);
if (!contentDir) {
throw new Error(`[rehype-markdown-links] Could not detect content directory for: ${currentFilePath}`);
}
// Normalize base: ensure it ends with / and doesn't have double slashes
const normalizedBase = base === '/' ? '' : base.endsWith('/') ? base.slice(0, -1) : base;
return (tree) => {
visit(tree, 'element', (node) => {
if (node.tagName !== 'a' || typeof node.properties?.href !== 'string') {
// Only process anchor tags with href
if (node.tagName !== 'a' || !node.properties?.href) {
return;
}
const href = node.properties.href;
// Skip external links (including protocol-relative URLs like //cdn.example.com)
if (href.includes('://') || href.startsWith('//') || href.startsWith('mailto:') || href.startsWith('tel:')) {
// Skip if not a string (shouldn't happen, but be safe)
if (typeof href !== 'string') {
return;
}
// Split href into path vs query+fragment suffix
const delimIdx = findFirstDelimiter(href);
const linkPath = delimIdx === -1 ? href : href.substring(0, delimIdx);
const suffix = delimIdx === -1 ? '' : href.substring(delimIdx);
// Skip external links (http://, https://, mailto:, etc.)
if (href.includes('://') || href.startsWith('mailto:') || href.startsWith('tel:')) {
return;
}
// Only process .md links
if (!linkPath.endsWith('.md')) return;
// Only transform paths starting with ./, ../, or / (absolute)
if (!href.startsWith('./') && !href.startsWith('../') && !href.startsWith('/')) {
return;
}
// Resolve the target file's absolute path on disk
let targetPath;
if (linkPath.startsWith('/docs/')) {
// Absolute /docs/ path — resolve from content root
targetPath = path.join(contentDir, linkPath.slice(5)); // strip '/docs'
} else if (linkPath.startsWith('/')) {
// Other absolute paths — resolve from content root
targetPath = path.join(contentDir, linkPath);
// Extract path portion (before ? and #) to check if it's a .md file
const firstDelimiter = Math.min(
href.indexOf('?') === -1 ? Infinity : href.indexOf('?'),
href.indexOf('#') === -1 ? Infinity : href.indexOf('#'),
);
const pathPortion = firstDelimiter === Infinity ? href : href.substring(0, firstDelimiter);
// Don't transform if path doesn't end with .md
if (!pathPortion.endsWith('.md')) {
return;
}
// Split the URL into parts: path, anchor, and query
let urlPath = pathPortion;
let anchor = '';
let query = '';
// Extract query string and anchor from original href
if (firstDelimiter !== Infinity) {
const suffix = href.substring(firstDelimiter);
const anchorInSuffix = suffix.indexOf('#');
if (suffix.startsWith('?')) {
if (anchorInSuffix !== -1) {
query = suffix.substring(0, anchorInSuffix);
anchor = suffix.substring(anchorInSuffix);
} else {
// Relative path (./sibling.md, ../other.md, bare.md) — resolve from current file
targetPath = path.resolve(path.dirname(currentFilePath), linkPath);
query = suffix;
}
} else {
// starts with #
anchor = suffix;
}
}
// Compute the target's path relative to content root
const relativeToContent = path.relative(contentDir, targetPath);
// Track if this was an absolute path (for base path prepending)
const isAbsolute = urlPath.startsWith('/');
// Safety: skip if target resolves outside content root
if (relativeToContent.startsWith('..')) return;
// Convert file path to URL: strip .md, handle index, ensure leading/trailing slashes
let urlPath = relativeToContent.replace(/\.md$/, '');
// index.md becomes the directory root
if (urlPath.endsWith('/index') || urlPath === 'index') {
urlPath = urlPath.slice(0, -'index'.length);
// Strip /docs/ prefix from absolute paths (repo-relative → site-relative)
if (urlPath.startsWith('/docs/')) {
urlPath = urlPath.slice(5); // Remove '/docs' prefix, keeping the leading /
}
// Build absolute URL with base path, normalizing any double slashes
const raw = normalizedBase + '/' + urlPath.replace(/\/?$/, '/') + suffix;
node.properties.href = raw.replace(/\/\/+/g, '/');
// Transform .md to /
// Special case: index.md → directory root (e.g., ./tutorials/index.md → ./tutorials/)
if (urlPath.endsWith('/index.md')) {
urlPath = urlPath.replace(/\/index\.md$/, '/');
} else {
urlPath = urlPath.replace(/\.md$/, '/');
}
// Prepend base path to absolute URLs for subdirectory deployments
if (isAbsolute && normalizedBase) {
urlPath = normalizedBase + urlPath;
}
// Reconstruct the href
node.properties.href = urlPath + query + anchor;
});
};
}
/** Find the index of the first ? or # in a string, or -1 if neither exists. */
export function findFirstDelimiter(str) {
const q = str.indexOf('?');
const h = str.indexOf('#');
if (q === -1) return h;
if (h === -1) return q;
return Math.min(q, h);
}
/** Walk up from a file path to find the content docs directory. */
export function detectContentDir(filePath) {
const segments = filePath.split(path.sep);
// Look for src/content/docs in the path
for (let i = segments.length - 1; i >= 2; i--) {
if (segments[i - 2] === 'src' && segments[i - 1] === 'content' && segments[i] === 'docs') {
return segments.slice(0, i + 1).join(path.sep);
}
}
return null;
}

View File

@ -17,12 +17,12 @@
--sl-nav-height: 6.25rem; /* Base nav height (~3.5rem) + banner height (2.75rem) */
/* Full-width content - override Starlight's default 45rem/67.5rem */
--sl-content-width: 65rem;
--sl-content-width: 100%;
/* Primary accent colors - purple to match Docusaurus */
--sl-color-accent-low: #e0e0ff;
--sl-color-accent: #5E5ED0;
--sl-color-accent-high: #3333CC;
--sl-color-accent: #8C8CFF;
--sl-color-accent-high: #4141FF;
/* Text colors */
--sl-color-white: #1e293b;
@ -41,7 +41,7 @@
--sl-line-height: 1.7;
/* Code highlighting */
--sl-color-bg-inline-code: rgba(94, 94, 208, 0.1);
--sl-color-bg-inline-code: rgba(140, 140, 255, 0.1);
}
/* ============================================
@ -49,7 +49,7 @@
============================================ */
:root[data-theme='dark'] {
/* Full-width content - override Starlight's default */
--sl-content-width: 65rem;
--sl-content-width: 100%;
/* Primary accent colors - purple to match Docusaurus */
--sl-color-accent-low: #2a2a5a;
@ -138,7 +138,7 @@
/* Active state - thin left accent bar */
.sidebar-content a[aria-current='page'] {
background-color: rgba(94, 94, 208, 0.08);
background-color: rgba(140, 140, 255, 0.08);
color: var(--sl-color-accent);
border-left-color: var(--sl-color-accent);
font-weight: 600;
@ -281,7 +281,7 @@ header.header .header.sl-flex {
.card:hover {
transform: translateY(-3px);
border-color: var(--sl-color-accent);
box-shadow: 0 8px 24px rgba(94, 94, 208, 0.15);
box-shadow: 0 8px 24px rgba(140, 140, 255, 0.15);
}
:root[data-theme='dark'] .card {
@ -339,25 +339,10 @@ button:hover {
MISC ENHANCEMENTS
============================================ */
/* Prevent horizontal scrollbar from full-viewport-width breakout elements (e.g. iframe) */
html {
overflow-x: clip;
}
/* Smooth scrolling */
@media (prefers-reduced-motion: no-preference) {
html {
scroll-behavior: smooth;
}
}
/* Disable hover animations for users who prefer reduced motion */
@media (prefers-reduced-motion: reduce) {
.card:hover,
.sl-link-card:hover {
transform: none;
}
}
/* Better link underlines */
.sl-markdown-content a:not(.sl-link-card) {
@ -386,7 +371,7 @@ table {
/* Blockquotes */
blockquote {
border-left-color: var(--sl-color-accent);
background-color: rgba(94, 94, 208, 0.05);
background-color: rgba(140, 140, 255, 0.05);
border-radius: 0 8px 8px 0;
padding: 1rem 1.25rem;
}
@ -410,7 +395,7 @@ blockquote {
}
.starlight-aside--tip .starlight-aside__title {
color: #047857;
color: #059669;
}
:root[data-theme='dark'] .starlight-aside--tip {
@ -423,11 +408,11 @@ blockquote {
/* Note aside */
.starlight-aside--note {
background-color: rgba(94, 94, 208, 0.08);
background-color: rgba(140, 140, 255, 0.08);
}
.starlight-aside--note .starlight-aside__title {
color: #5C5CCC;
color: #8C8CFF;
}
:root[data-theme='dark'] .starlight-aside--note {
@ -444,7 +429,7 @@ blockquote {
}
.starlight-aside--caution .starlight-aside__title {
color: #a14908;
color: #d97706;
}
:root[data-theme='dark'] .starlight-aside--caution {
@ -461,7 +446,7 @@ blockquote {
}
.starlight-aside--danger .starlight-aside__title {
color: #be1c1c;
color: #dc2626;
}
:root[data-theme='dark'] .starlight-aside--danger {
@ -479,10 +464,16 @@ blockquote {
}
/* ============================================
FOOTER - No custom styling needed
The only <footer> in Starlight is the content footer
(meta/pagination), which should stay transparent.
FOOTER - Minimal styling
============================================ */
footer {
background-color: var(--sl-color-black);
border-top: 1px solid var(--sl-color-hairline);
}
:root[data-theme='dark'] footer {
background-color: #020617;
}
/* ============================================
RESPONSIVE ADJUSTMENTS