Compare commits

..

6 Commits

47 changed files with 432 additions and 209 deletions

View File

@ -0,0 +1,79 @@
---
title: "Set Up Monorepo Support"
description: Manage multiple projects in a single repository with isolated artifacts
sidebar:
order: 8
---
BMad Method supports working in monorepo environments where multiple projects share a single repository. This allows you to centralize methodology files while keeping project artifacts isolated and organized.
:::note[Prerequisites]
- BMad Method v6.0.1+
- A repository containing multiple projects
:::
## Why Use Monorepo Support?
In a standard setup, BMad expects one project per repository. In a monorepo, you might have `apps/frontend`, `apps/backend`, and `packages/shared`. Without monorepo support, BMad's artifacts (plans, architecture, stories) would be mixed or require installing BMad in every sub-directory.
Monorepo support allows you to:
- **Centralize Methodology**: Install BMad once at the root based on your team's process.
- **Isolate Artifacts**: Automatically route outputs to project-specific folders (e.g., `_bmad-output/frontend/`).
- **Context Switch Easily**: Switch between projects without reinstalling or reconfiguring.
## Project Structure
When monorepo support is active, the structure changes slightly to accommodate multiple projects:
```
monorepo-root/
├── _bmad/ # Methodology (installed core & modules)
│ └── .current_project # Context marker (e.g., "app-alpha")
├── _bmad-output/ # Unified output directory
│ ├── app-alpha/ # Sub-project artifacts
│ │ ├── planning-artifacts/
│ │ └── implementation-artifacts/
│ └── app-beta/ # Sub-project artifacts
│ └── planning-artifacts/
├── apps/ # Actual source code
│ ├── app-alpha/
│ └── app-beta/
└── package.json
```
## How It Works
### Context Injection
Core and BMM workflows automatically check for the existence of `_bmad/.current_project`.
- **If found**: It reads the content (e.g., "app-alpha") and overrides the `output_folder` to `_bmad-output/app-alpha`.
- **If not found**: It behaves like a standard single-project installation, outputting to `_bmad-output` root.
### The /set-project Command
You can easily manage the active project context using the `/set-project` workflow.
**To set a context:**
1. Run `/set-project` in your chat.
2. Select "Set Project Context".
3. Enter the name of your project (e.g., `frontend`, `backend`, `mobile-app`).
**To clear context (return to single-project mode):**
1. Run `/set-project`.
2. Select "Clear Project Context".
### Inline Override
You can temporarily override the project context for a specific command without changing the global `.current_project` state. This is useful for one-off tasks in a different project.
Use the `#project:` syntax (or `#p:` for short) anywhere in your prompt:
```bash
# Full syntax
/create-prd #project:myproject_name
# Short alias
/create-story #p:frontend
```
**Note:** The inline override takes precedence over the `.current_project` file. If no project is specified via `#` or `.current_project`, BMad defaults to the root `_bmad-output` folder.

View File

@ -18,6 +18,12 @@ The fastest way to understand BMad is to try it.
Install BMad and run `/bmad-help` — it will guide you through everything based on your project and installed modules.
:::
## Advanced Setup
- **[Monorepo Support](./how-to/monorepo-setup.md)** — Manage multiple projects in a single repository.
- **[Non-Interactive Installation](./how-to/non-interactive-installation.md)** — Automate BMad installation for CI/CD.
## How to Use These Docs
These docs are organized into four sections based on what you're trying to do:

View File

@ -34,3 +34,16 @@ Load and read full config from {main_config} and resolve basic variables.
### 3. Verification
- Display the full resolved output path for confirmation.
## Inline Project Overrides
You can also temporarily run a command against a different project without changing the global context file. Use the `#project:NAME` or `#p:NAME` syntax in your command invocation.
**Examples:**
- `/create-prd #project:my-app`
- `/sprint-planning #p:admin-portal`
**Precedence:**
1. **Inline Override** (`#p:NAME`)
2. **Global Context File** (`_bmad/.current_project`)
3. **Default Config** (if neither is present)

View File

@ -51,11 +51,6 @@ This uses **step-file architecture** for disciplined execution:
Load and read full config from {main_config} and resolve basic variables.
**Monorepo Context Check:**
1. Check if `{project-root}/_bmad/.current_project` exists.
2. If it exists, read its content as `{project_suffix}` and override output folder:
- `output_folder`: `{project-root}/_bmad-output/{project_suffix}`
- `project_name`, `output_folder`, `planning_artifacts`, `user_name`, `communication_language`, `document_output_language`, `user_skill_level`
### 2. First Step EXECUTION

View File

