feat(opencode): add JSON-only integration and compact AGENTS.md generator (#570)

* feat: add OpenCode integration implementation plan for BMAD-METHOD

* installer(opencode): add OpenCode target metadata in install.config.yaml

* chore(deps): add comment-json for JSONC parsing in OpenCode integration

* feat(installer/opencode): implement setupOpenCode with minimal instructions merge and BMAD-managed agents/commands

* feat(installer): add OpenCode (SST) to IDE selector and CLI --ide help

* fix(opencode): align generated opencode.json(c) with schema (instructions as strings; agent.prompt; command.template; remove unsupported fields)

* feat(installer): enhance OpenCode setup with agent selection and prefix options

* fix: update configuration file references from `bmad-core/core-config.yaml` to `.bmad-core/core-config.yaml` across multiple agent and task files for consistency and clarity.

* refactor: streamline OpenCode configuration prompts and normalize instruction paths for agents and tasks

* feat: add tools property to agent definitions for enhanced functionality. Otherwise opencode consders the subagents as readonly

* feat: add extraction of 'whenToUse'  from agents markdown files for improved agent configuration in opencode

* feat: enhance task purpose extraction from markdown files with improved parsing and cleanup logic

* feat: add collision warnings for non-BMAD-managed agent and command keys during setup

* feat: generate and update AGENTS.md for OpenCode integration with agent and task details

* feat: add compact AGENTS.md generator and JSON-only integration for OpenCode

* chore(docs): remove completed OpenCode integration implementation plans

* feat: enable default prefixes for agent and command keys to avoid collisions

* fix: remove unnecessary line breaks in 'whenToUse' descriptions for QA agents to mathc the rest of the agents definitions and improve programatic parsing of whenToUse prop

* fix: update OpenCode references to remove 'SST' for consistency across documentation and configuration

* fix: update agent mode from 'subagent' to 'all' for consistency in agent definitions

* fix: consolidate 'whenToUse' description format for clarity and consistent parsing
This commit is contained in:
Javier Gomez
2025-09-12 00:44:41 +02:00
committed by GitHub
parent 2b247ea385
commit f09e282d72
38 changed files with 915 additions and 96 deletions

View File

@@ -0,0 +1,40 @@
# feat(opencode): compact AGENTS.md generator and JSON-only integration
## What
Add JSON-only OpenCode integration and a compact AGENTS.md generator (no large embeds; clickable file links) with idempotent merges for BMAD instructions, agents, and commands.
## Why
Keep OpenCode config schemacompliant and small, avoid key collisions, and provide a readable agents/tasks index without inflating AGENTS.md.
## How
- Ensure `.bmad-core/core-config.yaml` in `instructions`
- Merge only selected packages agents/commands into opencode.json file
- Orchestrators `mode: primary`; all agents enable `write`, `edit`, `bash`
- Descriptions from `whenToUse`/task `Purpose` with sanitization + fallbacks
- Explicit warnings for nonBMAD collisions; AGENTS.md uses a strict 3column table with links
## Testing
- Run: `npx bmad-method install -f -i opencode`
- Verify: `opencode.json[c]` updated/created as expected, `AGENTS.md` OpenCode section is compact with links
- Prepush checks:
```bash
npm run pre-release
# or individually
npm run validate
npm run format:check
npm run lint
# if anything fails
npm run fix
# or
npm run format
npm run lint:fix
```
Fixes #<issue-number>
Targets: `next` branch

View File

@@ -19,7 +19,7 @@ REQUEST-RESOLUTION: Match user requests to your commands/dependencies flexibly (
activation-instructions:
- STEP 1: Read THIS ENTIRE FILE - it contains your complete persona definition
- STEP 2: Adopt the persona defined in the 'agent' and 'persona' sections below
- STEP 3: Load and read `bmad-core/core-config.yaml` (project configuration) before any greeting
- STEP 3: Load and read `.bmad-core/core-config.yaml` (project configuration) before any greeting
- STEP 4: Greet user with your name/role and immediately run `*help` to display available commands
- DO NOT: Load any other agent files during activation
- ONLY load dependency files when user selects them for execution via command or request of a task

View File

@@ -19,7 +19,7 @@ REQUEST-RESOLUTION: Match user requests to your commands/dependencies flexibly (
activation-instructions:
- STEP 1: Read THIS ENTIRE FILE - it contains your complete persona definition
- STEP 2: Adopt the persona defined in the 'agent' and 'persona' sections below
- STEP 3: Load and read `bmad-core/core-config.yaml` (project configuration) before any greeting
- STEP 3: Load and read `.bmad-core/core-config.yaml` (project configuration) before any greeting
- STEP 4: Greet user with your name/role and immediately run `*help` to display available commands
- DO NOT: Load any other agent files during activation
- ONLY load dependency files when user selects them for execution via command or request of a task

View File

@@ -19,7 +19,7 @@ REQUEST-RESOLUTION: Match user requests to your commands/dependencies flexibly (
activation-instructions:
- STEP 1: Read THIS ENTIRE FILE - it contains your complete persona definition
- STEP 2: Adopt the persona defined in the 'agent' and 'persona' sections below
- STEP 3: Load and read `bmad-core/core-config.yaml` (project configuration) before any greeting
- STEP 3: Load and read `.bmad-core/core-config.yaml` (project configuration) before any greeting
- STEP 4: Greet user with your name/role and immediately run `*help` to display available commands
- DO NOT: Load any other agent files during activation
- ONLY load dependency files when user selects them for execution via command or request of a task

View File

@@ -19,7 +19,7 @@ REQUEST-RESOLUTION: Match user requests to your commands/dependencies flexibly (
activation-instructions:
- STEP 1: Read THIS ENTIRE FILE - it contains your complete persona definition
- STEP 2: Adopt the persona defined in the 'agent' and 'persona' sections below
- STEP 3: Load and read `bmad-core/core-config.yaml` (project configuration) before any greeting
- STEP 3: Load and read `.bmad-core/core-config.yaml` (project configuration) before any greeting
- STEP 4: Greet user with your name/role and immediately run `*help` to display available commands
- DO NOT: Load any other agent files during activation
- ONLY load dependency files when user selects them for execution via command or request of a task
@@ -31,7 +31,7 @@ activation-instructions:
- Assess user goal against available agents and workflows in this bundle
- If clear match to an agent's expertise, suggest transformation with *agent command
- If project-oriented, suggest *workflow-guidance to explore options
- Load resources only when needed - never pre-load (Exception: Read `bmad-core/core-config.yaml` during activation)
- Load resources only when needed - never pre-load (Exception: Read `.bmad-core/core-config.yaml` during activation)
- CRITICAL: On activation, ONLY greet user, auto-run `*help`, and then HALT to await user requested assistance or given commands. ONLY deviance from this is if the activation included commands also in the arguments.
agent:
name: BMad Orchestrator

View File

@@ -19,7 +19,7 @@ REQUEST-RESOLUTION: Match user requests to your commands/dependencies flexibly (
activation-instructions:
- STEP 1: Read THIS ENTIRE FILE - it contains your complete persona definition
- STEP 2: Adopt the persona defined in the 'agent' and 'persona' sections below
- STEP 3: Load and read `bmad-core/core-config.yaml` (project configuration) before any greeting
- STEP 3: Load and read `.bmad-core/core-config.yaml` (project configuration) before any greeting
- STEP 4: Greet user with your name/role and immediately run `*help` to display available commands
- DO NOT: Load any other agent files during activation
- ONLY load dependency files when user selects them for execution via command or request of a task

View File

@@ -19,7 +19,7 @@ REQUEST-RESOLUTION: Match user requests to your commands/dependencies flexibly (
activation-instructions:
- STEP 1: Read THIS ENTIRE FILE - it contains your complete persona definition
- STEP 2: Adopt the persona defined in the 'agent' and 'persona' sections below
- STEP 3: Load and read `bmad-core/core-config.yaml` (project configuration) before any greeting
- STEP 3: Load and read `.bmad-core/core-config.yaml` (project configuration) before any greeting
- STEP 4: Greet user with your name/role and immediately run `*help` to display available commands
- DO NOT: Load any other agent files during activation
- ONLY load dependency files when user selects them for execution via command or request of a task

View File

@@ -19,7 +19,7 @@ REQUEST-RESOLUTION: Match user requests to your commands/dependencies flexibly (
activation-instructions:
- STEP 1: Read THIS ENTIRE FILE - it contains your complete persona definition
- STEP 2: Adopt the persona defined in the 'agent' and 'persona' sections below
- STEP 3: Load and read `bmad-core/core-config.yaml` (project configuration) before any greeting
- STEP 3: Load and read `.bmad-core/core-config.yaml` (project configuration) before any greeting
- STEP 4: Greet user with your name/role and immediately run `*help` to display available commands
- DO NOT: Load any other agent files during activation
- ONLY load dependency files when user selects them for execution via command or request of a task

View File

@@ -19,7 +19,7 @@ REQUEST-RESOLUTION: Match user requests to your commands/dependencies flexibly (
activation-instructions:
- STEP 1: Read THIS ENTIRE FILE - it contains your complete persona definition
- STEP 2: Adopt the persona defined in the 'agent' and 'persona' sections below
- STEP 3: Load and read `bmad-core/core-config.yaml` (project configuration) before any greeting
- STEP 3: Load and read `.bmad-core/core-config.yaml` (project configuration) before any greeting
- STEP 4: Greet user with your name/role and immediately run `*help` to display available commands
- DO NOT: Load any other agent files during activation
- ONLY load dependency files when user selects them for execution via command or request of a task
@@ -35,11 +35,7 @@ agent:
id: qa
title: Test Architect & Quality Advisor
icon: 🧪
whenToUse: |
Use for comprehensive test architecture review, quality gate decisions,
and code improvement. Provides thorough analysis including requirements
traceability, risk assessment, and test strategy.
Advisory only - teams choose their quality bar.
whenToUse: Use for comprehensive test architecture review, quality gate decisions, and code improvement. Provides thorough analysis including requirements traceability, risk assessment, and test strategy. Advisory only - teams choose their quality bar.
customization: null
persona:
role: Test Architect with Quality Advisory Authority

View File

@@ -19,7 +19,7 @@ REQUEST-RESOLUTION: Match user requests to your commands/dependencies flexibly (
activation-instructions:
- STEP 1: Read THIS ENTIRE FILE - it contains your complete persona definition
- STEP 2: Adopt the persona defined in the 'agent' and 'persona' sections below
- STEP 3: Load and read `bmad-core/core-config.yaml` (project configuration) before any greeting
- STEP 3: Load and read `.bmad-core/core-config.yaml` (project configuration) before any greeting
- STEP 4: Greet user with your name/role and immediately run `*help` to display available commands
- DO NOT: Load any other agent files during activation
- ONLY load dependency files when user selects them for execution via command or request of a task

View File

@@ -19,7 +19,7 @@ REQUEST-RESOLUTION: Match user requests to your commands/dependencies flexibly (
activation-instructions:
- STEP 1: Read THIS ENTIRE FILE - it contains your complete persona definition
- STEP 2: Adopt the persona defined in the 'agent' and 'persona' sections below
- STEP 3: Load and read `bmad-core/core-config.yaml` (project configuration) before any greeting
- STEP 3: Load and read `.bmad-core/core-config.yaml` (project configuration) before any greeting
- STEP 4: Greet user with your name/role and immediately run `*help` to display available commands
- DO NOT: Load any other agent files during activation
- ONLY load dependency files when user selects them for execution via command or request of a task

View File

@@ -181,7 +181,7 @@ npx bmad-method install
## Core Configuration (core-config.yaml)
**New in V4**: The `bmad-core/core-config.yaml` file is a critical innovation that enables BMad to work seamlessly with any project structure, providing maximum flexibility and backwards compatibility.
**New in V4**: The `.bmad-core/core-config.yaml` file is a critical innovation that enables BMad to work seamlessly with any project structure, providing maximum flexibility and backwards compatibility.
### What is core-config.yaml?

View File

@@ -16,8 +16,8 @@ Implement fixes based on QA results (gate and assessments) for a specific story.
```yaml
required:
- story_id: '{epic}.{story}' # e.g., "2.2"
- qa_root: from `bmad-core/core-config.yaml` key `qa.qaLocation` (e.g., `docs/project/qa`)
- story_root: from `bmad-core/core-config.yaml` key `devStoryLocation` (e.g., `docs/project/stories`)
- qa_root: from `.bmad-core/core-config.yaml` key `qa.qaLocation` (e.g., `docs/project/qa`)
- story_root: from `.bmad-core/core-config.yaml` key `devStoryLocation` (e.g., `docs/project/stories`)
optional:
- story_title: '{title}' # derive from story H1 if missing
@@ -45,7 +45,7 @@ optional:
### 0) Load Core Config & Locate Story
- Read `bmad-core/core-config.yaml` and resolve `qa_root` and `story_root`
- Read `.bmad-core/core-config.yaml` and resolve `qa_root` and `story_root`
- Locate story file in `{story_root}/{epic}.{story}.*.md`
- HALT if missing and ask for correct story id/path
@@ -113,7 +113,7 @@ Status Rule:
## Blocking Conditions
- Missing `bmad-core/core-config.yaml`
- Missing `.bmad-core/core-config.yaml`
- Story file not found for `story_id`
- No QA artifacts found (neither gate nor assessments)
- HALT and request QA to generate at least a gate file (or proceed only with clear developer-provided fix list)

View File

@@ -9,11 +9,11 @@ Quick NFR validation focused on the core four: security, performance, reliabilit
```yaml
required:
- story_id: '{epic}.{story}' # e.g., "1.3"
- story_path: `bmad-core/core-config.yaml` for the `devStoryLocation`
- story_path: `.bmad-core/core-config.yaml` for the `devStoryLocation`
optional:
- architecture_refs: `bmad-core/core-config.yaml` for the `architecture.architectureFile`
- technical_preferences: `bmad-core/core-config.yaml` for the `technicalPreferences`
- architecture_refs: `.bmad-core/core-config.yaml` for the `architecture.architectureFile`
- technical_preferences: `.bmad-core/core-config.yaml` for the `technicalPreferences`
- acceptance_criteria: From story file
```

View File

@@ -16,7 +16,7 @@ Generate a standalone quality gate file that provides a clear pass/fail decision
## Gate File Location
**ALWAYS** check the `bmad-core/core-config.yaml` for the `qa.qaLocation/gates`
**ALWAYS** check the `.bmad-core/core-config.yaml` for the `qa.qaLocation/gates`
Slug rules:
@@ -126,7 +126,7 @@ waiver:
## Output Requirements
1. **ALWAYS** create gate file at: `qa.qaLocation/gates` from `bmad-core/core-config.yaml`
1. **ALWAYS** create gate file at: `qa.qaLocation/gates` from `.bmad-core/core-config.yaml`
2. **ALWAYS** append this exact format to story's QA Results section:
```text

View File

@@ -186,7 +186,7 @@ NFR assessment: qa.qaLocation/assessments/{epic}.{story}-nfr-{YYYYMMDD}.md
**Template and Directory:**
- Render from `../templates/qa-gate-tmpl.yaml`
- Create directory defined in `qa.qaLocation/gates` (see `bmad-core/core-config.yaml`) if missing
- Create directory defined in `qa.qaLocation/gates` (see `.bmad-core/core-config.yaml`) if missing
- Save to: `qa.qaLocation/gates/{epic}.{story}-{slug}.yml`
Gate file structure:

View File

@@ -2230,7 +2230,7 @@ npx bmad-method install
## Core Configuration (core-config.yaml)
**New in V4**: The `bmad-core/core-config.yaml` file is a critical innovation that enables BMad to work seamlessly with any project structure, providing maximum flexibility and backwards compatibility.
**New in V4**: The `.bmad-core/core-config.yaml` file is a critical innovation that enables BMad to work seamlessly with any project structure, providing maximum flexibility and backwards compatibility.
### What is core-config.yaml?

View File

@@ -7970,7 +7970,7 @@ npx bmad-method install
## Core Configuration (core-config.yaml)
**New in V4**: The `bmad-core/core-config.yaml` file is a critical innovation that enables BMad to work seamlessly with any project structure, providing maximum flexibility and backwards compatibility.
**New in V4**: The `.bmad-core/core-config.yaml` file is a critical innovation that enables BMad to work seamlessly with any project structure, providing maximum flexibility and backwards compatibility.
### What is core-config.yaml?

View File

@@ -653,7 +653,7 @@ npx bmad-method install
## Core Configuration (core-config.yaml)
**New in V4**: The `bmad-core/core-config.yaml` file is a critical innovation that enables BMad to work seamlessly with any project structure, providing maximum flexibility and backwards compatibility.
**New in V4**: The `.bmad-core/core-config.yaml` file is a critical innovation that enables BMad to work seamlessly with any project structure, providing maximum flexibility and backwards compatibility.
### What is core-config.yaml?

8
dist/agents/dev.txt vendored
View File

@@ -110,8 +110,8 @@ Implement fixes based on QA results (gate and assessments) for a specific story.
```yaml
required:
- story_id: '{epic}.{story}' # e.g., "2.2"
- qa_root: from `bmad-core/core-config.yaml` key `qa.qaLocation` (e.g., `docs/project/qa`)
- story_root: from `bmad-core/core-config.yaml` key `devStoryLocation` (e.g., `docs/project/stories`)
- qa_root: from `.bmad-core/core-config.yaml` key `qa.qaLocation` (e.g., `docs/project/qa`)
- story_root: from `.bmad-core/core-config.yaml` key `devStoryLocation` (e.g., `docs/project/stories`)
optional:
- story_title: '{title}' # derive from story H1 if missing
@@ -139,7 +139,7 @@ optional:
### 0) Load Core Config & Locate Story
- Read `bmad-core/core-config.yaml` and resolve `qa_root` and `story_root`
- Read `.bmad-core/core-config.yaml` and resolve `qa_root` and `story_root`
- Locate story file in `{story_root}/{epic}.{story}.*.md`
- HALT if missing and ask for correct story id/path
@@ -207,7 +207,7 @@ Status Rule:
## Blocking Conditions
- Missing `bmad-core/core-config.yaml`
- Missing `.bmad-core/core-config.yaml`
- Story file not found for `story_id`
- No QA artifacts found (neither gate nor assessments)
- HALT and request QA to generate at least a gate file (or proceed only with clear developer-provided fix list)

15
dist/agents/qa.txt vendored
View File

@@ -55,8 +55,7 @@ agent:
id: qa
title: Test Architect & Quality Advisor
icon: 🧪
whenToUse: |
Use for comprehensive test architecture review, quality gate decisions,
whenToUse: Use for comprehensive test architecture review, quality gate decisions,
and code improvement. Provides thorough analysis including requirements
traceability, risk assessment, and test strategy.
Advisory only - teams choose their quality bar.
@@ -121,11 +120,11 @@ Quick NFR validation focused on the core four: security, performance, reliabilit
```yaml
required:
- story_id: '{epic}.{story}' # e.g., "1.3"
- story_path: `bmad-core/core-config.yaml` for the `devStoryLocation`
- story_path: `.bmad-core/core-config.yaml` for the `devStoryLocation`
optional:
- architecture_refs: `bmad-core/core-config.yaml` for the `architecture.architectureFile`
- technical_preferences: `bmad-core/core-config.yaml` for the `technicalPreferences`
- architecture_refs: `.bmad-core/core-config.yaml` for the `architecture.architectureFile`
- technical_preferences: `.bmad-core/core-config.yaml` for the `technicalPreferences`
- acceptance_criteria: From story file
```
@@ -475,7 +474,7 @@ Generate a standalone quality gate file that provides a clear pass/fail decision
## Gate File Location
**ALWAYS** check the `bmad-core/core-config.yaml` for the `qa.qaLocation/gates`
**ALWAYS** check the `.bmad-core/core-config.yaml` for the `qa.qaLocation/gates`
Slug rules:
@@ -585,7 +584,7 @@ waiver:
## Output Requirements
1. **ALWAYS** create gate file at: `qa.qaLocation/gates` from `bmad-core/core-config.yaml`
1. **ALWAYS** create gate file at: `qa.qaLocation/gates` from `.bmad-core/core-config.yaml`
2. **ALWAYS** append this exact format to story's QA Results section:
```text
@@ -810,7 +809,7 @@ NFR assessment: qa.qaLocation/assessments/{epic}.{story}-nfr-{YYYYMMDD}.md
**Template and Directory:**
- Render from `../templates/qa-gate-tmpl.yaml`
- Create directory defined in `qa.qaLocation/gates` (see `bmad-core/core-config.yaml`) if missing
- Create directory defined in `qa.qaLocation/gates` (see `.bmad-core/core-config.yaml`) if missing
- Save to: `qa.qaLocation/gates/{epic}.{story}-{slug}.yml`
Gate file structure:

View File

@@ -286,7 +286,7 @@ optional:
### 0) Load Core Config & Locate Story
- Read `bmad-core/core-config.yaml` and resolve `qa_root`, `story_root`, and `project_root`
- Read `.bmad-core/core-config.yaml` and resolve `qa_root`, `story_root`, and `project_root`
- Locate story file in `{story_root}/{epic}.{story}.*.md`
- HALT if missing and ask for correct story id/path
@@ -386,7 +386,7 @@ Status Rule:
## Blocking Conditions
- Missing `bmad-core/core-config.yaml`
- Missing `.bmad-core/core-config.yaml`
- Story file not found for `story_id`
- No QA artifacts found (neither gate nor assessments)
- HALT and request QA to generate at least a gate file (or proceed only with clear developer-provided fix list)

View File

@@ -55,8 +55,7 @@ agent:
id: game-qa
title: Game Test Architect & TDD Enforcer (Godot)
icon: 🎮🧪
whenToUse: |
Use for Godot game testing architecture, test-driven development enforcement,
whenToUse: Use for Godot game testing architecture, test-driven development enforcement,
performance validation, and gameplay quality assurance. Ensures all code is
test-first, performance targets are met, and player experience is validated.
Enforces GUT for GDScript and GoDotTest/GodotTestDriver for C# with TDD practices.

View File

@@ -558,8 +558,7 @@ agent:
id: game-qa
title: Game Test Architect & TDD Enforcer (Godot)
icon: 🎮🧪
whenToUse: |
Use for Godot game testing architecture, test-driven development enforcement,
whenToUse: Use for Godot game testing architecture, test-driven development enforcement,
performance validation, and gameplay quality assurance. Ensures all code is
test-first, performance targets are met, and player experience is validated.
Enforces GUT for GDScript and GoDotTest/GodotTestDriver for C# with TDD practices.
@@ -8943,7 +8942,7 @@ optional:
### 0) Load Core Config & Locate Story
- Read `bmad-core/core-config.yaml` and resolve `qa_root`, `story_root`, and `project_root`
- Read `.bmad-core/core-config.yaml` and resolve `qa_root`, `story_root`, and `project_root`
- Locate story file in `{story_root}/{epic}.{story}.*.md`
- HALT if missing and ask for correct story id/path
@@ -9043,7 +9042,7 @@ Status Rule:
## Blocking Conditions
- Missing `bmad-core/core-config.yaml`
- Missing `.bmad-core/core-config.yaml`
- Story file not found for `story_id`
- No QA artifacts found (neither gate nor assessments)
- HALT and request QA to generate at least a gate file (or proceed only with clear developer-provided fix list)
@@ -19903,7 +19902,7 @@ optional:
### 0) Load Core Config & Locate Story
- Read `bmad-core/core-config.yaml` and resolve `qa_root`, `story_root`, and `project_root`
- Read `.bmad-core/core-config.yaml` and resolve `qa_root`, `story_root`, and `project_root`
- Locate story file in `{story_root}/{epic}.{story}.*.md`
- HALT if missing and ask for correct story id/path
@@ -20003,7 +20002,7 @@ Status Rule:
## Blocking Conditions
- Missing `bmad-core/core-config.yaml`
- Missing `.bmad-core/core-config.yaml`
- Story file not found for `story_id`
- No QA artifacts found (neither gate nor assessments)
- HALT and request QA to generate at least a gate file (or proceed only with clear developer-provided fix list)

View File

@@ -505,8 +505,7 @@ agent:
id: qa
title: Test Architect & Quality Advisor
icon: 🧪
whenToUse: |
Use for comprehensive test architecture review, quality gate decisions,
whenToUse: Use for comprehensive test architecture review, quality gate decisions,
and code improvement. Provides thorough analysis including requirements
traceability, risk assessment, and test strategy.
Advisory only - teams choose their quality bar.
@@ -1141,7 +1140,7 @@ npx bmad-method install
## Core Configuration (core-config.yaml)
**New in V4**: The `bmad-core/core-config.yaml` file is a critical innovation that enables BMad to work seamlessly with any project structure, providing maximum flexibility and backwards compatibility.
**New in V4**: The `.bmad-core/core-config.yaml` file is a critical innovation that enables BMad to work seamlessly with any project structure, providing maximum flexibility and backwards compatibility.
### What is core-config.yaml?
@@ -6499,8 +6498,8 @@ Implement fixes based on QA results (gate and assessments) for a specific story.
```yaml
required:
- story_id: '{epic}.{story}' # e.g., "2.2"
- qa_root: from `bmad-core/core-config.yaml` key `qa.qaLocation` (e.g., `docs/project/qa`)
- story_root: from `bmad-core/core-config.yaml` key `devStoryLocation` (e.g., `docs/project/stories`)
- qa_root: from `.bmad-core/core-config.yaml` key `qa.qaLocation` (e.g., `docs/project/qa`)
- story_root: from `.bmad-core/core-config.yaml` key `devStoryLocation` (e.g., `docs/project/stories`)
optional:
- story_title: '{title}' # derive from story H1 if missing
@@ -6528,7 +6527,7 @@ optional:
### 0) Load Core Config & Locate Story
- Read `bmad-core/core-config.yaml` and resolve `qa_root` and `story_root`
- Read `.bmad-core/core-config.yaml` and resolve `qa_root` and `story_root`
- Locate story file in `{story_root}/{epic}.{story}.*.md`
- HALT if missing and ask for correct story id/path
@@ -6596,7 +6595,7 @@ Status Rule:
## Blocking Conditions
- Missing `bmad-core/core-config.yaml`
- Missing `.bmad-core/core-config.yaml`
- Story file not found for `story_id`
- No QA artifacts found (neither gate nor assessments)
- HALT and request QA to generate at least a gate file (or proceed only with clear developer-provided fix list)
@@ -9085,11 +9084,11 @@ Quick NFR validation focused on the core four: security, performance, reliabilit
```yaml
required:
- story_id: '{epic}.{story}' # e.g., "1.3"
- story_path: `bmad-core/core-config.yaml` for the `devStoryLocation`
- story_path: `.bmad-core/core-config.yaml` for the `devStoryLocation`
optional:
- architecture_refs: `bmad-core/core-config.yaml` for the `architecture.architectureFile`
- technical_preferences: `bmad-core/core-config.yaml` for the `technicalPreferences`
- architecture_refs: `.bmad-core/core-config.yaml` for the `architecture.architectureFile`
- technical_preferences: `.bmad-core/core-config.yaml` for the `technicalPreferences`
- acceptance_criteria: From story file
```
@@ -9439,7 +9438,7 @@ Generate a standalone quality gate file that provides a clear pass/fail decision
## Gate File Location
**ALWAYS** check the `bmad-core/core-config.yaml` for the `qa.qaLocation/gates`
**ALWAYS** check the `.bmad-core/core-config.yaml` for the `qa.qaLocation/gates`
Slug rules:
@@ -9549,7 +9548,7 @@ waiver:
## Output Requirements
1. **ALWAYS** create gate file at: `qa.qaLocation/gates` from `bmad-core/core-config.yaml`
1. **ALWAYS** create gate file at: `qa.qaLocation/gates` from `.bmad-core/core-config.yaml`
2. **ALWAYS** append this exact format to story's QA Results section:
```text
@@ -9774,7 +9773,7 @@ NFR assessment: qa.qaLocation/assessments/{epic}.{story}-nfr-{YYYYMMDD}.md
**Template and Directory:**
- Render from `../templates/qa-gate-tmpl.yaml`
- Create directory defined in `qa.qaLocation/gates` (see `bmad-core/core-config.yaml`) if missing
- Create directory defined in `qa.qaLocation/gates` (see `.bmad-core/core-config.yaml`) if missing
- Save to: `qa.qaLocation/gates/{epic}.{story}-{slug}.yml`
Gate file structure:

View File

@@ -976,7 +976,7 @@ npx bmad-method install
## Core Configuration (core-config.yaml)
**New in V4**: The `bmad-core/core-config.yaml` file is a critical innovation that enables BMad to work seamlessly with any project structure, providing maximum flexibility and backwards compatibility.
**New in V4**: The `.bmad-core/core-config.yaml` file is a critical innovation that enables BMad to work seamlessly with any project structure, providing maximum flexibility and backwards compatibility.
### What is core-config.yaml?

View File

@@ -353,8 +353,7 @@ agent:
id: qa
title: Test Architect & Quality Advisor
icon: 🧪
whenToUse: |
Use for comprehensive test architecture review, quality gate decisions,
whenToUse: Use for comprehensive test architecture review, quality gate decisions,
and code improvement. Provides thorough analysis including requirements
traceability, risk assessment, and test strategy.
Advisory only - teams choose their quality bar.
@@ -895,7 +894,7 @@ npx bmad-method install
## Core Configuration (core-config.yaml)
**New in V4**: The `bmad-core/core-config.yaml` file is a critical innovation that enables BMad to work seamlessly with any project structure, providing maximum flexibility and backwards compatibility.
**New in V4**: The `.bmad-core/core-config.yaml` file is a critical innovation that enables BMad to work seamlessly with any project structure, providing maximum flexibility and backwards compatibility.
### What is core-config.yaml?
@@ -3299,8 +3298,8 @@ Implement fixes based on QA results (gate and assessments) for a specific story.
```yaml
required:
- story_id: '{epic}.{story}' # e.g., "2.2"
- qa_root: from `bmad-core/core-config.yaml` key `qa.qaLocation` (e.g., `docs/project/qa`)
- story_root: from `bmad-core/core-config.yaml` key `devStoryLocation` (e.g., `docs/project/stories`)
- qa_root: from `.bmad-core/core-config.yaml` key `qa.qaLocation` (e.g., `docs/project/qa`)
- story_root: from `.bmad-core/core-config.yaml` key `devStoryLocation` (e.g., `docs/project/stories`)
optional:
- story_title: '{title}' # derive from story H1 if missing
@@ -3328,7 +3327,7 @@ optional:
### 0) Load Core Config & Locate Story
- Read `bmad-core/core-config.yaml` and resolve `qa_root` and `story_root`
- Read `.bmad-core/core-config.yaml` and resolve `qa_root` and `story_root`
- Locate story file in `{story_root}/{epic}.{story}.*.md`
- HALT if missing and ask for correct story id/path
@@ -3396,7 +3395,7 @@ Status Rule:
## Blocking Conditions
- Missing `bmad-core/core-config.yaml`
- Missing `.bmad-core/core-config.yaml`
- Story file not found for `story_id`
- No QA artifacts found (neither gate nor assessments)
- HALT and request QA to generate at least a gate file (or proceed only with clear developer-provided fix list)
@@ -3542,11 +3541,11 @@ Quick NFR validation focused on the core four: security, performance, reliabilit
```yaml
required:
- story_id: '{epic}.{story}' # e.g., "1.3"
- story_path: `bmad-core/core-config.yaml` for the `devStoryLocation`
- story_path: `.bmad-core/core-config.yaml` for the `devStoryLocation`
optional:
- architecture_refs: `bmad-core/core-config.yaml` for the `architecture.architectureFile`
- technical_preferences: `bmad-core/core-config.yaml` for the `technicalPreferences`
- architecture_refs: `.bmad-core/core-config.yaml` for the `architecture.architectureFile`
- technical_preferences: `.bmad-core/core-config.yaml` for the `technicalPreferences`
- acceptance_criteria: From story file
```
@@ -3896,7 +3895,7 @@ Generate a standalone quality gate file that provides a clear pass/fail decision
## Gate File Location
**ALWAYS** check the `bmad-core/core-config.yaml` for the `qa.qaLocation/gates`
**ALWAYS** check the `.bmad-core/core-config.yaml` for the `qa.qaLocation/gates`
Slug rules:
@@ -4006,7 +4005,7 @@ waiver:
## Output Requirements
1. **ALWAYS** create gate file at: `qa.qaLocation/gates` from `bmad-core/core-config.yaml`
1. **ALWAYS** create gate file at: `qa.qaLocation/gates` from `.bmad-core/core-config.yaml`
2. **ALWAYS** append this exact format to story's QA Results section:
```text
@@ -4231,7 +4230,7 @@ NFR assessment: qa.qaLocation/assessments/{epic}.{story}-nfr-{YYYYMMDD}.md
**Template and Directory:**
- Render from `../templates/qa-gate-tmpl.yaml`
- Create directory defined in `qa.qaLocation/gates` (see `bmad-core/core-config.yaml`) if missing
- Create directory defined in `qa.qaLocation/gates` (see `.bmad-core/core-config.yaml`) if missing
- Save to: `qa.qaLocation/gates/{epic}.{story}-{slug}.yml`
Gate file structure:

View File

@@ -922,7 +922,7 @@ npx bmad-method install
## Core Configuration (core-config.yaml)
**New in V4**: The `bmad-core/core-config.yaml` file is a critical innovation that enables BMad to work seamlessly with any project structure, providing maximum flexibility and backwards compatibility.
**New in V4**: The `.bmad-core/core-config.yaml` file is a critical innovation that enables BMad to work seamlessly with any project structure, providing maximum flexibility and backwards compatibility.
### What is core-config.yaml?

View File

@@ -187,6 +187,53 @@ If you want to do the planning on the web with Claude (Sonnet 4 or Opus), Gemini
npx bmad-method install
```
### OpenCode
BMAD integrates with OpenCode via a project-level `opencode.jsonc`/`opencode.json` (JSON-only, no Markdown fallback).
- Installation:
- Run `npx bmad-method install` and choose `OpenCode` in the IDE list.
- The installer will detect an existing `opencode.jsonc`/`opencode.json` or create a minimal `opencode.jsonc` if missing.
- It will:
- Ensure `instructions` includes `.bmad-core/core-config.yaml` (and each selected expansion packs `config.yaml`).
- Merge BMAD agents and commands using file references (`{file:./.bmad-core/...}`), idempotently.
- Preserve other top-level fields and user-defined entries.
- Prefixes and collisions:
- You can opt-in to prefix agent keys with `bmad-` and command keys with `bmad:tasks:` to avoid name collisions.
- If a key already exists and is not BMAD-managed, the installer will skip it and suggest enabling prefixes.
- What gets added:
- `instructions`: `.bmad-core/core-config.yaml` plus any selected expansion pack `config.yaml` files.
- `agent`: BMAD agents from core and selected packs.
- `prompt`: `{file:./.bmad-core/agents/<id>.md}` (or pack path)
- `mode`: `primary` for orchestrators, otherwise `all`
- `tools`: `{ write: true, edit: true, bash: true }`
- `description`: extracted from the agents `whenToUse`
- `command`: BMAD tasks from core and selected packs.
- `template`: `{file:./.bmad-core/tasks/<id>.md}` (or pack path)
- `description`: extracted from the tasks “Purpose” section
- Selected Packages Only:
- The installer includes agents and tasks only from the packages you selected in the earlier step (core and chosen packs).
- Refresh after changes:
- Re-run:
```bash
npx bmad-method install -f -i opencode
```
- The installer safely updates entries without duplication and preserves your custom fields and comments.
- Optional convenience script:
- You can add a script to your projects `package.json` for quick refreshes:
```json
{
"scripts": {
"bmad:opencode": "bmad-method install -f -i opencode"
}
}
```
### Codex (CLI & Web)
BMAD integrates with OpenAI Codex via `AGENTS.md` and committed core agent files.
@@ -501,7 +548,7 @@ When creating custom web bundles or uploading to AI platforms, include your `tec
## Core Configuration
The `bmad-core/core-config.yaml` file is a critical config that enables BMad to work seamlessly with differing project structures, more options will be made available in the future. Currently the most important is the devLoadAlwaysFiles list section in the yaml.
The `.bmad-core/core-config.yaml` file is a critical config that enables BMad to work seamlessly with differing project structures, more options will be made available in the future. Currently the most important is the devLoadAlwaysFiles list section in the yaml.
### Developer Context Files

View File

@@ -31,7 +31,7 @@ activation-instructions:
- Assess user goal against available agents and workflows in this bundle
- If clear match to an agent's expertise, suggest transformation with *agent command
- If project-oriented, suggest *workflow-guidance to explore options
- Load resources only when needed - never pre-load (Exception: Read `bmad-core/core-config.yaml` during activation)
- Load resources only when needed - never pre-load (Exception: Read `.bmad-core/core-config.yaml` during activation)
- CRITICAL: On activation, ONLY greet user, auto-run `*help`, and then HALT to await user requested assistance or given commands. ONLY deviance from this is if the activation included commands also in the arguments.
agent:
name: BMad Orchestrator

View File

@@ -34,8 +34,7 @@ agent:
id: game-qa
title: Game Test Architect & TDD Enforcer (Godot)
icon: 🎮🧪
whenToUse: |
Use for Godot game testing architecture, test-driven development enforcement,
whenToUse: Use for Godot game testing architecture, test-driven development enforcement,
performance validation, and gameplay quality assurance. Ensures all code is
test-first, performance targets are met, and player experience is validated.
Enforces GUT for GDScript and GoDotTest/GodotTestDriver for C# with TDD practices.

View File

@@ -50,7 +50,7 @@ optional:
### 0) Load Core Config & Locate Story
- Read `bmad-core/core-config.yaml` and resolve `qa_root`, `story_root`, and `project_root`
- Read `.bmad-core/core-config.yaml` and resolve `qa_root`, `story_root`, and `project_root`
- Locate story file in `{story_root}/{epic}.{story}.*.md`
- HALT if missing and ask for correct story id/path
@@ -150,7 +150,7 @@ Status Rule:
## Blocking Conditions
- Missing `bmad-core/core-config.yaml`
- Missing `.bmad-core/core-config.yaml`
- Story file not found for `story_id`
- No QA artifacts found (neither gate nor assessments)
- HALT and request QA to generate at least a gate file (or proceed only with clear developer-provided fix list)

47
package-lock.json generated
View File

@@ -1,17 +1,18 @@
{
"name": "bmad-method",
"version": "4.42.1",
"version": "4.43.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "bmad-method",
"version": "4.42.1",
"version": "4.43.0",
"license": "MIT",
"dependencies": {
"@kayvan/markdown-tree-parser": "^1.6.1",
"chalk": "^4.1.2",
"commander": "^14.0.0",
"comment-json": "^4.2.5",
"fs-extra": "^11.3.1",
"glob": "^11.0.3",
"ignore": "^7.0.5",
@@ -2950,6 +2951,12 @@
"dev": true,
"license": "MIT"
},
"node_modules/array-timsort": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/array-timsort/-/array-timsort-1.0.3.tgz",
"integrity": "sha512-/+3GRL7dDAGEfM6TseQk/U+mi18TU2Ms9I3UlLdUMhz2hbvGNTKdj9xniwXfUqgYhHxRx0+8UnKkvlNwVU+cWQ==",
"license": "MIT"
},
"node_modules/array-union": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz",
@@ -3674,6 +3681,22 @@
"node": ">=20"
}
},
"node_modules/comment-json": {
"version": "4.2.5",
"resolved": "https://registry.npmjs.org/comment-json/-/comment-json-4.2.5.tgz",
"integrity": "sha512-bKw/r35jR3HGt5PEPm1ljsQQGyCrR8sFGNiN5L+ykDHdpO8Smxkrkla9Yi6NkQyUrb8V54PGhfMs6NrIwtxtdw==",
"license": "MIT",
"dependencies": {
"array-timsort": "^1.0.3",
"core-util-is": "^1.0.3",
"esprima": "^4.0.1",
"has-own-prop": "^2.0.0",
"repeat-string": "^1.6.1"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/compare-func": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/compare-func/-/compare-func-2.0.0.tgz",
@@ -3806,7 +3829,6 @@
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz",
"integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==",
"dev": true,
"license": "MIT"
},
"node_modules/cosmiconfig": {
@@ -4845,7 +4867,6 @@
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
"integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
"dev": true,
"license": "BSD-2-Clause",
"bin": {
"esparse": "bin/esparse.js",
@@ -5502,6 +5523,15 @@
"node": ">=8"
}
},
"node_modules/has-own-prop": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/has-own-prop/-/has-own-prop-2.0.0.tgz",
"integrity": "sha512-Pq0h+hvsVm6dDEa8x82GnLSYHOzNDt7f0ddFa3FqcQlgzEiptPqL+XrOJNavjOzSYiYWIrgeVYYgGlLmnxwilQ==",
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/highlight.js": {
"version": "10.7.3",
"resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz",
@@ -12050,6 +12080,15 @@
"url": "https://opencollective.com/unified"
}
},
"node_modules/repeat-string": {
"version": "1.6.1",
"resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz",
"integrity": "sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==",
"license": "MIT",
"engines": {
"node": ">=0.10"
}
},
"node_modules/require-directory": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",

View File

@@ -75,6 +75,7 @@
"@kayvan/markdown-tree-parser": "^1.6.1",
"chalk": "^4.1.2",
"commander": "^14.0.0",
"comment-json": "^4.2.5",
"fs-extra": "^11.3.1",
"glob": "^11.0.3",
"ignore": "^7.0.5",

View File

@@ -49,7 +49,7 @@ program
.option('-d, --directory <path>', 'Installation directory')
.option(
'-i, --ide <ide...>',
'Configure for specific IDE(s) - can specify multiple (cursor, claude-code, windsurf, trae, roo, kilo, cline, gemini, qwen-code, github-copilot, codex, codex-web, auggie-cli, iflow-cli, other)',
'Configure for specific IDE(s) - can specify multiple (cursor, claude-code, windsurf, trae, roo, kilo, cline, gemini, qwen-code, github-copilot, codex, codex-web, auggie-cli, iflow-cli, opencode, other)',
)
.option(
'-e, --expansion-packs <packs...>',
@@ -410,6 +410,7 @@ async function promptInstallation() {
{ name: 'Auggie CLI (Augment Code)', value: 'auggie-cli' },
{ name: 'Codex CLI', value: 'codex' },
{ name: 'Codex Web', value: 'codex-web' },
{ name: 'OpenCode', value: 'opencode' },
],
},
]);
@@ -478,6 +479,43 @@ async function promptInstallation() {
answers.githubCopilotConfig = { configChoice };
}
// Configure OpenCode immediately if selected
if (ides.includes('opencode')) {
console.log(chalk.cyan('\n⚙ OpenCode Configuration'));
console.log(
chalk.dim(
'OpenCode will include agents and tasks from the packages you selected above; choose optional key prefixes (defaults: no prefixes).\n',
),
);
const { useAgentPrefix, useCommandPrefix } = await inquirer.prompt([
{
type: 'confirm',
name: 'useAgentPrefix',
message: "Prefix agent keys with 'bmad-'? (e.g., 'bmad-dev')",
default: true,
},
{
type: 'confirm',
name: 'useCommandPrefix',
message: "Prefix command keys with 'bmad:tasks:'? (e.g., 'bmad:tasks:create-doc')",
default: true,
},
]);
answers.openCodeConfig = {
opencode: {
useAgentPrefix,
useCommandPrefix,
},
// pass previously selected packages so IDE setup only applies those
selectedPackages: {
includeCore: selectedItems.includes('bmad-core'),
packs: answers.expansionPacks || [],
},
};
}
// Configure Auggie CLI (Augment Code) immediately if selected
if (ides.includes('auggie-cli')) {
console.log(chalk.cyan('\n📍 Auggie CLI Location Configuration'));

View File

@@ -171,3 +171,14 @@ ide-configurations:
# 2. Commit `.bmad-core/` and `AGENTS.md` to your repository.
# 3. Open the repo in Codex Web and reference agents naturally (e.g., "As dev, ...").
# 4. Re-run this installer to refresh agent sections when the core changes.
opencode:
name: OpenCode CLI
format: jsonc-config
file: opencode.jsonc
instructions: |
# To use BMAD agents with OpenCode CLI:
# 1. The installer creates/updates `opencode.jsonc` at your project root.
# 2. It ensures the BMAD core instructions file is referenced: `./.bmad-core/core-config.yaml`.
# 3. If an existing `opencode.json` or `opencode.jsonc` is present, it is preserved and only `instructions` are minimally merged.
# 4. Run `opencode` in this project to use your configured agents and commands.

View File

@@ -3,6 +3,7 @@ const fs = require('fs-extra');
const yaml = require('js-yaml');
const chalk = require('chalk');
const inquirer = require('inquirer');
const cjson = require('comment-json');
const fileManager = require('./file-manager');
const configLoader = require('./config-loader');
const { extractYamlFromAgent } = require('../../lib/yaml-utils');
@@ -44,6 +45,9 @@ class IdeSetup extends BaseIdeSetup {
case 'cursor': {
return this.setupCursor(installDir, selectedAgent);
}
case 'opencode': {
return this.setupOpenCode(installDir, selectedAgent, spinner, preConfiguredSettings);
}
case 'claude-code': {
return this.setupClaudeCode(installDir, selectedAgent);
}
@@ -93,6 +97,643 @@ class IdeSetup extends BaseIdeSetup {
}
}
async setupOpenCode(installDir, selectedAgent, spinner = null, preConfiguredSettings = null) {
// Minimal JSON-only integration per plan:
// - If opencode.json or opencode.jsonc exists: only ensure instructions include .bmad-core/core-config.yaml
// - If none exists: create minimal opencode.jsonc with $schema and instructions array including that file
const jsonPath = path.join(installDir, 'opencode.json');
const jsoncPath = path.join(installDir, 'opencode.jsonc');
const hasJson = await fileManager.pathExists(jsonPath);
const hasJsonc = await fileManager.pathExists(jsoncPath);
// Determine key prefix preferences (with sensible defaults)
// Defaults: non-prefixed (agents = "dev", commands = "create-doc")
let useAgentPrefix = false;
let useCommandPrefix = false;
// Allow pre-configuration (if passed) to skip prompts
const pre = preConfiguredSettings && preConfiguredSettings.opencode;
if (pre && typeof pre.useAgentPrefix === 'boolean') useAgentPrefix = pre.useAgentPrefix;
if (pre && typeof pre.useCommandPrefix === 'boolean') useCommandPrefix = pre.useCommandPrefix;
// If no pre-config and in interactive mode, prompt the user
if (!pre) {
// Pause spinner during prompts if active
let spinnerWasActive = false;
if (spinner && spinner.isSpinning) {
spinner.stop();
spinnerWasActive = true;
}
try {
const resp = await inquirer.prompt([
{
type: 'confirm',
name: 'useAgentPrefix',
message:
"Prefix agent keys with 'bmad-'? (Recommended to avoid collisions, e.g., 'bmad-dev')",
default: true,
},
{
type: 'confirm',
name: 'useCommandPrefix',
message:
"Prefix command keys with 'bmad:tasks:'? (Recommended, e.g., 'bmad:tasks:create-doc')",
default: true,
},
]);
useAgentPrefix = resp.useAgentPrefix;
useCommandPrefix = resp.useCommandPrefix;
} catch {
// Keep defaults if prompt fails or is not interactive
} finally {
if (spinner && spinnerWasActive) spinner.start();
}
}
const ensureInstructionRef = (obj) => {
const preferred = '.bmad-core/core-config.yaml';
const alt = './.bmad-core/core-config.yaml';
if (!obj.instructions) obj.instructions = [];
if (!Array.isArray(obj.instructions)) obj.instructions = [obj.instructions];
// Normalize alternative form (with './') to preferred without './'
obj.instructions = obj.instructions.map((it) =>
typeof it === 'string' && it === alt ? preferred : it,
);
const hasPreferred = obj.instructions.some(
(it) => typeof it === 'string' && it === preferred,
);
if (!hasPreferred) obj.instructions.push(preferred);
return obj;
};
const mergeBmadAgentsAndCommands = async (configObj) => {
// Ensure objects exist
if (!configObj.agent || typeof configObj.agent !== 'object') configObj.agent = {};
if (!configObj.command || typeof configObj.command !== 'object') configObj.command = {};
if (!configObj.instructions) configObj.instructions = [];
if (!Array.isArray(configObj.instructions)) configObj.instructions = [configObj.instructions];
// Track a concise summary of changes
const summary = {
target: null,
created: false,
agentsAdded: 0,
agentsUpdated: 0,
agentsSkipped: 0,
commandsAdded: 0,
commandsUpdated: 0,
commandsSkipped: 0,
};
// Determine package scope: previously SELECTED packages in installer UI
const selectedPackages = preConfiguredSettings?.selectedPackages || {
includeCore: true,
packs: [],
};
// Helper: ensure an instruction path is present without './' prefix, de-duplicating './' variants
const ensureInstructionPath = (pathNoDot) => {
const withDot = `./${pathNoDot}`;
// Normalize any existing './' variant to non './'
configObj.instructions = configObj.instructions.map((it) =>
typeof it === 'string' && it === withDot ? pathNoDot : it,
);
const has = configObj.instructions.some((it) => typeof it === 'string' && it === pathNoDot);
if (!has) configObj.instructions.push(pathNoDot);
};
// Helper: detect orchestrator agents to set as primary mode
const isOrchestratorAgent = (agentId) => /(^|-)orchestrator$/i.test(agentId);
// Helper: extract whenToUse string from an agent markdown file
const extractWhenToUseFromFile = async (absPath) => {
try {
const raw = await fileManager.readFile(absPath);
const yamlMatch = raw.match(/```ya?ml\r?\n([\s\S]*?)```/);
const yamlBlock = yamlMatch ? yamlMatch[1].trim() : null;
if (!yamlBlock) return null;
// Try quoted first, then unquoted
const quoted = yamlBlock.match(/whenToUse:\s*"([^"]+)"/i);
if (quoted && quoted[1]) return quoted[1].trim();
const unquoted = yamlBlock.match(/whenToUse:\s*([^\n\r]+)/i);
if (unquoted && unquoted[1]) return unquoted[1].trim();
} catch {
// ignore
}
return null;
};
// Helper: extract Purpose string from a task file (YAML fenced block, Markdown heading, or inline 'Purpose:')
const extractTaskPurposeFromFile = async (absPath) => {
const cleanupAndSummarize = (text) => {
if (!text) return null;
let t = String(text);
// Drop code fences and HTML comments
t = t.replaceAll(/```[\s\S]*?```/g, '');
t = t.replaceAll(/<!--([\s\S]*?)-->/g, '');
// Normalize line endings
t = t.replaceAll(/\r\n?/g, '\n');
// Take the first non-empty paragraph
const paragraphs = t.split(/\n\s*\n/g).map((p) => p.trim());
let first = paragraphs.find((p) => p.length > 0) || '';
// Remove leading list markers, quotes, and headings remnants
first = first.replaceAll(/^\s*[>*-]\s+/gm, '');
first = first.replaceAll(/^#{1,6}\s+/gm, '');
// Strip simple Markdown formatting
first = first.replaceAll(/\*\*([^*]+)\*\*/g, '$1').replaceAll(/\*([^*]+)\*/g, '$1');
first = first.replaceAll(/`([^`]+)`/g, '$1');
// Collapse whitespace
first = first.replaceAll(/\s+/g, ' ').trim();
if (!first) return null;
// Prefer ending at a sentence boundary if long
const maxLen = 320;
if (first.length > maxLen) {
const boundary = first.slice(0, maxLen + 40).match(/^[\s\S]*?[.!?](\s|$)/);
const cut = boundary ? boundary[0] : first.slice(0, maxLen);
return cut.trim();
}
return first;
};
try {
const raw = await fileManager.readFile(absPath);
// 1) YAML fenced block: look for Purpose fields
const yamlMatch = raw.match(/```ya?ml\r?\n([\s\S]*?)```/);
const yamlBlock = yamlMatch ? yamlMatch[1].trim() : null;
if (yamlBlock) {
try {
const data = yaml.load(yamlBlock);
if (data) {
let val = data.Purpose ?? data.purpose;
if (!val && data.task && (data.task.Purpose || data.task.purpose)) {
val = data.task.Purpose ?? data.task.purpose;
}
if (typeof val === 'string') {
const cleaned = cleanupAndSummarize(val);
if (cleaned) return cleaned;
}
}
} catch {
// ignore YAML parse errors
}
// Fallback regex inside YAML block
const quoted = yamlBlock.match(/(?:^|\n)\s*(?:Purpose|purpose):\s*"([^"]+)"/);
if (quoted && quoted[1]) {
const cleaned = cleanupAndSummarize(quoted[1]);
if (cleaned) return cleaned;
}
const unquoted = yamlBlock.match(/(?:^|\n)\s*(?:Purpose|purpose):\s*([^\n\r]+)/);
if (unquoted && unquoted[1]) {
const cleaned = cleanupAndSummarize(unquoted[1]);
if (cleaned) return cleaned;
}
}
// 2) Markdown heading section: ## Purpose (any level >= 2)
const headingRe = /^(#{2,6})\s*Purpose\s*$/im;
const headingMatch = headingRe.exec(raw);
if (headingMatch) {
const headingLevel = headingMatch[1].length;
const sectionStart = headingMatch.index + headingMatch[0].length;
const rest = raw.slice(sectionStart);
// Next heading of same or higher level ends the section
const nextHeadingRe = new RegExp(`^#{1,${headingLevel}}\\s+[^\n]+`, 'im');
const nextMatch = nextHeadingRe.exec(rest);
const section = nextMatch ? rest.slice(0, nextMatch.index) : rest;
const cleaned = cleanupAndSummarize(section);
if (cleaned) return cleaned;
}
// 3) Inline single-line fallback: Purpose: ...
const inline = raw.match(/(?:^|\n)\s*Purpose\s*:\s*([^\n\r]+)/i);
if (inline && inline[1]) {
const cleaned = cleanupAndSummarize(inline[1]);
if (cleaned) return cleaned;
}
} catch {
// ignore
}
return null;
};
// Build core sets
const coreAgentIds = new Set();
const coreTaskIds = new Set();
if (selectedPackages.includeCore) {
for (const id of await this.getCoreAgentIds(installDir)) coreAgentIds.add(id);
for (const id of await this.getCoreTaskIds(installDir)) coreTaskIds.add(id);
}
// Build packs info: { packId, packPath, packKey, agents:Set, tasks:Set }
const packsInfo = [];
if (Array.isArray(selectedPackages.packs)) {
for (const packId of selectedPackages.packs) {
const dotPackPath = path.join(installDir, `.${packId}`);
const altPackPath = path.join(installDir, 'expansion-packs', packId);
const packPath = (await fileManager.pathExists(dotPackPath))
? dotPackPath
: (await fileManager.pathExists(altPackPath))
? altPackPath
: null;
if (!packPath) continue;
// Ensure pack config.yaml is added to instructions (relative path, no './')
const packConfigAbs = path.join(packPath, 'config.yaml');
if (await fileManager.pathExists(packConfigAbs)) {
const relCfg = path.relative(installDir, packConfigAbs).replaceAll('\\', '/');
ensureInstructionPath(relCfg);
}
const packKey = packId.replace(/^bmad-/, '').replaceAll('/', '-');
const info = { packId, packPath, packKey, agents: new Set(), tasks: new Set() };
const glob = require('glob');
const agentsDir = path.join(packPath, 'agents');
if (await fileManager.pathExists(agentsDir)) {
const files = glob.sync('*.md', { cwd: agentsDir });
for (const f of files) info.agents.add(path.basename(f, '.md'));
}
const tasksDir = path.join(packPath, 'tasks');
if (await fileManager.pathExists(tasksDir)) {
const files = glob.sync('*.md', { cwd: tasksDir });
for (const f of files) info.tasks.add(path.basename(f, '.md'));
}
packsInfo.push(info);
}
}
// Generate agents - core first (respect optional agent prefix)
for (const agentId of coreAgentIds) {
const p = await this.findAgentPath(agentId, installDir); // prefers core
if (!p) continue;
const rel = path.relative(installDir, p).replaceAll('\\', '/');
const fileRef = `{file:./${rel}}`;
const baseKey = agentId;
const key = useAgentPrefix
? baseKey.startsWith('bmad-')
? baseKey
: `bmad-${baseKey}`
: baseKey;
const existing = configObj.agent[key];
const whenToUse = await extractWhenToUseFromFile(p);
const agentDef = {
prompt: fileRef,
mode: isOrchestratorAgent(agentId) ? 'primary' : 'all',
tools: { write: true, edit: true, bash: true },
...(whenToUse ? { description: whenToUse } : {}),
};
if (!existing) {
configObj.agent[key] = agentDef;
summary.agentsAdded++;
} else if (
existing &&
typeof existing === 'object' &&
typeof existing.prompt === 'string' &&
existing.prompt.includes(rel)
) {
existing.prompt = agentDef.prompt;
existing.mode = agentDef.mode;
if (whenToUse) existing.description = whenToUse;
existing.tools = { write: true, edit: true, bash: true };
configObj.agent[key] = existing;
summary.agentsUpdated++;
} else {
summary.agentsSkipped++;
// Collision warning: key exists but does not appear BMAD-managed (different prompt path)
console.log(
chalk.yellow(
`⚠︎ Skipped agent key '${key}' (existing entry not BMAD-managed). Tip: enable agent prefixes to avoid collisions.`,
),
);
}
}
// Generate agents - expansion packs (forced pack-specific prefix)
for (const pack of packsInfo) {
for (const agentId of pack.agents) {
const p = path.join(pack.packPath, 'agents', `${agentId}.md`);
if (!(await fileManager.pathExists(p))) continue;
const rel = path.relative(installDir, p).replaceAll('\\', '/');
const fileRef = `{file:./${rel}}`;
const prefixedKey = `bmad-${pack.packKey}-${agentId}`;
const existing = configObj.agent[prefixedKey];
const whenToUse = await extractWhenToUseFromFile(p);
const agentDef = {
prompt: fileRef,
mode: isOrchestratorAgent(agentId) ? 'primary' : 'all',
tools: { write: true, edit: true, bash: true },
...(whenToUse ? { description: whenToUse } : {}),
};
if (!existing) {
configObj.agent[prefixedKey] = agentDef;
summary.agentsAdded++;
} else if (
existing &&
typeof existing === 'object' &&
typeof existing.prompt === 'string' &&
existing.prompt.includes(rel)
) {
existing.prompt = agentDef.prompt;
existing.mode = agentDef.mode;
if (whenToUse) existing.description = whenToUse;
existing.tools = { write: true, edit: true, bash: true };
configObj.agent[prefixedKey] = existing;
summary.agentsUpdated++;
} else {
summary.agentsSkipped++;
console.log(
chalk.yellow(
`⚠︎ Skipped agent key '${prefixedKey}' (existing entry not BMAD-managed). Tip: enable agent prefixes to avoid collisions.`,
),
);
}
}
}
// Generate commands - core first (respect optional command prefix)
for (const taskId of coreTaskIds) {
const p = await this.findTaskPath(taskId, installDir); // prefers core/common
if (!p) continue;
const rel = path.relative(installDir, p).replaceAll('\\', '/');
const fileRef = `{file:./${rel}}`;
const key = useCommandPrefix ? `bmad:tasks:${taskId}` : `${taskId}`;
const existing = configObj.command[key];
const purpose = await extractTaskPurposeFromFile(p);
const cmdDef = { template: fileRef, ...(purpose ? { description: purpose } : {}) };
if (!existing) {
configObj.command[key] = cmdDef;
summary.commandsAdded++;
} else if (
existing &&
typeof existing === 'object' &&
typeof existing.template === 'string' &&
existing.template.includes(rel)
) {
existing.template = cmdDef.template;
if (purpose) existing.description = purpose;
configObj.command[key] = existing;
summary.commandsUpdated++;
} else {
summary.commandsSkipped++;
console.log(
chalk.yellow(
`⚠︎ Skipped command key '${key}' (existing entry not BMAD-managed). Tip: enable command prefixes to avoid collisions.`,
),
);
}
}
// Generate commands - expansion packs (forced pack-specific prefix)
for (const pack of packsInfo) {
for (const taskId of pack.tasks) {
const p = path.join(pack.packPath, 'tasks', `${taskId}.md`);
if (!(await fileManager.pathExists(p))) continue;
const rel = path.relative(installDir, p).replaceAll('\\', '/');
const fileRef = `{file:./${rel}}`;
const prefixedKey = `bmad:${pack.packKey}:${taskId}`;
const existing = configObj.command[prefixedKey];
const purpose = await extractTaskPurposeFromFile(p);
const cmdDef = { template: fileRef, ...(purpose ? { description: purpose } : {}) };
if (!existing) {
configObj.command[prefixedKey] = cmdDef;
summary.commandsAdded++;
} else if (
existing &&
typeof existing === 'object' &&
typeof existing.template === 'string' &&
existing.template.includes(rel)
) {
existing.template = cmdDef.template;
if (purpose) existing.description = purpose;
configObj.command[prefixedKey] = existing;
summary.commandsUpdated++;
} else {
summary.commandsSkipped++;
console.log(
chalk.yellow(
`⚠︎ Skipped command key '${prefixedKey}' (existing entry not BMAD-managed). Tip: enable command prefixes to avoid collisions.`,
),
);
}
}
}
return { configObj, summary };
};
// Helper: generate AGENTS.md section for OpenCode (acts as system prompt memory)
const generateOpenCodeAgentsMd = async () => {
try {
const filePath = path.join(installDir, 'AGENTS.md');
const startMarker = '<!-- BEGIN: BMAD-AGENTS-OPENCODE -->';
const endMarker = '<!-- END: BMAD-AGENTS-OPENCODE -->';
const agents = selectedAgent ? [selectedAgent] : await this.getAllAgentIds(installDir);
const tasks = await this.getAllTaskIds(installDir);
let section = '';
section += `${startMarker}\n`;
section += `# BMAD-METHOD Agents and Tasks (OpenCode)\n\n`;
section += `OpenCode reads AGENTS.md during initialization and uses it as part of its system prompt for the session. This section is auto-generated by BMAD-METHOD for OpenCode.\n\n`;
section += `## How To Use With OpenCode\n\n`;
section += `- Run \`opencode\` in this project. OpenCode will read \`AGENTS.md\` and your OpenCode config (opencode.json[c]).\n`;
section += `- Reference a role naturally, e.g., "As dev, implement ..." or use commands defined in your BMAD tasks.\n`;
section += `- Commit \`.bmad-core\` and \`AGENTS.md\` if you want teammates to share the same configuration.\n`;
section += `- Refresh this section after BMAD updates: \`npx bmad-method install -f -i opencode\`.\n\n`;
section += `### Helpful Commands\n\n`;
section += `- List agents: \`npx bmad-method list:agents\`\n`;
section += `- Reinstall BMAD core and regenerate this section: \`npx bmad-method install -f -i opencode\`\n`;
section += `- Validate configuration: \`npx bmad-method validate\`\n\n`;
// Brief context note for modes and tools
section += `Note\n`;
section += `- Orchestrators run as mode: primary; other agents as all.\n`;
section += `- All agents have tools enabled: write, edit, bash.\n\n`;
section += `## Agents\n\n`;
section += `### Directory\n\n`;
section += `| Title | ID | When To Use |\n|---|---|---|\n`;
// Fallback descriptions for core agents (used if whenToUse is missing)
const fallbackDescriptions = {
'ux-expert':
'Use for UI/UX design, wireframes, prototypes, front-end specs, and user experience optimization',
sm: 'Use for story creation, epic management, retrospectives in party-mode, and agile process guidance',
qa: 'Ensure quality strategy, test design, risk profiling, and QA gates across features',
po: 'Backlog management, story refinement, acceptance criteria, sprint planning, prioritization decisions',
pm: 'PRDs, product strategy, feature prioritization, roadmap planning, and stakeholder communication',
dev: 'Code implementation, debugging, refactoring, and development best practices',
'bmad-orchestrator':
'Workflow coordination, multi-agent tasks, role switching guidance, and when unsure which specialist to consult',
'bmad-master':
'Comprehensive cross-domain execution for tasks that do not require a specific persona',
architect:
'System design, architecture docs, technology selection, API design, and infrastructure planning',
analyst:
'Discovery/research, competitive analysis, project briefs, initial discovery, and brownfield documentation',
};
const sanitizeDesc = (s) => {
if (!s) return '';
let t = String(s).trim();
// Drop surrounding single/double/backtick quotes
t = t.replaceAll(/^['"`]+|['"`]+$/g, '');
// Collapse whitespace
t = t.replaceAll(/\s+/g, ' ').trim();
return t;
};
const agentSummaries = [];
for (const agentId of agents) {
const agentPath = await this.findAgentPath(agentId, installDir);
if (!agentPath) continue;
let whenToUse = '';
try {
const raw = await fileManager.readFile(agentPath);
const yamlMatch = raw.match(/```ya?ml\r?\n([\s\S]*?)```/);
const yamlBlock = yamlMatch ? yamlMatch[1].trim() : null;
if (yamlBlock) {
try {
const data = yaml.load(yamlBlock);
if (data && typeof data.whenToUse === 'string') {
whenToUse = data.whenToUse;
}
} catch {
// ignore YAML parse errors
}
if (!whenToUse) {
// Fallback regex supporting single or double quotes
const m1 = yamlBlock.match(/whenToUse:\s*"([^\n"]+)"/i);
const m2 = yamlBlock.match(/whenToUse:\s*'([^\n']+)'/i);
const m3 = yamlBlock.match(/whenToUse:\s*([^\n\r]+)/i);
whenToUse = (m1?.[1] || m2?.[1] || m3?.[1] || '').trim();
}
}
} catch {
// ignore read/parse errors for agent metadata extraction
}
const title = await this.getAgentTitle(agentId, installDir);
const finalDesc = sanitizeDesc(whenToUse) || fallbackDescriptions[agentId] || '—';
agentSummaries.push({ agentId, title, whenToUse: finalDesc, path: agentPath });
// Strict 3-column row
section += `| ${title} | ${agentId} | ${finalDesc} |\n`;
}
section += `\n`;
for (const { agentId, title, whenToUse, path: agentPath } of agentSummaries) {
const relativePath = path.relative(installDir, agentPath).replaceAll('\\', '/');
section += `### ${title} (id: ${agentId})\n`;
section += `Source: [${relativePath}](${relativePath})\n\n`;
if (whenToUse) section += `- When to use: ${whenToUse}\n`;
section += `- How to activate: Mention "As ${agentId}, ..." to get role-aligned behavior\n`;
section += `- Full definition: open the source file above (content not embedded)\n\n`;
}
if (tasks && tasks.length > 0) {
section += `## Tasks\n\n`;
section += `These are reusable task briefs; use the paths to open them as needed.\n\n`;
for (const taskId of tasks) {
const taskPath = await this.findTaskPath(taskId, installDir);
if (!taskPath) continue;
const relativePath = path.relative(installDir, taskPath).replaceAll('\\', '/');
section += `### Task: ${taskId}\n`;
section += `Source: [${relativePath}](${relativePath})\n`;
section += `- How to use: Reference the task in your prompt or execute via your configured commands.\n`;
section += `- Full brief: open the source file above (content not embedded)\n\n`;
}
}
section += `${endMarker}\n`;
let finalContent = '';
if (await fileManager.pathExists(filePath)) {
const existing = await fileManager.readFile(filePath);
if (existing.includes(startMarker) && existing.includes(endMarker)) {
const pattern = String.raw`${startMarker}[\s\S]*?${endMarker}`;
const replaced = existing.replace(new RegExp(pattern, 'm'), section);
finalContent = replaced;
} else {
finalContent = existing.trimEnd() + `\n\n` + section;
}
} else {
finalContent += '# Project Agents\n\n';
finalContent += 'This file provides guidance and memory for your coding CLI.\n\n';
finalContent += section;
}
await fileManager.writeFile(filePath, finalContent);
console.log(chalk.green('✓ Created/updated AGENTS.md for OpenCode CLI integration'));
console.log(
chalk.dim(
'OpenCode reads AGENTS.md automatically on init. Run `opencode` in this project to use BMAD agents.',
),
);
} catch {
console.log(chalk.yellow('⚠︎ Skipped creating AGENTS.md for OpenCode (write failed)'));
}
};
if (hasJson || hasJsonc) {
// Preserve existing top-level fields; only touch instructions
const targetPath = hasJsonc ? jsoncPath : jsonPath;
try {
const raw = await fs.readFile(targetPath, 'utf8');
// Use comment-json for both .json and .jsonc for resilience
const parsed = cjson.parse(raw, undefined, true);
ensureInstructionRef(parsed);
const { configObj, summary } = await mergeBmadAgentsAndCommands(parsed);
const output = cjson.stringify(parsed, null, 2);
await fs.writeFile(targetPath, output + (output.endsWith('\n') ? '' : '\n'));
console.log(
chalk.green(
'✓ Updated OpenCode config: ensured BMAD instructions and merged agents/commands',
),
);
// Summary output
console.log(
chalk.dim(
` File: ${path.basename(targetPath)} | Agents +${summary.agentsAdded} ~${summary.agentsUpdated} ${summary.agentsSkipped} | Commands +${summary.commandsAdded} ~${summary.commandsUpdated} ${summary.commandsSkipped}`,
),
);
// Ensure AGENTS.md is created/updated for OpenCode as well
await generateOpenCodeAgentsMd();
} catch (error) {
console.log(chalk.red('✗ Failed to update existing OpenCode config'), error.message);
return false;
}
return true;
}
// Create minimal opencode.jsonc
const minimal = {
$schema: 'https://opencode.ai/config.json',
instructions: ['.bmad-core/core-config.yaml'],
agent: {},
command: {},
};
try {
const { configObj, summary } = await mergeBmadAgentsAndCommands(minimal);
const output = cjson.stringify(minimal, null, 2);
await fs.writeFile(jsoncPath, output + (output.endsWith('\n') ? '' : '\n'));
console.log(
chalk.green('✓ Created opencode.jsonc with BMAD instructions, agents, and commands'),
);
console.log(
chalk.dim(
` File: opencode.jsonc | Agents +${summary.agentsAdded} | Commands +${summary.commandsAdded}`,
),
);
// Also create/update AGENTS.md for OpenCode on new-config path
await generateOpenCodeAgentsMd();
return true;
} catch (error) {
console.log(chalk.red('✗ Failed to create opencode.jsonc'), error.message);
return false;
}
}
async setupCodex(installDir, selectedAgent, options) {
options = options ?? { webEnabled: false };
// Codex reads AGENTS.md at the project root as project memory (CLI & Web).
@@ -230,7 +871,6 @@ class IdeSetup extends BaseIdeSetup {
if (options.webEnabled) {
if (exists) {
let gi = await fileManager.readFile(gitignorePath);
// Remove lines that ignore BMAD dot-folders
const updated = gi
.split(/\r?\n/)
.filter((l) => !/^\s*\.bmad-core\/?\s*$/.test(l) && !/^\s*\.bmad-\*\/?\s*$/.test(l))

View File

@@ -409,10 +409,23 @@ class Installer {
for (const ide of ides) {
spinner.text = `Setting up ${ide} integration...`;
let preConfiguredSettings = null;
if (ide === 'github-copilot') {
preConfiguredSettings = config.githubCopilotConfig;
} else if (ide === 'auggie-cli') {
preConfiguredSettings = config.augmentCodeConfig;
switch (ide) {
case 'github-copilot': {
preConfiguredSettings = config.githubCopilotConfig;
break;
}
case 'auggie-cli': {
preConfiguredSettings = config.augmentCodeConfig;
break;
}
case 'opencode': {
preConfiguredSettings = config.openCodeConfig;
break;
}
default: {
// no pre-configured settings
break;
}
}
await ideSetup.setup(ide, installDir, config.agent, spinner, preConfiguredSettings);
}