feat(bmm): add monorepo/multi-project support via skills

- Add monorepo_context config section to module.yaml for path resolution
- Add bmad-project-list, bmad-project-new, bmad-project-switch skills
- Config-level hook: LLM reads config and applies context before path vars
- Inline overrides via #project:NAME or #p:NAME in invocations
- Installer: ensure 0-context workflow dir is installed
- Add docs/how-to/monorepo-setup.md
- 100% backward compatible when no .current_project file

Made-with: Cursor
This commit is contained in:
sno 2026-03-15 17:58:58 +01:00
parent 42b1d0f657
commit fad8a7923e
18 changed files with 495 additions and 0 deletions

View File

@ -0,0 +1,48 @@
---
title: "Monorepo / Multi-Project Setup"
description: Run multiple independent projects within a single BMAD installation
sidebar:
order: 8
---
# Monorepo / Multi-Project Setup
BMAD supports running multiple independent projects within a single installation. This is useful for monorepos, multi-app repositories, or any workspace where you want isolated artifact directories per project.
## How It Works
A `.current_project` file in your `_bmad/` directory stores the active project name. When set, all artifact output paths are redirected to project-specific subdirectories.
## Quick Start
1. List existing projects by invoking `skill:bmad-project-list`
2. Create a new project by invoking `skill:bmad-project-new` with a project name
3. Switch to an existing project by invoking `skill:bmad-project-switch` with a project name
4. Clear project context by invoking `skill:bmad-project-switch` and entering `CLEAR`
## Directory Structure
When a project context is active, for example `my-app`:
- `_bmad-output/my-app/planning-artifacts/` for PRDs, briefs, UX designs
- `_bmad-output/my-app/implementation-artifacts/` for sprint plans and stories
- `_bmad-output/my-app/knowledge/` for long-term project knowledge
## Inline Overrides
Temporarily target a different project without changing the global context:
- `#project:my-app`
- `#p:my-app`
**Precedence:**
1. Inline override (`#p:NAME`) — highest priority
2. Global context file (`.current_project`)
3. Default config paths
## Security
- Path traversal (`..`) is rejected
- Absolute paths are rejected
- Only alphanumeric characters, dots, dashes, underscores, and forward slashes are allowed

View File

@ -27,3 +27,15 @@ agent:
- trigger: IR or fuzzy match on implementation-readiness - trigger: IR or fuzzy match on implementation-readiness
exec: "skill:bmad-check-implementation-readiness" exec: "skill:bmad-check-implementation-readiness"
description: "[IR] Implementation Readiness: Ensure the PRD, UX, and Architecture and Epics and Stories List are all aligned" description: "[IR] Implementation Readiness: Ensure the PRD, UX, and Architecture and Epics and Stories List are all aligned"
- trigger: PN or fuzzy match on project-new
exec: "skill:bmad-project-new"
description: "[PN] Project New: Create a new project and make it the active project context"
- trigger: PS or fuzzy match on project-switch
exec: "skill:bmad-project-switch"
description: "[PS] Project Switch: Switch the active project context for monorepo support"
- trigger: PL or fuzzy match on project-list
exec: "skill:bmad-project-list"
description: "[PL] Project List: Show available project contexts"

View File

@ -36,3 +36,15 @@ agent:
- trigger: CR or fuzzy match on code-review - trigger: CR or fuzzy match on code-review
exec: "skill:bmad-code-review" exec: "skill:bmad-code-review"
description: "[CR] Code Review: Initiate a comprehensive code review across multiple quality facets. For best results, use a fresh context and a different quality LLM if available" description: "[CR] Code Review: Initiate a comprehensive code review across multiple quality facets. For best results, use a fresh context and a different quality LLM if available"
- trigger: PN or fuzzy match on project-new
exec: "skill:bmad-project-new"
description: "[PN] Project New: Create a new project and make it the active project context"
- trigger: PS or fuzzy match on project-switch
exec: "skill:bmad-project-switch"
description: "[PS] Project Switch: Switch the active project context for monorepo support"
- trigger: PL or fuzzy match on project-list
exec: "skill:bmad-project-list"
description: "[PL] Project List: Show available project contexts"

View File