@ -373,6 +373,7 @@ _This comprehensive research document serves as an authoritative reference on {{
#### If 'C' (Complete Research):
- **Replace** the template placeholder `[Research overview and methodology will be appended here]` in the `## Research Overview` section near the top of the document with a concise 2-3 paragraph overview summarizing the research scope, key findings, and a pointer to the full executive summary in the Research Synthesis section
- Append the complete document to the research file
- Update frontmatter: `stepsCompleted: [1, 2, 3, 4, 5]`
- Complete the domain research workflow
@ -380,7 +381,7 @@ _This comprehensive research document serves as an authoritative reference on {{
## APPEND TO DOCUMENT:
When user selects 'C', append the complete comprehensive research document using the full structure above.
When user selects 'C', append the complete comprehensive research document using the full structure above. Also replace the `[Research overview and methodology will be appended here]` placeholder in the Research Overview section at the top of the document.
## SUCCESS METRICS:

View File

@ -389,13 +389,14 @@ _This comprehensive market research document serves as an authoritative market r
#### If 'C' (Complete Research):
- **Replace** the template placeholder `[Research overview and methodology will be appended here]` in the `## Research Overview` section near the top of the document with a concise 2-3 paragraph overview summarizing the research scope, key findings, and a pointer to the full executive summary in the Research Synthesis section
- Append the final content to the research document
- Update frontmatter: `stepsCompleted: [1, 2, 3, 4]`
- Complete the market research workflow
## APPEND TO DOCUMENT:
When user selects 'C', append the content directly to the research document using the structure from step 4.
When user selects 'C', append the content directly to the research document using the structure from step 4. Also replace the `[Research overview and methodology will be appended here]` placeholder in the Research Overview section at the top of the document.
## SUCCESS METRICS:

View File

@ -416,6 +416,7 @@ _This comprehensive technical research document serves as an authoritative techn
#### If 'C' (Complete Research):
- **Replace** the template placeholder `[Research overview and methodology will be appended here]` in the `## Research Overview` section near the top of the document with a concise 2-3 paragraph overview summarizing the research scope, key findings, and a pointer to the full executive summary in the Research Synthesis section
- Append the complete technical document to the research file
- Update frontmatter: `stepsCompleted: [1, 2, 3, 4, 5, 6]`
- Complete the technical research workflow
@ -423,7 +424,7 @@ _This comprehensive technical research document serves as an authoritative techn
## APPEND TO DOCUMENT:
When user selects 'C', append the complete comprehensive technical research document using the full structure above.
When user selects 'C', append the complete comprehensive technical research document using the full structure above. Also replace the `[Research overview and methodology will be appended here]` placeholder in the Research Overview section at the top of the document.
## SUCCESS METRICS:

View File

@ -20,10 +20,6 @@ main_config: '{project-root}/_bmad/bmm/config.yaml'
Load and read full config from {main_config} and resolve basic variables.
**Monorepo Context Check:**
1. Check if `{project-root}/_bmad/.current_project` exists.
2. If it exists, read its content as `{project_suffix}` and override output folder:
- `output_folder`: `{project-root}/_bmad-output/{project_suffix}`
- `project_name`, `output_folder`, `planning_artifacts`, `user_name`
- `communication_language`, `document_output_language`, `user_skill_level`

View File

@ -20,10 +20,6 @@ main_config: '{project-root}/_bmad/bmm/config.yaml'
Load and read full config from {main_config} and resolve basic variables.
**Monorepo Context Check:**
1. Check if `{project-root}/_bmad/.current_project` exists.
2. If it exists, read its content as `{project_suffix}` and override output folder:
- `output_folder`: `{project-root}/_bmad-output/{project_suffix}`
- `project_name`, `output_folder`, `planning_artifacts`, `user_name`
- `communication_language`, `document_output_language`, `user_skill_level`

View File

@ -20,10 +20,6 @@ main_config: '{project-root}/_bmad/bmm/config.yaml'
Load and read full config from {main_config} and resolve basic variables.
**Monorepo Context Check:**
1. Check if `{project-root}/_bmad/.current_project` exists.
2. If it exists, read its content as `{project_suffix}` and override output folder:
- `output_folder`: `{project-root}/_bmad-output/{project_suffix}`
- `project_name`, `output_folder`, `planning_artifacts`, `user_name`
- `communication_language`, `document_output_language`, `user_skill_level`

View File

@ -46,15 +46,6 @@ This uses **step-file architecture** for disciplined execution:
## INITIALIZATION SEQUENCE
### 1. Configuration Loading
Load and read full config from {main_config} and resolve basic variables.
**Monorepo Context Check:**
1. Check if `{project-root}/_bmad/.current_project` exists.
2. If it exists, read its content as `{project_suffix}` and override output folder:
- `output_folder`: `{project-root}/_bmad-output/{project_suffix}`
- `project_name`, `output_folder`, `planning_artifacts`, `user_name`
- `communication_language`, `document_output_language`, `user_skill_level`
- `date` as system-generated current datetime

View File

@ -50,10 +50,6 @@ This uses **step-file architecture** for disciplined execution:
Load and read full config from {main_config} and resolve basic variables.
**Monorepo Context Check:**
1. Check if `{project-root}/_bmad/.current_project` exists.
2. If it exists, read its content as `{project_suffix}` and override output folder:
- `output_folder`: `{project-root}/_bmad-output/{project_suffix}`
- `project_name`, `output_folder`, `planning_artifacts`, `user_name`
- `communication_language`, `document_output_language`, `user_skill_level`
- `date` as system-generated current datetime

View File

@ -50,10 +50,6 @@ This uses **step-file architecture** for disciplined execution:
Load and read full config from {main_config} and resolve basic variables.
**Monorepo Context Check:**
1. Check if `{project-root}/_bmad/.current_project` exists.
2. If it exists, read its content as `{project_suffix}` and override output folder:
- `output_folder`: `{project-root}/_bmad-output/{project_suffix}`
- `project_name`, `output_folder`, `planning_artifacts`, `user_name`
- `communication_language`, `document_output_language`, `user_skill_level`

View File

@ -26,10 +26,6 @@ This uses **micro-file architecture** for disciplined execution:
Load and read full config from {main_config} and resolve basic variables.
**Monorepo Context Check:**
1. Check if `{project-root}/_bmad/.current_project` exists.
2. If it exists, read its content as `{project_suffix}` and override output folder:
- `output_folder`: `{project-root}/_bmad-output/{project_suffix}`
- `project_name`, `output_folder`, `planning_artifacts`, `user_name`
- `communication_language`, `document_output_language`, `user_skill_level`

View File

@ -46,11 +46,6 @@ description: 'Critical validation workflow that assesses PRD, Architecture, and
Load and read full config from {main_config} and resolve basic variables.
**Monorepo Context Check:**
1. Check if `{project-root}/_bmad/.current_project` exists.
2. If it exists, read its content as `{project_suffix}` and override output folder:
- `output_folder`: `{project-root}/_bmad-output/{project_suffix}`
- `project_name`, `output_folder`, `planning_artifacts`, `user_name`, `communication_language`, `document_output_language`
- ✅ YOU MUST ALWAYS SPEAK OUTPUT In your Agent communication style with the config `{communication_language}`

View File

@ -29,11 +29,6 @@ This uses **micro-file architecture** for disciplined execution:
Load and read full config from {main_config} and resolve basic variables.
**Monorepo Context Check:**
1. Check if `{project-root}/_bmad/.current_project` exists.
2. If it exists, read its content as `{project_suffix}` and override output folder:
- `output_folder`: `{project-root}/_bmad-output/{project_suffix}`
Load config from `{project-root}/_bmad/bmm/config.yaml` and resolve:
- `project_name`, `output_folder`, `planning_artifacts`, `user_name`

View File

@ -50,10 +50,6 @@ This uses **step-file architecture** for disciplined execution:
Load and read full config from {main_config} and resolve basic variables.
**Monorepo Context Check:**
1. Check if `{project-root}/_bmad/.current_project` exists.
2. If it exists, read its content as `{project_suffix}` and override output folder:
- `output_folder`: `{project-root}/_bmad-output/{project_suffix}`
- `project_name`, `output_folder`, `planning_artifacts`, `user_name`, `communication_language`, `document_output_language`
- ✅ YOU MUST ALWAYS SPEAK OUTPUT In your Agent communication style with the config `{communication_language}`

View File

@ -40,15 +40,7 @@
- [x] Done - Item completed successfully
- [N/A] Skip - Item not applicable to this change
- [!] Action-needed - Item requires attention or follow-up
### 1. Configuration Loading
Load and read full config from {main_config} and resolve basic variables.
**Monorepo Context Check:**
1. Check if `{project-root}/_bmad/.current_project` exists.
2. If it exists, read its content as `{project_suffix}` and override output folder:
- `output_folder`: `{project-root}/_bmad-output/{project_suffix}`
<action>Maintain running notes of findings and impacts discovered</action>
<action>Present checklist progress after each major section</action>
<action if="checklist cannot be completed">Identify blocking issues and work with user to resolve before continuing</action>

View File

@ -18,22 +18,7 @@
<critical>🎯 ZERO USER INTERVENTION: Process should be fully automated except for initial epic/story selection or missing documents</critical>
<step n="1" goal="Determine target story">
<check if="{project-root}/_bmad/.current_project exists">
<action>Read content as project_suffix</action>
<!-- Sanitization and Validation -->
<action>Trim whitespace and newlines from project_suffix</action>
<check if="project_suffix contains '..' or starts with '/' or starts with '\'">
<output>🚫 Security Error: Invalid project context path detected.</output>
<action>HALT</action>
</check>
<check if="project_suffix matches regex '[^a-zA-Z0-9._-]|^\s*$'">
<output>🚫 Error: Project context must only contain alphanumeric characters, dots, dashes, or underscores.</output>
<action>HALT</action>
</check>
<action>Override output_folder to {project-root}/_bmad-output/{project_suffix}</action>
<action>Output "Monorepo context detected. Output folder redirected to: {output_folder}"</action>
</check>
<check if="{{story_path}} is provided by user or user provided the epic and story number such as 2-4 or 1.6 or epic 1 story 5">
<action>Parse user-provided story path: extract epic_num, story_num, story_title from format like "1-2-user-auth"</action>
<action>Set {{epic_num}}, {{story_num}}, {{story_key}} from user input</action>

View File

@ -13,25 +13,7 @@
<critical>User skill level ({user_skill_level}) affects conversation style ONLY, not code updates.</critical>
<step n="1" goal="Find next ready story and load it" tag="sprint-status">
<check if="{project-root}/_bmad/.current_project exists">
<action>Read content as project_suffix</action>
<!-- Sanitization and Validation -->
<action>Trim whitespace and newlines from project_suffix</action>
<check if="project_suffix contains '..' or starts with '/' or starts with '\'">
<output>🚫 Security Error: Invalid project context path detected.</output>
<action>HALT</action>
</check>
<check if="project_suffix matches regex '[^a-zA-Z0-9._-]|^\s*$'">
<output>🚫 Error: Project context must only contain alphanumeric characters, dots, dashes, or underscores.</output>
<action>HALT</action>
</check>
<check if="project_suffix.length > 100">
<output>🚫 Error: Project context name is too long (max 100 characters).</output>
<action>HALT</action>
</check>
<action>Override output_folder to {project-root}/_bmad-output/{project_suffix}</action>
</check>
<check if="{{story_path}} is provided">
<action>Use {{story_path}} directly</action>
<action>Read COMPLETE story file</action>

View File

@ -3,17 +3,6 @@
<critical>The workflow execution engine is governed by: {project-root}/_bmad/core/tasks/workflow.xml</critical>
<critical>You MUST have already loaded and processed: {installed_path}/workflow.yaml</critical>
## 1. Configuration Loading
Load and read full config from {main_config} and resolve basic variables.
**Monorepo Context Check:**
1. Check if `{project-root}/_bmad/.current_project` exists.
2. If it exists, read its content as `{project_suffix}` and override output folder:
- `output_folder`: `{project-root}/_bmad-output/{project_suffix}`
3. Re-derive dependent path variables to reflect the new `output_folder`:
- `implementation_artifacts`: `{output_folder}/implementation`
- `planning_artifacts`: `{output_folder}/planning`
<critical>Communicate all responses in {communication_language} and language MUST be tailored to {user_skill_level}</critical>
<critical>Generate all documents in {document_output_language}</critical>

View File

@ -4,14 +4,6 @@
<critical>You MUST have already loaded and processed: {project-root}/_bmad/bmm/workflows/4-implementation/sprint-planning/workflow.yaml</critical>
## 📚 Document Discovery
### 1. Configuration Loading
Load and read full config from {main_config} and resolve basic variables.
**Monorepo Context Check:**
1. Check if `{project-root}/_bmad/.current_project` exists.
2. If it exists, read its content as `{project_suffix}` and override output folder:
- `output_folder`: `{project-root}/_bmad-output/{project_suffix}`
- Full Epic Loading

View File

@ -2,14 +2,6 @@
<critical>The workflow execution engine is governed by: {project-root}/_bmad/core/tasks/workflow.xml</critical>
## 1. Configuration Loading
Load and read full config from {main_config} and resolve basic variables.
**Monorepo Context Check:**
1. Check if `{project-root}/_bmad/.current_project` exists.
2. If it exists, read its content as `{project_suffix}` and override output folder:
- `output_folder`: `{project-root}/_bmad-output/{project_suffix}`
<critical>You MUST have already loaded and processed: {project-root}/_bmad/bmm/workflows/4-implementation/sprint-status/workflow.yaml</critical>
<critical>Modes: interactive (default), validate, data</critical>
<critical>⚠️ ABSOLUTELY NO TIME ESTIMATES. Do NOT mention hours, days, weeks, or timelines.</critical>

View File

@ -27,11 +27,6 @@ This uses **step-file architecture** for focused execution:
Load and read full config from {main_config} and resolve basic variables.
**Monorepo Context Check:**
1. Check if `{project-root}/_bmad/.current_project` exists.
2. If it exists, read its content as `{project_suffix}` and override output folder:
- `output_folder`: `{project-root}/_bmad-output/{project_suffix}`
Load config from `{project-root}/_bmad/bmm/config.yaml` and resolve:
- `user_name`, `communication_language`, `user_skill_level`

View File

@ -68,11 +68,6 @@ This uses **step-file architecture** for disciplined execution:
Load and read full config from {main_config} and resolve basic variables.
**Monorepo Context Check:**
1. Check if `{project-root}/_bmad/.current_project` exists.
2. If it exists, read its content as `{project_suffix}` and override output folder:
- `output_folder`: `{project-root}/_bmad-output/{project_suffix}`
Load config from `{project-root}/_bmad/bmm/config.yaml` and resolve:
- `project_name`, `planning_artifacts`, `implementation_artifacts`, `user_name`
- `communication_language`, `document_output_language`, `user_skill_level`

View File

@ -83,10 +83,6 @@
Load and read full config from {main_config} and resolve basic variables.
**Monorepo Context Check:**
1. Check if `{project-root}/_bmad/.current_project` exists.
2. If it exists, read its content as `{project_suffix}` and override output folder:
- `output_folder`: `{project-root}/_bmad-output/{project_suffix}`
<step n="3" goal="Check for existing documentation and determine workflow mode" if="resume_mode == false">
<action>Check if {project_knowledge}/index.md exists</action>

View File

@ -95,15 +95,7 @@ Your choice [1/2/3]:
- Best for: Quick project overview, initial understanding
- File reading: Minimal (configs, README, package.json, etc.)
## 1. Configuration Loading
Load and read full config from {main_config} and resolve basic variables.
**Monorepo Context Check:**
1. Check if `{project-root}/_bmad/.current_project` exists.
2. If it exists, read its content as `{project_suffix}` and override output folder:
- `output_folder`: `{project-root}/_bmad-output/{project_suffix}`
json, etc.)
**2. Deep Scan** (10-30 minutes)

View File

@ -29,12 +29,6 @@ This uses **micro-file architecture** for disciplined execution:
Load and read full config from {main_config} and resolve basic variables.
**Monorepo Context Check:**
1. Check if `{project-root}/_bmad/.current_project` exists.
2. If it exists, read its content as `{project_suffix}` and override output folder:
- `output_folder`: `{project-root}/_bmad-output/{project_suffix}`
Load config from `{project-root}/_bmad/bmm/config.yaml` and resolve:
- `project_name`, `output_folder`, `user_name`
- `communication_language`, `document_output_language`, `user_skill_level`

View File

@ -18,14 +18,6 @@ Check project for existing test framework:
- Search online for current recommended test framework for that stack
- Suggest the meta framework and use it (or ask user to confirm)
### 1. Configuration Loading
Load and read full config from {main_config} and resolve basic variables.
**Monorepo Context Check:**
1. Check if `{project-root}/_bmad/.current_project` exists.
2. If it exists, read its content as `{project_suffix}` and override output folder:
- `output_folder`: `{project-root}/_bmad-output/{project_suffix}`
### Step 1: Identify Features

View File

@ -18,7 +18,11 @@
<flow>
<step n="1" title="Load and Initialize Workflow">
<substep n="1a" title="Load Configuration and Resolve Variables">
<substep n="1a" title="Monorepo Context Injection">
{{monorepo_context_logic}}
</substep>
<substep n="1b" title="Load Configuration and Resolve Variables">
<action>Read workflow.yaml from provided path</action>
<mandate>Load config_source (REQUIRED for all modules)</mandate>
<phase n="1">Load external config from config_source path</phase>
@ -27,7 +31,7 @@
<phase n="4">Ask user for input of any variables that are still unknown</phase>
</substep>
<substep n="1b" title="Load Required Components">
<substep n="1c" title="Load Required Components">
<mandate>Instructions: Read COMPLETE file from path OR embedded list (REQUIRED)</mandate>
<check>If template path → Read COMPLETE template file</check>
<check>If validation path → Note path for later loading when needed</check>
@ -35,7 +39,7 @@
<note>Data files (csv, json) → Store paths only, load on-demand when instructions reference them</note>
</substep>
<substep n="1c" title="Initialize Output" if="template-workflow">
<substep n="1d" title="Initialize Output" if="template-workflow">
<action>Resolve default_output_file path with all variables and {{date}}</action>
<action>Create output directory if doesn't exist</action>
<action>If template-workflow → Write template to output file with placeholders</action>

View File

@ -20,6 +20,7 @@
<flow>
<step n="1" title="Method Registry Loading">
<!-- Retained inline check because this workflow may be invoked as a standalone tool mid-conversation, bypassing the OS-level context injection. -->
<check if="{project-root}/_bmad/.current_project exists">
<action>Read content as project_suffix</action>
<!-- Sanitization and Validation -->

View File

@ -36,12 +36,6 @@ This uses **micro-file architecture** for disciplined execution:
Load and read full config from {main_config} and resolve basic variables.
**Monorepo Context Check:**
1. Check if `{project-root}/_bmad/.current_project` exists.
2. If it exists, read its content as `{project_suffix}` and override output folder:
- `output_folder`: `{project-root}/_bmad-output/{project_suffix}`
Load config from `{project-root}/_bmad/core/config.yaml` and resolve:
- `project_name`, `output_folder`, `user_name`
- `communication_language`, `document_output_language`, `user_skill_level`

View File

@ -29,11 +29,6 @@ This uses **micro-file architecture** with **sequential conversation orchestrati
Load and read full config from {main_config} and resolve basic variables.
**Monorepo Context Check:**
1. Check if `{project-root}/_bmad/.current_project` exists.
2. If it exists, read its content as `{project_suffix}` and override output folder:
- `output_folder`: `{project-root}/_bmad-output/{project_suffix}`
- `project_name`, `output_folder`, `user_name`
- `communication_language`, `document_output_language`, `user_skill_level`
- `date` as a system-generated value

View File

@ -0,0 +1,206 @@
/**
* Context Logic Integration Tests
*
* Validates the centralized monorepo context logic deduplication:
* 1. context-logic.js exports a valid XML block
* 2. All workflow templates that need it use the {{monorepo_context_logic}} placeholder
* 3. No stale hardcoded <monorepo-context-check> blocks exist in templates
* 4. src/core/tasks/workflow.xml uses the placeholder (not a hardcoded block)
* 5. All JS consumers correctly import context-logic.js
* 6. MONOREPO_CONTEXT_LOGIC string integrity (key fields are present)
*/
const fs = require('fs-extra');
const path = require('node:path');
// ANSI colors
const c = {
reset: '\u001B[0m',
green: '\u001B[32m',
red: '\u001B[31m',
yellow: '\u001B[33m',
cyan: '\u001B[36m',
dim: '\u001B[2m',
};
let passed = 0;
let failed = 0;
function ok(condition, testName, detail = '') {
if (condition) {
console.log(`${c.green}${c.reset} ${testName}`);
passed++;
} else {
console.log(`${c.red}${c.reset} ${testName}`);
if (detail) console.log(` ${c.dim}${detail}${c.reset}`);
failed++;
}
}
async function readFile(p) {
return fs.readFile(p, 'utf8');
}
async function exists(p) {
return fs.pathExists(p);
}
async function runTests() {
console.log(`${c.cyan}============================================================`);
console.log(' Context Logic Integration Tests');
console.log(`============================================================${c.reset}\n`);
const root = path.join(__dirname, '..');
const sharedDir = path.join(root, 'tools/cli/installers/lib/ide/shared');
const templatesDir = path.join(root, 'tools/cli/installers/lib/ide/templates');
const combinedDir = path.join(templatesDir, 'combined');
// ────────────────────────────────────────────────────────────
// Suite 1: context-logic.js module integrity
// ────────────────────────────────────────────────────────────
console.log(`${c.yellow}Suite 1: context-logic.js module integrity${c.reset}\n`);
const contextLogicPath = path.join(sharedDir, 'context-logic.js');
ok(await exists(contextLogicPath), 'context-logic.js file exists');
let MONOREPO_CONTEXT_LOGIC;
try {
const mod = require(contextLogicPath);
ok(typeof mod.MONOREPO_CONTEXT_LOGIC === 'string', 'exports MONOREPO_CONTEXT_LOGIC as a string');
ok(mod.MONOREPO_CONTEXT_LOGIC.length > 0, 'MONOREPO_CONTEXT_LOGIC is non-empty');
MONOREPO_CONTEXT_LOGIC = mod.MONOREPO_CONTEXT_LOGIC;
} catch (error) {
ok(false, 'context-logic.js is require()-able', error.message);
MONOREPO_CONTEXT_LOGIC = '';
}
// Key content checks
ok(MONOREPO_CONTEXT_LOGIC.includes('<monorepo-context-check'), 'has opening <monorepo-context-check> tag');
ok(MONOREPO_CONTEXT_LOGIC.includes('</monorepo-context-check>'), 'has closing </monorepo-context-check> tag');
ok(MONOREPO_CONTEXT_LOGIC.includes('#project:NAME'), 'documents #project:NAME syntax');
ok(MONOREPO_CONTEXT_LOGIC.includes('#p:NAME'), 'documents #p:NAME short alias');
ok(MONOREPO_CONTEXT_LOGIC.includes('.current_project'), 'includes .current_project fallback logic');
ok(MONOREPO_CONTEXT_LOGIC.includes('path traversal'), 'includes path traversal security check');
ok(MONOREPO_CONTEXT_LOGIC.includes('output_folder'), 'overrides output_folder path variable');
ok(MONOREPO_CONTEXT_LOGIC.includes('planning_artifacts'), 'overrides planning_artifacts path variable');
ok(MONOREPO_CONTEXT_LOGIC.includes('HALT'), 'halts on security violation');
console.log('');
// ────────────────────────────────────────────────────────────
// Suite 2: JS consumers import context-logic.js correctly
// ────────────────────────────────────────────────────────────
console.log(`${c.yellow}Suite 2: JS consumers import context-logic.js${c.reset}\n`);
const consumers = [
{
file: 'tools/cli/installers/lib/core/installer.js',
expectedImport: "require('../ide/shared/context-logic')",
},
{
file: 'tools/cli/installers/lib/ide/_config-driven.js',
expectedImport: "require('./shared/context-logic')",
},
{
file: 'tools/cli/installers/lib/ide/shared/workflow-command-generator.js',
expectedImport: "require('./context-logic')",
},
];
for (const { file, expectedImport } of consumers) {
const fullPath = path.join(root, file);
const content = await readFile(fullPath);
ok(content.includes(expectedImport), `${path.basename(file)} imports context-logic correctly`);
ok(content.includes("replaceAll('{{monorepo_context_logic}}'"), `${path.basename(file)} uses replaceAll for placeholder`);
}
console.log('');
// ────────────────────────────────────────────────────────────
// Suite 3: Templates use placeholder, not hardcoded blocks
// ────────────────────────────────────────────────────────────
console.log(`${c.yellow}Suite 3: Templates use {{monorepo_context_logic}} placeholder${c.reset}\n`);
// These templates MUST have the placeholder (they are rendered directly as IDE workflow commands)
const mustHavePlaceholder = [
path.join(templatesDir, 'workflow-command-template.md'),
path.join(templatesDir, 'workflow-commander.md'),
path.join(combinedDir, 'antigravity.md'),
path.join(combinedDir, 'claude-workflow.md'),
path.join(combinedDir, 'claude-workflow-yaml.md'),
path.join(combinedDir, 'default-workflow.md'),
path.join(combinedDir, 'default-workflow-yaml.md'),
path.join(combinedDir, 'kiro-workflow.md'),
path.join(combinedDir, 'opencode-workflow.md'),
path.join(combinedDir, 'windsurf-workflow.md'),
];
for (const filePath of mustHavePlaceholder) {
const rel = path.relative(root, filePath);
const content = await readFile(filePath);
ok(content.includes('{{monorepo_context_logic}}'), `${path.basename(filePath)} has {{monorepo_context_logic}} placeholder`);
// Must NOT have raw hardcoded block (only the shared module should have it)
ok(!content.includes('<monorepo-context-check'), `${path.basename(filePath)} has NO hardcoded <monorepo-context-check> block`);
}
console.log('');
// ────────────────────────────────────────────────────────────
// Suite 4: No rogue hardcoded blocks anywhere in templates dir
// ────────────────────────────────────────────────────────────
console.log(`${c.yellow}Suite 4: No hardcoded blocks in templates directory${c.reset}\n`);
const walkDir = async (dir) => {
const entries = await fs.readdir(dir, { withFileTypes: true });
const files = [];
for (const e of entries) {
const full = path.join(dir, e.name);
if (e.isDirectory()) files.push(...(await walkDir(full)));
else if (e.isFile()) files.push(full);
}
return files;
};
const allTemplateFiles = await walkDir(templatesDir);
const rogueFiles = [];
for (const f of allTemplateFiles) {
const content = await readFile(f);
if (content.includes('<monorepo-context-check') && !f.includes('context-logic.js')) {
rogueFiles.push(path.relative(root, f));
}
}
ok(
rogueFiles.length === 0,
`No hardcoded <monorepo-context-check> blocks in templates (found ${rogueFiles.length})`,
rogueFiles.length > 0 ? `Rogue files: ${rogueFiles.join(', ')}` : '',
);
console.log('');
// ────────────────────────────────────────────────────────────
// Suite 5: src/core/tasks/workflow.xml uses placeholder
// ────────────────────────────────────────────────────────────
console.log(`${c.yellow}Suite 5: src/core/tasks/workflow.xml uses placeholder${c.reset}\n`);
const srcWorkflowXml = path.join(root, 'src/core/tasks/workflow.xml');
ok(await exists(srcWorkflowXml), 'src/core/tasks/workflow.xml exists');
const srcXmlContent = await readFile(srcWorkflowXml);
ok(srcXmlContent.includes('{{monorepo_context_logic}}'), 'workflow.xml (src) uses {{monorepo_context_logic}} placeholder');
ok(!srcXmlContent.includes('<monorepo-context-check'), 'workflow.xml (src) has NO hardcoded <monorepo-context-check> block');
// ────────────────────────────────────────────────────────────
// Results
// ────────────────────────────────────────────────────────────
console.log(`\n${c.cyan}============================================================`);
console.log(` Results: ${c.green}${passed} passed${c.reset}${c.cyan}, ${c.red}${failed} failed${c.reset}${c.cyan}`);
console.log(`============================================================${c.reset}\n`);
if (failed === 0) {
console.log(`${c.green}✨ All context-logic integration tests passed!${c.reset}\n`);
process.exit(0);
} else {
console.log(`${c.red}${failed} test(s) failed${c.reset}\n`);
process.exit(1);
}
}
runTests().catch((error) => {
console.error(error);
process.exit(1);
});

View File

@ -1,9 +1,19 @@
/**
* Monorepo Support Validation Tests
*
* Verifies that:
* Architecture after deduplication:
* - Monorepo context logic lives ONLY in context-logic.js
* - workflow.xml (src) uses {{monorepo_context_logic}} placeholder injected at install time
* - Individual source workflow files do NOT have inline checks (that's the deduplication!)
* - Only code-review/instructions.xml, dev-story/instructions.xml, create-story/instructions.xml
* and advanced-elicitation/workflow.xml are XML workflows checked; XML workflows that go through
* workflow.xml no longer need inline checks.
*
* Verifies:
* 1. The set-project workflow is correctly registered.
* 2. All core and BMM workflows contain the monorepo context logic.
* 2. No source workflow file has a stale inline "Monorepo Context Check" block.
* 3. Only the canonical SINGLE source (context-logic.js) defines the check.
* 4. set-project implementation still manages .current_project.
*/
const fs = require('fs-extra');
@ -55,46 +65,39 @@ async function runTests() {
console.log('');
// 2. Verify context logic in workflows
console.log(`${colors.yellow}Test Suite 2: Workflow Context Logic${colors.reset}\n`);
// 2. Verify NO stale inline "Monorepo Context Check" blocks in source workflow files
// These are redundant since workflow.xml now handles context injection via context-logic.js
console.log(`${colors.yellow}Test Suite 2: No Stale Inline Monorepo Context Checks${colors.reset}\n`);
console.log(` ${colors.dim}(Inline checks were moved to workflow.xml via context-logic.js)${colors.reset}\n`);
const workflowFiles = glob.sync('src/{core,bmm}/workflows/**/*.{md,xml}', { cwd: projectRoot });
// Workflows that MUST have the check
const requiredWorkflows = [
'brainstorming',
'party-mode',
'create-product-brief',
'create-prd',
'create-architecture',
'code-review',
'create-story',
'dev-story',
'set-project', // Should not have the check itself, but let's exclude it
];
for (const file of workflowFiles) {
const basename = path.basename(path.dirname(file));
if (basename === 'set-project' || basename === '0-context') continue;
// skip the context-logic source itself (it's the canonical source)
if (file.includes('context-logic')) continue;
const content = await fs.readFile(path.join(projectRoot, file), 'utf8');
const isXml = file.endsWith('.xml');
if (isXml) {
assert(content.includes('_bmad/.current_project'), `XML workflow contains context check: ${file}`);
} else {
// Only check Markdown files that look like main workflow/instruction files
const filename = path.basename(file);
if (filename.includes('workflow') || filename.includes('instructions')) {
assert(content.includes('_bmad/.current_project'), `Markdown workflow contains context check: ${file}`);
}
}
assert(!content.includes('**Monorepo Context Check:**'), `No stale inline check block in: ${file}`);
}
console.log('');
// 3. Verify set-project implementation
console.log(`${colors.yellow}Test Suite 3: set-project Implementation${colors.reset}\n`);
// 3. Verify canonical source is context-logic.js (single source of truth)
console.log(`${colors.yellow}Test Suite 3: Single Source of Truth${colors.reset}\n`);
const contextLogicPath = path.join(projectRoot, 'tools/cli/installers/lib/ide/shared/context-logic.js');
assert(await fs.pathExists(contextLogicPath), 'context-logic.js exists as canonical source');
const srcWorkflowXml = path.join(projectRoot, 'src/core/tasks/workflow.xml');
const xmlContent = await fs.readFile(srcWorkflowXml, 'utf8');
assert(xmlContent.includes('{{monorepo_context_logic}}'), 'workflow.xml uses {{monorepo_context_logic}} placeholder');
assert(!xmlContent.includes('**Monorepo Context Check:**'), 'workflow.xml has no stale inline check');
console.log('');
// 4. Verify set-project implementation
console.log(`${colors.yellow}Test Suite 4: set-project Implementation${colors.reset}\n`);
try {
const setProjectPath = path.join(projectRoot, 'src/bmm/workflows/0-context/set-project/workflow.md');
const exists = await fs.pathExists(setProjectPath);
@ -102,6 +105,7 @@ async function runTests() {
if (exists) {
const content = await fs.readFile(setProjectPath, 'utf8');
assert(content.includes('_bmad/.current_project'), 'set-project implementation manages .current_project');
assert(content.includes('my-app'), 'set-project examples use generic public-friendly names');
}
} catch (error) {
assert(false, 'set-project check failed', error.message);

View File

@ -16,6 +16,7 @@ const { IdeConfigManager } = require('./ide-config-manager');
const { CustomHandler } = require('../custom/handler');
const prompts = require('../../../lib/prompts');
const { BMAD_FOLDER_NAME } = require('../ide/shared/path-utils');
const { MONOREPO_CONTEXT_LOGIC } = require('../ide/shared/context-logic');
class Installer {
constructor() {
@ -89,6 +90,10 @@ class Installer {
// Read the file content
let content = await fs.readFile(sourcePath, 'utf8');
// Apply replacements
content = content.replaceAll('{{monorepo_context_logic}}', MONOREPO_CONTEXT_LOGIC);
content = content.replaceAll('_bmad', this.bmadFolderName);
// Write to target with replaced content
await fs.ensureDir(path.dirname(targetPath));
await fs.writeFile(targetPath, content, 'utf8');

View File

@ -391,12 +391,15 @@ LOAD and execute from: {project-root}/{{bmadFolderName}}/{{path}}
// No default
}
const { MONOREPO_CONTEXT_LOGIC } = require('./shared/context-logic');
let rendered = template
.replaceAll('{{name}}', artifact.name || '')
.replaceAll('{{module}}', artifact.module || 'core')
.replaceAll('{{path}}', pathToUse)
.replaceAll('{{description}}', artifact.description || `${artifact.name} ${artifact.type || ''}`)
.replaceAll('{{workflow_path}}', pathToUse);
.replaceAll('{{workflow_path}}', pathToUse)
.replaceAll('{{monorepo_context_logic}}', MONOREPO_CONTEXT_LOGIC);
// Replace _bmad placeholder with actual folder name
rendered = rendered.replaceAll('_bmad', this.bmadFolderName);

View File

@ -0,0 +1,48 @@
/**
* Monorepo Context Logic XML Block
*
* robust, secure, and centralized logic for handling:
* 1. Inline project overrides (#p:NAME)
* 2. .current_project file fallback
* 3. Path variable overrides
*/
const MONOREPO_CONTEXT_LOGIC = `
<monorepo-context-check CRITICAL="TRUE" priority="before-config">
<!-- Step 1: Check for inline project override in user invocation -->
<!-- Supported syntax: #project:NAME (full) or #p:NAME (short alias) -->
<action>Scan user's invocation message for pattern #project:NAME or #p:NAME (case-insensitive)</action>
<check if="inline override found">
<action>Set project_suffix = extracted NAME</action>
<output>🎯 Inline project override: {project_suffix}</output>
</check>
<!-- Step 2: Fall back to .current_project file -->
<check if="project_suffix not yet set AND {project-root}/_bmad/.current_project exists">
<action>Read {project-root}/_bmad/.current_project as project_suffix</action>
</check>
<!-- Step 3: Validate -->
<check if="project_suffix is set">
<action>Trim whitespace and newlines from project_suffix</action>
<!-- Security: Prevent path traversal and invalid chars -->
<check if="project_suffix contains '..' OR starts with '/' OR starts with '\\'">
<output>🚫 Security Error: Invalid project context path traversal detected.</output>
<action>HALT</action>
</check>
<check if="project_suffix matching regex [^a-zA-Z0-9._-]">
<output>🚫 Error: project_suffix must only contain alphanumeric characters, dots, dashes, or underscores.</output>
<action>HALT</action>
</check>
<!-- Step 4: Override path variables -->
<action>Override output_folder = {project-root}/_bmad-output/{project_suffix}</action>
<action>Override planning_artifacts = {output_folder}/planning-artifacts</action>
<action>Override implementation_artifacts = {output_folder}/implementation-artifacts</action>
<action>Override project_knowledge = {output_folder}/knowledge</action>
<action>Override sprint_status_file = {output_folder}/sprint-status.yaml</action>
<output>🗂 Monorepo context: {project_suffix} outputs to {output_folder}</output>
</check>
</monorepo-context-check>
`;
module.exports = { MONOREPO_CONTEXT_LOGIC };

View File

@ -151,12 +151,15 @@ class WorkflowCommandGenerator {
}
}
const { MONOREPO_CONTEXT_LOGIC } = require('./context-logic');
// Replace template variables
return template
.replaceAll('{{name}}', workflow.name)
.replaceAll('{{module}}', workflow.module)
.replaceAll('{{description}}', workflow.description)
.replaceAll('{{workflow_path}}', workflowPath)
.replaceAll('{{monorepo_context_logic}}', MONOREPO_CONTEXT_LOGIC)
.replaceAll('_bmad', this.bmadFolderName);
}

View File

@ -3,6 +3,8 @@ name: '{{name}}'
description: '{{description}}'
---
{{monorepo_context_logic}}
Read the entire workflow file at: {project-root}/_bmad/{{workflow_path}}
Follow all instructions in the workflow file exactly as written.

View File

@ -7,6 +7,8 @@ disable-model-invocation: true
IT IS CRITICAL THAT YOU FOLLOW THESE STEPS - while staying in character as the current agent persona you may have loaded:
<steps CRITICAL="TRUE">
0. {{monorepo_context_logic}}
1. Always LOAD the FULL @{project-root}/{{bmadFolderName}}/core/tasks/workflow.xml
2. READ its entire contents - this is the CORE OS for EXECUTING the specific workflow-config @{project-root}/{{bmadFolderName}}/{{path}}
3. Pass the yaml path @{project-root}/{{bmadFolderName}}/{{path}} as 'workflow-config' parameter to the workflow.xml instructions

View File

@ -4,4 +4,6 @@ description: '{{description}}'
disable-model-invocation: true
---
{{monorepo_context_logic}}
IT IS CRITICAL THAT YOU FOLLOW THIS COMMAND: LOAD the FULL @{project-root}/{{bmadFolderName}}/{{path}}, READ its entire contents and follow its directions exactly!

View File

@ -4,4 +4,6 @@ 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!
{{monorepo_context_logic}}
IT IS CRITICAL THAT YOU FOLLOW THIS COMMAND: LOAD the FULL @{project-root}/{{bmadFolderName}}/{{path}}, READ its entire contents and follow its directions exactly!

View File

@ -8,6 +8,7 @@ Execute the BMAD '{{name}}' workflow.
CRITICAL: You must load and follow the workflow definition exactly.
WORKFLOW INSTRUCTIONS:
{{monorepo_context_logic}}
1. LOAD the workflow file from {project-root}/{{bmadFolderName}}/{{path}}
2. READ its entire contents

View File

@ -5,6 +5,8 @@ auto_execution_mode: "iterate"
# {{name}}
{{monorepo_context_logic}}
Read the entire workflow file at {project-root}/_bmad/{{workflow_path}}
Follow all instructions in the workflow file exactly as written.

View File

@ -3,6 +3,8 @@ description: '{{description}}'
disable-model-invocation: true
---
{{monorepo_context_logic}}
IT IS CRITICAL THAT YOU FOLLOW THESE STEPS - while staying in character as the current agent persona you may have loaded:
<steps CRITICAL="TRUE">

View File

@ -3,4 +3,6 @@ description: '{{description}}'
disable-model-invocation: true
---
{{monorepo_context_logic}}
IT IS CRITICAL THAT YOU FOLLOW THIS COMMAND: LOAD the FULL @{{workflow_path}}, READ its entire contents and follow its directions exactly!