Compare commits

..

44 Commits

Author SHA1 Message Date
Dicky Moore dde139a560 fix: restore installer edge-case handling and path normalization 2026-02-08 20:04:58 +00:00
Dicky Moore c108ae4314 fix: remove stale xml docs reference and tighten workflow wording 2026-02-08 19:54:37 +00:00
Dicky Moore ee7f5a8969 fix: harden document workflow choices and config path handling 2026-02-08 19:43:46 +00:00
Dicky Moore 197c0f185b
Merge branch 'main' into phase1-md-workflows-clean 2026-02-08 19:23:31 +00:00
Dicky Moore a94df6dcd8 fix: address latest coderabbit workflow guidance issues 2026-02-08 18:02:21 +00:00
Alex Verkhovsky a1101534b2
fix(docs): comprehensive documentation site review fixes (#1578)
* fix(docs): comprehensive documentation site review fixes

Rehype plugins:
- Rewrite rehype-markdown-links for correct relative .md resolution
- Handle raw HTML base paths and bare .md links in rehype-base-paths
- Guard protocol-relative URLs (//...) in all link processors
- Use file.path instead of file.history[0] for vfile compatibility
- Fail build when content directory cannot be detected
- Export helpers for testability; add 107 unit tests

Build & CI:
- Revert cancel-in-progress to false to avoid mid-deploy cancellation
- Remove redundant link-validation CI step (build validates internally)
- Remove unnecessary fetch-depth:0 from docs deploy workflow
- Refuse docs build on Windows (platform guard)
- Remove dead build scripts and stale references

Tooling:
- Add DOCS_ROOT boundary check in validate-doc-links.js
- Handle directory paths and prefix stripping in link validator
- Remove dead regex and add // guard in fix-doc-links.js

Accessibility & CSS:
- Darken caution/danger aside title colors for WCAG AA 4.5:1 contrast
- Fix 100vw scrollbar overflow (banner width:100%, html overflow-x:clip)
- Add :focus-visible ring to banner link for keyboard navigation
- Remove dead CSS declaration and add missing code block lang

Documentation content:
- Convert /docs/ absolute links to relative paths and fix llms.txt URLs
- Correct command file paths and naming in commands reference
- Update stale shard-doc command to current /bmad-shard-doc format
- Fix incomplete sentence in install-bmad.md
- Add Quick Flow next steps and fix 404 link path
- Expand thin content pages with substantive detail
- Add sidebar ordering frontmatter to all content pages
- Remove BMGD docs (moved to dedicated repo)
- Remove unused assets and misleading diagram caption
- Add non-iframe fallback link to workflow map diagram
- Remove dead noscript block from workflow-map
- Standardize BMAD to BMad, fix spelling/grammar, normalize headings

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(docs): add non-interactive installation to sidebar, rewrite and reorder how-to guides

- Move non-interactive-installation.md into how-to/ directory so it appears
  in the sidebar navigation (was orphaned at docs root)
- Rewrite the page based on editorial review: consolidate redundant sections,
  add missing how-to structure (prerequisites, "What You Get"), condense
  installation modes from 5 subsections to a table, cut speculative examples
- Reorder how-to sidebar: Install (1), Non-Interactive (2), Upgrade to v6 (3),
  then the rest following user journey order
- Fix README link to point to docs site instead of repo-internal markdown path

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(docs): address documentation review findings

Fix broken directory tree, grammar errors, inconsistent naming,
missing admonition/headings, enable lastUpdated timestamps in CI,
and remove footer CSS that misapplied to the content footer.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(docs): move bleeding-edge install command out of Verify Installation

The alternative npx command for installing from main was misplaced
inside the "Verify Installation" section. Move it to a tip admonition
under Step 1 where users look for install options.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-08 11:58:22 -06:00
Dicky Moore c916b78dea chore: acknowledge migration scope and request focused review
This PR is intentionally large due to coupled workflow.md migration and step-flow parity updates across runtime, installer, and tests.\n\nFollow-up work can be split by domain if maintainers prefer smaller review surfaces.
2026-02-08 17:50:56 +00:00
Dicky Moore c1484c3dcc fix: restore workflow input contracts after md migration 2026-02-08 16:33:34 +00:00
Dicky Moore d87d654af7 fix(document-project): gate resume completion on subworkflow success 2026-02-08 16:14:37 +00:00
Dicky Moore e0cfff50da fix: tighten workflow routing and validation consistency 2026-02-08 15:52:47 +00:00
Dicky Moore 80328a6d2a fix: restore help grounding and align installer defaults 2026-02-08 15:44:56 +00:00
Dicky Moore 3a7d8933b5 fix(kiro): migrate yaml workflow template runner to workflow.md 2026-02-08 15:35:54 +00:00
Dicky Moore bff2808986
Merge branch 'main' into phase1-md-workflows-clean 2026-02-08 15:32:11 +00:00
Michael Pursifull 24cf444366
feat: extend Layer 1 file-ref validator to scan CSV workflow-file references (#1573)
* feat: extend validate-file-refs.js to scan CSV workflow-file references

Add CSV file reference extraction to the Layer 1 validation pipeline,
preventing broken _bmad/ workflow-file paths in module-help.csv files.
Closes the gap identified after PR #1529 where CSV references were
unvalidated despite being a source of repeat community issues.

Refs: #1519

* fix: include test:refs in aggregate test script

Add CSV file-ref extraction tests to the aggregate `npm test` pipeline,
matching the existing pattern for test:schemas and test:install.

Thanks to CodeRabbit for catching the omission.

* fix: address review feedback on CSV validator extension

- Surface CSV parse errors visibly instead of silently swallowing
  (no Layer 2c schema validator exists yet to catch these)
- Add explanatory comments for the !VERBOSE logging pattern
  (non-verbose prints file headers only when issues found)
- Add verbose-mode diagnostics for extensionless path handling
  ([SKIP] when nothing exists, [OK-DIR] for valid directories)

* refactor: collect-then-print to eliminate confusing !VERBOSE pattern

Replace the split header-printing logic (print early in verbose mode,
print late in non-verbose mode with a !VERBOSE guard) with a simpler
collect-then-print approach. Refs are now classified into ok[] and
broken[] arrays first, then printed in a single location with one
straightforward if/else if decision.

Addresses alexeyv's review feedback about the counterintuitive
"if not verbose, log" pattern.

* feat: promote extensionless unresolved paths from silent skip to [UNRESOLVED]

Paths without file extensions that don't exist as files or directories
are now flagged as [UNRESOLVED] — a distinct tag from [BROKEN] (which
means a file with a known extension wasn't found). Both count toward
the broken reference total and appear in CI annotations.

This catches real bugs like wrong directory names in installed_path
metadata and dead invoke-workflow references to removed workflows.
Extensionless paths that DO exist as directories are still [OK-DIR].

---------

Co-authored-by: Alex Verkhovsky <alexey.verkhovsky@gmail.com>
Co-authored-by: Brian <bmadcode@gmail.com>
2026-02-08 09:19:53 -06:00
Alex Verkhovsky fc5ef57a5a
feat: add Kiro IDE support via config-driven installer (#1589)
Replace broken kiro-cli.js custom installer with config-driven approach
using platform-codes.yaml. Creates Kiro-specific templates with
inclusion: manual frontmatter and #[[file:...]] reference syntax.
2026-02-08 09:18:28 -06:00
Dicky Moore 45a9f3bf4e test: remove redundant validate-workflow exclusion in guard scan 2026-02-08 15:07:46 +00:00
Dicky Moore a1c054006a refine workflow contracts for review findings, halt protocol, and sprint tracking 2026-02-08 15:01:58 +00:00
Dicky Moore e7d7bbc3ea docs(correct-course): clarify installed vs source workflow paths 2026-02-08 14:46:45 +00:00
Dicky Moore 25354fc131 Merge remote-tracking branch 'origin/phase1-md-workflows-clean' into phase1-md-workflows-clean 2026-02-08 14:25:57 +00:00
Dicky Moore f74f6047f2 fix: hide internal workflow task from codex exports 2026-02-08 14:23:22 +00:00
Dicky Moore 5caf92cfdb fix: restore help routing and external module install tolerance 2026-02-08 14:23:22 +00:00
Dicky Moore 9604f6d00c fix: align workflow path guidance with installed runtime 2026-02-08 14:23:22 +00:00
Dicky Moore 98831695d0 fix: define installed workflow paths in qa and create-story 2026-02-08 14:23:21 +00:00
Dicky Moore e86fa2ee9e fix: restore runtime workflow paths and standalone parsing 2026-02-08 14:23:21 +00:00
Dicky Moore 3feb0d378f fix: resolve workflow guardrails and installer regressions 2026-02-08 14:23:21 +00:00
Dicky Moore 2a9b447e04 fix: add workflow loader fallback guidance and guards 2026-02-08 14:23:21 +00:00
Dicky Moore cfdb2db0f5 fix: correct workflow command defaults and bmm document config paths 2026-02-08 14:23:21 +00:00
Dicky Moore 454ae910f9 fix: harden install XML guard and remove no-op placeholder replacement 2026-02-08 14:23:06 +00:00
Dicky Moore 87c4292021 Finish step-flow migration for dev workflows and fix installer regressions 2026-02-08 14:23:06 +00:00
Dicky Moore 7fc8318e08 Migrate remaining workflows to workflow.md and fix CLI/test regressions 2026-02-08 14:22:30 +00:00
Dicky Moore c636042dba Migrate QA automate workflow to workflow.md 2026-02-08 14:22:30 +00:00
Dicky Moore 3aba7f089d Update validate-workflow references 2026-02-08 14:22:30 +00:00
Dicky Moore 21efe68512 Remove reintroduced TEA/excalidraw artifacts 2026-02-08 14:22:30 +00:00
Dicky Moore 052cc839fe Fix remaining workflow.yaml/xml references 2026-02-08 14:22:30 +00:00
Dicky Moore 0c34f528da Drop YAML workflow support from CLI tooling 2026-02-08 14:22:30 +00:00
Dicky Moore 2212d92260 Remove workflow.xml runner and update CLI wording 2026-02-08 14:21:37 +00:00
Dicky Moore 9054719d93 Migrate workflow runner references to workflow.md 2026-02-08 14:21:37 +00:00
Dicky Moore a1bc1a37f4 Convert Phase 2/3 workflows to MD 2026-02-08 14:11:58 +00:00
Dicky Moore 730ba6bdb0 Add advanced-elicitation MD workflow and guard 2026-02-08 14:11:58 +00:00
Dicky Moore ba91d9c03f Convert remaining Phase 1 workflows to MD 2026-02-08 14:11:58 +00:00
Dicky Moore 5042a895de Make workflow references platform-agnostic 2026-02-08 14:11:58 +00:00
Dicky Moore 129f2d4ac9 fix: hide internal workflow task from codex exports 2026-02-08 14:06:43 +00:00
Dicky Moore 40787c5035 fix: restore help routing and external module install tolerance 2026-02-08 14:06:43 +00:00
Davor Racic b1bfce9aa7
refactor: Complete @clack/prompts Migration & Installer Output Consolidation (#1586)
* feat(cli): complete @clack/prompts migration

Full migration of BMAD CLI installer from legacy terminal libraries
(chalk, ora, boxen, figlet, wrap-ansi, cli-table3, readline) to unified
@clack/prompts v1.0.0 visual system.

Foundation (prompts.js + cli-utils.js):
  - Extended prompts.js wrapper with box, spinner, progress, taskLog,
    path, autocomplete, selectKey, stream, color re-export
  - Refactored cli-utils.js: displayLogo uses box(), sections use note(),
    steps use log.step(), removed boxen/figlet/wrap-ansi/cli-table3

UI orchestration (ui.js):
  - Replaced ~100 console.log+chalk calls with log.*, note(), box()
  - Replaced ora spinner with @clack spinner
  - Module selection: autocompleteMultiselect with locked core module,
    bulleted post-selection display, maxItems for no-scroll

Spinner migration (installer.js):
  - Replaced 40+ ora spinner calls with @clack spinner
  - All spinner.stop() calls include meaningful messages
  - Failure paths use spinner.error() (red cross) instead of stop()

Readline migration (agent/installer.js + config-collector.js):
  - Migrated readline prompts to @clack text/confirm/select
  - Fixed chalk.dim bug (chalk was never imported)
  - Removed chalk from config-collector.js

IDE handlers + modules (7 files):
  - Replaced chalk+ora across all IDE handlers and module manager
  - Fixed options.installer undefined bug in manager.js update()

Cleanup:
  - Removed ora, boxen, figlet, wrap-ansi, cli-table3 from dependencies
  - chalk stays (used outside tools/cli/ scope)
  - Replaced hand-drawn Unicode update box in bmad-cli.js with box()
  - Added process.stdin.setMaxListeners(25) for sequential prompts

Spinner wrapper adds isSpinning state tracking (not native to @clack).
Removed dead groupMultiselect and sortKey sort calls.

Ref: tech-spec-installer-clack-migration-ui-enhancement.md

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat(cli): consolidate installer output to single spinner + summary

Replace ~40 lines of output from 15+ spinner start/stop cycles with a
single animated spinner during installation and a final note() summary
block showing checkmarks per step.

Key changes:
- Add results collector pattern in install() method
- Replace spinner.stop/start pairs with addResult + spinner.message
- Add renderInstallSummary() using prompts.note() with colored output
- Propagate silent flag through IDE handlers and module manager
- Add spinner race condition guards (start while spinning, stop while stopped)
- Add no-op spinner pattern for silent external module cloning
- Fix stdin listener limit to be defensive with Math.max
- Add GIT_TERMINAL_PROMPT=0 for non-interactive git operations
- Merge locked values into initialValue for autocomplete prompts

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(cli): resolve code review findings from @clack/prompts migration

Address 31 issues across 14 CLI files found during PR #1586 review
(Augment Code + CodeRabbit):

- Fix bmadDir ReferenceError by hoisting declaration before try block
- Wrap console.log monkey-patch in try/finally for safe restoration
- Fix transformWorkflowPath dead code and undefined return path
- Fix broken symlink crash in _config-driven.js and codex.js cleanup
- Pass installer instance through update() for agent recompilation
- Fix @clack/prompts API: defaultValue→default, initialValue→default
- Use nullish coalescing (??) instead of logical OR for falsy values
- Forward options in recursive promptToolSelection calls
- Remove no-op replaceAll('_bmad','_bmad') in manager and generator
- Remove unused confirm prompt in config-collector hasNoConfig branch
- Guard spinner.message() when spinner is not running
- Add missing methods to silent spinner stub (cancel, clear, isSpinning)
- Wrap install.js error handler with inner try/catch + console fallback
- Gate codex per-entry error log with silent flag
- Add return statements to all stream wrapper methods
- Remove dead variables (availableNames, hasCustomContentItems)
- Filter core module from update flow selection
- Replace borderColor ternary chain with object map
- Fix Kilo "agents" label to "modes" in IDE manager
- Normalize error return shape for unsupported IDEs
- Fix spinner message timing before dependency resolution
- Guard undefined moduleDir in dependency-resolver
- Fix workflowsInstalled counter inflation in custom handler
- Migrate console.warn calls to prompts.log.warn
- Replace console.log() with prompts.log.message('')
- Fix legacyBmadPath hardcoded to .bmad instead of entry.name
- Fix focusedValue never assigned breaking SPACE toggle and hints

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-08 00:40:13 -06:00
104 changed files with 4128 additions and 3325 deletions

View File

@ -6,7 +6,6 @@ on:
- main
paths:
- "docs/**"
- "src/modules/*/docs/**"
- "website/**"
- "tools/build-docs.mjs"
- ".github/workflows/docs.yaml"
@ -19,6 +18,7 @@ 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,12 +28,13 @@ 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: "20"
node-version-file: ".nvmrc"
cache: "npm"
- name: Install dependencies

View File

@ -84,10 +84,8 @@ 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:

View File

@ -147,6 +147,15 @@ 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](docs/non-interactive-installation.md) for all available options.
See [Non-Interactive Installation Guide](http://docs.bmad-method.org/how-to/non-interactive-installation/) 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](/docs/index.md)
[Return to Home](./index.md)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 126 KiB

View File

@ -1,376 +0,0 @@
---
title: "Game Types Reference"
description: 24 game type templates with genre-specific GDD sections for BMGD
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 |

View File

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

@ -1,162 +0,0 @@
---
title: "Quick Flow Workflows"
description: Create tech specs and execute implementations with BMGD Quick Flow
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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 118 KiB

View File

@ -1,11 +1,17 @@
---
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.
Dozens of methods are built in - things like First Principles, Red Team vs Blue Team, Pre-mortem Analysis, Socratic Questioning, and more.
## 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.
## When to Use It
@ -22,3 +28,22 @@ 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,6 +1,8 @@
---
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.
@ -24,7 +26,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,6 +1,8 @@
---
title: "Brainstorming"
description: Interactive creative sessions using 60+ proven ideation techniques
sidebar:
order: 2
---
Unlock your creativity through guided exploration.

View File

@ -1,6 +1,8 @@
---
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,6 +1,8 @@
---
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,6 +1,8 @@
---
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.
@ -69,7 +71,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,27 +1,73 @@
---
title: "Quick Flow"
description: Fast-track for small changes - skip the full methodology
sidebar:
order: 1
---
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
1. **Run `quick-spec`** — generates a focused tech-spec
2. **Run `quick-dev`** — implements it
That's it.
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
- Refactoring
- Small features
- Prototyping
- 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 to Use Full BMad Method Instead
## When NOT to Use It
- New products
- Major features
- Multiple teams involved
- Stakeholder alignment needed
- 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.
:::
## How It Works
Quick Flow has two commands, each backed by a structured workflow. You can run them together or independently.
### quick-spec: Plan
Run `quick-spec` and Barry (the Quick Flow agent) walks you through a conversational discovery process:
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.
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.
### 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.

View File

@ -1,6 +1,8 @@
---
title: "Why Solutioning Matters"
description: Understanding why the solutioning phase is critical for multi-epic projects
sidebar:
order: 3
---
@ -8,7 +10,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
@ -18,7 +20,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,34 +1,35 @@
---
title: "BMad Method Customization Guide"
title: "How to Customize BMad"
description: Customize agents, workflows, and modules while preserving update compatibility
sidebar:
order: 7
---
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.
Use the `.customize.yaml` files to tailor agent behavior, personas, and menus while preserving your changes across updates.
The Customization Guidance outlined here, while targeted at understanding BMad Method customization, applies to any other module use within the BMad Method.
## When to Use This
## Types of Customization
- 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
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.
:::note[Prerequisites]
- BMad installed in your project (see [How to Install BMad](./install-bmad.md))
- A text editor for YAML files
:::
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.
:::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.
:::
## Agent Customization
## Steps
### Agent Customization Areas
### 1. Locate Customization Files
- 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
After installation, find one `.customize.yaml` file per agent in:
## How to customize an agent.
**1. Locate Customization Files**
After installation, find agent customization files in:
```
```text
_bmad/_config/agents/
├── core-bmad-master.customize.yaml
├── bmm-dev.customize.yaml
@ -36,28 +37,22 @@ _bmad/_config/agents/
└── ... (one file per installed agent)
```
**2. Edit Any Agent**
### 2. Edit the Customization File
Open the `.customize.yaml` file for the agent you want to modify. All sections are optional - customize only what you need.
Open the `.customize.yaml` file for the agent you want to modify. Every section is optional -- customize only what you need.
**3. Rebuild the Agent**
| 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 |
After editing, IT IS CRITICAL to rebuild the agent to apply changes:
Sections marked **Replaces** overwrite the agent's defaults entirely. Sections marked **Appends** add to the existing configuration.
```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
**Agent Name**
Change how the agent introduces itself:
@ -67,7 +62,7 @@ agent:
name: 'Spongebob' # Default: "Amelia"
```
#### Persona
**Persona**
Replace the agent's personality, role, and communication style:
@ -81,9 +76,9 @@ persona:
- 'Favor composition over inheritance'
```
**Note:** The persona section replaces the entire default persona (not merged).
The `persona` section replaces the entire default persona, so include all four fields if you set it.
#### Memories
**Memories**
Add persistent context the agent will always remember:
@ -91,12 +86,12 @@ Add persistent context the agent will always remember:
memories:
- 'Works at Krusty Krab'
- 'Favorite Celebrity: David Hasslehoff'
- 'Learned in Epic 1 that its not cool to just pretend that tests have passed'
- 'Learned in Epic 1 that it is not cool to just pretend that tests have passed'
```
### Custom Menu Items
**Menu Items**
Any custom items you add here will be included in the agents display menu.
Add custom entries to the agent's display menu. Each item needs a `trigger`, a target (`workflow` path or `action` reference), and a `description`:
```yaml
menu:
@ -108,18 +103,18 @@ menu:
description: Deploy to production
```
### Critical Actions
**Critical Actions**
Add instructions that execute before the agent starts:
Define instructions that run when the agent starts up:
```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**
Define reusable prompts for `action="#id"` menu handlers:
Create reusable prompts that menu items can reference with `action="#id"`:
```yaml
prompts:
@ -131,29 +126,47 @@ 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?**
- 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
- 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
**Agent not loading?**
- Check for YAML syntax errors
- Ensure required fields aren't left empty if you uncommented them
- Try reverting to the template and rebuilding
- 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
**Need to reset?**
**Need to reset an agent?**
- Remove content from the `.customize.yaml` file (or delete the file)
- Run `npx bmad-method build <agent-name>` to regenerate defaults
- Clear or delete the agent's `.customize.yaml` file
- Run `npx bmad-method install` and select **Recompile Agents** to restore defaults
## Workflow Customization
Information about customizing existing BMad Method workflows and skills are coming soon.
Customization of existing BMad Method workflows and skills is coming soon.
## Module Customization
Information on how to build expansion modules that augment BMad, or make other existing module customizations are coming soon.
Guidance on building expansion modules and customizing existing modules is coming soon.

View File

@ -1,6 +1,8 @@
---
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.
@ -44,8 +46,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
@ -76,5 +78,5 @@ Pay close attention here to prevent reinventing the wheel or making decisions th
## More Information
- **[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
- **[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

View File

@ -1,6 +1,8 @@
---
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.
@ -38,10 +40,11 @@ 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,11 +1,13 @@
---
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, [this guide](/docs/non-interactive-installation.md).
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).
## When to Use This
@ -27,6 +29,13 @@ 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:
@ -41,6 +50,7 @@ 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.
@ -55,7 +65,7 @@ The installer guides you through the rest — custom content, settings, etc.
## What You Get
```
```text
your-project/
├── _bmad/
│ ├── bmm/ # Your selected modules
@ -63,22 +73,16 @@ your-project/
│ ├── core/ # Required core module
│ └── ...
├── _bmad-output/ # Generated artifacts
└── .claude/ # Claude Code commands (if using Claude Code)
├── .claude/ # Claude Code commands (if using Claude Code)
└── .kiro/ # Kiro steering files (if using Kiro)
```
## 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](/docs/how-to/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](./get-answers-about-bmad.md) for how to point your AI at the right sources.

View File

@ -0,0 +1,171 @@
---
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,76 +1,123 @@
---
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
- 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
- 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.
## Steps
### 1. Load an Agent
### 1. Load the DEV Agent
For quick fixes, you can use:
Start a **fresh chat** in your AI IDE and load the DEV agent with its slash command:
- **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
```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.
:::
### 2. Describe the Change
Simply tell the agent what you need:
Tell the agent what you need in plain language. Be specific about the problem and, if you know it, where the relevant code lives.
```
Fix the login validation bug that allows empty passwords
```
:::note[Example Prompts]
**Bug fix** -- "Fix the login validation bug that allows empty passwords. The validation logic is in `src/auth/validate.ts`."
or
**Refactoring** -- "Refactor the UserService to use async/await instead of callbacks."
```
Refactor the UserService to use async/await instead of callbacks
```
**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.
### 3. Let the Agent Work
The agent will:
- Analyze the relevant code
- Propose a solution
- Implement the change
- Run tests (if available)
- 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
### 4. Review and Commit
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).
Review the changes made and commit when satisfied.
### 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.
:::
## Learning Your Codebase
This approach is also excellent for exploring unfamiliar code:
The DEV agent is also useful for exploring unfamiliar code. Load it in a fresh chat and ask questions:
```
Explain how the authentication system works in this codebase
```
:::note[Example Prompts]
"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."
LLMs are excellent at interpreting and analyzing code, whether it was AI-generated or not. Use the agent to:
"What does the `ProcessOrder` function do and what calls it?"
:::
- Learn about your project
- Understand how things are built
- Explore unfamiliar parts of the codebase
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.
## When to Upgrade to Formal Planning
Consider using Quick Flow or full BMad Method when:
Consider using [Quick Flow](../explanation/quick-flow.md) or the full BMad Method when:
- 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
- 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

View File

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

View File

@ -1,6 +1,8 @@
---
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.
@ -20,7 +22,7 @@ Use the BMad installer to upgrade from v4 to v6, which includes automatic detect
### 1. Run the Installer
Follow the [Installer Instructions](/docs/how-to/install-bmad.md).
Follow the [Installer Instructions](./install-bmad.md).
### 2. Handle Legacy Installation
@ -29,7 +31,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 manual remove the folder yourself.
If you named your bmad method folder something else - you will need to manually remove the folder yourself.
### 3. Clean Up IDE Commands
@ -63,16 +65,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

@ -7,14 +7,12 @@ The BMad Method (**B**reakthrough **M**ethod of **A**gile AI **D**riven Developm
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](/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.
- **[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.
## How to Use These Docs
@ -27,8 +25,6 @@ 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:
@ -36,12 +32,11 @@ 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:
@ -50,8 +45,6 @@ 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](/docs/tutorials/getting-started.md)** and build your first project.
Ready to dive in? **[Get Started with BMad](./tutorials/getting-started.md)** and build your first project.

View File

@ -1,314 +0,0 @@
---
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,13 +1,18 @@
---
title: Agents
description: Default BMM agents with their menu triggers and primary workflows
sidebar:
order: 2
---
This page lists the default BMM (Agile suite) agents that install with BMAD Method, along with their menu triggers and primary workflows.
## 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
Notes:
- Triggers are the short menu codes (e.g., `CP`) and fuzzy matches shown in each agent menu.
- Slash commands are generated separately. See `docs/reference/commands.md` for the slash command list and where they are defined.
- Slash commands are generated separately. See [Commands](./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,34 +1,131 @@
---
title: Commands
description: How BMAD commands are generated and where to find them.
description: Reference for BMad slash commands — what they are, how they work, and where to find them.
sidebar:
order: 3
---
# Commands
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)).
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.
## Commands vs. Agent Menu Triggers
## How to Discover Commands (Recommended)
BMad offers two ways to start work, and they serve different purposes.
- Type `/bmad` in your IDE and use autocomplete to browse agents/workflows.
- Run `/bmad-help` to get guided next steps and context-aware recommendations.
| 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 |
## Where Commands Are Generated
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.
The installer writes command files into your project (example paths for Claude Code):
## How Commands Are Generated
- `.claude/commands/bmad/<module>/agents/`
- `.claude/commands/bmad/<module>/workflows/`
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.
These folders are the **canonical, project-specific command list**.
The installer uses templates for each command type:
## Common Commands
| 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.md`) 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 |
- `/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`)
:::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.
:::
## Why This Page Is Short
## Where Command Files Live
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.
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.

View File

@ -1,6 +1,8 @@
---
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,22 +1,106 @@
---
title: Testing Options
description: Built-in QA agent and the standalone Test Architect module for advanced testing
description: Comparing the built-in QA agent (Quinn) with the Test Architect (TEA) module for test automation.
sidebar:
order: 5
---
# Testing Options
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.
BMad provides a built-in QA agent for quick test automation and a separate Test Architect (TEA) module for advanced testing.
## Which Should You Use?
## Built-in QA (Quinn)
| 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) |
Use the built-in QA agent for fast, straightforward test coverage:
:::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.
:::
- Trigger: `QA` or `bmad-bmm-qa-automate`
- Best for: small projects, quick coverage, standard patterns
## 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
## Test Architect (TEA) Module
TEA is a standalone module with advanced testing workflows (test design, ATDD, automate, review, trace, NFR assessment).
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.
- Documentation: <https://bmad-code-org.github.io/bmad-method-test-architecture-enterprise/>
- Install: `npx bmad-method@alpha install` and select the TEA module
- **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).

View File

@ -1,19 +1,23 @@
---
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 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.
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.
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" width="100%" height="100%" frameborder="0" style="border-radius: 8px; border: 1px solid #334155; min-height: 900px;"></iframe>
<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>
*[Interactive diagram - hover over outputs to see artifact flows]*
<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>
## Phase 1: Analysis (Optional)
@ -21,7 +25,7 @@ Explore the problem space and validate ideas before committing to planning.
| Workflow | Purpose | Produces |
| ---------------------- | -------------------------------------------------------------------------- | ------------------------- |
| `brainstorm` | Brainstorm Project Ideas with guided facilitation of a brainstorming coach | `brainstorming-report.md` |
| `brainstorming` | 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](/docs/reference/workflow-map.md)** to explore phases, workflows, and context management.
**[Open the Workflow Map](../reference/workflow-map.md)** to explore phases, workflows, and context management.
Based on your project's complexity, BMad offers three planning tracks:
@ -149,7 +149,7 @@ You've learned the foundation of building with BMad:
Your project now has:
```
```text
your-project/
├── _bmad/ # BMad configuration
├── _bmad-output/

View File

@ -83,7 +83,7 @@ export default [
// CLI scripts under tools/** and test/**
{
files: ['tools/**/*.js', 'tools/**/*.mjs', 'test/**/*.js'],
files: ['tools/**/*.js', 'tools/**/*.mjs', 'test/**/*.js', 'test/**/*.mjs'],
rules: {
// Allow CommonJS patterns for Node CLI scripts
'unicorn/prefer-module': 'off',

390
package-lock.json generated
View File

@ -12,20 +12,15 @@
"@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"
},
@ -777,16 +772,6 @@
"sisteransi": "^1.0.5"
}
},
"node_modules/@colors/colors": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz",
"integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==",
"license": "MIT",
"optional": true,
"engines": {
"node": ">=0.1.90"
}
},
"node_modules/@ctrl/tinycolor": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/@ctrl/tinycolor/-/tinycolor-4.2.0.tgz",
@ -2029,9 +2014,9 @@
}
},
"node_modules/@isaacs/brace-expansion": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.1.tgz",
"integrity": "sha512-WMz71T1JS624nWj2n2fnYAuPovhv7EUhk69R6i9dsVyzxt5eM3bjwvgk9L+APE1TRscGysAVMANkB0jh0LQZrQ==",
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.0.tgz",
"integrity": "sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==",
"license": "MIT",
"dependencies": {
"@isaacs/balanced-match": "^4.0.1"
@ -3993,6 +3978,7 @@
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz",
"integrity": "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==",
"dev": true,
"license": "ISC",
"dependencies": {
"string-width": "^4.1.0"
@ -4985,26 +4971,6 @@
"dev": true,
"license": "MIT"
},
"node_modules/base64-js": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
"integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"license": "MIT"
},
"node_modules/baseline-browser-mapping": {
"version": "2.9.19",
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.19.tgz",
@ -5042,59 +5008,12 @@
"url": "https://github.com/sponsors/wooorm"
}
},
"node_modules/bl": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz",
"integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==",
"license": "MIT",
"dependencies": {
"buffer": "^5.5.0",
"inherits": "^2.0.4",
"readable-stream": "^3.4.0"
}
},
"node_modules/bl/node_modules/readable-stream": {
"version": "3.6.2",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
"integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
"license": "MIT",
"dependencies": {
"inherits": "^2.0.3",
"string_decoder": "^1.1.1",
"util-deprecate": "^1.0.1"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/boolbase": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
"integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==",
"license": "ISC"
},
"node_modules/boxen": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/boxen/-/boxen-5.1.2.tgz",
"integrity": "sha512-9gYgQKXx+1nP8mP7CzFyaUARhg7D3n1dF/FnErWmu9l6JvGpNUN278h0aSb+QjoiKSWG+iZ3uHrcqk0qrY9RQQ==",
"license": "MIT",
"dependencies": {
"ansi-align": "^3.0.0",
"camelcase": "^6.2.0",
"chalk": "^4.1.0",
"cli-boxes": "^2.2.1",
"string-width": "^4.2.2",
"type-fest": "^0.20.2",
"widest-line": "^3.1.0",
"wrap-ansi": "^7.0.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/brace-expansion": {
"version": "1.1.12",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
@ -5163,30 +5082,6 @@
"node-int64": "^0.4.0"
}
},
"node_modules/buffer": {
"version": "5.7.1",
"resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz",
"integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"license": "MIT",
"dependencies": {
"base64-js": "^1.3.1",
"ieee754": "^1.1.13"
}
},
"node_modules/buffer-from": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
@ -5255,6 +5150,7 @@
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz",
"integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=10"
@ -5433,18 +5329,6 @@
"node": ">=0.8.0"
}
},
"node_modules/cli-boxes": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-2.2.1.tgz",
"integrity": "sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw==",
"license": "MIT",
"engines": {
"node": ">=6"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/cli-cursor": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz",
@ -5461,33 +5345,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/cli-spinners": {
"version": "2.9.2",
"resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz",
"integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==",
"license": "MIT",
"engines": {
"node": ">=6"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/cli-table3": {
"version": "0.6.5",
"resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.5.tgz",
"integrity": "sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==",
"license": "MIT",
"dependencies": {
"string-width": "^4.2.0"
},
"engines": {
"node": "10.* || >= 12.*"
},
"optionalDependencies": {
"@colors/colors": "1.5.0"
}
},
"node_modules/cli-truncate": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-5.1.1.tgz",
@ -5560,15 +5417,6 @@
"node": ">=8"
}
},
"node_modules/clone": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz",
"integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==",
"license": "MIT",
"engines": {
"node": ">=0.8"
}
},
"node_modules/clsx": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
@ -5942,18 +5790,6 @@
"node": ">=0.10.0"
}
},
"node_modules/defaults": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz",
"integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==",
"license": "MIT",
"dependencies": {
"clone": "^1.0.2"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/defu": {
"version": "6.1.4",
"resolved": "https://registry.npmjs.org/defu/-/defu-6.1.4.tgz",
@ -7034,21 +6870,6 @@
}
}
},
"node_modules/figlet": {
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/figlet/-/figlet-1.10.0.tgz",
"integrity": "sha512-aktIwEZZ6Gp9AWdMXW4YCi0J2Ahuxo67fNJRUIWD81w8pQ0t9TS8FFpbl27ChlTLF06VkwjDesZSzEVzN75rzA==",
"license": "MIT",
"dependencies": {
"commander": "^14.0.0"
},
"bin": {
"figlet": "bin/index.js"
},
"engines": {
"node": ">= 17.0.0"
}
},
"node_modules/file-entry-cache": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz",
@ -7906,26 +7727,6 @@
"@babel/runtime": "^7.23.2"
}
},
"node_modules/ieee754": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
"integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"license": "BSD-3-Clause"
},
"node_modules/ignore": {
"version": "7.0.5",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz",
@ -8022,6 +7823,7 @@
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
"dev": true,
"license": "ISC"
},
"node_modules/ini": {
@ -8206,15 +8008,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/is-interactive": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz",
"integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==",
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/is-number": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
@ -8250,18 +8043,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/is-unicode-supported": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz",
"integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==",
"license": "MIT",
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/is-wsl": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.0.tgz",
@ -9523,22 +9304,6 @@
"dev": true,
"license": "MIT"
},
"node_modules/log-symbols": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz",
"integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==",
"license": "MIT",
"dependencies": {
"chalk": "^4.1.0",
"is-unicode-supported": "^0.1.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/log-update": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/log-update/-/log-update-6.1.0.tgz",
@ -10985,6 +10750,7 @@
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz",
"integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=6"
@ -11296,6 +11062,7 @@
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz",
"integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==",
"dev": true,
"license": "MIT",
"dependencies": {
"mimic-fn": "^2.1.0"
@ -11344,81 +11111,6 @@
"node": ">= 0.8.0"
}
},
"node_modules/ora": {
"version": "5.4.1",
"resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz",
"integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==",
"license": "MIT",
"dependencies": {
"bl": "^4.1.0",
"chalk": "^4.1.0",
"cli-cursor": "^3.1.0",
"cli-spinners": "^2.5.0",
"is-interactive": "^1.0.0",
"is-unicode-supported": "^0.1.0",
"log-symbols": "^4.1.0",
"strip-ansi": "^6.0.0",
"wcwidth": "^1.0.1"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/ora/node_modules/ansi-regex": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/ora/node_modules/cli-cursor": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz",
"integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==",
"license": "MIT",
"dependencies": {
"restore-cursor": "^3.1.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/ora/node_modules/restore-cursor": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz",
"integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==",
"license": "MIT",
"dependencies": {
"onetime": "^5.1.0",
"signal-exit": "^3.0.2"
},
"engines": {
"node": ">=8"
}
},
"node_modules/ora/node_modules/signal-exit": {
"version": "3.0.7",
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
"integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
"license": "ISC"
},
"node_modules/ora/node_modules/strip-ansi": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
"license": "MIT",
"dependencies": {
"ansi-regex": "^5.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/p-limit": {
"version": "6.2.0",
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-6.2.0.tgz",
@ -12726,26 +12418,6 @@
"queue-microtask": "^1.2.2"
}
},
"node_modules/safe-buffer": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"license": "MIT"
},
"node_modules/sax": {
"version": "1.4.4",
"resolved": "https://registry.npmjs.org/sax/-/sax-1.4.4.tgz",
@ -13098,15 +12770,6 @@
"dev": true,
"license": "MIT"
},
"node_modules/string_decoder": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
"integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
"license": "MIT",
"dependencies": {
"safe-buffer": "~5.2.0"
}
},
"node_modules/string-argv": {
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.2.tgz",
@ -13691,18 +13354,6 @@
"node": ">=4"
}
},
"node_modules/type-fest": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz",
"integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==",
"license": "(MIT OR CC0-1.0)",
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/uc.micro": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz",
@ -14152,6 +13803,7 @@
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
"dev": true,
"license": "MIT"
},
"node_modules/v8-to-istanbul": {
@ -14317,15 +13969,6 @@
"makeerror": "1.0.12"
}
},
"node_modules/wcwidth": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz",
"integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==",
"license": "MIT",
"dependencies": {
"defaults": "^1.0.3"
}
},
"node_modules/web-namespaces": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/web-namespaces/-/web-namespaces-2.0.1.tgz",
@ -14362,18 +14005,6 @@
"node": ">=4"
}
},
"node_modules/widest-line": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/widest-line/-/widest-line-3.1.0.tgz",
"integrity": "sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg==",
"license": "MIT",
"dependencies": {
"string-width": "^4.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/word-wrap": {
"version": "1.2.5",
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",
@ -14388,6 +14019,7 @@
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
"integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
"dev": true,
"license": "MIT",
"dependencies": {
"ansi-styles": "^4.0.0",
@ -14444,6 +14076,7 @@
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
@ -14453,6 +14086,7 @@
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
"dev": true,
"license": "MIT",
"dependencies": {
"ansi-regex": "^5.0.1"

View File

@ -25,13 +25,11 @@
},
"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",
@ -41,9 +39,10 @@
"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:install && npm run validate:schemas && npm run lint && npm run lint:md && npm run format:check",
"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: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"
@ -68,20 +67,15 @@
"@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

@ -15,9 +15,9 @@
## EXECUTION PROTOCOLS:
- 🎯 Show your analysis before taking any action
- 🎯 Provide a brief rationale before taking any action
- 🌐 Search the web to verify technology versions and options
- ⚠️ Present A/P/C menu after each major decision category
- ⚠️ Present A/P/C menu after drafting decision content (and when user requests refinement)
- 💾 ONLY save when user chooses C (Continue)
- 📖 Update frontmatter `stepsCompleted: [1, 2, 3, 4]` before loading next step
- 🚫 FORBIDDEN to load next step until C is selected
@ -44,7 +44,7 @@ This step will generate content and present choices for each decision category:
- Project context file may contain technical preferences and rules
- Technical preferences discovered in step 3 are available
- Focus on decisions not already made by starter template or existing preferences
- Collaborative decision making, not recommendations
- Collaborative decision making first; recommendations are allowed only with explicit rationale and user confirmation
## YOUR TASK:
@ -58,7 +58,7 @@ Facilitate collaborative architectural decision making, leveraging existing tech
"Based on our technical preferences discussion in step 3, let's build on those foundations:
**Your Technical Preferences:**
{{user_technical_preferences_from_step_3}}
{{user_technical_preferences}}
**Starter Template Decisions:**
{{starter_template_decisions}}
@ -72,7 +72,7 @@ Based on technical preferences, starter template choice, and project context, id
**Already Decided (Don't re-decide these):**
- {{starter_template_decisions}}
- {{user_technology_preferences}}
- {{user_technical_preferences}}
- {{project_context_technical_rules}}
**Critical Decisions:** Must be decided before implementation can proceed
@ -165,7 +165,7 @@ If decision involves specific technology:
```
Search the web: "{{technology}} latest stable version"
Search the web: "{{technology}} current LTS version"
Search the web: "{{technology}} current LTS version" (only if the technology publishes an LTS track)
Search the web: "{{technology}} production readiness"
```

View File

@ -36,7 +36,7 @@ Load config from `{project-root}/_bmad/bmm/config.yaml` and resolve:
### Paths
- `installed_path` = `{project-root}/_bmad/bmm/workflows/3-solutioning/architecture`
- `installed_path` = `{project-root}/_bmad/bmm/workflows/3-solutioning/create-architecture`
- `template_path` = `{installed_path}/architecture-decision-template.md`
- `data_files_path` = `{installed_path}/data/`

View File

@ -88,11 +88,18 @@ Search for required documents using these patterns (sharded means a large docume
1. `{planning_artifacts}/*ux*.md` (whole document)
2. `{planning_artifacts}/*ux*/index.md` (sharded version)
**Deterministic Selection Rules (required):**
- When multiple files match the same priority level, prefer exact filename matches (`prd.md`, `architecture.md`, `ux-design.md`) before wildcard matches.
- If multiple wildcard matches remain, prefer files under `{planning_artifacts}` root before nested paths.
- If ambiguity still remains, present candidates to the user and require explicit selection before extraction.
- Record the final selected files in `inputDocuments` frontmatter to keep downstream steps deterministic.
Before proceeding, Ask the user if there are any other documents to include for analysis, and if anything found should be excluded. Wait for user confirmation. Once confirmed, create the {outputFile} from the {epicsTemplate} and in the front matter list the files in the array of `inputDocuments: []`.
### 3. Extract Functional Requirements (FRs)
From the PRD document (full or sharded), read then entire document and extract ALL functional requirements:
From the PRD document (full or sharded), read the entire document and extract ALL functional requirements:
**Extraction Method:**

View File

@ -12,9 +12,10 @@ reviewFindingsFile: '{story_dir}/review-findings.json'
<action>Initialize findings artifacts:
- Set {{review_findings}} = [] (in-memory array)
- Set {{review_findings_file}} = {reviewFindingsFile}
- Set {{review_findings_schema}} = "id,severity,type,summary,detail,file_line,proof,suggested_fix,reviewer,timestamp"
- Each finding record MUST contain:
id, severity, type, summary, detail, file_line, proof, suggested_fix, reviewer, timestamp
- `file_line` format MUST be `path/to/file:line`
- `file_line` is the required `file:line` locator field and MUST use `path/to/file:line` format
- `reviewer` value MUST be `senior-dev-review`
- `timestamp` MUST use system ISO datetime
</action>
@ -35,28 +36,67 @@ reviewFindingsFile: '{story_dir}/review-findings.json'
1. Read the AC requirement
2. Search implementation files for evidence
3. Determine: IMPLEMENTED, PARTIAL, or MISSING using this algorithm:
- Parse each AC into explicit clauses (single requirement statements).
- Evaluate each clause independently, then derive overall AC status from clause outcomes.
- IMPLEMENTED:
- Direct code evidence exists for ALL AC clauses, and
- At least one corroborating test OR deterministic runtime verification exists, and
- Any docs/comments are supported by code/test evidence.
- EVERY clause has direct code evidence tied to the story execution path, and
- Evidence includes at least one strong corroborator for AC behavior (automated test, integration test, or reproducible runtime proof), and
- Weak evidence (docs/comments/README) is only supplemental.
- PARTIAL:
- Some AC clauses have direct implementation evidence but one or more clauses are missing OR only indirectly covered, or
- Evidence is helper/utility code not clearly wired to the story path, or
- Evidence is docs/comments only without strong corroboration.
- One or more clauses have direct evidence, but at least one clause lacks direct evidence, OR
- Coverage is only indirect (helper/generic utility not proven wired), OR
- Evidence is mostly weak and not corroborated by code/tests.
- MISSING:
- No credible code/test/docs evidence addresses the AC clauses.
4. Evidence-strength rules:
- Code + tests = strong evidence
- Code only = medium evidence
- Docs/comments/README only = weak evidence (cannot justify IMPLEMENTED alone)
- No clause has credible direct implementation evidence, and
- No test/runtime proof demonstrates AC behavior.
4. Evidence-type rules:
- Strong evidence: implementation code plus validating tests/runtime proof.
- Medium evidence: implementation code without validating tests.
- Weak evidence: comments, README/docs, design notes, screenshots, or unverifiable logs.
- Weak evidence alone cannot qualify an AC as IMPLEMENTED.
5. Indirect evidence rules:
- Generic helpers/utilities count as PARTIAL unless explicitly wired by call sites OR integration tests.
- Helper functions/utilities count as indirect until explicit call sites or integration coverage prove the AC path.
- Generic capability not wired to this story remains PARTIAL.
6. Severity mapping for AC gaps:
- MISSING critical-path AC → HIGH
- MISSING non-critical AC → MEDIUM
- PARTIAL critical-path AC → HIGH
- PARTIAL non-critical AC → MEDIUM
7. If AC is PARTIAL or MISSING, append a finding object to {{review_findings}}.
- MISSING + security/data-loss/compliance/core user flow risk -> HIGH.
- MISSING + non-core behavior or secondary UX/documentation requirement -> MEDIUM.
- PARTIAL + security/data-integrity/compliance risk -> HIGH.
- PARTIAL + degraded core behavior -> MEDIUM.
- PARTIAL + optional/non-critical behavior gap with safe fallback -> LOW.
7. Classification examples:
- IMPLEMENTED example: AC requires validation + error response, and code path plus passing test covers all clauses.
- PARTIAL example: helper exists and one clause passes, but integration path for another clause is unproven.
- MISSING example: AC text exists, but no matching code path or tests are found.
8. If AC is PARTIAL or MISSING, append a finding object to {{review_findings}} with status, severity, and clause-level proof.
</action>
<action>When creating findings from any action above, populate fields using this mapping:
- id:
- Git discrepancy: `GIT-DIFF-{{index}}`
- AC gap: `AC-{{ac_id}}-{{status}}-{{index}}`
- Task mismatch: `TASK-{{task_id}}-MISMATCH-{{index}}`
- Code-quality issue: `CQ-{{category}}-{{index}}`
- severity:
- Use explicit severity rule from the originating action block
- type:
- `story-sync` for git/story discrepancies
- `acceptance-criteria` for AC gaps
- `task-audit` for task completion mismatches
- `code-quality` for quality/security/performance/test issues
- summary:
- One-line, user-facing issue statement
- detail:
- Include violated expectation plus observed behavior
- file_line:
- `path/to/file:line` evidence anchor (use most relevant file and line)
- proof:
- Concrete evidence snippet (code, test output, or git command result)
- suggested_fix:
- Actionable implementation guidance
- reviewer:
- `senior-dev-review`
- timestamp:
- System ISO datetime at finding creation time
</action>
<!-- Task Completion Audit -->
@ -94,7 +134,8 @@ reviewFindingsFile: '{story_dir}/review-findings.json'
<action>Persist findings contract for downstream step:
- Save {{review_findings}} as JSON array to {{review_findings_file}}
- Ensure JSON is valid and each finding includes all required fields
- Set {{findings_contract}} = "JSON array at {{review_findings_file}}"
- Set {{findings_contract}} = "JSON array at {{review_findings_file}} with schema {{review_findings_schema}}"
- Step 4 MUST load findings from {{review_findings_file}} and validate against {{review_findings_schema}} before presenting or resolving
</action>
<action>Example finding record (must match real records):

View File

@ -6,11 +6,20 @@ reviewFindingsFile: '{story_dir}/review-findings.json'
---
<step n="4" goal="Present findings and fix them">
<action>Load structured findings from {reviewFindingsFile}</action>
<action>Resolve findings artifact input:
- Use {{review_findings_file}} from step 3 when present
- Otherwise fallback to {reviewFindingsFile}
- Set {{review_findings_schema}} = "id,severity,type,summary,detail,file_line,proof,suggested_fix,reviewer,timestamp" if not already set
</action>
<action>Load structured findings JSON array from {{review_findings_file}}</action>
<action>Validate findings schema for each entry:
id, severity, type, summary, detail, file_line, proof, suggested_fix, reviewer, timestamp
</action>
<action>If findings file missing or malformed: HALT with explicit error and return to step 3 generation</action>
<action>Validation contract:
- `file_line` is the required `file:line` locator in `path/to/file:line` format
- Reject non-array JSON, missing required keys, or invalid file_line formatting
- If findings file missing/unreadable/malformed: HALT with explicit error and return to step 3 generation
</action>
<action>Categorize findings: HIGH (must fix), MEDIUM (should fix), LOW (nice to fix)</action>
<action>Set {{fixed_count}} = 0</action>
<action>Set {{action_count}} = 0</action>

View File

@ -3,6 +3,22 @@ name: code-review
description: "Perform an adversarial senior developer code review with concrete findings across quality, tests, architecture, security, and performance"
main_config: '{project-root}/_bmad/bmm/config.yaml'
web_bundle: false
input_file_patterns:
architecture:
description: "System architecture for review context"
whole: "{planning_artifacts}/*architecture*.md"
sharded: "{planning_artifacts}/*architecture*/*.md"
load_strategy: "FULL_LOAD"
ux_design:
description: "UX design specification (if UI review)"
whole: "{planning_artifacts}/*ux*.md"
sharded: "{planning_artifacts}/*ux*/*.md"
load_strategy: "FULL_LOAD"
epics:
description: "Epic and story requirements for review context"
whole: "{planning_artifacts}/*epic*.md"
sharded: "{planning_artifacts}/*epic*/*.md"
load_strategy: "FULL_LOAD"
---
# Code Review Workflow

View File

@ -3,6 +3,35 @@ name: correct-course
description: "Navigate significant changes during sprint execution by analyzing impact, proposing solutions, and routing for implementation"
main_config: '{project-root}/_bmad/bmm/config.yaml'
web_bundle: false
input_file_patterns:
prd:
description: "Product requirements for impact analysis"
whole: "{planning_artifacts}/*prd*.md"
sharded: "{planning_artifacts}/*prd*/*.md"
load_strategy: "FULL_LOAD"
epics:
description: "All epics to analyze change impact"
whole: "{planning_artifacts}/*epic*.md"
sharded: "{planning_artifacts}/*epic*/*.md"
load_strategy: "FULL_LOAD"
architecture:
description: "System architecture and decisions"
whole: "{planning_artifacts}/*architecture*.md"
sharded: "{planning_artifacts}/*architecture*/*.md"
load_strategy: "FULL_LOAD"
ux_design:
description: "UX design specification (if UI impacts)"
whole: "{planning_artifacts}/*ux*.md"
sharded: "{planning_artifacts}/*ux*/*.md"
load_strategy: "FULL_LOAD"
tech_spec:
description: "Technical specification"
whole: "{planning_artifacts}/*tech-spec*.md"
load_strategy: "FULL_LOAD"
document_project:
description: "Brownfield project documentation (optional)"
sharded: "{project_knowledge}/index.md"
load_strategy: "INDEX_GUIDED"
---
## Initialization
@ -18,16 +47,24 @@ web_bundle: false
- `sprint_status` = `{implementation_artifacts}/sprint-status.yaml`
- `date` (system-generated)
- `installed_path` = `{project-root}/_bmad/bmm/workflows/4-implementation/correct-course`
- `source_path` = `{project-root}/src/bmm/workflows/4-implementation/correct-course`
- Note: `installed_path` targets the installed runtime tree under `_bmad/...`; `source_path` is the repository authoring path.
- `default_output_file` = `{planning_artifacts}/sprint-change-proposal-{date}.md`
<workflow>
<critical>Communicate all responses in {communication_language} and generate all documents in {document_output_language}</critical>
<step n="1" goal="Analyze changes and propose corrective actions">
<action>Read and follow instructions at: {installed_path}/instructions.md</action>
<action>Resolve workflow content path:
- If `{installed_path}/instructions.md` exists and is readable, set {{workflow_path}} = `{installed_path}`
- Else if `{source_path}/instructions.md` exists and is readable, set {{workflow_path}} = `{source_path}`
- Else emit an error listing both paths and HALT
</action>
<action>Read and follow instructions at: {{workflow_path}}/instructions.md</action>
</step>
<step n="2" goal="Validate proposal quality">
<invoke-task>Validate against checklist at {installed_path}/checklist.md using {project-root}/_bmad/core/tasks/validate-workflow.md</invoke-task>
<action>If {{workflow_path}} is not set from step 1, repeat path resolution using checklist.md</action>
<invoke-task>Validate against checklist at {{workflow_path}}/checklist.md using {project-root}/_bmad/core/tasks/validate-workflow.md</invoke-task>
</step>
</workflow>

View File

@ -3,6 +3,28 @@ name: create-story
description: "Create the next user story from epics+stories with enhanced context analysis and direct ready-for-dev marking"
main_config: '{project-root}/_bmad/bmm/config.yaml'
web_bundle: false
input_file_patterns:
prd:
description: "PRD (fallback - epics file should have most content)"
whole: "{planning_artifacts}/*prd*.md"
sharded: "{planning_artifacts}/*prd*/*.md"
load_strategy: "SELECTIVE_LOAD"
architecture:
description: "Architecture (fallback - epics file should have relevant sections)"
whole: "{planning_artifacts}/*architecture*.md"
sharded: "{planning_artifacts}/*architecture*/*.md"
load_strategy: "SELECTIVE_LOAD"
ux:
description: "UX design (fallback - epics file should have relevant sections)"
whole: "{planning_artifacts}/*ux*.md"
sharded: "{planning_artifacts}/*ux*/*.md"
load_strategy: "SELECTIVE_LOAD"
epics:
description: "Enhanced epics+stories file with BDD and source hints"
whole: "{planning_artifacts}/*epic*.md"
sharded_index: "{planning_artifacts}/*epic*/index.md"
sharded_single: "{planning_artifacts}/*epic*/epic-{{epic_num}}.md"
load_strategy: "SELECTIVE_LOAD"
---
## Initialization
@ -135,7 +157,7 @@ web_bundle: false
</step>
<step n="2" goal="Load and analyze core artifacts">
<critical>🔬 EXHAUSTIVE ARTIFACT ANALYSIS - This is where you prevent future developer fuckups!</critical>
<critical>🔬 EXHAUSTIVE ARTIFACT ANALYSIS - This is where you prevent future developer mistakes!</critical>
<!-- Load all available content through discovery protocol -->
<invoke-protocol

View File

@ -10,8 +10,14 @@ nextStepFile: './step-09-mark-review-ready.md'
<action>Initialize review-tracking variables before checks:
- If {{resolved_review_items}} is undefined: set {{resolved_review_items}} = []
- If {{unresolved_review_items}} is undefined: set {{unresolved_review_items}} = []
- Set {{review_continuation}} by checking current task title/original task list for prefix "[AI-Review]"
- Set {{date}} from system-generated timestamp in project date format
- Set {{review_continuation}} = false
- If current {{task_title}} starts with "[AI-Review]", set {{review_continuation}} = true
- Else scan {{original_task_list}}; if any item starts with "[AI-Review]", set {{review_continuation}} = true
- Set {{date}} from system-generated timestamp formatted for project change log entries
- Set {{resolved_count}} = length({{resolved_review_items}})
- Set {{review_match_threshold}} = 0.60
- Define normalize(text): lowercase, trim, remove "[AI-Review]" prefix and punctuation, collapse whitespace
- Define token_set(text): unique whitespace-separated normalized tokens
</action>
<!-- VALIDATION GATES -->
@ -23,28 +29,58 @@ nextStepFile: './step-09-mark-review-ready.md'
<!-- REVIEW FOLLOW-UP HANDLING -->
<check if="task is review follow-up (has [AI-Review] prefix)">
<action>Extract review item details (severity, description, related AC/file)</action>
<action>Add current review task to resolution tracking list: append structured entry to {{resolved_review_items}}</action>
<action>Load all items from "Senior Developer Review (AI) → Action Items" as candidate list {{review_action_items}}</action>
<action>Set {{task_text_norm}} = normalize(current review follow-up task description)</action>
<action>Initialize {{best_match}} = null, {{best_score}} = 0, {{best_shared_tokens}} = 0, {{tie_candidates}} = []</action>
<action>For each candidate action item:
1. Set {{candidate_text_norm}} = normalize(candidate text)
2. If {{task_text_norm}} == {{candidate_text_norm}} OR either contains the other:
- set {{candidate_score}} = 1.0 and mark as strong match
3. Else:
- compute Jaccard score = |token_set(task) ∩ token_set(candidate)| / |token_set(task) token_set(candidate)|
- set {{candidate_score}} to computed score
4. Track shared-token count for tie-breaking
5. Keep highest score candidate; if same score, keep candidate with more shared tokens
6. If score and shared-token count both tie, add candidate to {{tie_candidates}}
</action>
<action>Set {{match_found}} = true only if {{best_score}} >= {{review_match_threshold}}</action>
<!-- Mark task in Review Follow-ups section -->
<!-- Mark task in Review Follow-ups section (always, regardless of action-item match result) -->
<action>Mark task checkbox [x] in "Tasks/Subtasks → Review Follow-ups (AI)" section</action>
<!-- CRITICAL: Also mark corresponding action item in review section -->
<action>Find matching action item in "Senior Developer Review (AI) → Action Items" using fuzzy matching:
1. Normalize strings (lowercase, trim, remove "[AI-Review]" prefix/punctuation)
2. Try exact and substring matches first
3. If none, compute token-overlap/Jaccard score per candidate
4. Select highest-scoring candidate when score >= 0.60
5. If tie at best score, prefer the candidate with more shared tokens; log ambiguity
<check if="{{match_found}} == true">
<action>Mark matched action item checkbox [x] in "Senior Developer Review (AI) → Action Items"</action>
<action>Append structured entry to {{resolved_review_items}}:
- task: current review follow-up task
- matched_action_item: {{best_match}}
- match_score: {{best_score}}
- resolved_at: {{date}}
- status: "matched"
</action>
<check if="matching action item found">
<action>Mark that action item checkbox [x] as resolved</action>
<check if="{{tie_candidates}} is not empty">
<action>Log ambiguity warning with tied candidates and selected best_match</action>
</check>
<check if="no candidate meets threshold">
<action>Log warning and append task to {{unresolved_review_items}}</action>
<action>Add resolution note in Dev Agent Record that no corresponding action item was found</action>
<action>Add to Dev Agent Record → Completion Notes: "✅ Resolved review finding [{{severity}}]: {{description}} (matched action item, score {{best_score}})"</action>
</check>
<action>Add to Dev Agent Record → Completion Notes: "✅ Resolved review finding [{{severity}}]: {{description}}"</action>
<check if="{{match_found}} == false">
<action>Log warning: no candidate met threshold {{review_match_threshold}} for task "{{task_text_norm}}"</action>
<action>Append structured entry to {{resolved_review_items}}:
- task: current review follow-up task
- matched_action_item: null
- match_score: {{best_score}}
- resolved_at: {{date}}
- status: "unmatched"
</action>
<action>Append structured entry to {{unresolved_review_items}}:
- task: current review follow-up task
- reason: "No corresponding action item met fuzzy-match threshold"
- best_candidate: {{best_match}}
- best_score: {{best_score}}
- recorded_at: {{date}}
</action>
<action>Add resolution note in Dev Agent Record that no corresponding action item was found, while follow-up checkbox was still marked complete</action>
</check>
</check>
<!-- ONLY MARK COMPLETE IF ALL VALIDATION PASS -->
@ -56,7 +92,12 @@ nextStepFile: './step-09-mark-review-ready.md'
<check if="ANY validation fails">
<action>DO NOT mark task complete - fix issues first</action>
<action>HALT if unable to fix validation failures</action>
<action>If unable to fix validation failures, invoke HALT protocol from dev-story/workflow.md with:
- reason_code: DEV-STORY-STEP-08-VALIDATION-FAIL
- step_id: step-08-mark-task-complete
- message: "Task completion validation failed and remediation was unsuccessful."
- required_action: "Fix failing validations/tests, then resume."
</action>
</check>
<check if="review_continuation == true and {{resolved_review_items}} is not empty">

View File

@ -10,9 +10,14 @@ nextStepFile: './step-10-closeout.md'
<action>Confirm File List includes every changed file</action>
<action>Execute enhanced definition-of-done validation</action>
<action>Update the story Status to: "review"</action>
<action>Initialize sprint tracking state:
- If {sprint_status} exists and is readable, load file and set {{current_sprint_status}} from tracking mode/content
- If file does not exist, unreadable, or indicates no sprint tracking, set {{current_sprint_status}} = "no-sprint-tracking"
<action>Initialize sprint tracking state deterministically before any sprint-status check:
- Set {{current_sprint_status}} = "no-sprint-tracking"
- Set {{sprint_tracking_enabled}} = false
- If {sprint_status} exists and is readable:
- Load the FULL file: {sprint_status}
- If file content indicates tracking disabled OR development_status section is missing, keep "no-sprint-tracking"
- Else set {{current_sprint_status}} = "enabled" and {{sprint_tracking_enabled}} = true
- If file missing/unreadable, keep defaults and continue with story-only status update
</action>
<!-- Enhanced Definition of Done Validation -->
@ -31,31 +36,31 @@ nextStepFile: './step-10-closeout.md'
</action>
<!-- Mark story ready for review - sprint status conditional -->
<check if="{sprint_status} file exists AND {{current_sprint_status}} != 'no-sprint-tracking'">
<action>Load the FULL file: {sprint_status}</action>
<check if="{{sprint_tracking_enabled}} == true">
<action>Find development_status key matching {{story_key}}</action>
<check if="story key found in sprint status">
<action>Verify current status is "in-progress" (expected previous state)</action>
<action>Update development_status[{{story_key}}] = "review"</action>
<action>Save file, preserving ALL comments and structure including STATUS DEFINITIONS</action>
<output>✅ Story status updated to "review" in sprint-status.yaml</output>
</check>
<check if="{sprint_status} file does NOT exist OR {{current_sprint_status}} == 'no-sprint-tracking'">
<output> Story status updated to "review" in story file (no sprint tracking configured)</output>
</check>
<check if="story key not found in sprint status">
<output>⚠️ Story file updated, but sprint-status update failed: {{story_key}} not found
Story status is set to "review" in file, but sprint-status.yaml may be out of sync.
</output>
</check>
</check>
<check if="{{sprint_tracking_enabled}} == false">
<output> Story status updated to "review" in story file (no sprint tracking configured)</output>
</check>
<!-- Final validation gates -->
<action if="any task is incomplete">HALT - Complete remaining tasks before marking ready for review</action>
<action if="regression failures exist">HALT - Fix regression issues before completing</action>
<action if="File List is incomplete">HALT - Update File List with all changed files</action>
<action if="definition-of-done validation fails">HALT - Address DoD failures before completing</action>
<action if="any task is incomplete">Invoke HALT protocol (reason_code: DEV-STORY-STEP-09-INCOMPLETE-TASKS, step_id: step-09-mark-review-ready, message: "Incomplete tasks remain before review-ready transition.", required_action: "Complete all tasks/subtasks and rerun validations.")</action>
<action if="regression failures exist">Invoke HALT protocol (reason_code: DEV-STORY-STEP-09-REGRESSION-FAIL, step_id: step-09-mark-review-ready, message: "Regression suite has failures.", required_action: "Fix failing tests and rerun full regression suite.")</action>
<action if="File List is incomplete">Invoke HALT protocol (reason_code: DEV-STORY-STEP-09-FILE-LIST-INCOMPLETE, step_id: step-09-mark-review-ready, message: "File List does not include all changed files.", required_action: "Update File List with all added/modified/deleted paths.")</action>
<action if="definition-of-done validation fails">Invoke HALT protocol (reason_code: DEV-STORY-STEP-09-DOD-FAIL, step_id: step-09-mark-review-ready, message: "Definition-of-done checks failed.", required_action: "Address DoD failures and rerun validation.")</action>
</step>
## Next

View File

@ -2,7 +2,7 @@
name: dev-story
description: "Execute a story by implementing tasks/subtasks, writing tests, validating, and updating the story file per acceptance criteria"
projectRoot: '{project-root}'
main_config: '{projectRoot}/_bmad/bmm/config.yaml'
main_config: '{project-root}/_bmad/bmm/config.yaml'
web_bundle: false
---
@ -45,22 +45,36 @@ Implement a ready story end-to-end with strict validation gates, accurate progre
- Change Log
- Status
- Execute steps in order and do not skip validation gates.
- Continue until the story is complete unless a defined HALT condition triggers.
- Continue until the story is complete unless the HALT protocol below is triggered.
## HALT Definition
- HALT triggers:
- Required inputs/files are missing or unreadable.
- Validation gates fail and cannot be remediated in current step.
- Test/regression failures persist after fix attempts.
- Story state becomes inconsistent (e.g., malformed task structure preventing safe updates).
## HALT Protocol (Normative)
- Scope:
- Every `HALT` instruction in this workflow and all `steps/*.md` files MUST use this protocol.
- Operational definition:
- HALT is a deterministic hard-stop event raised when execution cannot safely continue.
- A HALT event MUST include:
- `reason_code` (stable machine-readable code)
- `step_id` (current step file + step number)
- `message` (human-readable failure summary)
- `required_action` (what user/operator must do before resume)
- Trigger criteria:
- Required inputs/files are missing, unreadable, or malformed.
- Validation gates fail and cannot be remediated in the current step.
- Test/regression failures persist after attempted fixes.
- Story state is inconsistent (for example malformed task structure preventing safe updates).
- HALT behavior:
- Stop executing further steps immediately.
- Persist current story-file edits and workflow state safely.
- Emit explicit user-facing error message describing trigger and remediation needed.
- Do not apply partial completion marks after HALT.
- Stop execution immediately and skip all downstream steps.
- Persist workflow checkpoint: current step id, resolved variables, and pending task context.
- Persist only already-applied safe edits; do not apply new partial completion marks after HALT.
- Emit logger event exactly in this format:
- `HALT[{reason_code}] step={step_id} story={story_key|unknown} detail=\"{message}\"`
- Emit user-facing prompt exactly in this format:
- `Workflow HALTED at {step_id} ({reason_code}): {message}. Required action: {required_action}. Reply RESUME after remediation.`
- Resume semantics:
- Manual resume only after user confirms the blocking issue is resolved.
- Resume from the last incomplete step checkpoint, re-running validations before progressing.
- Manual resume only (no automatic retry loop).
- Resume is checkpoint-based: restart from the halted step after user confirms remediation.
- Re-run the failed validation/input check before executing further actions.
- If the same HALT condition repeats, stop again with updated evidence.
## Execution
Read fully and follow: `steps/step-01-find-story.md`.

View File

@ -3,6 +3,32 @@ name: retrospective
description: "Run after epic completion to review overall success, extract lessons learned, and surface impact on upcoming work"
main_config: '{project-root}/_bmad/bmm/config.yaml'
web_bundle: false
agent_manifest: '{project-root}/_bmad/_config/agent-manifest.csv'
input_file_patterns:
epics:
description: "The completed epic for retrospective"
whole: "{planning_artifacts}/*epic*.md"
sharded_index: "{planning_artifacts}/*epic*/index.md"
sharded_single: "{planning_artifacts}/*epic*/epic-{{epic_num}}.md"
load_strategy: "SELECTIVE_LOAD"
previous_retrospective:
description: "Previous epic's retrospective (optional)"
pattern: "{implementation_artifacts}/**/epic-{{prev_epic_num}}-retro-*.md"
load_strategy: "SELECTIVE_LOAD"
architecture:
description: "System architecture for context"
whole: "{planning_artifacts}/*architecture*.md"
sharded: "{planning_artifacts}/*architecture*/*.md"
load_strategy: "FULL_LOAD"
prd:
description: "Product requirements for context"
whole: "{planning_artifacts}/*prd*.md"
sharded: "{planning_artifacts}/*prd*/*.md"
load_strategy: "FULL_LOAD"
document_project:
description: "Brownfield project documentation (optional)"
sharded: "{planning_artifacts}/*.md"
load_strategy: "INDEX_GUIDED"
---
## Initialization
@ -14,6 +40,7 @@ web_bundle: false
- `user_skill_level`
- `planning_artifacts`
- `implementation_artifacts`
- `agent_manifest` = `{project-root}/_bmad/_config/agent-manifest.csv`
- `sprint_status_file` = `{implementation_artifacts}/sprint-status.yaml`
- `story_directory` = `{implementation_artifacts}`
- `retrospectives_folder` = `{implementation_artifacts}`

View File

@ -3,6 +3,14 @@ name: sprint-planning
description: "Generate and manage the sprint status tracking file for Phase 4 implementation, extracting all epics and stories from epic files and tracking their status through the development lifecycle"
main_config: '{project-root}/_bmad/bmm/config.yaml'
web_bundle: false
epics_location: "{planning_artifacts}"
epics_pattern: "epic*.md"
input_file_patterns:
epics:
description: "All epics with user stories"
whole: "{epics_location}/{epics_pattern}"
sharded: "{epics_location}/epic*/*.md"
load_strategy: "FULL_LOAD"
---
## Initialization
@ -14,6 +22,8 @@ web_bundle: false
- `planning_artifacts`
- `project_name`
- `date` (system-generated)
- `epics_location` = `{planning_artifacts}`
- `epics_pattern` = `epic*.md`
- `installed_path` = `{project-root}/_bmad/bmm/workflows/4-implementation/sprint-planning`
- `status_file` = `{implementation_artifacts}/sprint-status.yaml`
- `default_output_file` = `{status_file}`

View File

@ -10,10 +10,26 @@
<step n="1" goal="Validate workflow and get project info">
<invoke-workflow path="{project-root}/_bmad/bmm/workflows/workflow-status">
<param>mode: data</param>
<param>data_request: project_config</param>
</invoke-workflow>
<action>Initialize status defaults:
- Set status_exists = false
- Set status_file_found = false
- Set standalone_mode = true
- Set warning = ""
- Set suggestion = ""
- Set next_workflow = ""
- Set next_agent = ""
</action>
<action>Attempt to load workflow status directly from `{output_folder}/bmm-workflow-status.yaml`:
- If file exists, is readable, and parses correctly:
- Set status_exists = true
- Set status_file_found = true
- Set standalone_mode = false
- Set status_file_path = `{output_folder}/bmm-workflow-status.yaml`
- Extract field_type, warning, suggestion, next_workflow, next_agent if present
- If file is missing, unreadable, or malformed:
- Keep defaults and continue in standalone mode
</action>
<check if="status_exists == false">
<output>{{suggestion}}</output>
@ -35,11 +51,11 @@
</check>
</check>
<!-- Now validate sequencing -->
<invoke-workflow path="{project-root}/_bmad/bmm/workflows/workflow-status">
<param>mode: validate</param>
<param>calling_workflow: document-project</param>
</invoke-workflow>
<!-- Validate sequencing from loaded status fields -->
<action>Validate sequencing locally from loaded status fields:
- If warning is empty, continue
- If warning contains guidance, require explicit user confirmation before continuing
</action>
<check if="warning != ''">
<output>{{warning}}</output>
@ -58,12 +74,23 @@
<critical>SMART LOADING STRATEGY: Check state file FIRST before loading any CSV files</critical>
<action>Check for existing state file at: {output_folder}/project-scan-report.json</action>
<action>Set resume_mode = false</action>
<check if="project-scan-report.json exists">
<action>Read state file and extract: timestamps, mode, scan_level, current_step, completed_steps, project_classification</action>
<action>Extract cached project_type_id(s) from state file if present</action>
<action>Calculate age of state file (current time - last_updated)</action>
<check if="state file age >= 24 hours">
<action>Display: "Found old state file (>24 hours). Starting fresh scan."</action>
<action>Create archive directory: {output_folder}/.archive/</action>
<action>Archive old state file to: {output_folder}/.archive/project-scan-report-{{timestamp}}.json</action>
<action>Set resume_mode = false</action>
<action>Continue to Step 3</action>
</check>
<check if="state file age < 24 hours">
<ask>I found an in-progress workflow state from {{last_updated}}.
**Current Progress:**
@ -86,6 +113,7 @@ Your choice [1/2/3]:
<check if="user selects 1">
<action>Set resume_mode = true</action>
<action>Set workflow_mode = {{mode}}</action>
<action>Set subworkflow_success = false</action>
<action>Load findings summaries from state file</action>
<action>Load cached project_type_id(s) from state file</action>
@ -98,10 +126,27 @@ Your choice [1/2/3]:
<check if="workflow_mode == deep_dive">
<action>Read fully and follow: {installed_path}/workflows/deep-dive-instructions.md with resume context</action>
<action>Set subworkflow_success = true only if delegated workflow completed without HALT/error</action>
<check if="subworkflow_success != true">
<output>Sub-workflow failed or was aborted during resume deep-dive mode. Exiting without marking completion.</output>
<action>Exit workflow</action>
</check>
<action>After sub-workflow completes, continue to Step 4</action>
</check>
<check if="workflow_mode == initial_scan OR workflow_mode == full_rescan">
<action>Read fully and follow: {installed_path}/workflows/full-scan-instructions.md with resume context</action>
<action>Set subworkflow_success = true only if delegated workflow completed without HALT/error</action>
<check if="subworkflow_success != true">
<output>Sub-workflow failed or was aborted during resume full-scan mode. Exiting without marking completion.</output>
<action>Exit workflow</action>
</check>
<action>After sub-workflow completes, continue to Step 4</action>
</check>
<check if="workflow_mode != deep_dive AND workflow_mode != initial_scan AND workflow_mode != full_rescan">
<output>Invalid resume workflow mode '{{workflow_mode}}'. Exiting without marking completion.</output>
<action>Exit workflow</action>
</check>
</check>
@ -110,7 +155,7 @@ Your choice [1/2/3]:
<action>Create archive directory: {output_folder}/.archive/</action>
<action>Move old state file to: {output_folder}/.archive/project-scan-report-{{timestamp}}.json</action>
<action>Set resume_mode = false</action>
<action>Continue to Step 0.5</action>
<action>Continue to Step 3</action>
</check>
<check if="user selects 3">
@ -118,11 +163,17 @@ Your choice [1/2/3]:
<action>Exit workflow</action>
</check>
<check if="state file age >= 24 hours">
<action>Display: "Found old state file (>24 hours). Starting fresh scan."</action>
<action>Archive old state file to: {output_folder}/.archive/project-scan-report-{{timestamp}}.json</action>
<check if="user input is not 1, 2, or 3">
<output>Invalid selection. Expected 1, 2, or 3. Exiting workflow without changes.</output>
<action>Exit workflow</action>
</check>
</check>
</check>
<check if="project-scan-report.json does not exist">
<action>Set resume_mode = false</action>
<action>Continue to Step 0.5</action>
<action>Continue to Step 3</action>
</check>
</step>
@ -149,6 +200,11 @@ Your choice [1/2/3]:
<action>Set workflow_mode = "full_rescan"</action>
<action>Display: "Starting full project rescan..."</action>
<action>Read fully and follow: {installed_path}/workflows/full-scan-instructions.md</action>
<action>Set subworkflow_success = true only if delegated workflow completed without HALT/error</action>
<check if="subworkflow_success != true">
<output>Sub-workflow failed or was aborted during full rescan. Exiting without marking completion.</output>
<action>Exit workflow</action>
</check>
<action>After sub-workflow completes, continue to Step 4</action>
</check>
@ -157,6 +213,11 @@ Your choice [1/2/3]:
<action>Set scan_level = "exhaustive"</action>
<action>Display: "Starting deep-dive documentation mode..."</action>
<action>Read fully and follow: {installed_path}/workflows/deep-dive-instructions.md</action>
<action>Set subworkflow_success = true only if delegated workflow completed without HALT/error</action>
<check if="subworkflow_success != true">
<output>Sub-workflow failed or was aborted during deep-dive mode. Exiting without marking completion.</output>
<action>Exit workflow</action>
</check>
<action>After sub-workflow completes, continue to Step 4</action>
</check>
@ -164,12 +225,22 @@ Your choice [1/2/3]:
<action>Display message: "Keeping existing documentation. Exiting workflow."</action>
<action>Exit workflow</action>
</check>
<check if="user input is not 1, 2, or 3">
<output>Invalid selection. Expected 1, 2, or 3. Exiting workflow without changes.</output>
<action>Exit workflow</action>
</check>
</check>
<check if="index.md does not exist">
<action>Set workflow_mode = "initial_scan"</action>
<action>Display: "No existing documentation found. Starting initial project scan..."</action>
<action>Read fully and follow: {installed_path}/workflows/full-scan-instructions.md</action>
<action>Set subworkflow_success = true only if delegated workflow completed without HALT/error</action>
<check if="subworkflow_success != true">
<output>Sub-workflow failed or was aborted during initial scan. Exiting without marking completion.</output>
<action>Exit workflow</action>
</check>
<action>After sub-workflow completes, continue to Step 4</action>
</check>
@ -177,16 +248,25 @@ Your choice [1/2/3]:
<step n="4" goal="Update status and complete">
<check if="status_file_found == true">
<invoke-workflow path="{project-root}/_bmad/bmm/workflows/workflow-status">
<param>mode: update</param>
<param>action: complete_workflow</param>
<param>workflow_name: document-project</param>
</invoke-workflow>
<check if="resume_mode == true AND subworkflow_success != true">
<output>Resume flow did not complete a delegated sub-workflow successfully. Exiting without completion update.</output>
<action>Exit workflow</action>
</check>
<check if="success == true">
<check if="status_file_found == true">
<action>Attempt status update in {{status_file_path}}:
- Mark workflow `document-project` as completed
- Persist updated timestamp and completion metadata
- Set status_update_success = true on success
- If write fails, set status_update_success = false and capture status_update_error
</action>
<check if="status_update_success == true">
<output>Status updated!</output>
</check>
<check if="status_update_success != true">
<output>⚠️ Status update skipped: {{status_update_error}}</output>
</check>
</check>
<output>**✅ Document Project Workflow Complete, {user_name}!**
@ -196,26 +276,22 @@ Your choice [1/2/3]:
- Mode: {{workflow_mode}}
- Scan Level: {{scan_level}}
- Output: {output_folder}/index.md and related files
{{#if status_file_found}}
**Status Updated:**
- Progress tracking updated
**Next Steps:**
- **Next required:** {{next_workflow}} ({{next_agent}} agent)
Check status anytime with: `workflow-status`
{{else}}
**Next Steps:**
Since no workflow is in progress:
- Refer to the BMM workflow guide if unsure what to do next
- Or run `workflow-init` to create a workflow path and get guided next steps
{{/if}}
</output>
<check if="status_file_found == true AND status_update_success == true">
<output>**Status Updated:** Progress tracking updated.
**Next Steps:**
- **Next required:** {{next_workflow}} ({{next_agent}} agent)
- Run `bmad-help` if you need recommended next workflows.</output>
</check>
<check if="status_file_found == false OR status_update_success != true">
<output>**Next Steps:**
- Refer to the BMM workflow guide if unsure what to do next
- Run `bmad-help` to get guided workflow recommendations</output>
</check>
</step>
</workflow>

View File

@ -35,13 +35,15 @@ Determine what was just completed:
1. **Load catalog** — Load `{project-root}/_bmad/_config/bmad-help.csv`
2. **Resolve output locations** — Scan each folder under `_bmad/` (except `_config`) for `config.yaml`. For each workflow row, resolve its `output-location` variables against that module's config so artifact paths can be searched.
2. **Resolve output locations and config** — Scan each folder under `_bmad/` (except `_config`) for `config.yaml`. For each workflow row, resolve its `output-location` variables against that module's config so artifact paths can be searched. Also extract `communication_language` and `project_knowledge` from each scanned module's config.
3. **Analyze input** — Task may provide a workflow name/code, conversational phrase, or nothing. Infer what was just completed using INPUT ANALYSIS above.
3. **Ground in project knowledge** — If `project_knowledge` resolves to an existing path, read available documentation files (architecture docs, project overview, tech stack references) for grounding context. Use discovered project facts when composing any project-specific output. Never fabricate project-specific details. If documentation is unavailable, state that clearly.
4. **Detect active module** — Use MODULE DETECTION above to determine which module the user is working in.
4. **Analyze input** — Task may provide a workflow name/code, conversational phrase, or nothing. Infer what was just completed using INPUT ANALYSIS above.
5. **Present recommendations** — Show next steps based on completed workflows, phase/sequence ordering (KEY RULES), and artifact detection. Format per the following
5. **Detect active module** — Use MODULE DETECTION above to determine which module the user is working in.
6. **Present recommendations** — Show next steps based on completed workflows, phase/sequence ordering (KEY RULES), and artifact detection. Format per the following
## RECOMMENDED OUTPUT FORMAT
@ -49,14 +51,28 @@ Determine what was just completed:
**Required items next** — List the next required workflow
For each item show:
- Workflow **name**
- **Command** (use the catalog command name; present it in your platform's command format, e.g., `bmad-example-build-prototype`)
- **Command** when `command` is present
- **Agent load instruction + code** when `command` is empty
- **Agent** title and display name from the CSV (e.g., "🎨 Alex (Designer)")
- Brief **description**
### Display branching rules
- When `command` has a value:
- Present the command in your platform's command format (for example: `bmad-example-build-prototype`)
- When `command` is empty:
- Do not invent a slash command
- Show how to load the agent from `agent` and then invoke using the workflow/code description
- Example:
- `Explain Concept (EC)`
- `Load: /tech-writer, then ask to "EC about [topic]"`
- `Agent: Tech Writer`
- `Description: Create clear technical explanations with examples`
### Additional response output guidance to convey:
- Present all output in `{communication_language}` when available in module config
- Run each workflow in a **fresh context window**
- Load the agent using the platform's command format for `agent-command`, or run the workflow command directly
- For **validation workflows**: recommend using a different high-quality LLM if available
- For conversational requests: match the user's tone while presenting clearly
6. Return to the calling process after presenting recommendations.
7. Return to the calling process after presenting recommendations.

View File

@ -13,8 +13,8 @@ standalone: true
- Parse YAML and fail fast with explicit error if parsing fails.
- Require `user_name`; if missing, abort initialization with descriptive error.
- Apply explicit defaults when optional keys are absent:
- `communication_language = "en"`
- `document_output_language = "en"`
- `communication_language = "English"`
- `document_output_language = "English"`
- Log resolved values and config source path.
## Purpose

View File

@ -28,7 +28,7 @@ When called during template workflow processing:
<workflow>
<step n="1" title="Method Registry Loading">
<action>Load and read {{methods}} and {{agent-party}}</action>
<action>Load and read {{methods}} and {{agent_manifest}}</action>
<csv-structure>
<i>category: Method grouping (core, structural, risk, etc.)</i>

View File

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

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

View File

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

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

@ -0,0 +1,3 @@
module,phase,name,code,sequence,workflow-file,command,required,agent,options,description,output-location,outputs,
bmm,anytime,Document Project,DP,,_bmad/bmm/workflows/document-project/workflow.yaml,bmad-bmm-document-project,false,analyst,Create Mode,"Analyze project",project-knowledge,*,
bmm,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

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

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

134
test/test-file-refs-csv.js Normal file
View File

@ -0,0 +1,134 @@
/**
* 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}`);
// Keep this legacy .yaml reference to verify backward-compatible CSV parsing.
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);

View File

@ -14,12 +14,15 @@
const path = require('node:path');
const os = require('node:os');
const fs = require('fs-extra');
const csv = require('csv-parse/sync');
const yaml = require('yaml');
const { YamlXmlBuilder } = require('../tools/cli/lib/yaml-xml-builder');
const { ManifestGenerator } = require('../tools/cli/installers/lib/core/manifest-generator');
const { WorkflowCommandGenerator } = require('../tools/cli/installers/lib/ide/shared/workflow-command-generator');
const { TaskToolCommandGenerator } = require('../tools/cli/installers/lib/ide/shared/task-tool-command-generator');
const { ConfigDrivenIdeSetup } = require('../tools/cli/installers/lib/ide/_config-driven');
const { IdeManager } = require('../tools/cli/installers/lib/ide/manager');
const { CodexSetup } = require('../tools/cli/installers/lib/ide/codex');
const { ModuleManager } = require('../tools/cli/installers/lib/modules/manager');
const { BMAD_FOLDER_NAME } = require('../tools/cli/installers/lib/ide/shared/path-utils');
@ -197,16 +200,19 @@ async function runTests() {
try {
const builder = new YamlXmlBuilder();
// Test path resolution logic (if exposed)
// This would test {project-root}, {installed_path}, {config_source} resolution
// Basic path-variable substitution contract used across workflow templates
const testPath = '{project-root}/_bmad/bmm/config.yaml';
const projectRootStub = path.join(os.tmpdir(), 'bmad-test-project');
const resolvedPath = testPath.replace('{project-root}', projectRootStub);
const testPath = '{project-root}/bmad/bmm/config.yaml';
const expectedPattern = /\/bmad\/bmm\/config\.yaml$/;
assert(builder && typeof builder.deepMerge === 'function', 'Path suite uses initialized YamlXmlBuilder instance');
assert(resolvedPath.startsWith(projectRootStub), 'Path variable replaces {project-root} with resolved root');
assert(
true, // Placeholder - would test actual resolution
'Path variable resolution pattern matches expected format',
'Note: This test validates path resolution logic exists',
resolvedPath.endsWith(path.join(BMAD_FOLDER_NAME, 'bmm', 'config.yaml')),
'Path variable resolution preserves canonical BMAD folder path',
`Resolved path was: ${resolvedPath}`,
);
} catch (error) {
assert(false, 'Path resolution works', error.message);
@ -348,10 +354,9 @@ async function runTests() {
];
const allowedExtensions = new Set(['.md', '.yaml', '.yml', '.xml']);
const forbiddenRef = 'validate-workflow.xml';
const excludedFile = path.join(projectRoot, 'src', 'core', 'tasks', 'validate-workflow.xml');
const offenders = [];
const files = await collectFiles(searchTargets, allowedExtensions, new Set([excludedFile]));
const files = await collectFiles(searchTargets, allowedExtensions);
for (const fullPath of files) {
const content = await fs.readFile(fullPath, 'utf8');
if (content.includes(forbiddenRef)) {
@ -678,6 +683,179 @@ internal: true
console.log('');
// ============================================================
// Test 17: Help Task Agent-Only Guidance Guard
// ============================================================
console.log(`${colors.yellow}Test Suite 17: Help Task Agent-Only Guidance Guard${colors.reset}\n`);
try {
const helpTaskPath = path.join(projectRoot, 'src', 'core', 'tasks', 'help.md');
const moduleHelpPath = path.join(projectRoot, 'src', 'bmm', 'module-help.csv');
const helpTaskContent = await fs.readFile(helpTaskPath, 'utf8');
const moduleHelpRows = csv.parse(await fs.readFile(moduleHelpPath, 'utf8'), {
columns: true,
skip_empty_lines: true,
});
const hasAgentOnlyRows = moduleHelpRows.some((row) => !row.command && row.agent);
assert(hasAgentOnlyRows, 'Help catalog includes agent-only rows with empty command values');
assert(
helpTaskContent.includes('When `command` is empty') && helpTaskContent.includes('Do not invent a slash command'),
'Help task includes explicit guidance for agent-only rows without commands',
);
} catch (error) {
assert(false, 'Help task agent-only guidance guard runs', error.message);
}
console.log('');
// ============================================================
// Test 18: Codex Task Visibility Guard
// ============================================================
console.log(`${colors.yellow}Test Suite 18: Codex Task Visibility Guard${colors.reset}\n`);
try {
const tmpRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'bmad-codex-visibility-'));
const projectDir = path.join(tmpRoot, 'project');
const bmadDir = path.join(tmpRoot, BMAD_FOLDER_NAME);
await fs.ensureDir(projectDir);
await fs.copy(path.join(projectRoot, 'src', 'core'), path.join(bmadDir, 'core'));
await fs.copy(path.join(projectRoot, 'src', 'bmm'), path.join(bmadDir, 'bmm'));
const manifestGenerator = new ManifestGenerator();
await manifestGenerator.generateManifests(bmadDir, ['bmm'], [], { ides: ['codex'] });
const codexSetup = new CodexSetup();
await codexSetup.setup(projectDir, bmadDir, {
selectedModules: ['bmm'],
preCollectedConfig: { installLocation: 'project' },
});
const promptsDir = path.join(projectDir, '.codex', 'prompts');
const generated = await fs.readdir(promptsDir);
assert(!generated.includes('bmad-workflow.md'), 'Codex export excludes internal workflow runner task prompt', generated.join(', '));
await fs.remove(tmpRoot);
} catch (error) {
assert(false, 'Codex task visibility guard runs', error.message);
}
console.log('');
// ============================================================
// Test 19: Empty Target Artifact Filter Guard
// ============================================================
console.log(`${colors.yellow}Test Suite 19: Empty Artifact Target Guard${colors.reset}\n`);
try {
const tmpRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'bmad-empty-target-'));
const projectDir = path.join(tmpRoot, 'project');
const bmadDir = path.join(tmpRoot, BMAD_FOLDER_NAME);
await fs.ensureDir(projectDir);
await fs.ensureDir(bmadDir);
const setup = new ConfigDrivenIdeSetup('test', {
name: 'Test IDE',
preferred: false,
installer: { target_dir: '.test', template_type: 'default' },
});
const result = await setup.installToTarget(
projectDir,
bmadDir,
{ target_dir: '.test', template_type: 'default', artifact_types: [] },
{ silent: true },
);
assert(
result.success &&
result.results.agents === 0 &&
result.results.workflows === 0 &&
result.results.tasks === 0 &&
result.results.tools === 0,
'Installer short-circuits explicit empty artifact target',
);
assert(!(await fs.pathExists(path.join(projectDir, '.test'))), 'Installer does not create output directory for empty artifact target');
await fs.remove(tmpRoot);
} catch (error) {
assert(false, 'Empty artifact target guard runs', error.message);
}
console.log('');
// ============================================================
// Test 20: Split Template Extension Override Guard
// ============================================================
console.log(`${colors.yellow}Test Suite 20: Split Template Extension Guard${colors.reset}\n`);
try {
const setup = new ConfigDrivenIdeSetup('test', {
name: 'Test IDE',
preferred: false,
installer: { target_dir: '.test', template_type: 'default' },
});
setup.loadSplitTemplates = async () => 'template-content';
const loaded = await setup.loadTemplateWithMetadata('default', 'workflow', {
header_template: 'header.md',
extension: 'toml',
});
assert(loaded.extension === '.toml', 'Split template loader preserves configured extension override');
} catch (error) {
assert(false, 'Split template extension guard runs', error.message);
}
console.log('');
// ============================================================
// Test 21: Workflow Path Normalization Guard
// ============================================================
console.log(`${colors.yellow}Test Suite 21: Workflow Path Normalization Guard${colors.reset}\n`);
try {
const generator = new WorkflowCommandGenerator();
generator.loadWorkflowManifest = async () => [
{
name: 'create-story',
description: 'Create Story',
module: 'bmm',
path: String.raw`C:\repo\src\bmm\workflows\4-implementation\create-story\workflow.md`,
},
{
name: 'validate-workflow',
description: 'Validate Workflow',
module: 'core',
path: String.raw`C:\repo\_bmad\core\workflows\validate-workflow\workflow.md`,
},
];
generator.generateCommandContent = async () => 'content';
const { artifacts } = await generator.collectWorkflowArtifacts('/tmp');
const createStory = artifacts.find((artifact) => artifact.name === 'create-story');
const validateWorkflow = artifacts.find((artifact) => artifact.name === 'validate-workflow');
assert(
createStory?.workflowPath === 'bmm/workflows/4-implementation/create-story/workflow.md',
'Workflow artifact path normalizes Windows src path to module-relative path',
createStory?.workflowPath,
);
assert(
validateWorkflow?.workflowPath === 'core/workflows/validate-workflow/workflow.md',
'Workflow artifact path strips _bmad prefix after separator normalization',
validateWorkflow?.workflowPath,
);
} catch (error) {
assert(false, 'Workflow path normalization guard runs', error.message);
}
console.log('');
// ============================================================
// Summary
// ============================================================

1050
test/test-rehype-plugins.mjs Normal file

File diff suppressed because it is too large Load Diff

View File

@ -52,6 +52,12 @@ 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();
@ -118,9 +124,6 @@ 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`);
@ -152,20 +155,18 @@ function generateLlmsTxt(outputDir) {
'',
'## Quick Start',
'',
`- **[Quick Start](${siteUrl}/docs/modules/bmm/quick-start)** - Get started with BMAD Method`,
`- **[Installation](${siteUrl}/docs/getting-started/installation)** - Installation guide`,
`- **[Getting Started](${siteUrl}/tutorials/getting-started/)** - Tutorial: install and learn how BMad works`,
`- **[Installation](${siteUrl}/how-to/install-bmad/)** - How to install BMad Method`,
'',
'## Core Concepts',
'',
`- **[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`,
`- **[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`,
'',
'## Modules',
'',
`- **[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`,
`- **[Official Modules](${siteUrl}/reference/modules/)** - BMM, BMB, BMGD, and more`,
'',
'---',
'',
@ -401,32 +402,6 @@ 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
/**
@ -444,33 +419,6 @@ 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
// =============================================================================
@ -496,7 +444,7 @@ function printBanner(title) {
/**
* Verify internal documentation links by running the link-checking script.
*
* Executes the Node script tools/check-doc-links.js from the project root and
* Executes the Node script tools/validate-doc-links.js from the project root and
* exits the process with code 1 if the check fails.
*/

View File

@ -2,6 +2,14 @@ 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');
@ -27,17 +35,17 @@ async function checkForUpdate() {
}).trim();
if (result && result !== packageJson.version) {
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('');
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,
});
}
} 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';
console.log(chalk.cyan('Debug mode enabled\n'));
await prompts.log.info('Debug mode enabled');
}
const config = await ui.promptInstall(options);
// Handle cancel
if (config.actionType === 'cancel') {
console.log(chalk.yellow('Installation cancelled.'));
await prompts.log.warn('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);
console.log(chalk.green('\n✨ Quick update complete!'));
console.log(chalk.cyan(`Updated ${result.moduleCount} modules with preserved settings (${result.modules.join(', ')})`));
await prompts.log.success('Quick update complete!');
await prompts.log.info(`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();
messageLoader.displayEndMessage();
await 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);
console.log(chalk.green('\n✨ Agent recompilation complete!'));
console.log(chalk.cyan(`Recompiled ${result.agentCount} agents with customizations applied`));
await prompts.log.success('Agent recompilation complete!');
await prompts.log.info(`Recompiled ${result.agentCount} agents with customizations applied`);
process.exit(0);
return;
}
@ -80,21 +80,22 @@ module.exports = {
// Display version-specific end message from install-messages.yaml
const { MessageLoader } = require('../installers/lib/message-loader');
const messageLoader = new MessageLoader();
messageLoader.displayEndMessage();
await messageLoader.displayEndMessage();
process.exit(0);
}
} catch (error) {
// Check if error has a complete formatted message
try {
if (error.fullMessage) {
console.error(error.fullMessage);
if (error.stack) {
console.error('\n' + chalk.dim(error.stack));
}
await prompts.log.error(error.fullMessage);
} else {
// Generic error handling for all other errors
console.error(chalk.red('Installation failed:'), error.message);
console.error(chalk.dim(error.stack));
await prompts.log.error(`Installation failed: ${error.message}`);
}
if (error.stack) {
await prompts.log.message(error.stack);
}
} catch {
console.error(error.fullMessage || error.message || error);
}
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))) {
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.'));
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.');
process.exit(0);
return;
}
@ -32,8 +32,8 @@ module.exports = {
const manifestData = await manifest._readRaw(bmadDir);
if (!manifestData) {
console.log(chalk.yellow('No BMAD installation manifest found.'));
console.log(chalk.dim('\nRun "bmad install" to set up a new installation.'));
await prompts.log.warn('No BMAD installation manifest found.');
await prompts.log.message('Run "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
ui.displayStatus({
await ui.displayStatus({
installation,
modules,
availableUpdates,
@ -55,9 +55,9 @@ module.exports = {
process.exit(0);
} catch (error) {
console.error(chalk.red('Status check failed:'), error.message);
await prompts.log.error(`Status check failed: ${error.message}`);
if (process.env.BMAD_DEBUG) {
console.error(chalk.dim(error.stack));
await prompts.log.message(error.stack);
}
process.exit(1);
}

View File

@ -1,7 +1,6 @@
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');
@ -260,15 +259,9 @@ 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`;
// 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}`));
await prompts.log.step(moduleDisplayName);
await prompts.log.message(` \u2713 ${moduleConfig.subheader}`);
return false; // No new fields
}
@ -322,7 +315,7 @@ class ConfigCollector {
}
// Show "no config" message for modules with no new questions (that have config keys)
console.log(chalk.dim(` ${moduleName.toUpperCase()} module already up to date`));
await prompts.log.message(` \u2713 ${moduleName.toUpperCase()} module already up to date`);
return false; // No new fields
}
@ -350,15 +343,15 @@ class ConfigCollector {
if (questions.length > 0) {
// Only show header if we actually have questions
CLIUtils.displayModuleConfigHeader(moduleName, moduleConfig.header, moduleConfig.subheader);
console.log(); // Line break before questions
await CLIUtils.displayModuleConfigHeader(moduleName, moduleConfig.header, moduleConfig.subheader);
await prompts.log.message('');
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
console.log(chalk.dim(` ${moduleName.toUpperCase()} module configuration updated`));
await prompts.log.message(` \u2713 ${moduleName.toUpperCase()} module configuration updated`);
}
// Store all answers for cross-referencing
@ -588,7 +581,7 @@ class ConfigCollector {
// Skip prompts mode: use all defaults without asking
if (this.skipPrompts) {
console.log(chalk.cyan('Using default configuration for'), chalk.magenta(moduleDisplayName));
await prompts.log.info(`Using default configuration for ${moduleDisplayName}`);
// Use defaults for all questions
for (const question of questions) {
const hasDefault = question.default !== undefined && question.default !== null && question.default !== '';
@ -597,12 +590,10 @@ class ConfigCollector {
}
}
} else {
console.log();
console.log(chalk.cyan('?') + ' ' + chalk.magenta(moduleDisplayName));
await prompts.log.step(moduleDisplayName);
let customize = true;
if (moduleName === 'core') {
// Core module: no confirm prompt, so add spacing manually to match visual style
console.log(chalk.gray('│'));
// Core module: no confirm prompt, continues directly
} else {
// Non-core modules: show "Accept Defaults?" confirm prompt (clack adds spacing)
const customizeAnswer = await prompts.prompt([
@ -621,7 +612,7 @@ class ConfigCollector {
const questionsWithoutDefaults = questions.filter((q) => q.default === undefined || q.default === null || q.default === '');
if (questionsWithoutDefaults.length > 0) {
console.log(chalk.dim(`\n Asking required questions for ${moduleName.toUpperCase()}...`));
await prompts.log.message(` Asking required questions for ${moduleName.toUpperCase()}...`);
const promptedAnswers = await prompts.prompt(questionsWithoutDefaults);
Object.assign(allAnswers, promptedAnswers);
}
@ -747,32 +738,15 @@ class ConfigCollector {
const hasNoConfig = actualConfigKeys.length === 0;
if (hasNoConfig && (moduleConfig.subheader || moduleConfig.header)) {
// 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
await prompts.log.step(moduleDisplayName);
if (moduleConfig.subheader) {
console.log(chalk.dim(` ${moduleConfig.subheader}`));
await prompts.log.message(` \u2713 ${moduleConfig.subheader}`);
} else {
console.log(chalk.dim(` ✓ No custom configuration required`));
await prompts.log.message(` \u2713 No custom configuration required`);
}
} else {
// Module has config but just no questions to ask
console.log(chalk.dim(` ${moduleName.toUpperCase()} module configured`));
await prompts.log.message(` \u2713 ${moduleName.toUpperCase()} module configured`);
}
}
@ -981,14 +955,15 @@ class ConfigCollector {
}
// Add current value indicator for existing configs
const color = await prompts.getColor();
if (existingValue !== null && existingValue !== undefined) {
if (typeof existingValue === 'boolean') {
message += chalk.dim(` (current: ${existingValue ? 'true' : 'false'})`);
message += color.dim(` (current: ${existingValue ? 'true' : 'false'})`);
} else if (Array.isArray(existingValue)) {
message += chalk.dim(` (current: ${existingValue.join(', ')})`);
message += color.dim(` (current: ${existingValue.join(', ')})`);
} else if (questionType !== 'list') {
// Show the cleaned value (without {project-root}/) for display
message += chalk.dim(` (current: ${existingValue})`);
message += color.dim(` (current: ${existingValue})`);
}
} else if (item.example && questionType === 'input') {
// Show example for input fields
@ -998,7 +973,7 @@ class ConfigCollector {
exampleText = this.replacePlaceholders(exampleText, moduleName, moduleConfig);
exampleText = exampleText.replace('{project-root}/', '');
}
message += chalk.dim(` (e.g., ${exampleText})`);
message += color.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) {
console.log(chalk.cyan('Resolving module dependencies...'));
await prompts.log.info('Resolving module dependencies...');
}
// Always include core as base
@ -50,7 +50,7 @@ class DependencyResolver {
// Report results (only in verbose mode)
if (options.verbose) {
this.reportResults(organizedFiles, selectedModules);
await this.reportResults(organizedFiles, selectedModules);
}
return {
@ -90,8 +90,12 @@ class DependencyResolver {
}
}
if (!moduleDir) {
continue;
}
if (!(await fs.pathExists(moduleDir))) {
console.warn(chalk.yellow(`Module directory not found: ${moduleDir}`));
await prompts.log.warn('Module directory not found: ' + moduleDir);
continue;
}
@ -179,7 +183,7 @@ class DependencyResolver {
}
}
} catch (error) {
console.warn(chalk.yellow(`Failed to parse frontmatter in ${file.name}: ${error.message}`));
await prompts.log.warn('Failed to parse frontmatter in ' + file.name + ': ' + error.message);
}
}
@ -658,8 +662,8 @@ class DependencyResolver {
/**
* Report resolution results
*/
reportResults(organized, selectedModules) {
console.log(chalk.green('\n✓ Dependency resolution complete'));
async reportResults(organized, selectedModules) {
await prompts.log.success('Dependency resolution complete');
for (const [module, files] of Object.entries(organized)) {
const isSelected = selectedModules.includes(module) || module === 'core';
@ -667,31 +671,31 @@ class DependencyResolver {
files.agents.length + files.tasks.length + files.tools.length + files.templates.length + files.data.length + files.other.length;
if (totalFiles > 0) {
console.log(chalk.cyan(`\n ${module.toUpperCase()} module:`));
console.log(chalk.dim(` Status: ${isSelected ? 'Selected' : 'Dependencies only'}`));
await prompts.log.info(` ${module.toUpperCase()} module:`);
await prompts.log.message(` Status: ${isSelected ? 'Selected' : 'Dependencies only'}`);
if (files.agents.length > 0) {
console.log(chalk.dim(` Agents: ${files.agents.length}`));
await prompts.log.message(` Agents: ${files.agents.length}`);
}
if (files.tasks.length > 0) {
console.log(chalk.dim(` Tasks: ${files.tasks.length}`));
await prompts.log.message(` Tasks: ${files.tasks.length}`);
}
if (files.templates.length > 0) {
console.log(chalk.dim(` Templates: ${files.templates.length}`));
await prompts.log.message(` Templates: ${files.templates.length}`);
}
if (files.data.length > 0) {
console.log(chalk.dim(` Data files: ${files.data.length}`));
await prompts.log.message(` Data files: ${files.data.length}`);
}
if (files.other.length > 0) {
console.log(chalk.dim(` Other files: ${files.other.length}`));
await prompts.log.message(` Other files: ${files.other.length}`);
}
}
}
if (this.missingDependencies.size > 0) {
console.log(chalk.yellow('\n ⚠ Missing dependencies:'));
await prompts.log.warn('Missing dependencies:');
for (const missing of this.missingDependencies) {
console.log(chalk.yellow(` - ${missing}`));
await prompts.log.warn(` - ${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');
@ -88,7 +88,7 @@ class CustomHandler {
try {
config = yaml.parse(configContent);
} catch (parseError) {
console.warn(chalk.yellow(`Warning: YAML parse error in ${configPath}:`, parseError.message));
await prompts.log.warn('YAML parse error in ' + configPath + ': ' + parseError.message);
return null;
}
@ -111,7 +111,7 @@ class CustomHandler {
isInstallConfig: isInstallConfig, // Track which type this is
};
} catch (error) {
console.warn(chalk.yellow(`Warning: Failed to read ${configPath}:`, error.message));
await prompts.log.warn('Failed to read ' + configPath + ': ' + error.message);
return null;
}
}
@ -268,14 +268,13 @@ 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}`);
}
@ -322,7 +321,7 @@ class CustomHandler {
await fs.writeFile(customizePath, templateContent, 'utf8');
// Only show customize creation in verbose mode
if (process.env.BMAD_VERBOSE_INSTALL === 'true') {
console.log(chalk.dim(` Created customize: custom-${agentName}.customize.yaml`));
await prompts.log.message(' Created customize: custom-' + agentName + '.customize.yaml');
}
}
}
@ -346,14 +345,10 @@ class CustomHandler {
// Only show compilation details in verbose mode
if (process.env.BMAD_VERBOSE_INSTALL === 'true') {
console.log(
chalk.dim(
` Compiled agent: ${agentName} -> ${path.relative(targetAgentsPath, targetMdPath)}${hasSidecar ? ' (with sidecar)' : ''}`,
),
);
await prompts.log.message(' Compiled agent: ' + agentName + ' -> ' + path.relative(targetAgentsPath, targetMdPath));
}
} catch (error) {
console.warn(chalk.yellow(` Failed to compile agent ${agentName}:`, error.message));
await prompts.log.warn(' 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) {
async cleanup(projectDir, options = {}) {
// 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);
console.log(chalk.dim(`Removed ${this.name} BMAD configuration`));
if (!options.silent) await prompts.log.message(`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 = {}) {
console.log(chalk.cyan(`Setting up ${this.name}...`));
if (!options.silent) await prompts.log.info(`Setting up ${this.name}...`);
// Clean up any old BMAD installation first
await this.cleanup(projectDir);
await this.cleanup(projectDir, options);
if (!this.installerConfig) {
return { success: false, reason: 'no-config' };
@ -66,6 +66,12 @@ class ConfigDrivenIdeSetup extends BaseIdeSetup {
*/
async installToTarget(projectDir, bmadDir, config, options) {
const { target_dir, template_type, artifact_types } = config;
// Skip explicitly empty targets to avoid creating empty command directories.
if (Array.isArray(artifact_types) && artifact_types.length === 0) {
return { success: true, results: { agents: 0, workflows: 0, tasks: 0, tools: 0 } };
}
const targetPath = path.join(projectDir, target_dir);
await this.ensureDir(targetPath);
@ -95,7 +101,7 @@ class ConfigDrivenIdeSetup extends BaseIdeSetup {
results.tools = taskToolResult.tools;
}
this.printSummary(results, target_dir);
await this.printSummary(results, target_dir, options);
return { success: true, results };
}
@ -250,12 +256,13 @@ class ConfigDrivenIdeSetup extends BaseIdeSetup {
// Check for separate header/body templates
if (header_template || body_template) {
const template = await this.loadSplitTemplates(templateType, artifactType, header_template, body_template);
return { template, extension: '.md' };
return { template, extension: this.normalizeExtension(config.extension) };
}
// Load combined template with extension detection
const templateBaseName = artifactType ? `${templateType}-${artifactType}` : templateType;
for (const extension of supportedExtensions) {
const templateName = `${templateType}-${artifactType}${extension}`;
const templateName = `${templateBaseName}${extension}`;
const templatePath = path.join(__dirname, 'templates', 'combined', templateName);
if (await fs.pathExists(templatePath)) {
return {
@ -329,6 +336,19 @@ class ConfigDrivenIdeSetup extends BaseIdeSetup {
return `${header}\n${body}`;
}
normalizeExtension(extension) {
if (!extension) {
return '.md';
}
const trimmed = String(extension).trim();
if (trimmed === '') {
return '.md';
}
return trimmed.startsWith('.') ? trimmed : `.${trimmed}`;
}
/**
* Get default minimal template
* @param {string} artifactType - Artifact type
@ -414,32 +434,28 @@ LOAD and execute from: {project-root}/{{bmadFolderName}}/{{path}}
* @param {Object} results - Installation results
* @param {string} targetDir - Target directory (relative)
*/
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}`));
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}`);
}
/**
* Cleanup IDE configuration
* @param {string} projectDir - Project directory
*/
async cleanup(projectDir) {
async cleanup(projectDir, options = {}) {
// Clean all target directories
if (this.installerConfig?.targets) {
for (const target of this.installerConfig.targets) {
await this.cleanupTarget(projectDir, target.target_dir);
await this.cleanupTarget(projectDir, target.target_dir, options);
}
} else if (this.installerConfig?.target_dir) {
await this.cleanupTarget(projectDir, this.installerConfig.target_dir);
await this.cleanupTarget(projectDir, this.installerConfig.target_dir, options);
}
}
@ -448,7 +464,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) {
async cleanupTarget(projectDir, targetDir, options = {}) {
const targetPath = path.join(projectDir, targetDir);
if (!(await fs.pathExists(targetPath))) {
@ -471,25 +487,22 @@ 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);
const stat = await fs.stat(entryPath);
if (stat.isFile()) {
await fs.remove(entryPath);
removedCount++;
} else if (stat.isDirectory()) {
try {
await fs.remove(entryPath);
removedCount++;
} catch {
// Skip entries that can't be removed (broken symlinks, permission errors)
}
}
}
if (removedCount > 0) {
console.log(chalk.dim(` Cleaned ${removedCount} BMAD files from ${targetDir}`));
if (removedCount > 0 && !options.silent) {
await prompts.log.message(` Cleaned ${removedCount} BMAD files from ${targetDir}`);
}
}
}

View File

@ -1,7 +1,6 @@
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');
@ -43,12 +42,11 @@ class CodexSetup extends BaseIdeSetup {
default: 'global',
});
// Display detailed instructions for the chosen option
console.log('');
// Show brief confirmation hint (detailed instructions available via verbose)
if (installLocation === 'project') {
console.log(this.getProjectSpecificInstructions());
await prompts.log.info('Prompts installed to: <project>/.codex/prompts (requires CODEX_HOME)');
} else {
console.log(this.getGlobalInstructions());
await prompts.log.info('Prompts installed to: ~/.codex/prompts');
}
// Confirm the choice
@ -58,7 +56,7 @@ class CodexSetup extends BaseIdeSetup {
});
if (!confirmed) {
console.log(chalk.yellow("\n Let's choose a different installation option.\n"));
await prompts.log.warn("Let's choose a different installation option.");
}
}
@ -72,7 +70,7 @@ class CodexSetup extends BaseIdeSetup {
* @param {Object} options - Setup options
*/
async setup(projectDir, bmadDir, options = {}) {
console.log(chalk.cyan(`Setting up ${this.name}...`));
if (!options.silent) await prompts.log.info(`Setting up ${this.name}...`);
// Always use CLI mode
const mode = 'cli';
@ -84,7 +82,7 @@ class CodexSetup extends BaseIdeSetup {
const destDir = this.getCodexPromptDir(projectDir, installLocation);
await fs.ensureDir(destDir);
await this.clearOldBmadFiles(destDir);
await this.clearOldBmadFiles(destDir, options);
// Collect artifacts and write using underscore format
const agentGen = new AgentCommandGenerator(this.bmadFolderName);
@ -124,16 +122,11 @@ class CodexSetup extends BaseIdeSetup {
const written = agentCount + workflowCount + tasksWritten;
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`));
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.dim(` - ${written} Codex prompt files written`));
console.log(chalk.dim(` - Destination: ${destDir}`));
return {
success: true,
@ -262,7 +255,7 @@ class CodexSetup extends BaseIdeSetup {
return written;
}
async clearOldBmadFiles(destDir) {
async clearOldBmadFiles(destDir, options = {}) {
if (!(await fs.pathExists(destDir))) {
return;
}
@ -272,7 +265,7 @@ class CodexSetup extends BaseIdeSetup {
entries = await fs.readdir(destDir);
} catch (error) {
// Directory exists but can't be read - skip cleanup
console.warn(chalk.yellow(`Warning: Could not read directory ${destDir}: ${error.message}`));
if (!options.silent) await prompts.log.warn(`Warning: Could not read directory ${destDir}: ${error.message}`);
return;
}
@ -291,15 +284,11 @@ 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) {
// Skip files that can't be processed
console.warn(chalk.dim(` Skipping ${entry}: ${error.message}`));
if (!options.silent) {
await prompts.log.message(` Skipping ${entry}: ${error.message}`);
}
}
}
}
@ -315,22 +304,16 @@ class CodexSetup extends BaseIdeSetup {
*/
getGlobalInstructions(destDir) {
const lines = [
'IMPORTANT: Codex Configuration',
'',
chalk.bold.cyan('═'.repeat(70)),
chalk.bold.yellow(' IMPORTANT: Codex Configuration'),
chalk.bold.cyan('═'.repeat(70)),
'/prompts installed globally to your HOME DIRECTORY.',
'',
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)),
'These prompts reference a specific _bmad path.',
"To use with other projects, you'd need to copy the _bmad dir.",
'',
'You can now use /commands in Codex CLI',
' Example: /bmad_bmm_pm',
' Type / to see all available commands',
];
return lines.join('\n');
}
@ -345,43 +328,34 @@ class CodexSetup extends BaseIdeSetup {
const isWindows = os.platform() === 'win32';
const commonLines = [
'Project-Specific Codex Configuration',
'',
chalk.bold.cyan('═'.repeat(70)),
chalk.bold.yellow(' Project-Specific Codex Configuration'),
chalk.bold.cyan('═'.repeat(70)),
`Prompts will be installed to: ${destDir || '<project>/.codex/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'),
'REQUIRED: You must set CODEX_HOME to use these prompts',
'',
];
const windowsLines = [
chalk.bold(' Create a codex.cmd file in your project root:'),
'Create a codex.cmd file in your project root:',
'',
chalk.green(' @echo off'),
chalk.green(' set CODEX_HOME=%~dp0.codex'),
chalk.green(' codex %*'),
' @echo off',
' set CODEX_HOME=%~dp0.codex',
' codex %*',
'',
chalk.dim(String.raw` Then run: .\codex instead of codex`),
chalk.dim(' (The %~dp0 gets the directory of the .cmd file)'),
String.raw`Then run: .\codex instead of codex`,
'(The %~dp0 gets the directory of the .cmd file)',
];
const unixLines = [
chalk.bold(' Add this alias to your ~/.bashrc or ~/.zshrc:'),
'Add this alias to your ~/.bashrc or ~/.zshrc:',
'',
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)),
' alias codex=\'CODEX_HOME="$PWD/.codex" codex\'',
'',
'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 = {}) {
console.log(chalk.cyan(`Setting up ${this.name}...`));
if (!options.silent) await prompts.log.info(`Setting up ${this.name}...`);
// Clean up any old BMAD installation first
await this.cleanup(projectDir);
await this.cleanup(projectDir, options);
// 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
console.log(chalk.yellow('Warning: Could not parse existing .kilocodemodes, starting fresh'));
await prompts.log.warn('Warning: Could not parse existing .kilocodemodes, starting fresh');
config = {};
}
}
@ -88,14 +88,11 @@ class KiloSetup extends BaseIdeSetup {
const taskCount = taskToolCounts.tasks || 0;
const toolCount = taskToolCounts.tools || 0;
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'));
if (!options.silent) {
await prompts.log.success(
`${this.name} configured: ${addedCount} modes, ${workflowCount} workflows, ${taskCount} tasks, ${toolCount} tools → ${this.configFile}`,
);
}
return {
success: true,
@ -174,7 +171,7 @@ class KiloSetup extends BaseIdeSetup {
/**
* Cleanup KiloCode configuration
*/
async cleanup(projectDir) {
async cleanup(projectDir, options = {}) {
const fs = require('fs-extra');
const kiloModesPath = path.join(projectDir, this.configFile);
@ -192,12 +189,12 @@ class KiloSetup extends BaseIdeSetup {
if (removedCount > 0) {
await fs.writeFile(kiloModesPath, yaml.stringify(config, { lineWidth: 0 }));
console.log(chalk.dim(`Removed ${removedCount} BMAD modes from .kilocodemodes`));
if (!options.silent) await prompts.log.message(`Removed ${removedCount} BMAD modes from .kilocodemodes`);
}
}
} catch {
// If parsing fails, leave file as-is
console.log(chalk.yellow('Warning: Could not parse .kilocodemodes for cleanup'));
if (!options.silent) await prompts.log.warn('Warning: Could not parse .kilocodemodes for cleanup');
}
}

View File

@ -1,326 +0,0 @@
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, kiro-cli.js) - for platforms with unique installation logic
* 1. Custom installer files (codex.js, kilo.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, kiro-cli.js)
* 1. Load custom installer files first (codex.js, kilo.js)
* 2. Load config-driven handlers from platform-codes.yaml
*/
async loadHandlers() {
// Load custom installer files
this.loadCustomInstallerFiles();
await 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
*/
loadCustomInstallerFiles() {
async loadCustomInstallerFiles() {
const ideDir = __dirname;
const customFiles = ['codex.js', 'kilo.js', 'kiro-cli.js'];
const customFiles = ['codex.js', 'kilo.js'];
for (const file of customFiles) {
const filePath = path.join(ideDir, file);
@ -81,7 +81,7 @@ class IdeManager {
}
}
} catch (error) {
console.log(chalk.yellow(` Warning: Could not load ${file}: ${error.message}`));
await prompts.log.warn(`Warning: Could not load ${file}: ${error.message}`);
}
}
}
@ -171,17 +171,45 @@ class IdeManager {
const handler = this.handlers.get(ideName.toLowerCase());
if (!handler) {
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' };
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' };
}
try {
await handler.setup(projectDir, bmadDir, options);
return { success: true, ide: ideName };
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 };
} catch (error) {
console.error(chalk.red(`Failed to setup ${ideName}:`), error.message);
return { success: false, error: error.message };
await prompts.log.error(`Failed to setup ${ideName}: ${error.message}`);
return { success: false, ide: ideName, error: error.message };
}
}
@ -254,7 +282,7 @@ class IdeManager {
const handler = this.handlers.get(ideName.toLowerCase());
if (!handler) {
console.warn(chalk.yellow(`⚠️ IDE '${ideName}' is not yet supported for custom agent installation`));
await prompts.log.warn(`IDE '${ideName}' is not yet supported for custom agent installation`);
continue;
}
@ -266,7 +294,7 @@ class IdeManager {
}
}
} catch (error) {
console.warn(chalk.yellow(`⚠️ Failed to install ${ideName} launcher: ${error.message}`));
await prompts.log.warn(`Failed to install ${ideName} launcher: ${error.message}`);
}
}

View File

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

View File

@ -1,6 +1,5 @@
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,5 +1,6 @@
const path = require('node:path');
const fs = require('fs-extra');
const yaml = require('yaml');
/**
* Helpers for gathering BMAD agents/tasks from the installed tree.
@ -149,8 +150,33 @@ async function getTasksFromDir(dirPath, moduleName) {
const filePath = path.join(dirPath, file);
const content = await fs.readFile(filePath, 'utf8');
// Skip internal/engine files (not user-facing tasks)
if (content.includes('internal="true"')) {
let isInternal = false;
let isStandalone = true;
if (file.endsWith('.md')) {
// Parse markdown frontmatter for standalone/internal flags.
const frontmatterMatch = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
if (frontmatterMatch) {
try {
const frontmatter = yaml.parse(frontmatterMatch[1]) || {};
isInternal = frontmatter.internal === true || frontmatter.internal === 'true';
if (frontmatter.standalone === false || frontmatter.standalone === 'false') {
isStandalone = false;
}
} catch {
// Keep defaults when frontmatter parsing fails.
}
}
} else {
// XML tasks rely on attributes for standalone/internal visibility.
isInternal = /internal\s*=\s*["']true["']/i.test(content);
if (/standalone\s*=\s*["']false["']/i.test(content)) {
isStandalone = false;
}
}
// Skip internal/engine or explicitly non-standalone tasks.
if (isInternal || !isStandalone) {
continue;
}

View File

@ -1,7 +1,6 @@
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 chalk = require('chalk');
const prompts = require('../../../../lib/prompts');
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) {
console.log(chalk.yellow('Workflow manifest not found. Skipping command generation.'));
await prompts.log.warn('Workflow manifest not found. Skipping command generation.');
return { generated: 0 };
}
@ -68,7 +68,8 @@ class WorkflowCommandGenerator {
for (const workflow of allWorkflows) {
const commandContent = await this.generateCommandContent(workflow, bmadDir);
// Calculate the relative workflow path (e.g., bmm/workflows/4-implementation/sprint-planning/workflow.md)
let workflowRelPath = workflow.path;
let workflowRelPath = workflow.path || '';
workflowRelPath = workflowRelPath.replaceAll('\\', '/');
// Remove _bmad/ prefix if present to get relative path from project root
// Handle both absolute paths (/path/to/_bmad/...) and relative paths (_bmad/...)
if (workflowRelPath.includes('_bmad/')) {
@ -76,6 +77,11 @@ class WorkflowCommandGenerator {
if (parts.length > 1) {
workflowRelPath = parts.slice(1).join('/');
}
} else if (workflowRelPath.includes('/src/')) {
const match = workflowRelPath.match(/\/src\/([^/]+)\/(.+)/);
if (match) {
workflowRelPath = `${match[1]}/${match[2]}`;
}
}
artifacts.push({
type: 'workflow-command',

View File

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

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

View File

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

View File

@ -0,0 +1,19 @@
---
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.md]]
2. READ its entire contents - this is the CORE OS for EXECUTING the specific workflow-config #[[file:{{bmadFolderName}}/{{path}}]]
3. Pass workflow path to workflow.md using YAML parameter key `workflow-config` with value `{{bmadFolderName}}/{{path}}`
Example invocation parameter block:
```yaml
workflow-config: {{bmadFolderName}}/{{path}}
```
4. Follow workflow.md task 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

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

View File

@ -1,12 +1,12 @@
const path = require('node:path');
const fs = require('fs-extra');
const yaml = require('yaml');
const chalk = require('chalk');
const ora = require('ora');
const prompts = require('../../../lib/prompts');
const { XmlHandler } = require('../../../lib/xml-handler');
const { getProjectRoot, getSourcePath, getModulePath } = require('../../../lib/project-root');
const { filterCustomizationData } = require('../../../lib/agent/compiler');
const { ExternalModuleManager } = require('./external-manager');
const { BMAD_FOLDER_NAME } = require('../ide/shared/path-utils');
/**
* Manages the installation, updating, and removal of BMAD modules.
@ -16,7 +16,7 @@ const { ExternalModuleManager } = require('./external-manager');
* @class ModuleManager
* @requires fs-extra
* @requires yaml
* @requires chalk
* @requires prompts
* @requires XmlHandler
*
* @example
@ -27,7 +27,7 @@ const { ExternalModuleManager } = require('./external-manager');
class ModuleManager {
constructor(options = {}) {
this.xmlHandler = new XmlHandler();
this.bmadFolderName = 'bmad'; // Default, can be overridden
this.bmadFolderName = BMAD_FOLDER_NAME; // Default, can be overridden
this.customModulePaths = new Map(); // Initialize custom module paths
this.externalModuleManager = new ExternalModuleManager(); // For external official modules
}
@ -151,26 +151,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') {
console.log(chalk.dim(` Updated sidecar file: ${relativeToBmad}`));
await prompts.log.message(` Updated sidecar file: ${relativeToBmad}`);
}
} else {
// User has modified the file, preserve it
if (process.env.BMAD_VERBOSE_INSTALL === 'true') {
console.log(chalk.dim(` Preserving user-modified file: ${relativeToBmad}`));
await prompts.log.message(` 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') {
console.log(chalk.dim(` Added new sidecar file: ${relativeToBmad}`));
await prompts.log.message(` Added new sidecar file: ${relativeToBmad}`);
}
}
} else {
// New installation
await this.copyFileWithPlaceholderReplacement(sourceFilePath, targetFilePath, true);
if (process.env.BMAD_VERBOSE_INSTALL === 'true') {
console.log(chalk.dim(` Copied sidecar file: ${relativeToBmad}`));
await prompts.log.message(` Copied sidecar file: ${relativeToBmad}`);
}
}
@ -287,7 +287,7 @@ class ModuleManager {
moduleInfo.dependencies = config.dependencies || [];
moduleInfo.defaultSelected = config.default_selected === undefined ? false : config.default_selected;
} catch (error) {
console.warn(`Failed to read config for ${defaultName}:`, error.message);
await prompts.log.warn(`Failed to read config for ${defaultName}: ${error.message}`);
}
return moduleInfo;
@ -298,7 +298,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) {
async findModuleSource(moduleCode, options = {}) {
const projectRoot = getProjectRoot();
// First check custom module paths if they exist
@ -315,7 +315,7 @@ class ModuleManager {
}
// Check external official modules
const externalSource = await this.findExternalModuleSource(moduleCode);
const externalSource = await this.findExternalModuleSource(moduleCode, options);
if (externalSource) {
return externalSource;
}
@ -347,7 +347,7 @@ class ModuleManager {
* @param {string} moduleCode - Code of the external module
* @returns {string} Path to the cloned repository
*/
async cloneExternalModule(moduleCode) {
async cloneExternalModule(moduleCode, options = {}) {
const { execSync } = require('node:child_process');
const moduleInfo = await this.externalModuleManager.getModuleByCode(moduleCode);
@ -357,10 +357,32 @@ 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;
@ -368,21 +390,30 @@ class ModuleManager {
// Check if already cloned
if (await fs.pathExists(moduleCacheDir)) {
// Try to update if it's a git repo
const fetchSpinner = ora(`Fetching ${moduleInfo.name}...`).start();
const fetchSpinner = await createSpinner();
fetchSpinner.start(`Fetching ${moduleInfo.name}...`);
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: 'pipe' });
execSync('git reset --hard origin/HEAD', { cwd: moduleCacheDir, stdio: 'pipe' });
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' },
});
const newRef = execSync('git rev-parse HEAD', { cwd: moduleCacheDir, stdio: 'pipe' }).toString().trim();
fetchSpinner.succeed(`Fetched ${moduleInfo.name}`);
fetchSpinner.stop(`Fetched ${moduleInfo.name}`);
// Force dependency install if we got new code
if (currentRef !== newRef) {
needsDependencyInstall = true;
}
} catch {
fetchSpinner.warn(`Fetch failed, re-downloading ${moduleInfo.name}`);
fetchSpinner.error(`Fetch failed, re-downloading ${moduleInfo.name}`);
// If update fails, remove and re-clone
await fs.remove(moduleCacheDir);
wasNewClone = true;
@ -393,14 +424,16 @@ class ModuleManager {
// Clone if not exists or was removed
if (wasNewClone) {
const fetchSpinner = ora(`Fetching ${moduleInfo.name}...`).start();
const fetchSpinner = await createSpinner();
fetchSpinner.start(`Fetching ${moduleInfo.name}...`);
try {
execSync(`git clone --depth 1 "${moduleInfo.url}" "${moduleCacheDir}"`, {
stdio: 'pipe',
stdio: ['ignore', 'pipe', 'pipe'],
env: { ...process.env, GIT_TERMINAL_PROMPT: '0' },
});
fetchSpinner.succeed(`Fetched ${moduleInfo.name}`);
fetchSpinner.stop(`Fetched ${moduleInfo.name}`);
} catch (error) {
fetchSpinner.fail(`Failed to fetch ${moduleInfo.name}`);
fetchSpinner.error(`Failed to fetch ${moduleInfo.name}`);
throw new Error(`Failed to clone external module '${moduleCode}': ${error.message}`);
}
}
@ -414,17 +447,18 @@ class ModuleManager {
// Force install if we updated or cloned new
if (needsDependencyInstall || wasNewClone || nodeModulesMissing) {
const installSpinner = ora(`Installing dependencies for ${moduleInfo.name}...`).start();
const installSpinner = await createSpinner();
installSpinner.start(`Installing dependencies for ${moduleInfo.name}...`);
try {
execSync('npm install --production --no-audit --no-fund --prefer-offline --no-progress', {
execSync('npm install --omit=dev --no-audit --no-fund --no-progress --legacy-peer-deps', {
cwd: moduleCacheDir,
stdio: 'pipe',
stdio: ['ignore', 'pipe', 'pipe'],
timeout: 120_000, // 2 minute timeout
});
installSpinner.succeed(`Installed dependencies for ${moduleInfo.name}`);
installSpinner.stop(`Installed dependencies for ${moduleInfo.name}`);
} catch (error) {
installSpinner.warn(`Failed to install dependencies for ${moduleInfo.name}`);
console.warn(chalk.yellow(` Warning: ${error.message}`));
installSpinner.error(`Failed to install dependencies for ${moduleInfo.name}`);
if (!silent) await prompts.log.warn(` Warning: ${error.message}`);
}
} else {
// Check if package.json is newer than node_modules
@ -439,17 +473,18 @@ class ModuleManager {
}
if (packageJsonNewer) {
const installSpinner = ora(`Installing dependencies for ${moduleInfo.name}...`).start();
const installSpinner = await createSpinner();
installSpinner.start(`Installing dependencies for ${moduleInfo.name}...`);
try {
execSync('npm install --production --no-audit --no-fund --prefer-offline --no-progress', {
execSync('npm install --omit=dev --no-audit --no-fund --no-progress --legacy-peer-deps', {
cwd: moduleCacheDir,
stdio: 'pipe',
stdio: ['ignore', 'pipe', 'pipe'],
timeout: 120_000, // 2 minute timeout
});
installSpinner.succeed(`Installed dependencies for ${moduleInfo.name}`);
installSpinner.stop(`Installed dependencies for ${moduleInfo.name}`);
} catch (error) {
installSpinner.warn(`Failed to install dependencies for ${moduleInfo.name}`);
console.warn(chalk.yellow(` Warning: ${error.message}`));
installSpinner.error(`Failed to install dependencies for ${moduleInfo.name}`);
if (!silent) await prompts.log.warn(` Warning: ${error.message}`);
}
}
}
@ -463,7 +498,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) {
async findExternalModuleSource(moduleCode, options = {}) {
const moduleInfo = await this.externalModuleManager.getModuleByCode(moduleCode);
if (!moduleInfo) {
@ -471,7 +506,7 @@ class ModuleManager {
}
// Clone the external module repo
const cloneDir = await this.cloneExternalModule(moduleCode);
const cloneDir = await this.cloneExternalModule(moduleCode, options);
// The module-definition specifies the path to module.yaml relative to repo root
// We need to return the directory containing module.yaml
@ -492,7 +527,7 @@ class ModuleManager {
* @param {Object} options.logger - Logger instance for output
*/
async install(moduleName, bmadDir, fileTrackingCallback = null, options = {}) {
const sourcePath = await this.findModuleSource(moduleName);
const sourcePath = await this.findModuleSource(moduleName, { silent: options.silent });
const targetPath = path.join(bmadDir, moduleName);
// Check if source module exists
@ -513,14 +548,14 @@ class ModuleManager {
const customContent = await fs.readFile(rootCustomConfigPath, 'utf8');
customConfig = yaml.parse(customContent);
} catch (error) {
console.warn(chalk.yellow(`Warning: Failed to read custom.yaml for ${moduleName}:`, error.message));
await prompts.log.warn(`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));
await prompts.log.warn(`Warning: Failed to read custom.yaml for ${moduleName}: ${error.message}`);
}
}
@ -528,7 +563,7 @@ class ModuleManager {
if (customConfig) {
options.moduleConfig = { ...options.moduleConfig, ...customConfig };
if (options.logger) {
options.logger.log(chalk.cyan(` Merged custom configuration for ${moduleName}`));
options.logger.log(` Merged custom configuration for ${moduleName}`);
}
}
@ -581,7 +616,7 @@ class ModuleManager {
* @param {string} bmadDir - Target bmad directory
* @param {boolean} force - Force update (overwrite modifications)
*/
async update(moduleName, bmadDir, force = false) {
async update(moduleName, bmadDir, force = false, options = {}) {
const sourcePath = await this.findModuleSource(moduleName);
const targetPath = path.join(bmadDir, moduleName);
@ -598,7 +633,7 @@ class ModuleManager {
if (force) {
// Force update - remove and reinstall
await fs.remove(targetPath);
return await this.install(moduleName, bmadDir);
return await this.install(moduleName, bmadDir, null, { installer: options.installer });
} else {
// Selective update - preserve user modifications
await this.syncModule(sourcePath, targetPath);
@ -672,7 +707,7 @@ class ModuleManager {
const config = yaml.parse(configContent);
Object.assign(moduleInfo, config);
} catch (error) {
console.warn(`Failed to read installed module config:`, error.message);
await prompts.log.warn(`Failed to read installed module config: ${error.message}`);
}
}
@ -734,7 +769,7 @@ class ModuleManager {
// Check for localskip="true" in the agent tag
const agentMatch = content.match(/<agent[^>]*\slocalskip="true"[^>]*>/);
if (agentMatch) {
console.log(chalk.dim(` Skipping web-only agent: ${path.basename(file)}`));
await prompts.log.message(` Skipping web-only agent: ${path.basename(file)}`);
continue; // Skip this agent
}
}
@ -766,7 +801,6 @@ class ModuleManager {
mdContent = mdContent.replaceAll('_bmad', this.bmadFolderName);
mdContent = this.stripWebBundleFromFrontmatter(mdContent);
await fs.writeFile(targetFile, mdContent, 'utf8');
}
@ -776,6 +810,8 @@ class ModuleManager {
return content;
}
const newline = frontmatterMatch[0].includes('\r\n') ? '\r\n' : '\n';
try {
const yaml = require('yaml');
const parsed = yaml.parse(frontmatterMatch[1]);
@ -793,7 +829,8 @@ class ModuleManager {
})
.trimEnd();
return content.replace(frontmatterMatch[0], `---\n${serialized}\n---`);
const normalized = newline === '\r\n' ? serialized.replaceAll('\n', '\r\n') : serialized;
return content.replace(frontmatterMatch[0], `---${newline}${normalized}${newline}---`);
} catch (error) {
console.warn(`Warning: Failed to parse workflow frontmatter for web_bundle removal: ${error.message}`);
return content;
@ -847,7 +884,7 @@ class ModuleManager {
await this.copyFileWithPlaceholderReplacement(genericTemplatePath, customizePath);
// Only show customize creation in verbose mode
if (process.env.BMAD_VERBOSE_INSTALL === 'true') {
console.log(chalk.dim(` Created customize: ${moduleName}-${agentName}.customize.yaml`));
await prompts.log.message(` Created customize: ${moduleName}-${agentName}.customize.yaml`);
}
// Store original hash for modification detection
@ -947,10 +984,10 @@ class ModuleManager {
const copiedFiles = await this.copySidecarToMemory(sourceSidecarPath, agentName, bmadMemoryPath, isUpdate, bmadDir, installer);
if (process.env.BMAD_VERBOSE_INSTALL === 'true' && copiedFiles.length > 0) {
console.log(chalk.dim(` Sidecar files processed: ${copiedFiles.length} files`));
await prompts.log.message(` Sidecar files processed: ${copiedFiles.length} files`);
}
} else if (process.env.BMAD_VERBOSE_INSTALL === 'true') {
console.log(chalk.yellow(` Warning: Agent marked as having sidecar but ${sidecarDirName} directory not found`));
await prompts.log.warn(` Warning: Agent marked as having sidecar but ${sidecarDirName} directory not found`);
}
}
@ -969,14 +1006,12 @@ class ModuleManager {
// Only show compilation details in verbose mode
if (process.env.BMAD_VERBOSE_INSTALL === 'true') {
console.log(
chalk.dim(
await prompts.log.message(
` Compiled agent: ${agentName} -> ${path.relative(targetPath, targetMdPath)}${hasSidecar ? ' (with sidecar)' : ''}`,
),
);
}
} catch (error) {
console.warn(chalk.yellow(` Failed to compile agent ${agentName}:`, error.message));
await prompts.log.warn(` Failed to compile agent ${agentName}: ${error.message}`);
}
}
}
@ -1096,11 +1131,11 @@ class ModuleManager {
}
if (!workflowsVendored) {
console.log(chalk.cyan(`\n Vendoring cross-module workflows for ${moduleName}...`));
await prompts.log.info(`\n Vendoring cross-module workflows for ${moduleName}...`);
workflowsVendored = true;
}
console.log(chalk.dim(` Processing: ${agentFile}`));
await prompts.log.message(` Processing: ${agentFile}`);
for (const item of workflowInstallItems) {
const sourceWorkflowPath = item.workflow; // Where to copy FROM
@ -1112,7 +1147,7 @@ class ModuleManager {
// Or: {project-root}/bmad/bmm/workflows/4-implementation/create-story/workflow.md
const sourceMatch = sourceWorkflowPath.match(/\{project-root\}\/(?:_?bmad)\/([^/]+)\/workflows\/(.+)/);
if (!sourceMatch) {
console.warn(chalk.yellow(` Could not parse workflow path: ${sourceWorkflowPath}`));
await prompts.log.warn(` Could not parse workflow path: ${sourceWorkflowPath}`);
continue;
}
@ -1124,7 +1159,7 @@ class ModuleManager {
// Or: {project-root}/bmad/bmgd/workflows/4-production/create-story/workflow.md
const installMatch = installWorkflowPath.match(/\{project-root\}\/(?:_?bmad)\/([^/]+)\/workflows\/(.+)/);
if (!installMatch) {
console.warn(chalk.yellow(` Could not parse workflow-install path: ${installWorkflowPath}`));
await prompts.log.warn(` Could not parse workflow-install path: ${installWorkflowPath}`);
continue;
}
@ -1138,15 +1173,13 @@ class ModuleManager {
// Check if source workflow exists
if (!(await fs.pathExists(actualSourceWorkflowPath))) {
console.warn(chalk.yellow(` Source workflow not found: ${actualSourceWorkflowPath}`));
await prompts.log.warn(` Source workflow not found: ${actualSourceWorkflowPath}`);
continue;
}
// Copy the entire workflow folder
console.log(
chalk.dim(
await prompts.log.message(
` Vendoring: ${sourceModule}/workflows/${sourceWorkflowSubPath.replace(workflowSuffixPattern, '')}${installModule}/workflows/${installWorkflowSubPath.replace(workflowSuffixPattern, '')}`,
),
);
await fs.ensureDir(path.dirname(actualDestWorkflowPath));
@ -1163,7 +1196,7 @@ class ModuleManager {
}
if (workflowsVendored) {
console.log(chalk.green(` ✓ Workflow vendoring complete\n`));
await prompts.log.success(` Workflow vendoring complete\n`);
}
}
@ -1185,7 +1218,7 @@ class ModuleManager {
if (updatedContent !== fileContent) {
await fs.writeFile(workflowPath, updatedContent, 'utf8');
console.log(chalk.dim(` Updated workflow config to: ${this.bmadFolderName}/${newModuleName}/config.yaml`));
await prompts.log.message(` Updated workflow config to: ${this.bmadFolderName}/${newModuleName}/config.yaml`);
}
}
@ -1201,7 +1234,7 @@ class ModuleManager {
if (moduleName === 'core') {
sourcePath = getSourcePath('core');
} else {
sourcePath = await this.findModuleSource(moduleName);
sourcePath = await this.findModuleSource(moduleName, { silent: options.silent });
if (!sourcePath) {
// No source found, skip module installer
return;
@ -1240,11 +1273,11 @@ class ModuleManager {
});
if (!result) {
console.warn(chalk.yellow(`Module installer for ${moduleName} returned false`));
await prompts.log.warn(`Module installer for ${moduleName} returned false`);
}
}
} catch (error) {
console.error(chalk.red(`Error running module installer for ${moduleName}: ${error.message}`));
await prompts.log.error(`Error running module installer for ${moduleName}: ${error.message}`);
}
}
@ -1266,7 +1299,7 @@ class ModuleManager {
await fs.writeFile(configPath, configContent, 'utf8');
} catch (error) {
console.warn(`Failed to process module config:`, error.message);
await prompts.log.warn(`Failed to process module config: ${error.message}`);
}
}
}

View File

@ -6,7 +6,7 @@
const fs = require('node:fs');
const path = require('node:path');
const yaml = require('yaml');
const readline = require('node:readline');
const prompts = require('../prompts');
const { compileAgent, compileAgentFile } = require('./compiler');
const { extractInstallConfig, getDefaultValues } = require('./template-engine');
@ -149,83 +149,47 @@ 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 };
console.log('\n📝 Agent Configuration\n');
if (installConfig.description) {
console.log(` ${installConfig.description}\n`);
}
await prompts.note(installConfig.description || '', 'Agent Configuration');
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]) {
console.log(chalk.dim(` ${q.var}: ${answers[q.var]} (already set)`));
await prompts.log.message(` ${q.var}: ${answers[q.var]} (already set)`);
continue;
}
let response;
switch (q.type) {
case 'text': {
const defaultHint = q.default ? ` (default: ${q.default})` : '';
response = await question(` ${q.prompt}${defaultHint}: `);
answers[q.var] = response || q.default || '';
const response = await prompts.text({
message: q.prompt,
default: q.default ?? '',
});
answers[q.var] = response ?? q.default ?? '';
break;
}
case 'boolean': {
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');
}
const response = await prompts.confirm({
message: q.prompt,
default: q.default,
});
answers[q.var] = response;
break;
}
case 'choice': {
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;
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;
break;
}
// No default
}
}
rl.close();
return answers;
}

View File

@ -1,9 +1,6 @@
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 = {
/**
@ -19,27 +16,32 @@ const CLIUtils = {
},
/**
* Display BMAD logo
* @param {boolean} clearScreen - Whether to clear the screen first (default: true for initial display only)
* Display BMAD logo using @clack intro + box
* @param {boolean} _clearScreen - Deprecated, ignored (no longer clears screen)
*/
displayLogo(clearScreen = true) {
if (clearScreen) {
console.clear();
}
async displayLogo(_clearScreen = true) {
const version = this.getVersion();
const color = await prompts.getColor();
// ASCII art logo
const logo = `
`;
const logo = [
' ██████╗ ███╗ ███╗ █████╗ ██████╗ ™',
' ██╔══██╗████╗ ████║██╔══██╗██╔══██╗',
' ██████╔╝██╔████╔██║███████║██║ ██║',
' ██╔══██╗██║╚██╔╝██║██╔══██║██║ ██║',
' ██████╔╝██║ ╚═╝ ██║██║ ██║██████╔╝',
' ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═════╝',
]
.map((line) => color.yellow(line))
.join('\n');
console.log(chalk.cyan(logo));
console.log(chalk.dim(` Build More, Architect Dreams`) + chalk.cyan.bold(` v${version}`) + '\n');
const tagline = ' Build More, Architect Dreams';
await prompts.box(`${logo}\n${tagline}`, `v${version}`, {
contentAlign: 'center',
rounded: true,
formatBorder: color.blue,
});
},
/**
@ -47,13 +49,8 @@ const CLIUtils = {
* @param {string} title - Section title
* @param {string} subtitle - Optional subtitle
*/
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');
async displaySection(title, subtitle = null) {
await prompts.note(subtitle || '', title);
},
/**
@ -61,25 +58,21 @@ const CLIUtils = {
* @param {string|Array} content - Content to display
* @param {Object} options - Box options
*/
displayBox(content, options = {}) {
const defaultOptions = {
padding: 1,
margin: 1,
borderStyle: 'round',
borderColor: 'cyan',
...options,
};
// Handle array content
async displayBox(content, options = {}) {
let text = content;
if (Array.isArray(content)) {
text = content.join('\n\n');
}
// Wrap text to prevent overflow
const wrapped = wrapAnsi(text, 76, { hard: true, wordWrap: true });
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;
console.log(boxen(wrapped, defaultOptions));
await prompts.box(text, options.title, {
rounded: options.borderStyle === 'round' || options.borderStyle === undefined,
formatBorder,
});
},
/**
@ -88,14 +81,9 @@ const CLIUtils = {
* @param {string} header - Custom header from module.yaml
* @param {string} subheader - Custom subheader from module.yaml
*/
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');
async displayModuleConfigHeader(moduleName, header = null, subheader = null) {
const title = header || `Configuring ${moduleName.toUpperCase()} Module`;
await prompts.note(subheader || '', title);
},
/**
@ -104,14 +92,9 @@ const CLIUtils = {
* @param {string} header - Custom header from module.yaml
* @param {string} subheader - Custom subheader from module.yaml
*/
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');
async displayModuleNoConfig(moduleName, header = null, subheader = null) {
const title = header || `${moduleName.toUpperCase()} Module - No Custom Configuration`;
await prompts.note(subheader || '', title);
},
/**
@ -120,42 +103,33 @@ const CLIUtils = {
* @param {number} total - Total steps
* @param {string} description - Step description
*/
displayStep(current, total, description) {
async displayStep(current, total, description) {
const progress = `[${current}/${total}]`;
console.log('\n' + chalk.cyan(progress) + ' ' + chalk.bold(description));
console.log(chalk.dim('─'.repeat(80 - progress.length - 1)) + '\n');
await prompts.log.step(`${progress} ${description}`);
},
/**
* Display completion message
* @param {string} message - Completion message
*/
displayComplete(message) {
console.log(
'\n' +
boxen(chalk.green('✨ ' + message), {
padding: 1,
margin: 1,
borderStyle: 'round',
borderColor: 'green',
}),
);
async displayComplete(message) {
const color = await prompts.getColor();
await prompts.box(`\u2728 ${message}`, 'Complete', {
rounded: true,
formatBorder: color.green,
});
},
/**
* Display error message
* @param {string} message - Error message
*/
displayError(message) {
console.log(
'\n' +
boxen(chalk.red('✗ ' + message), {
padding: 1,
margin: 1,
borderStyle: 'round',
borderColor: 'red',
}),
);
async displayError(message) {
const color = await prompts.getColor();
await prompts.box(`\u2717 ${message}`, 'Error', {
rounded: true,
formatBorder: color.red,
});
},
/**
@ -163,7 +137,7 @@ const CLIUtils = {
* @param {Array} items - Items to display
* @param {string} prefix - Item prefix
*/
formatList(items, prefix = '') {
formatList(items, prefix = '\u2022') {
return items.map((item) => ` ${prefix} ${item}`).join('\n');
},
@ -178,25 +152,6 @@ 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,11 +89,51 @@ async function note(message, title) {
/**
* Display a spinner for async operations
* @returns {Object} Spinner controller with start, stop, message methods
* Wraps @clack/prompts spinner with isSpinning state tracking
* @returns {Object} Spinner controller with start, stop, message, error, cancel, clear, isSpinning
*/
async function spinner() {
const clack = await getClack();
return clack.spinner();
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;
},
};
}
/**
@ -190,31 +230,6 @@ 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
@ -237,6 +252,7 @@ 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) {
@ -245,6 +261,7 @@ 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,
@ -255,7 +272,7 @@ async function autocompleteMultiselect(options) {
return 'Please select at least one item';
}
},
initialValue: options.initialValues,
initialValue: [...new Set([...(options.initialValues || []), ...(options.lockedValues || [])])],
render() {
const barColor = this.state === 'error' ? color.yellow : color.cyan;
const bar = barColor(clack.S_BAR);
@ -280,9 +297,17 @@ 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 && opt.value === this.focusedValue ? color.dim(` (${opt.hint})`) : '';
const checkbox = isSelected ? color.green(clack.S_CHECKBOX_SELECTED) : color.dim(clack.S_CHECKBOX_INACTIVE);
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);
return isHighlighted ? `${checkbox} ${label}${hintText}` : `${checkbox} ${color.dim(label)}`;
};
@ -322,6 +347,18 @@ 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
@ -335,8 +372,9 @@ 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 && prompt.focusedValue !== undefined) {
prompt.toggleSelected(prompt.focusedValue);
if (key && key.name === 'space' && !prompt.isNavigating) {
const focused = prompt.filteredOptions[prompt.cursor];
if (focused) prompt.toggleSelected(focused.value);
}
});
// === END FIX ===
@ -520,6 +558,131 @@ 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
@ -619,20 +782,28 @@ 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,
};

View File

@ -1,4 +1,3 @@
const chalk = require('chalk');
const path = require('node:path');
const os = require('node:os');
const fs = require('fs-extra');
@ -30,12 +29,12 @@ class UI {
* @returns {Object} Installation configuration
*/
async promptInstall(options = {}) {
CLIUtils.displayLogo();
await CLIUtils.displayLogo();
// Display version-specific start message from install-messages.yaml
const { MessageLoader } = require('../installers/lib/message-loader');
const messageLoader = new MessageLoader();
messageLoader.displayStartMessage();
await messageLoader.displayStartMessage();
// Get directory from options or prompt
let confirmedDirectory;
@ -47,7 +46,7 @@ class UI {
throw new Error(`Invalid directory: ${validation}`);
}
confirmedDirectory = expandedDir;
console.log(chalk.cyan('Using directory from command-line:'), chalk.bold(confirmedDirectory));
await prompts.log.info(`Using directory from command-line: ${confirmedDirectory}`);
} else {
confirmedDirectory = await this.getConfirmedDirectory();
}
@ -75,7 +74,7 @@ class UI {
for (const entry of entries) {
if (entry.isDirectory() && (entry.name === '.bmad' || entry.name === 'bmad')) {
hasLegacyBmadFolder = true;
legacyBmadPath = path.join(confirmedDirectory, '.bmad');
legacyBmadPath = path.join(confirmedDirectory, entry.name);
bmadDir = legacyBmadPath;
// Check if it has _cfg folder
@ -98,38 +97,30 @@ class UI {
// Handle legacy .bmad or _cfg folder - these are very old (v4 or alpha)
// Show version warning instead of offering conversion
if (hasLegacyBmadFolder || hasLegacyCfg) {
console.log('');
console.log(chalk.yellow.bold('⚠️ LEGACY INSTALLATION DETECTED'));
console.log(chalk.yellow('─'.repeat(80)));
console.log(
chalk.yellow(
'Found a ".bmad"/"bmad" folder, or a legacy "_cfg" folder under the bmad folder - this is from a old BMAD version that is out of date for automatic upgrade, manual intervention required.',
),
await prompts.log.warn('LEGACY INSTALLATION DETECTED');
await prompts.note(
'Found a ".bmad"/"bmad" folder, or a legacy "_cfg" folder under the bmad folder -\n' +
'this is from an old BMAD version that is out of date for automatic upgrade,\n' +
'manual intervention required.\n\n' +
'You have a legacy version installed (v4 or alpha).\n' +
'Legacy installations may have compatibility issues.\n\n' +
'For the best experience, we strongly recommend:\n' +
' 1. Delete your current BMAD installation folder (.bmad or bmad)\n' +
' 2. Run a fresh installation\n\n' +
'If you do not want to start fresh, you can attempt to proceed beyond this\n' +
'point IF you have ensured the bmad folder is named _bmad, and under it there\n' +
'is a _config folder. If you have a folder under your bmad folder named _cfg,\n' +
'you would need to rename it _config, and then restart the installer.\n\n' +
'Benefits of a fresh install:\n' +
' \u2022 Cleaner configuration without legacy artifacts\n' +
' \u2022 All new features properly configured\n' +
' \u2022 Fewer potential conflicts\n\n' +
'If you have already produced output from an earlier alpha version, you can\n' +
'still retain those artifacts. After installation, ensure you configured during\n' +
'install the proper file locations for artifacts depending on the module you\n' +
'are using, or move the files to the proper locations.',
'Legacy Installation Detected',
);
console.log(chalk.yellow('You have a legacy version installed (v4 or alpha).'));
console.log('');
console.log(chalk.dim('Legacy installations may have compatibility issues.'));
console.log('');
console.log(chalk.dim('For the best experience, we strongly recommend:'));
console.log(chalk.dim(' 1. Delete your current BMAD installation folder (.bmad or bmad)'));
console.log(
chalk.dim(
' 2. Run a fresh installation\n\nIf you do not want to start fresh, you can attempt to proceed beyond this point IF you have ensured the bmad folder is named _bmad, and under it there is a _config folder. If you have a folder under your bmad folder named _cfg, you would need to rename it _config, and then restart the installer.',
),
);
console.log('');
console.log(chalk.dim('Benefits of a fresh install:'));
console.log(chalk.dim(' • Cleaner configuration without legacy artifacts'));
console.log(chalk.dim(' • All new features properly configured'));
console.log(chalk.dim(' • Fewer potential conflicts'));
console.log(chalk.dim(''));
console.log(
chalk.dim(
'If you have already produced output from an earlier alpha version, you can still retain those artifacts. After installation, ensure you configured during install the proper file locations for artifacts depending on the module you are using, or move the files to the proper locations.',
),
);
console.log(chalk.yellow('─'.repeat(80)));
console.log('');
const proceed = await prompts.select({
message: 'How would you like to proceed?',
@ -147,37 +138,33 @@ class UI {
});
if (proceed === 'cancel') {
console.log('');
console.log(chalk.cyan('To do a fresh install:'));
console.log(chalk.dim(' 1. Delete the existing bmad folder in your project'));
console.log(chalk.dim(" 2. Run 'bmad install' again"));
console.log('');
await prompts.note('1. Delete the existing bmad folder in your project\n' + "2. Run 'bmad install' again", 'To do a fresh install');
process.exit(0);
return;
}
const ora = require('ora');
const spinner = ora('Updating folder structure...').start();
const s = await prompts.spinner();
s.start('Updating folder structure...');
try {
// Handle .bmad folder
if (hasLegacyBmadFolder) {
const newBmadPath = path.join(confirmedDirectory, '_bmad');
await fs.move(legacyBmadPath, newBmadPath);
bmadDir = newBmadPath;
spinner.succeed('Renamed ".bmad" to "_bmad"');
s.stop(`Renamed "${path.basename(legacyBmadPath)}" to "_bmad"`);
}
// Handle _cfg folder (either from .bmad or standalone)
const cfgPath = path.join(bmadDir, '_cfg');
if (await fs.pathExists(cfgPath)) {
spinner.start('Renaming configuration folder...');
s.start('Renaming configuration folder...');
const newCfgPath = path.join(bmadDir, '_config');
await fs.move(cfgPath, newCfgPath);
spinner.succeed('Renamed "_cfg" to "_config"');
s.stop('Renamed "_cfg" to "_config"');
}
} catch (error) {
spinner.fail('Failed to update folder structure');
console.error(chalk.red(`Error: ${error.message}`));
s.stop('Failed to update folder structure');
await prompts.log.error(`Error: ${error.message}`);
process.exit(1);
}
}
@ -239,7 +226,7 @@ class UI {
throw new Error(`Invalid action: ${options.action}. Valid actions: ${validActions.join(', ')}`);
}
actionType = options.action;
console.log(chalk.cyan('Using action from command-line:'), chalk.bold(actionType));
await prompts.log.info(`Using action from command-line: ${actionType}`);
} else {
actionType = await prompts.select({
message: 'How would you like to proceed?',
@ -274,7 +261,7 @@ class UI {
// Get existing installation info
const { installedModuleIds } = await this.getExistingInstallation(confirmedDirectory);
console.log(chalk.dim(` Found existing modules: ${[...installedModuleIds].join(', ')}`));
await prompts.log.message(`Found existing modules: ${[...installedModuleIds].join(', ')}`);
// Unified module selection - all modules in one grouped multiselect
let selectedModules;
@ -284,13 +271,13 @@ class UI {
.split(',')
.map((m) => m.trim())
.filter(Boolean);
console.log(chalk.cyan('Using modules from command-line:'), chalk.bold(selectedModules.join(', ')));
await prompts.log.info(`Using modules from command-line: ${selectedModules.join(', ')}`);
} else {
selectedModules = await this.selectAllModules(installedModuleIds);
selectedModules = selectedModules.filter((m) => m !== 'core');
}
// After module selection, ask about custom modules
console.log('');
let customModuleResult = { selectedCustomModules: [], customContentConfig: { hasCustomContent: false } };
if (options.customContent) {
@ -299,7 +286,7 @@ class UI {
.split(',')
.map((p) => p.trim())
.filter(Boolean);
console.log(chalk.cyan('Using custom content from command-line:'), chalk.bold(paths.join(', ')));
await prompts.log.info(`Using custom content from command-line: ${paths.join(', ')}`);
// Build custom content config similar to promptCustomContentSource
const customPaths = [];
@ -309,7 +296,7 @@ class UI {
const expandedPath = this.expandUserPath(customPath);
const validation = this.validateCustomContentPathSync(expandedPath);
if (validation) {
console.log(chalk.yellow(`⚠️ Skipping invalid custom content path: ${customPath} - ${validation}`));
await prompts.log.warn(`Skipping invalid custom content path: ${customPath} - ${validation}`);
continue;
}
@ -321,12 +308,12 @@ class UI {
const yaml = require('yaml');
moduleMeta = yaml.parse(moduleYaml);
} catch (error) {
console.log(chalk.yellow(`⚠️ Skipping custom content path: ${customPath} - failed to read module.yaml: ${error.message}`));
await prompts.log.warn(`Skipping custom content path: ${customPath} - failed to read module.yaml: ${error.message}`);
continue;
}
if (!moduleMeta.code) {
console.log(chalk.yellow(`⚠️ Skipping custom content path: ${customPath} - module.yaml missing 'code' field`));
await prompts.log.warn(`Skipping custom content path: ${customPath} - module.yaml missing 'code' field`);
continue;
}
@ -404,11 +391,11 @@ class UI {
.split(',')
.map((m) => m.trim())
.filter(Boolean);
console.log(chalk.cyan('Using modules from command-line:'), chalk.bold(selectedModules.join(', ')));
await prompts.log.info(`Using modules from command-line: ${selectedModules.join(', ')}`);
} else if (options.yes) {
// Use default modules when --yes flag is set
selectedModules = await this.getDefaultModules(installedModuleIds);
console.log(chalk.cyan('Using default modules (--yes flag):'), chalk.bold(selectedModules.join(', ')));
await prompts.log.info(`Using default modules (--yes flag): ${selectedModules.join(', ')}`);
} else {
selectedModules = await this.selectAllModules(installedModuleIds);
}
@ -420,7 +407,7 @@ class UI {
.split(',')
.map((p) => p.trim())
.filter(Boolean);
console.log(chalk.cyan('Using custom content from command-line:'), chalk.bold(paths.join(', ')));
await prompts.log.info(`Using custom content from command-line: ${paths.join(', ')}`);
// Build custom content config similar to promptCustomContentSource
const customPaths = [];
@ -430,7 +417,7 @@ class UI {
const expandedPath = this.expandUserPath(customPath);
const validation = this.validateCustomContentPathSync(expandedPath);
if (validation) {
console.log(chalk.yellow(`⚠️ Skipping invalid custom content path: ${customPath} - ${validation}`));
await prompts.log.warn(`Skipping invalid custom content path: ${customPath} - ${validation}`);
continue;
}
@ -442,12 +429,12 @@ class UI {
const yaml = require('yaml');
moduleMeta = yaml.parse(moduleYaml);
} catch (error) {
console.log(chalk.yellow(`⚠️ Skipping custom content path: ${customPath} - failed to read module.yaml: ${error.message}`));
await prompts.log.warn(`Skipping custom content path: ${customPath} - failed to read module.yaml: ${error.message}`);
continue;
}
if (!moduleMeta.code) {
console.log(chalk.yellow(`⚠️ Skipping custom content path: ${customPath} - module.yaml missing 'code' field`));
await prompts.log.warn(`Skipping custom content path: ${customPath} - module.yaml missing 'code' field`);
continue;
}
@ -531,7 +518,7 @@ class UI {
const allKnownValues = new Set([...preferredIdes, ...otherIdes].map((ide) => ide.value));
const unknownTools = configuredIdes.filter((id) => id && typeof id === 'string' && !allKnownValues.has(id));
if (unknownTools.length > 0) {
console.log(chalk.yellow(`⚠️ Previously configured tools are no longer available: ${unknownTools.join(', ')}`));
await prompts.log.warn(`Previously configured tools are no longer available: ${unknownTools.join(', ')}`);
}
// ─────────────────────────────────────────────────────────────────────────────
@ -569,21 +556,20 @@ class UI {
const selectedIdes = upgradeSelected || [];
if (selectedIdes.length === 0) {
console.log('');
const confirmNoTools = await prompts.confirm({
message: 'No tools selected. Continue without installing any tools?',
default: false,
});
if (!confirmNoTools) {
return this.promptToolSelection(projectDir);
return this.promptToolSelection(projectDir, options);
}
return { ides: [], skipIde: true };
}
// Display selected tools
this.displaySelectedTools(selectedIdes, preferredIdes, allTools);
await this.displaySelectedTools(selectedIdes, preferredIdes, allTools);
return { ides: selectedIdes, skipIde: false };
}
@ -609,25 +595,25 @@ class UI {
if (options.tools) {
// Check for explicit "none" value to skip tool installation
if (options.tools.toLowerCase() === 'none') {
console.log(chalk.cyan('Skipping tool configuration (--tools none)'));
await prompts.log.info('Skipping tool configuration (--tools none)');
return { ides: [], skipIde: true };
} else {
selectedIdes = options.tools
.split(',')
.map((t) => t.trim())
.filter(Boolean);
console.log(chalk.cyan('Using tools from command-line:'), chalk.bold(selectedIdes.join(', ')));
this.displaySelectedTools(selectedIdes, preferredIdes, allTools);
await prompts.log.info(`Using tools from command-line: ${selectedIdes.join(', ')}`);
await this.displaySelectedTools(selectedIdes, preferredIdes, allTools);
return { ides: selectedIdes, skipIde: false };
}
} else if (options.yes) {
// If --yes flag is set, skip tool prompt and use previously configured tools or empty
if (configuredIdes.length > 0) {
console.log(chalk.cyan('Using previously configured tools (--yes flag):'), chalk.bold(configuredIdes.join(', ')));
this.displaySelectedTools(configuredIdes, preferredIdes, allTools);
await prompts.log.info(`Using previously configured tools (--yes flag): ${configuredIdes.join(', ')}`);
await this.displaySelectedTools(configuredIdes, preferredIdes, allTools);
return { ides: configuredIdes, skipIde: false };
} else {
console.log(chalk.cyan('Skipping tool configuration (--yes flag, no previous tools)'));
await prompts.log.info('Skipping tool configuration (--yes flag, no previous tools)');
return { ides: [], skipIde: true };
}
}
@ -647,7 +633,6 @@ class UI {
// STEP 3: Confirm if no tools selected
// ─────────────────────────────────────────────────────────────────────────────
if (selectedIdes.length === 0) {
console.log('');
const confirmNoTools = await prompts.confirm({
message: 'No tools selected. Continue without installing any tools?',
default: false,
@ -655,7 +640,7 @@ class UI {
if (!confirmNoTools) {
// User wants to select tools - recurse
return this.promptToolSelection(projectDir);
return this.promptToolSelection(projectDir, options);
}
return {
@ -665,7 +650,7 @@ class UI {
}
// Display selected tools
this.displaySelectedTools(selectedIdes, preferredIdes, allTools);
await this.displaySelectedTools(selectedIdes, preferredIdes, allTools);
return {
ides: selectedIdes,
@ -708,15 +693,12 @@ class UI {
* Display installation summary
* @param {Object} result - Installation result
*/
showInstallSummary(result) {
// Clean, simple completion message
console.log('\n' + chalk.green.bold('✨ BMAD is ready to use!'));
// Show installation summary in a simple format
console.log(chalk.dim(`Installed to: ${result.path}`));
async showInstallSummary(result) {
let summary = `Installed to: ${result.path}`;
if (result.modules && result.modules.length > 0) {
console.log(chalk.dim(`Modules: ${result.modules.join(', ')}`));
summary += `\nModules: ${result.modules.join(', ')}`;
}
await prompts.note(summary, 'BMAD is ready to use!');
}
/**
@ -769,19 +751,19 @@ class UI {
const coreConfig = {};
if (options.userName) {
coreConfig.user_name = options.userName;
console.log(chalk.cyan('Using user name from command-line:'), chalk.bold(options.userName));
await prompts.log.info(`Using user name from command-line: ${options.userName}`);
}
if (options.communicationLanguage) {
coreConfig.communication_language = options.communicationLanguage;
console.log(chalk.cyan('Using communication language from command-line:'), chalk.bold(options.communicationLanguage));
await prompts.log.info(`Using communication language from command-line: ${options.communicationLanguage}`);
}
if (options.documentOutputLanguage) {
coreConfig.document_output_language = options.documentOutputLanguage;
console.log(chalk.cyan('Using document output language from command-line:'), chalk.bold(options.documentOutputLanguage));
await prompts.log.info(`Using document output language from command-line: ${options.documentOutputLanguage}`);
}
if (options.outputFolder) {
coreConfig.output_folder = options.outputFolder;
console.log(chalk.cyan('Using output folder from command-line:'), chalk.bold(options.outputFolder));
await prompts.log.info(`Using output folder from command-line: ${options.outputFolder}`);
}
// Load existing config to merge with provided options
@ -818,7 +800,7 @@ class UI {
document_output_language: 'English',
output_folder: '_bmad-output',
};
console.log(chalk.cyan('Using default configuration (--yes flag)'));
await prompts.log.info('Using default configuration (--yes flag)');
}
} else {
// Load existing configs first if they exist
@ -839,11 +821,11 @@ class UI {
* @returns {Array} Module choices for prompt
*/
async getModuleChoices(installedModuleIds, customContentConfig = null) {
const color = await prompts.getColor();
const moduleChoices = [];
const isNewInstallation = installedModuleIds.size === 0;
const customContentItems = [];
const hasCustomContentItems = false;
// Add custom content items
if (customContentConfig && customContentConfig.hasCustomContent && customContentConfig.customPath) {
@ -855,7 +837,7 @@ class UI {
const customInfo = await customHandler.getCustomInfo(customFile);
if (customInfo) {
customContentItems.push({
name: `${chalk.cyan('✓')} ${customInfo.name} ${chalk.gray(`(${customInfo.relativePath})`)}`,
name: `${color.cyan('\u2713')} ${customInfo.name} ${color.dim(`(${customInfo.relativePath})`)}`,
value: `__CUSTOM_CONTENT__${customFile}`, // Unique value for each custom content
checked: true, // Default to selected since user chose to provide custom content
path: customInfo.path, // Track path to avoid duplicates
@ -883,7 +865,7 @@ class UI {
if (!isDuplicate) {
allCustomModules.push({
name: `${chalk.cyan('✓')} ${mod.name} ${chalk.gray(`(cached)`)}`,
name: `${color.cyan('\u2713')} ${mod.name} ${color.dim('(cached)')}`,
value: mod.id,
checked: isNewInstallation ? mod.defaultSelected || false : installedModuleIds.has(mod.id),
hint: mod.description || undefined,
@ -934,22 +916,20 @@ class UI {
...choicesWithDefaults,
{
value: '__NONE__',
label: ' None / I changed my mind - skip module installation',
label: '\u26A0 None / I changed my mind - skip module installation',
checked: false,
},
];
const selected = await prompts.multiselect({
message: `Select modules to install ${chalk.dim('(↑/↓ navigates, SPACE toggles, ENTER to confirm)')}:`,
message: 'Select modules to install (use arrow keys, space to toggle):',
choices: choicesWithSkipOption,
required: true,
});
// If user selected both "__NONE__" and other items, honor the "None" choice
if (selected && selected.includes('__NONE__') && selected.length > 1) {
console.log();
console.log(chalk.yellow('⚠️ "None / I changed my mind" was selected, so no modules will be installed.'));
console.log();
await prompts.log.warn('"None / I changed my mind" was selected, so no modules will be installed.');
return [];
}
@ -982,8 +962,7 @@ class UI {
*/
async selectExternalModules(externalModuleChoices, defaultSelections = []) {
// Build a message showing available modules
const availableNames = externalModuleChoices.map((c) => c.name).join(', ');
const message = `Select official BMad modules to install ${chalk.dim('(↑/↓ navigates, SPACE toggles, ENTER to confirm)')}:`;
const message = 'Select official BMad modules to install (use arrow keys, space to toggle):';
// Mark choices as checked based on defaultSelections
const choicesWithDefaults = externalModuleChoices.map((choice) => ({
@ -1009,9 +988,7 @@ class UI {
// If user selected both "__NONE__" and other items, honor the "None" choice
if (selected && selected.includes('__NONE__') && selected.length > 1) {
console.log();
console.log(chalk.yellow('⚠️ "None / I changed my mind" was selected, so no external modules will be installed.'));
console.log();
await prompts.log.warn('"None / I changed my mind" was selected, so no external modules will be installed.');
return [];
}
@ -1033,100 +1010,98 @@ class UI {
const externalManager = new ExternalModuleManager();
const externalModules = await externalManager.listAvailable();
// Build grouped options
const groupedOptions = {};
// Build flat options list with group hints for autocompleteMultiselect
const allOptions = [];
const initialValues = [];
const lockedValues = ['core'];
// Core module is always installed — show it locked at the top
allOptions.push({ label: 'BMad Core Module', value: 'core', hint: 'Core configuration and shared resources' });
initialValues.push('core');
// Helper to build module entry with proper sorting and selection
const buildModuleEntry = (mod, value) => {
const buildModuleEntry = (mod, value, group) => {
const isInstalled = installedModuleIds.has(value);
const isDefault = mod.defaultSelected === true;
return {
label: mod.description ? `${mod.name}${mod.description}` : mod.name,
label: mod.name,
value,
// For sorting: defaultSelected=0, others=1
sortKey: isDefault ? 0 : 1,
// Pre-select if default selected OR already installed
selected: isDefault || isInstalled,
hint: mod.description || group,
// Pre-select only if already installed (not on fresh install)
selected: isInstalled,
};
};
// Group 1: BMad Core (BMM, BMB)
const coreModules = [];
// Local modules (BMM, BMB, etc.)
const localEntries = [];
for (const mod of localModules) {
if (!mod.isCustom && (mod.id === 'bmm' || mod.id === 'bmb')) {
const entry = buildModuleEntry(mod, mod.id);
coreModules.push(entry);
if (!mod.isCustom && mod.id !== 'core') {
const entry = buildModuleEntry(mod, mod.id, 'Local');
localEntries.push(entry);
if (entry.selected) {
initialValues.push(mod.id);
}
}
}
// Sort: defaultSelected first, then others
coreModules.sort((a, b) => a.sortKey - b.sortKey);
// Remove sortKey from final entries
if (coreModules.length > 0) {
groupedOptions['BMad Core'] = coreModules.map(({ label, value }) => ({ label, value }));
}
allOptions.push(...localEntries.map(({ label, value, hint }) => ({ label, value, hint })));
// Group 2: BMad Official Modules (type: bmad-org)
const officialModules = [];
for (const mod of externalModules) {
if (mod.type === 'bmad-org') {
const entry = buildModuleEntry(mod, mod.code);
const entry = buildModuleEntry(mod, mod.code, 'Official');
officialModules.push(entry);
if (entry.selected) {
initialValues.push(mod.code);
}
}
}
officialModules.sort((a, b) => a.sortKey - b.sortKey);
if (officialModules.length > 0) {
groupedOptions['BMad Official Modules'] = officialModules.map(({ label, value }) => ({ label, value }));
}
allOptions.push(...officialModules.map(({ label, value, hint }) => ({ label, value, hint })));
// Group 3: Community Modules (type: community)
const communityModules = [];
for (const mod of externalModules) {
if (mod.type === 'community') {
const entry = buildModuleEntry(mod, mod.code);
const entry = buildModuleEntry(mod, mod.code, 'Community');
communityModules.push(entry);
if (entry.selected) {
initialValues.push(mod.code);
}
}
}
communityModules.sort((a, b) => a.sortKey - b.sortKey);
if (communityModules.length > 0) {
groupedOptions['Community Modules'] = communityModules.map(({ label, value }) => ({ label, value }));
}
// Add "None" option at the end
groupedOptions[' '] = [
{
label: '⚠ None - Skip module installation',
allOptions.push(...communityModules.map(({ label, value, hint }) => ({ label, value, hint })), {
// "None" option at the end
label: '\u26A0 None - Skip module installation',
value: '__NONE__',
},
];
});
const selected = await prompts.groupMultiselect({
message: `Select modules to install ${chalk.dim('(↑/↓ navigates, SPACE toggles, ENTER to confirm)')}:`,
options: groupedOptions,
const selected = await prompts.autocompleteMultiselect({
message: 'Select modules to install:',
options: allOptions,
initialValues: initialValues.length > 0 ? initialValues : undefined,
lockedValues,
required: true,
selectableGroups: false,
maxItems: allOptions.length,
});
// If user selected both "__NONE__" and other items, honor the "None" choice
if (selected && selected.includes('__NONE__') && selected.length > 1) {
console.log();
console.log(chalk.yellow('⚠️ "None" was selected, so no modules will be installed.'));
console.log();
await prompts.log.warn('"None" was selected, so no modules will be installed.');
return [];
}
// Filter out the special '__NONE__' value
return selected ? selected.filter((m) => m !== '__NONE__') : [];
const result = selected ? selected.filter((m) => m !== '__NONE__') : [];
// Display selected modules as bulleted list
if (result.length > 0) {
const moduleLines = result.map((moduleId) => {
const opt = allOptions.find((o) => o.value === moduleId);
return ` \u2022 ${opt?.label || moduleId}`;
});
await prompts.log.message('Selected modules:\n' + moduleLines.join('\n'));
}
return result;
}
/**
@ -1185,7 +1160,7 @@ class UI {
* @param {string} directory - The directory path
*/
async displayDirectoryInfo(directory) {
console.log(chalk.cyan('\nResolved installation path:'), chalk.bold(directory));
await prompts.log.info(`Resolved installation path: ${directory}`);
const dirExists = await fs.pathExists(directory);
if (dirExists) {
@ -1201,12 +1176,10 @@ class UI {
const hasBmadInstall =
(await fs.pathExists(bmadResult.bmadDir)) && (await fs.pathExists(path.join(bmadResult.bmadDir, '_config', 'manifest.yaml')));
console.log(
chalk.gray(`Directory exists and contains ${files.length} item(s)`) +
(hasBmadInstall ? chalk.yellow(` including existing BMAD installation (${path.basename(bmadResult.bmadDir)})`) : ''),
);
const bmadNote = hasBmadInstall ? ` including existing BMAD installation (${path.basename(bmadResult.bmadDir)})` : '';
await prompts.log.message(`Directory exists and contains ${files.length} item(s)${bmadNote}`);
} else {
console.log(chalk.gray('Directory exists and is empty'));
await prompts.log.message('Directory exists and is empty');
}
}
}
@ -1227,7 +1200,7 @@ class UI {
});
if (!proceed) {
console.log(chalk.yellow("\nLet's try again with a different path.\n"));
await prompts.log.warn("Let's try again with a different path.");
}
return proceed;
@ -1239,7 +1212,7 @@ class UI {
});
if (!create) {
console.log(chalk.yellow("\nLet's try again with a different path.\n"));
await prompts.log.warn("Let's try again with a different path.");
}
return create;
@ -1459,7 +1432,7 @@ class UI {
return configs;
} catch {
// If loading fails, return empty configs
console.warn('Warning: Could not load existing configurations');
await prompts.log.warn('Could not load existing configurations');
return configs;
}
}
@ -1590,7 +1563,7 @@ class UI {
name: moduleData.name || moduleData.code,
});
console.log(chalk.green(`Confirmed local custom module: ${moduleData.name || moduleData.code}`));
await prompts.log.success(`Confirmed local custom module: ${moduleData.name || moduleData.code}`);
}
// Ask if user wants to add these to the installation
@ -1656,11 +1629,11 @@ class UI {
};
// Ask user about custom modules
console.log(chalk.cyan('\n⚙ Custom Modules'));
await prompts.log.info('Custom Modules');
if (cachedCustomModules.length > 0) {
console.log(chalk.dim('Found custom modules in your installation:'));
await prompts.log.message('Found custom modules in your installation:');
} else {
console.log(chalk.dim('No custom modules currently installed.'));
await prompts.log.message('No custom modules currently installed.');
}
// Build choices dynamically based on whether we have existing modules
@ -1686,14 +1659,14 @@ class UI {
case 'keep': {
// Keep all existing custom modules
result.selectedCustomModules = cachedCustomModules.map((m) => m.id);
console.log(chalk.dim(`Keeping ${result.selectedCustomModules.length} custom module(s)`));
await prompts.log.message(`Keeping ${result.selectedCustomModules.length} custom module(s)`);
break;
}
case 'select': {
// Let user choose which to keep
const selectChoices = cachedCustomModules.map((m) => ({
name: `${m.name} ${chalk.gray(`(${m.id})`)}`,
name: `${m.name} (${m.id})`,
value: m.id,
checked: m.checked,
}));
@ -1709,16 +1682,14 @@ class UI {
];
const keepModules = await prompts.multiselect({
message: `Select custom modules to keep ${chalk.dim('(↑/↓ navigates, SPACE toggles, ENTER to confirm)')}:`,
message: 'Select custom modules to keep (use arrow keys, space to toggle):',
choices: choicesWithSkip,
required: true,
});
// If user selected both "__NONE__" and other modules, honor the "None" choice
if (keepModules && keepModules.includes('__NONE__') && keepModules.length > 1) {
console.log();
console.log(chalk.yellow('⚠️ "None / I changed my mind" was selected, so no custom modules will be kept.'));
console.log();
await prompts.log.warn('"None / I changed my mind" was selected, so no custom modules will be kept.');
result.selectedCustomModules = [];
} else {
// Filter out the special '__NONE__' value
@ -1743,13 +1714,13 @@ class UI {
case 'remove': {
// Remove all custom modules
console.log(chalk.yellow('All custom modules will be removed from the installation'));
await prompts.log.warn('All custom modules will be removed from the installation');
break;
}
case 'cancel': {
// User cancelled - no custom modules
console.log(chalk.dim('No custom modules will be added'));
await prompts.log.message('No custom modules will be added');
break;
}
}
@ -1782,30 +1753,26 @@ class UI {
return true; // Not legacy, proceed
}
console.log('');
console.log(chalk.yellow.bold('⚠️ VERSION WARNING'));
console.log(chalk.yellow('─'.repeat(80)));
let warningContent;
if (installedVersion === 'unknown') {
console.log(chalk.yellow('Unable to detect your installed BMAD version.'));
console.log(chalk.yellow('This appears to be a legacy or unsupported installation.'));
warningContent = 'Unable to detect your installed BMAD version.\n' + 'This appears to be a legacy or unsupported installation.';
} else {
console.log(chalk.yellow(`You are updating from ${installedVersion} to ${currentVersion}.`));
console.log(chalk.yellow('You have a legacy version installed (v4 or alpha).'));
warningContent =
`You are updating from ${installedVersion} to ${currentVersion}.\n` + 'You have a legacy version installed (v4 or alpha).';
}
console.log('');
console.log(chalk.dim('For the best experience, we recommend:'));
console.log(chalk.dim(' 1. Delete your current BMAD installation folder'));
console.log(chalk.dim(` (the "${bmadFolderName}/" folder in your project)`));
console.log(chalk.dim(' 2. Run a fresh installation'));
console.log('');
console.log(chalk.dim('Benefits of a fresh install:'));
console.log(chalk.dim(' • Cleaner configuration without legacy artifacts'));
console.log(chalk.dim(' • All new features properly configured'));
console.log(chalk.dim(' • Fewer potential conflicts'));
console.log(chalk.yellow('─'.repeat(80)));
console.log('');
warningContent +=
'\n\nFor the best experience, we recommend:\n' +
' 1. Delete your current BMAD installation folder\n' +
` (the "${bmadFolderName}/" folder in your project)\n` +
' 2. Run a fresh installation\n\n' +
'Benefits of a fresh install:\n' +
' \u2022 Cleaner configuration without legacy artifacts\n' +
' \u2022 All new features properly configured\n' +
' \u2022 Fewer potential conflicts';
await prompts.log.warn('VERSION WARNING');
await prompts.note(warningContent, 'Version Warning');
const proceed = await prompts.select({
message: 'How would you like to proceed?',
@ -1823,11 +1790,10 @@ class UI {
});
if (proceed === 'cancel') {
console.log('');
console.log(chalk.cyan('To do a fresh install:'));
console.log(chalk.dim(` 1. Delete the "${bmadFolderName}/" folder in your project`));
console.log(chalk.dim(" 2. Run 'bmad install' again"));
console.log('');
await prompts.note(
`1. Delete the "${bmadFolderName}/" folder in your project\n` + "2. Run 'bmad install' again",
'To do a fresh install',
);
}
return proceed === 'proceed';
@ -1838,41 +1804,34 @@ class UI {
* @param {Array} modules - Array of module info objects with version info
* @param {Array} availableUpdates - Array of available updates
*/
displayModuleVersions(modules, availableUpdates = []) {
console.log('');
console.log(chalk.cyan.bold('📦 Module Versions'));
console.log(chalk.gray('─'.repeat(80)));
async displayModuleVersions(modules, availableUpdates = []) {
// Group modules by source
const builtIn = modules.filter((m) => m.source === 'built-in');
const external = modules.filter((m) => m.source === 'external');
const custom = modules.filter((m) => m.source === 'custom');
const unknown = modules.filter((m) => m.source === 'unknown');
const displayGroup = (group, title) => {
const lines = [];
const formatGroup = (group, title) => {
if (group.length === 0) return;
console.log(chalk.yellow(`\n${title}`));
for (const module of group) {
const updateInfo = availableUpdates.find((u) => u.name === module.name);
const versionDisplay = module.version || chalk.gray('unknown');
lines.push(title);
for (const mod of group) {
const updateInfo = availableUpdates.find((u) => u.name === mod.name);
const versionDisplay = mod.version || 'unknown';
if (updateInfo) {
console.log(
` ${chalk.cyan(module.name.padEnd(20))} ${versionDisplay}${chalk.green(updateInfo.latestVersion)} ${chalk.green('↑')}`,
);
lines.push(` ${mod.name.padEnd(20)} ${versionDisplay} \u2192 ${updateInfo.latestVersion} \u2191`);
} else {
console.log(` ${chalk.cyan(module.name.padEnd(20))} ${versionDisplay} ${chalk.gray('✓')}`);
lines.push(` ${mod.name.padEnd(20)} ${versionDisplay} \u2713`);
}
}
};
displayGroup(builtIn, 'Built-in Modules');
displayGroup(external, 'External Modules (Official)');
displayGroup(custom, 'Custom Modules');
displayGroup(unknown, 'Other Modules');
formatGroup(builtIn, 'Built-in Modules');
formatGroup(external, 'External Modules (Official)');
formatGroup(custom, 'Custom Modules');
formatGroup(unknown, 'Other Modules');
console.log('');
await prompts.note(lines.join('\n'), 'Module Versions');
}
/**
@ -1885,12 +1844,10 @@ class UI {
return [];
}
console.log('');
console.log(chalk.cyan.bold('🔄 Available Updates'));
console.log(chalk.gray('─'.repeat(80)));
await prompts.log.info('Available Updates');
const choices = availableUpdates.map((update) => ({
name: `${update.name} ${chalk.dim(`(v${update.installedVersion} v${update.latestVersion})`)}`,
name: `${update.name} (v${update.installedVersion} \u2192 v${update.latestVersion})`,
value: update.name,
checked: true, // Default to selecting all updates
}));
@ -1916,7 +1873,7 @@ class UI {
// Allow specific selection
const selected = await prompts.multiselect({
message: `Select modules to update ${chalk.dim('(↑/↓ navigates, SPACE toggles, ENTER to confirm)')}:`,
message: 'Select modules to update (use arrow keys, space to toggle):',
choices: choices,
required: true,
});
@ -1928,34 +1885,29 @@ class UI {
* Display status of all installed modules
* @param {Object} statusData - Status data with modules, installation info, and available updates
*/
displayStatus(statusData) {
async displayStatus(statusData) {
const { installation, modules, availableUpdates, bmadDir } = statusData;
console.log('');
console.log(chalk.cyan.bold('📋 BMAD Status'));
console.log(chalk.gray('─'.repeat(80)));
// Installation info
console.log(chalk.yellow('\nInstallation'));
console.log(` ${chalk.gray('Version:'.padEnd(20))} ${installation.version || chalk.gray('unknown')}`);
console.log(` ${chalk.gray('Location:'.padEnd(20))} ${bmadDir}`);
console.log(` ${chalk.gray('Installed:'.padEnd(20))} ${new Date(installation.installDate).toLocaleDateString()}`);
console.log(
` ${chalk.gray('Last Updated:'.padEnd(20))} ${installation.lastUpdated ? new Date(installation.lastUpdated).toLocaleDateString() : chalk.gray('unknown')}`,
);
const infoLines = [
`Version: ${installation.version || 'unknown'}`,
`Location: ${bmadDir}`,
`Installed: ${new Date(installation.installDate).toLocaleDateString()}`,
`Last Updated: ${installation.lastUpdated ? new Date(installation.lastUpdated).toLocaleDateString() : 'unknown'}`,
];
await prompts.note(infoLines.join('\n'), 'BMAD Status');
// Module versions
this.displayModuleVersions(modules, availableUpdates);
await this.displayModuleVersions(modules, availableUpdates);
// Update summary
if (availableUpdates.length > 0) {
console.log(chalk.yellow.bold(`\n⚠️ ${availableUpdates.length} update(s) available`));
console.log(chalk.dim(` Run 'bmad install' and select "Quick Update" to update`));
await prompts.log.warn(`${availableUpdates.length} update(s) available`);
await prompts.log.message('Run \'bmad install\' and select "Quick Update" to update');
} else {
console.log(chalk.green.bold('\n✓ All modules are up to date'));
await prompts.log.success('All modules are up to date');
}
console.log('');
}
/**
@ -1964,19 +1916,17 @@ class UI {
* @param {Array} preferredIdes - Array of preferred IDE objects
* @param {Array} allTools - Array of all tool objects
*/
displaySelectedTools(selectedIdes, preferredIdes, allTools) {
async displaySelectedTools(selectedIdes, preferredIdes, allTools) {
if (selectedIdes.length === 0) return;
const preferredValues = new Set(preferredIdes.map((ide) => ide.value));
console.log('');
console.log(chalk.dim(' Selected tools:'));
for (const ideValue of selectedIdes) {
const toolLines = selectedIdes.map((ideValue) => {
const tool = allTools.find((t) => t.value === ideValue);
const name = tool?.name || ideValue;
const marker = preferredValues.has(ideValue) ? ' ⭐' : '';
console.log(chalk.dim(`${name}${marker}`));
}
const marker = preferredValues.has(ideValue) ? ' \u2B50' : '';
return ` \u2022 ${name}${marker}`;
});
await prompts.log.message('Selected tools:\n' + toolLines.join('\n'));
}
}

View File

@ -20,11 +20,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:
// - [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
// Match all markdown links; filtering (external, anchors, assets) happens in convertToRepoRelative.
// This intentionally matches broadly so the handler can make context-aware decisions.
const ALL_MARKDOWN_LINKS_REGEX = /\[([^\]]*)\]\(([^)]+)\)/g;
/**
@ -64,8 +61,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
if (href.includes('://') || href.startsWith('mailto:') || href.startsWith('tel:')) {
// Skip external links (including protocol-relative URLs like //cdn.example.com)
if (href.includes('://') || href.startsWith('//') || href.startsWith('mailto:') || href.startsWith('tel:')) {
return null;
}

View File

@ -67,11 +67,11 @@ platforms:
category: ide
description: "Atlassian's Rovo development environment"
kiro-cli:
name: "Kiro CLI"
kiro:
name: "Kiro"
preferred: false
category: cli
description: "Kiro command-line interface"
category: ide
description: "Amazon's AI-powered IDE"
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
const LINK_REGEX = /\[([^\]]*)\]\((\/[^)]+)\)/g;
// Regex to match markdown links with site-relative paths or bare .md references
const LINK_REGEX = /\[([^\]]*)\]\(((?:\.{1,2}\/|\/)[^)]+|[\w][^)\s]*\.md(?:[?#][^)]*)?)\)/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,11 +108,27 @@ 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) {
function resolveLink(siteRelativePath, sourceFile) {
// Strip anchor and query
let checkPath = siteRelativePath.split('#')[0].split('?')[0];
// Strip /docs/ prefix if present (repo-relative links)
// 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)
if (checkPath.startsWith('/docs/')) {
checkPath = checkPath.slice(5); // Remove '/docs' but keep leading '/'
}
@ -129,12 +145,18 @@ function resolveLink(siteRelativePath) {
// Direct path (e.g., /path/file.md)
const direct = path.join(DOCS_ROOT, checkPath);
if (fs.existsSync(direct)) return direct;
if (fs.existsSync(direct) && fs.statSync(direct).isFile()) 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;
}
@ -144,7 +166,7 @@ function resolveLink(siteRelativePath) {
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;
@ -219,7 +241,7 @@ function processFile(filePath) {
}
// Validate the link target exists
const targetFile = resolveLink(linkPath);
const targetFile = resolveLink(linkPath, filePath);
if (!targetFile) {
// Link is broken - try to find the file

View File

@ -29,6 +29,7 @@
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');
@ -38,7 +39,7 @@ const STRICT = process.argv.includes('--strict');
// --- Constants ---
// File extensions to scan
const SCAN_EXTENSIONS = new Set(['.yaml', '.yml', '.md', '.xml']);
const SCAN_EXTENSIONS = new Set(['.yaml', '.yml', '.md', '.xml', '.csv']);
// Skip directories
const SKIP_DIRS = new Set(['node_modules', '_module-installer', '.git']);
@ -292,6 +293,46 @@ 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) {
@ -351,8 +392,12 @@ 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`);
@ -374,35 +419,45 @@ for (const filePath of files) {
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 check
// 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.
const broken = [];
if (VERBOSE && refs.length > 0) {
console.log(`\n${relativePath}`);
}
const ok = [];
for (const ref of refs) {
totalRefs++;
const resolved = resolveRef(ref);
if (resolved && !fs.existsSync(resolved)) {
// For paths without extensions, also check if it's a directory
// 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.
const hasExt = path.extname(resolved) !== '';
if (!hasExt) {
// Could be a directory reference — skip if not clearly a file
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++;
}
continue;
}
broken.push({ ref, resolved: path.relative(PROJECT_ROOT, resolved) });
broken.push({ ref, resolved: path.relative(PROJECT_ROOT, resolved), kind: 'broken' });
brokenRefs++;
continue;
}
if (VERBOSE && resolved) {
console.log(` [OK] ${ref.raw}`);
if (resolved) {
ok.push({ ref, tag: 'OK' });
}
}
@ -410,21 +465,33 @@ for (const filePath of files) {
const leaks = checkAbsolutePathLeaks(filePath, content);
totalLeaks += leaks.length;
// Report issues for this file
if (broken.length > 0 || leaks.length > 0) {
// Print results — file header appears once, in one place
const hasFileIssues = broken.length > 0 || leaks.length > 0;
if (hasFileIssues) {
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 } of broken) {
for (const { ref, resolved, kind } of broken) {
const location = ref.line ? `line ${ref.line}` : ref.key ? `key: ${ref.key}` : '';
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' });
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 });
if (process.env.GITHUB_ACTIONS) {
const line = ref.line || 1;
console.log(`::warning file=${relativePath},line=${line}::${escapeAnnotation(`Broken reference: ${ref.raw}${resolved}`)}`);
console.log(
`::warning file=${relativePath},line=${line}::${escapeAnnotation(`${tag === 'UNRESOLVED' ? 'Unresolved path' : 'Broken reference'}: ${ref.raw}${resolved}`)}`,
);
}
}
@ -435,6 +502,12 @@ for (const filePath of files) {
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}`);
}
}
}
@ -478,3 +551,4 @@ if (process.env.GITHUB_STEP_SUMMARY) {
}
process.exit(hasIssues && STRICT ? 1 : 0);
}

View File

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

Before

Width:  |  Height:  |  Size: 284 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.9 MiB

Some files were not shown because too many files have changed in this diff Show More