@ -42,3 +42,15 @@ agent:
- trigger: CC or fuzzy match on correct-course - trigger: CC or fuzzy match on correct-course
exec: "skill:bmad-correct-course" exec: "skill:bmad-correct-course"
description: "[CC] Course Correction: Use this so we can determine how to proceed if major need for change is discovered mid implementation" description: "[CC] Course Correction: Use this so we can determine how to proceed if major need for change is discovered mid implementation"
- trigger: PN or fuzzy match on project-new
exec: "skill:bmad-project-new"
description: "[PN] Project New: Create a new project and make it the active project context"
- trigger: PS or fuzzy match on project-switch
exec: "skill:bmad-project-switch"
description: "[PS] Project Switch: Switch the active project context for monorepo support"
- trigger: PL or fuzzy match on project-list
exec: "skill:bmad-project-list"
description: "[PL] Project List: Show available project contexts"

View File

@ -34,3 +34,15 @@ agent:
- trigger: CR or fuzzy match on code-review - trigger: CR or fuzzy match on code-review
exec: "skill:bmad-code-review" exec: "skill:bmad-code-review"
description: "[CR] Code Review: Initiate a comprehensive code review across multiple quality facets. For best results, use a fresh context and a different quality LLM if available" description: "[CR] Code Review: Initiate a comprehensive code review across multiple quality facets. For best results, use a fresh context and a different quality LLM if available"
- trigger: PN or fuzzy match on project-new
exec: "skill:bmad-project-new"
description: "[PN] Project New: Create a new project and make it the active project context"
- trigger: PS or fuzzy match on project-switch
exec: "skill:bmad-project-switch"
description: "[PS] Project Switch: Switch the active project context for monorepo support"
- trigger: PL or fuzzy match on project-list
exec: "skill:bmad-project-list"
description: "[PL] Project List: Show available project contexts"

View File

@ -35,3 +35,15 @@ agent:
- trigger: CC or fuzzy match on correct-course - trigger: CC or fuzzy match on correct-course
exec: "skill:bmad-correct-course" exec: "skill:bmad-correct-course"
description: "[CC] Course Correction: Use this so we can determine how to proceed if major need for change is discovered mid implementation" description: "[CC] Course Correction: Use this so we can determine how to proceed if major need for change is discovered mid implementation"
- trigger: PN or fuzzy match on project-new
exec: "skill:bmad-project-new"
description: "[PN] Project New: Create a new project and make it the active project context"
- trigger: PS or fuzzy match on project-switch
exec: "skill:bmad-project-switch"
description: "[PS] Project Switch: Switch the active project context for monorepo support"
- trigger: PL or fuzzy match on project-list
exec: "skill:bmad-project-list"
description: "[PL] Project List: Show available project contexts"

View File

@ -48,3 +48,35 @@ directories:
- "{planning_artifacts}" - "{planning_artifacts}"
- "{implementation_artifacts}" - "{implementation_artifacts}"
- "{project_knowledge}" - "{project_knowledge}"
# --- Monorepo / Multi-Project Context Support ---
# IMPORTANT: When loading this config, apply monorepo context resolution BEFORE using any path variables.
#
# Resolution order:
# 1. INLINE OVERRIDE: Scan user's invocation for #project:NAME or #p:NAME (case-insensitive).
# If found, set project_suffix = NAME.
# 2. FILE FALLBACK: If no inline override, check if {project-root}/_bmad/.current_project exists.
# If so, read its single-line content as project_suffix (trim whitespace/newlines).
# 3. VALIDATE (if project_suffix is set):
# - REJECT if empty, contains '..', starts with '/' or drive letter (e.g. C:)
# - REJECT if fails whitelist regex: ^[a-zA-Z0-9._\-/]+$
# - On rejection: output "Security Error: Invalid project context" and HALT
# 4. OVERRIDE PATHS (if valid project_suffix):
# - output_folder = {project-root}/_bmad-output/{project_suffix}
# - planning_artifacts = {output_folder}/planning-artifacts
# - implementation_artifacts = {output_folder}/implementation-artifacts
# - project_knowledge = {output_folder}/knowledge
# - sprint_status_file = {output_folder}/sprint-status.yaml
# - project_name = {project_suffix}
# 5. If no project_suffix is set, use the default paths from config as-is.
monorepo_context:
enabled: true
context_file: ".current_project"
inline_override_patterns:
- "#project:"
- "#p:"
validation:
whitelist_regex: "^[a-zA-Z0-9._\\-/]+$"
reject_traversal: true
reject_absolute: true

View File

@ -0,0 +1,6 @@
---
name: bmad-project-list
description: 'List available projects for monorepo support. Use when the user says "project list", "list projects", or "show projects"'
---
Follow the instructions in [workflow.md](workflow.md).

View File

@ -0,0 +1 @@
type: skill

View File

@ -0,0 +1,22 @@
# Project List
**Goal:** List the available project contexts for BMAD artifacts.
**Your Role:** Configuration Assistant.
## WORKFLOW ARCHITECTURE
### 1. Identify Projects
- Use your file listing or system capabilities to examine the `{project-root}/_bmad-output/` directory.
- Identify all subdirectories within this path. Each subdirectory represents an available project context, except:
- Ignore hidden directories starting with `.`
- Ignore standard BMAD artifact directories: `planning-artifacts`, `implementation-artifacts`, `test-artifacts`, `knowledge`, `docs`, `assets`
- The root `_bmad-output/` directory itself represents the `default (root)` unset project context.
### 2. Output Results
1. Display a formatted numbered list of the existing projects you found.
2. Clearly indicate the `default (root)` project context, usually as `[0]`.
3. Check whether an active project is currently set by reading the active project selector file in `{project-root}/_bmad/` (or as configured in `monorepo_context.context_file`). If it exists, indicate which project from the list is active, for example by adding `(ACTIVE)` next to the entry.
4. Inform the user that they can switch projects with PS (Project Switch) or create a new one with PN (Project New).

View File

@ -0,0 +1,6 @@
---
name: bmad-project-new
description: 'Create a new project for monorepo support. Use when the user says "project new", "new project", or "create project"'
---
Follow the instructions in [workflow.md](workflow.md).

View File

@ -0,0 +1 @@
type: skill

View File

@ -0,0 +1,54 @@
# Project New
**Goal:** Create a new project context with the standard BMAD artifact structure and make it active.
**Your Role:** Configuration Assistant.
## WORKFLOW ARCHITECTURE
This is a single-step workflow that creates directories and updates the active project context file.
### 1. Configuration Loading
Load and read full config from `{project-root}/_bmad/bmm/config.yaml` and resolve variables and artifact paths.
The context file is the default hidden project selector file in `{project-root}/_bmad/` (or as configured in `monorepo_context.context_file`).
### 2. Project Creation
1. **Analyze Request**: Determine the requested new project name from the user's initial invocation, such as "new project my-app".
2. **Wait for Input (If Missing)**: If the user did not provide a project name:
- Ask: `What should the new project be called?`
- Wait for input.
3. **Validate Project Name**
- **Cleanup**: Remove leading and trailing slashes and any occurrences of `_bmad-output/`.
- **Validate - No Traversal**: Reject if path contains `..`.
- **Validate - No Absolute**: Reject if path starts with `/` or a drive letter such as `C:`.
- **Validate - Empty/Whitespace**: Reject if empty or only whitespace.
- **Validate - Whitelist**: Match against regex `^[-a-zA-Z0-9._/]+$`.
- If invalid:
- Output: `Error: Invalid project name — must be a relative path and contain only alphanumeric characters, dots, dashes, underscores, or slashes. Traversal (..) is strictly forbidden.`
- Halt.
4. **Check for Existing Project**
- Check if `{project-root}/_bmad-output/<sanitized_path>` already exists.
- If it already exists:
- Ask: `The project <sanitized_path> already exists. Do you want to switch to it instead? (Yes/No)`
- Wait for input.
- If `Yes`:
- Write the active project selector file in `{project-root}/_bmad/` with content `<sanitized_path>`
- Output: `Project context set to: <sanitized_path>. Artifacts will go to _bmad-output/<sanitized_path>/.`
- Halt.
- If `No`:
- Inform the user to choose another name or use PS (Project Switch) to switch to an existing project.
- Halt.
5. **Create Project Structure**
- Create directory: `{project-root}/_bmad-output/<sanitized_path>/`
- Create directory: `{project-root}/_bmad-output/<sanitized_path>/planning-artifacts/`
- Create directory: `{project-root}/_bmad-output/<sanitized_path>/implementation-artifacts/`
- Create directory: `{project-root}/_bmad-output/<sanitized_path>/knowledge/`
- Write the active project selector file in `{project-root}/_bmad/` with content `<sanitized_path>`
- Output: `Created project <sanitized_path> and set it as active. Artifacts will go to _bmad-output/<sanitized_path>/.`
### 3. Verification
- Display the full resolved output path for confirmation.
- Mention that the user can use PS (Project Switch) to switch projects later and PL (Project List) to list available projects.

View File

@ -0,0 +1,6 @@
---
name: bmad-project-switch
description: 'Switch the active project for monorepo support. Use when the user says "project switch", "switch project", or "choose project"'
---
Follow the instructions in [workflow.md](workflow.md).

View File

@ -0,0 +1 @@
type: skill

View File

@ -0,0 +1,80 @@
# Project Switch
**Goal:** Switch the active project path for BMAD artifacts.
**Your Role:** Configuration Assistant.
## WORKFLOW ARCHITECTURE
This is a single-step workflow that updates a local state file.
### 1. Configuration Loading
Load and read full config from `{project-root}/_bmad/bmm/config.yaml` and resolve variables and artifact paths.
The context file is the default hidden project selector file in `{project-root}/_bmad/` (or as configured in `monorepo_context.context_file`).
### 2. Context Management
1. **Analyze Request**: Determine the requested project name from the user's initial invocation, such as "switch project my-app".
2. **Wait for Input (If Missing)**: If the user did not provide a project name:
- Use your file listing capabilities to examine the `{project-root}/_bmad-output/` directory.
- Output a formatted list of the existing projects you found, explicitly noting the `default (root)` project context.
Exclude hidden directories and standard BMAD artifact folders: `planning-artifacts`, `implementation-artifacts`, `test-artifacts`, `knowledge`, `docs`, `assets`.
- Present options in a numbered list format:
```text
Select a project:
[0] default (root) - Current location for standard artifacts
[1] project-name-1
[2] project-name-2
...
Enter a number to select, type an existing project name, or enter 'CLEAR' to reset to root:
```
- Wait for input.
3. **Process Input**: Once a project name is established:
- **Case: CLEAR**
- Delete the active project selector file in `{project-root}/_bmad/`
- Output: `Project context cleared. Artifacts will go to root _bmad-output/.`
- Halt.
- **Case: Numeric Selection**
- Map the number to the corresponding project from your earlier listing.
- If number is `0`, treat it as the `CLEAR` case.
- If the number is out of range, show an error and ask again.
- If valid, use that project name as the path.
- **Case: Path Provided** (text input)
- **Cleanup**: Remove leading and trailing slashes and any occurrences of `_bmad-output/`.
- **Validate - No Traversal**: Reject if path contains `..`.
- **Validate - No Absolute**: Reject if path starts with `/` or a drive letter such as `C:`.
- **Validate - Empty/Whitespace**: Reject if empty or only whitespace.
- **Validate - Whitelist**: Match against regex `^[-a-zA-Z0-9._/]+$`.
- **Check Results**
- If invalid:
- Output: `Error: Invalid project name — must be a relative path and contain only alphanumeric characters, dots, dashes, underscores, or slashes. Traversal (..) is strictly forbidden.`
- Halt.
- **Validate Existence**: Check if `{project-root}/_bmad-output/<sanitized_path>` exists on disk.
- If it does not exist:
- Output: `Error: Project <sanitized_path> does not exist. Use PN (Project New) to create it first, or PL (Project List) to see available projects.`
- Halt.
- Write the active project selector file in `{project-root}/_bmad/` with content `<sanitized_path>`
- Output: `Project context set to: <sanitized_path>. Artifacts will go to _bmad-output/<sanitized_path>/.`
### 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`) — highest priority
2. **Global Context File** (the active project selector file in `{project-root}/_bmad/`)
3. **Default Config** (if neither is present)

View File

@ -15,6 +15,7 @@ const path = require('node:path');
const os = require('node:os'); const os = require('node:os');
const fs = require('fs-extra'); const fs = require('fs-extra');
const { YamlXmlBuilder } = require('../tools/cli/lib/yaml-xml-builder'); const { YamlXmlBuilder } = require('../tools/cli/lib/yaml-xml-builder');
const { Installer } = require('../tools/cli/installers/lib/core/installer');
const { ManifestGenerator } = require('../tools/cli/installers/lib/core/manifest-generator'); const { ManifestGenerator } = require('../tools/cli/installers/lib/core/manifest-generator');
const { IdeManager } = require('../tools/cli/installers/lib/ide/manager'); const { IdeManager } = require('../tools/cli/installers/lib/ide/manager');
const { clearCache, loadPlatformCodes } = require('../tools/cli/installers/lib/ide/platform-codes'); const { clearCache, loadPlatformCodes } = require('../tools/cli/installers/lib/ide/platform-codes');
@ -1868,6 +1869,118 @@ async function runTests() {
console.log(''); console.log('');
// ============================================================
// Test 32: Monorepo config block emission
// ============================================================
console.log(`${colors.yellow}Test Suite 32: Monorepo Config Generation${colors.reset}\n`);
let tempFixture32;
try {
tempFixture32 = await fs.mkdtemp(path.join(os.tmpdir(), 'bmad-monorepo-config-'));
await fs.ensureDir(path.join(tempFixture32, 'bmm'));
const installer32 = new Installer();
const sourceModule32 = await fs.readFile(path.join(projectRoot, 'src', 'bmm', 'module.yaml'), 'utf8');
const extractedBlock32 = installer32.extractMonorepoContextBlock(sourceModule32);
assert(
extractedBlock32 && extractedBlock32.includes('monorepo_context:'),
'Installer extracts monorepo context block from module schema',
);
assert(extractedBlock32 && extractedBlock32.includes('INLINE OVERRIDE'), 'Extracted monorepo block preserves instructional comments');
await installer32.generateModuleConfigs(tempFixture32, {
core: {
user_name: 'TestUser',
communication_language: 'English',
document_output_language: 'English',
output_folder: '_bmad-output',
},
bmm: {
project_name: 'demo-project',
user_skill_level: 'intermediate',
planning_artifacts: '{project-root}/_bmad-output/planning-artifacts',
implementation_artifacts: '{project-root}/_bmad-output/implementation-artifacts',
project_knowledge: '{project-root}/docs',
},
});
const generatedConfig32 = await fs.readFile(path.join(tempFixture32, 'bmm', 'config.yaml'), 'utf8');
assert(
generatedConfig32.includes('# --- Monorepo / Multi-Project Context Support ---'),
'Generated bmm config preserves monorepo instruction header',
);
assert(generatedConfig32.includes('monorepo_context:'), 'Generated bmm config includes monorepo_context YAML');
assert(
generatedConfig32.includes('inline_override_patterns:') && generatedConfig32.includes('- "#p:"'),
'Generated bmm config preserves monorepo override patterns',
);
} catch (error) {
assert(false, 'Monorepo config generation test succeeds', error.message);
} finally {
if (tempFixture32) await fs.remove(tempFixture32).catch(() => {});
}
console.log('');
// ============================================================
// Test 33: Context skills are discoverable as skills
// ============================================================
console.log(`${colors.yellow}Test Suite 33: Context Skill Discovery${colors.reset}\n`);
let tempFixture33;
try {
tempFixture33 = await fs.mkdtemp(path.join(os.tmpdir(), 'bmad-context-skills-'));
await fs.ensureDir(path.join(tempFixture33, '_config'));
await fs.ensureDir(path.join(tempFixture33, 'bmm', 'agents'));
await fs.writeFile(path.join(tempFixture33, 'bmm', 'agents', 'test.md'), '<agent name="Test" title="T"><persona>p</persona></agent>');
const sourceContextDir33 = path.join(projectRoot, 'src', 'bmm', 'workflows', '0-context');
const targetContextDir33 = path.join(tempFixture33, 'bmm', 'workflows', '0-context');
await fs.copy(sourceContextDir33, targetContextDir33);
const generator33 = new ManifestGenerator();
await generator33.generateManifests(tempFixture33, ['bmm'], [], { ides: [] });
const newProjectSkill33 = generator33.skills.find((skill) => skill.canonicalId === 'bmad-project-new');
const setProjectSkill33 = generator33.skills.find((skill) => skill.canonicalId === 'bmad-project-switch');
const listProjectsSkill33 = generator33.skills.find((skill) => skill.canonicalId === 'bmad-project-list');
assert(newProjectSkill33 !== undefined, 'New project skill appears in skills[]');
assert(setProjectSkill33 !== undefined, 'Set project context skill appears in skills[]');
assert(listProjectsSkill33 !== undefined, 'List projects skill appears in skills[]');
assert(
newProjectSkill33 && newProjectSkill33.path.includes('workflows/0-context/bmad-project-new/SKILL.md'),
'New project skill keeps its workflow directory path',
);
assert(
setProjectSkill33 && setProjectSkill33.path.includes('workflows/0-context/bmad-project-switch/SKILL.md'),
'Set project skill keeps its workflow directory path',
);
assert(
listProjectsSkill33 && listProjectsSkill33.path.includes('workflows/0-context/bmad-project-list/SKILL.md'),
'List projects skill keeps its workflow directory path',
);
assert(
!generator33.workflows.some((workflow) => workflow.path.includes('0-context/bmad-project-new')),
'New project skill does not appear in workflows[]',
);
assert(
!generator33.workflows.some((workflow) => workflow.path.includes('0-context/bmad-project-switch')),
'Set project context skill does not appear in workflows[]',
);
assert(
!generator33.workflows.some((workflow) => workflow.path.includes('0-context/bmad-project-list')),
'List projects skill does not appear in workflows[]',
);
} catch (error) {
assert(false, 'Context skill discovery test succeeds', error.message);
} finally {
if (tempFixture33) await fs.remove(tempFixture33).catch(() => {});
}
console.log('');
// ============================================================ // ============================================================
// Summary // Summary
// ============================================================ // ============================================================

View File

@ -2067,6 +2067,12 @@ class Installer {
} }
} }
const staticConfigBlocks = await this.getStaticConfigBlocks(moduleName);
if (staticConfigBlocks.length > 0) {
yamlContent = yamlContent.trimEnd();
yamlContent += `\n\n${staticConfigBlocks.join('\n\n')}\n`;
}
// Write the clean config file with POSIX-compliant final newline // Write the clean config file with POSIX-compliant final newline
const content = header + yamlContent; const content = header + yamlContent;
await fs.writeFile(configPath, content.endsWith('\n') ? content : content + '\n', 'utf8'); await fs.writeFile(configPath, content.endsWith('\n') ? content : content + '\n', 'utf8');
@ -2077,6 +2083,65 @@ class Installer {
} }
} }
/**
* Return static config blocks that should be preserved from a module schema
* even though they are not collected interactively.
* @param {string} moduleName - Module name
* @returns {Promise<string[]>} Raw YAML/comment blocks to append to config.yaml
*/
async getStaticConfigBlocks(moduleName) {
const moduleSchemaPath = await this.resolveModuleSchemaPath(moduleName);
if (!moduleSchemaPath || !(await fs.pathExists(moduleSchemaPath))) {
return [];
}
const content = await fs.readFile(moduleSchemaPath, 'utf8');
const monorepoBlock = this.extractMonorepoContextBlock(content);
return monorepoBlock ? [monorepoBlock] : [];
}
/**
* Resolve the source module.yaml path for a module.
* @param {string} moduleName - Module name
* @returns {Promise<string|null>} Path to module.yaml or null if not found
*/
async resolveModuleSchemaPath(moduleName) {
if (this.configCollector.customModulePaths?.has(moduleName)) {
return path.join(this.configCollector.customModulePaths.get(moduleName), 'module.yaml');
}
const standardPath = path.join(getModulePath(moduleName), 'module.yaml');
if (await fs.pathExists(standardPath)) {
return standardPath;
}
const moduleSourcePath = await this.moduleManager.findModuleSource(moduleName, { silent: true });
return moduleSourcePath ? path.join(moduleSourcePath, 'module.yaml') : null;
}
/**
* Extract the monorepo context instructions and YAML block from module.yaml.
* @param {string} content - Raw module.yaml content
* @returns {string|null} Preserved block or null when absent
*/
extractMonorepoContextBlock(content) {
// Capture from the header comment through to the end of the monorepo_context YAML block.
// The block is expected at the end of the file, after any other config sections.
const headerIndex = content.indexOf('# --- Monorepo / Multi-Project Context Support ---');
if (headerIndex === -1) {
return null;
}
const block = content.slice(headerIndex).trimEnd();
// Sanity check: the block must contain the structured YAML key
if (!block.includes('monorepo_context:')) {
return null;
}
return block;
}
/** /**
* Install core with resolved dependencies * Install core with resolved dependencies
* @param {string} bmadDir - BMAD installation directory * @param {string} bmadDir - BMAD installation directory