memory location is non configurable _bmad/_memory for sidecar content

This commit is contained in:
Brian Madison 2025-12-16 15:43:38 +08:00
parent 59e4cc7b82
commit 32615afaf9
18 changed files with 204 additions and 169 deletions

View File

@ -159,8 +159,7 @@ The sidecar folder location is configured during BMAD core installation:
1. **Agent Declaration**: Agents declare `hasSidecar: true` in their metadata
2. **Sidecar Detection**: The installer automatically detects folders with "sidecar" in the name
3. **Installation**: Sidecar content is copied to the configured location
4. **Path Replacement**: The `{bmad_memory}` placeholder in agent configurations is replaced with the actual path to the installed instance of the sidecar folder. Now when you use the agent, depending on its design, will use the content in sidecar to record interactions, remember things you tell it, or serve a host of many other issues.
3. **Installation**: Sidecar content is always copied to the destination install \_bmad/\_memory folder.
### Example Structure
@ -231,8 +230,8 @@ Custom content can be distributed:
### Sidecar Issues
- Ensure the agent has `hasSidecar: true` in metadata
- Check that sidecar folders contain "sidecar" in the name
- Verify the bmad_memory configuration
- Check that sidecar folders contain "-sidecar" in the name
- Verify the folder on install got cloned to \_bmad/\_memory
- Ensure the custom agent has proper language in it to actually use the sidecar content, including loading memories on agent load.
## Support

View File

@ -23,8 +23,3 @@ output_folder:
prompt: "Where should default output files be saved unless specified in other modules?"
default: "_bmad-output"
result: "{project-root}/{value}"
bmad_memory:
prompt: "Some agents will record their own memories and history. Where should these be stored?"
default: "_bmad/_memory"
result: "{project-root}/{value}"

View File

@ -375,7 +375,6 @@ exec: "../../../core/tasks/validate.xml"
- `{project-root}` - Project root directory
- `_bmad` - BMAD installation folder
- `{bmad_memory}` - Agent installation directory (Expert agents)
- `{output_folder}` - Document output location
- `{user_name}` - User's name from config
- `{communication_language}` - Language preference

View File

@ -204,14 +204,6 @@ critical_actions:
- **Memory integration** - Past context becomes part of current session
- **Protocol adherence** - Ensures consistent behavior
### {bmad_memory} Variable
Special variable resolved during installation:
- Points to the agent's installation directory
- Used to reference sidecar files
- Example: `_bmad/custom/agents/journal-keeper/`
## What Gets Injected at Compile Time
Same as simple agents, PLUS:
@ -320,12 +312,11 @@ critical_actions:
## Best Practices
1. **Load sidecar files in critical_actions** - Must be explicit and MANDATORY
2. **Enforce domain restrictions** - Clear boundaries prevent scope creep
3. **Use {bmad_memory} paths** - Portable across installations
4. **Design for memory growth** - Structure sidecar files for accumulation
5. **Reference past naturally** - Don't dump memory, weave it into conversation
6. **Separate concerns** - Memories, instructions, knowledge in distinct files
7. **Include privacy features** - Users trust expert agents with personal data
2. **Enforce domain restrictions** - Clear boundaries prevent scope creep=
3. **Design for memory growth** - Structure sidecar files for accumulation
4. **Reference past naturally** - Don't dump memory, weave it into conversation
5. **Separate concerns** - Memories, instructions, knowledge in distinct files
6. **Include privacy features** - Users trust expert agents with personal data
## Common Patterns
@ -364,8 +355,8 @@ identity: |
- [ ] Sidecar folder structure created and populated
- [ ] memories.md has clear section structure
- [ ] instructions.md contains core directives
- [ ] Menu actions reference {bmad_memory} correctly
- [ ] File paths use {bmad_memory} variable
- [ ] Menu actions reference \_bmad/\_memory correctly
- [ ] File paths use \_bmad/\_memory/[agentname]-sidecar/ to reference sidecar content
- [ ] Install config personalizes sidecar references
- [ ] Agent folder named consistently: `{agent-name}/`
- [ ] YAML file named: `{agent-name}.agent.yaml`

View File

@ -9,7 +9,6 @@ default_selected: false # This module will not be selected by default for new in
## communication_language
## document_output_language
## output_folder
## bmad_memory
bmb_creations_output_folder:
prompt: "Where should BoMB generated agents, workflows and modules SOURCE be saved?"

View File

@ -20,9 +20,9 @@ agent:
- Reflection transforms experience into wisdom
critical_actions:
- "Load COMPLETE file {bmad_memory}/journal-keeper-sidecar/memories.md and remember all past insights"
- "Load COMPLETE file {bmad_memory}/journal-keeper-sidecar/instructions.md and follow ALL journaling protocols"
- "ONLY read/write files in {bmad_memory}/journal-keeper-sidecar/ - this is our private space"
- "Load COMPLETE file {project-root}/_bmad/_memory/journal-keeper-sidecar/memories.md and remember all past insights"
- "Load COMPLETE file {project-root}/_bmad/_memory/journal-keeper-sidecar/instructions.md and follow ALL journaling protocols"
- "ONLY read/write files in {project-root}/_bmad/_memory/journal-keeper-sidecar/ - this is our private space"
- "Track mood patterns, recurring themes, and breakthrough moments"
- "Reference past entries naturally to show continuity"
@ -120,7 +120,7 @@ agent:
description: "Write today's journal entry"
- trigger: quick
action: "Save a quick, unstructured entry to {bmad_memory}/journal-keeper-sidecar/entries/entry-{date}.md with timestamp and any patterns noticed"
action: "Save a quick, unstructured entry to {project-root}/_bmad/_memory/journal-keeper-sidecar/entries/entry-{date}.md with timestamp and any patterns noticed"
description: "Quick capture without prompts"
- trigger: mood
@ -140,13 +140,13 @@ agent:
description: "Reflect on the past week"
- trigger: insight
action: "Document this breakthrough in {bmad_memory}/journal-keeper-sidecar/breakthroughs.md with date and significance"
action: "Document this breakthrough in {project-root}/_bmad/_memory/journal-keeper-sidecar/breakthroughs.md with date and significance"
description: "Record a meaningful insight"
- trigger: read-back
action: "Load and share entries from {bmad_memory}/journal-keeper-sidecar/entries/ for requested timeframe, highlighting themes and growth"
action: "Load and share entries from {project-root}/_bmad/_memory/journal-keeper-sidecar/entries/ for requested timeframe, highlighting themes and growth"
description: "Review past entries"
- trigger: save
action: "Update {bmad_memory}/journal-keeper-sidecar/memories.md with today's session insights and emotional markers"
action: "Update {project-root}/_bmad/_memory/journal-keeper-sidecar/memories.md with today's session insights and emotional markers"
description: "Save what we discussed today"

View File

@ -204,14 +204,6 @@ critical_actions:
- **Memory integration** - Past context becomes part of current session
- **Protocol adherence** - Ensures consistent behavior
### {bmad_memory} Variable
Special variable resolved during installation:
- Points to the agent's installation directory
- Used to reference sidecar files
- Example: `_bmad/custom/agents/journal-keeper/`
## What Gets Injected at Compile Time
Same as simple agents, PLUS:
@ -321,7 +313,7 @@ critical_actions:
1. **Load sidecar files in critical_actions** - Must be explicit and MANDATORY
2. **Enforce domain restrictions** - Clear boundaries prevent scope creep
3. **Use {bmad_memory} paths** - Portable across installations
3. **Use \_bmad/\_memory/[agentname]-sidcar/ paths** - For reference to any sidecar content
4. **Design for memory growth** - Structure sidecar files for accumulation
5. **Reference past naturally** - Don't dump memory, weave it into conversation
6. **Separate concerns** - Memories, instructions, knowledge in distinct files
@ -364,8 +356,8 @@ identity: |
- [ ] Sidecar folder structure created and populated
- [ ] memories.md has clear section structure
- [ ] instructions.md contains core directives
- [ ] Menu actions reference {bmad_memory} correctly
- [ ] File paths use {bmad_memory} variable
- [ ] Menu actions reference \_bmad/\_memory/[agentname]-sidcar/ correctly if needing sidecar content reference
- [ ] File paths use \_bmad/\_memory/[agentname]-sidcar/ to reference where the file will be after sidecar content is installed
- [ ] Install config personalizes sidecar references
- [ ] Agent folder named consistently: `{agent-name}/`
- [ ] YAML file named: `{agent-name}.agent.yaml`

View File

@ -51,7 +51,7 @@ This uses **step-file architecture** for disciplined execution:
Load and read full config from `{project-root}/_bmad/bmb/config.yaml`:
- `project_name`, `user_name`, `bmad_memory`, `communication_language`, `document_output_language`, `bmb_creations_output_folder`
- `project_name`, `user_name`, `communication_language`, `document_output_language`, `bmb_creations_output_folder`
### 2. First Step EXECUTION

View File

@ -27,9 +27,8 @@ agent:
# Optional: Only include if agent needs memory/persistence
critical_actions:
- 'Load COMPLETE file [bmad_memory]/[agent-name]-sidecar/memories.md and integrate all past interactions'
- 'Load COMPLETE file [bmad_memory]/[agent-name]-sidecar/instructions.md and follow ALL protocols'
- 'ONLY read/write files in [bmad_memory]/[agent-name]-sidecar/* - this is our private workspace'
- 'Load COMPLETE file [project-root]/_bmad/_memory/[agent-name]-sidecar/memories.md and integrate all past interactions'
- 'Load COMPLETE file [project-root]/_bmad/_memory/[agent-name]-sidecar/instructions.md and follow ALL protocols'
# Optional: Embedded prompts for common interactions
prompts:
@ -167,16 +166,15 @@ Expert agents support three types of menu actions:
1. **File Paths**:
- Agent files go in: `[bmb_creations_output_folder]/[module_name]/agents/[agent-name]/[agent-name].yaml`
- Sidecar folders go in: `[bmb_creations_output_folder]/[module_name]/agents/[agent-name]/[agent-name]-sidecar/`
- Sidecar files go in folder: `[bmb_creations_output_folder]/[module_name]/agents/[agent-name]/[agent-name]-sidecar/`
2. **Variable Usage**:
- `bmad_memory` resolves to the agents sidecar folder destination after installation
- `module` is your module code/name
3. **Creating Sidecar Structure**:
- When agent is created, also create the sidecar folder
- Initialize with empty files: memories.md, instructions.md
- Create sessions/ subfolder
- Initialize with empty files: memories.md, instructions.md and any other files the agent will need to have special knowledge or files to record information to
- Create sessions/ subfolder if interactions will result in new sessions
- These files are automatically loaded due to critical_actions
4. **Choosing Menu Actions**:

View File

@ -9,7 +9,6 @@ default_selected: false
## communication_language
## document_output_language
## output_folder
## bmad_memory
game_project_name:
prompt: "What is the name of your game project?"

View File

@ -9,7 +9,6 @@ default_selected: true # This module will be selected by default for new install
## communication_language
## document_output_language
## output_folder
## bmad_memory
project_name:
prompt: "What is the title of your project you will be working on?"

View File

@ -2,6 +2,6 @@
Purpose: Record a log detailing the stories I have crafted over time for the user.
## Narratives Told Record
## Narratives Told Table Record
<!-- track stories created metadata with the user over time -->

View File

@ -2,6 +2,6 @@
Purpose: Record a log of learned users story telling or story building preferences.
## User Preferences
## User Preference Bullet List
<!-- record any user preferences about story crafting the user prefers -->

View File

@ -7,6 +7,7 @@ agent:
title: Master Storyteller
icon: 📖
module: cis
hasSidecar: true
persona:
role: Expert Storytelling Guide + Narrative Strategist
@ -15,8 +16,8 @@ agent:
principles: Powerful narratives leverage timeless human truths. Find the authentic story. Make the abstract concrete through vivid details.
critical_actions:
- "Load COMPLETE file {agent_sidecar_folder}/storyteller-sidecar/story-preferences.md and review remember the User Preferences"
- "Load COMPLETE file {agent_sidecar_folder}/storyteller-sidecar/stories-told.md and review the history of stories created for this user"
- "Load COMPLETE file {project-root}/_bmad/_memory/storyteller-sidecar/story-preferences.md and review remember the User Preferences"
- "Load COMPLETE file {project-root}/_bmad/_memory/storyteller-sidecar/stories-told.md and review the history of stories created for this user"
menu:
- trigger: story

View File

@ -9,4 +9,3 @@ default_selected: false # This module will not be selected by default for new in
## communication_language
## document_output_language
## output_folder
## bmad_memory

View File

@ -992,6 +992,7 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
isCustom: true,
moduleConfig: collectedModuleConfig,
isQuickUpdate: config._quickUpdate || false,
installer: this,
},
);
@ -1573,6 +1574,7 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
{
skipModuleInstaller: true, // We'll run it later after IDE setup
moduleConfig: moduleConfig, // Pass module config for conditional filtering
installer: this,
},
);
@ -1695,7 +1697,7 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
// Compile agents using the same compiler as modules
const { ModuleManager } = require('../modules/manager');
const moduleManager = new ModuleManager();
await moduleManager.compileModuleAgents(sourcePath, targetPath, 'core', bmadDir);
await moduleManager.compileModuleAgents(sourcePath, targetPath, 'core', bmadDir, this);
// Process agent files to inject activation block
await this.processAgentFiles(targetPath, 'core');
@ -2044,8 +2046,7 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
answers.memories = customizeData.memories;
}
// Get core config for bmad_memory
const coreConfigPath = path.join(bmadDir, 'bmb', 'config.yaml');
const coreConfigPath = path.join(bmadDir, 'core', 'config.yaml');
let coreConfig = {};
if (await fs.pathExists(coreConfigPath)) {
const yaml = require('yaml');
@ -2200,7 +2201,7 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
// Recompile agents (#1133)
const { ModuleManager } = require('../modules/manager');
const moduleManager = new ModuleManager();
await moduleManager.compileModuleAgents(sourcePath, targetPath, 'core', bmadDir);
await moduleManager.compileModuleAgents(sourcePath, targetPath, 'core', bmadDir, this);
await this.processAgentFiles(targetPath, 'core');
}
}
@ -2571,6 +2572,9 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
const customFiles = [];
const modifiedFiles = [];
// Memory is always in _bmad/_memory
const bmadMemoryPath = '_memory';
// Check if the manifest has hashes - if not, we can't detect modifications
let manifestHasHashes = false;
if (existingFilesManifest && existingFilesManifest.length > 0) {
@ -2635,6 +2639,10 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
continue;
}
if (relativePath.startsWith(bmadMemoryPath + '/') && path.dirname(relativePath).includes('-sidecar')) {
continue;
}
// Skip config.yaml files - these are regenerated on each install/update
if (fileName === 'config.yaml') {
continue;

View File

@ -56,50 +56,22 @@ class ModuleManager {
}
/**
* Copy a file and replace _bmad placeholder with actual folder name
* Copy a file to the target location
* @param {string} sourcePath - Source file path
* @param {string} targetPath - Target file path
* @param {boolean} overwrite - Whether to overwrite existing files (default: true)
*/
async copyFileWithPlaceholderReplacement(sourcePath, targetPath) {
// List of text file extensions that should have placeholder replacement
const textExtensions = ['.md', '.yaml', '.yml', '.txt', '.json', '.js', '.ts', '.html', '.css', '.sh', '.bat', '.csv'];
const ext = path.extname(sourcePath).toLowerCase();
// Check if this is a text file that might contain placeholders
if (textExtensions.includes(ext)) {
try {
// Read the file content
let content = await fs.readFile(sourcePath, 'utf8');
// Replace escape sequence _bmad with literal _bmad
if (content.includes('_bmad')) {
content = content.replaceAll('_bmad', '_bmad');
}
// Replace _bmad placeholder with actual folder name
if (content.includes('_bmad')) {
content = content.replaceAll('_bmad', this.bmadFolderName);
}
// Write to target with replaced content
await fs.ensureDir(path.dirname(targetPath));
await fs.writeFile(targetPath, content, 'utf8');
} catch {
// If reading as text fails (might be binary despite extension), fall back to regular copy
await fs.copy(sourcePath, targetPath, { overwrite: true });
}
} else {
// Binary file or other file type - just copy directly
await fs.copy(sourcePath, targetPath, { overwrite: true });
}
async copyFileWithPlaceholderReplacement(sourcePath, targetPath, overwrite = true) {
await fs.copy(sourcePath, targetPath, { overwrite });
}
/**
* Copy a directory recursively with placeholder replacement
* Copy a directory recursively
* @param {string} sourceDir - Source directory path
* @param {string} targetDir - Target directory path
* @param {boolean} overwrite - Whether to overwrite existing files (default: true)
*/
async copyDirectoryWithPlaceholderReplacement(sourceDir, targetDir) {
async copyDirectoryWithPlaceholderReplacement(sourceDir, targetDir, overwrite = true) {
await fs.ensureDir(targetDir);
const entries = await fs.readdir(sourceDir, { withFileTypes: true });
@ -108,13 +80,110 @@ class ModuleManager {
const targetPath = path.join(targetDir, entry.name);
if (entry.isDirectory()) {
await this.copyDirectoryWithPlaceholderReplacement(sourcePath, targetPath);
await this.copyDirectoryWithPlaceholderReplacement(sourcePath, targetPath, overwrite);
} else {
await this.copyFileWithPlaceholderReplacement(sourcePath, targetPath);
await this.copyFileWithPlaceholderReplacement(sourcePath, targetPath, overwrite);
}
}
}
/**
* Copy sidecar directory to _bmad/_memory location with update-safe handling
* @param {string} sourceSidecarPath - Source sidecar directory path
* @param {string} agentName - Name of the agent (for naming)
* @param {string} bmadMemoryPath - This should ALWAYS be _bmad/_memory
* @param {boolean} isUpdate - Whether this is an update (default: false)
* @param {string} bmadDir - BMAD installation directory
* @param {Object} installer - Installer instance for file tracking
*/
async copySidecarToMemory(sourceSidecarPath, agentName, bmadMemoryPath, isUpdate = false, bmadDir = null, installer = null) {
const crypto = require('node:crypto');
const sidecarTargetDir = path.join(bmadMemoryPath, `${agentName}-sidecar`);
// Ensure target directory exists
await fs.ensureDir(bmadMemoryPath);
await fs.ensureDir(sidecarTargetDir);
// Get existing files manifest for update checking
let existingFilesManifest = [];
if (isUpdate && installer) {
existingFilesManifest = await installer.readFilesManifest(bmadDir);
}
// Build map of existing sidecar files with their hashes
const existingSidecarFiles = new Map();
for (const fileEntry of existingFilesManifest) {
if (fileEntry.path && fileEntry.path.includes(`${agentName}-sidecar/`)) {
existingSidecarFiles.set(fileEntry.path, fileEntry.hash);
}
}
// Get all files in source sidecar
const sourceFiles = await this.getFileList(sourceSidecarPath);
for (const file of sourceFiles) {
const sourceFilePath = path.join(sourceSidecarPath, file);
const targetFilePath = path.join(sidecarTargetDir, file);
// Calculate current source file hash
const sourceHash = crypto
.createHash('sha256')
.update(await fs.readFile(sourceFilePath))
.digest('hex');
// Path relative to bmad directory
const relativeToBmad = path.join('_memory', `${agentName}-sidecar`, file);
if (isUpdate && (await fs.pathExists(targetFilePath))) {
// Calculate current target file hash
const currentTargetHash = crypto
.createHash('sha256')
.update(await fs.readFile(targetFilePath))
.digest('hex');
// Get the last known hash from files-manifest
const lastKnownHash = existingSidecarFiles.get(relativeToBmad);
if (lastKnownHash) {
// We have a record of this file
if (currentTargetHash === lastKnownHash) {
// File hasn't been modified by user, safe to update
await this.copyFileWithPlaceholderReplacement(sourceFilePath, targetFilePath, true);
if (process.env.BMAD_VERBOSE_INSTALL === 'true') {
console.log(chalk.dim(` Updated sidecar file: ${relativeToBmad}`));
}
} else {
// User has modified the file, preserve it
if (process.env.BMAD_VERBOSE_INSTALL === 'true') {
console.log(chalk.dim(` Preserving user-modified file: ${relativeToBmad}`));
}
}
} else {
// First time seeing this file in manifest, copy it
await this.copyFileWithPlaceholderReplacement(sourceFilePath, targetFilePath, true);
if (process.env.BMAD_VERBOSE_INSTALL === 'true') {
console.log(chalk.dim(` Added new sidecar file: ${relativeToBmad}`));
}
}
} else {
// New installation
await this.copyFileWithPlaceholderReplacement(sourceFilePath, targetFilePath, true);
if (process.env.BMAD_VERBOSE_INSTALL === 'true') {
console.log(chalk.dim(` Copied sidecar file: ${relativeToBmad}`));
}
}
// Track the file in the installer's file tracking system
if (installer && installer.installedFiles) {
installer.installedFiles.add(targetFilePath);
}
}
// Return list of files that were processed
const processedFiles = sourceFiles.map((file) => path.join('_memory', `${agentName}-sidecar`, file));
return processedFiles;
}
/**
* List all available modules (excluding core which is always installed)
* @returns {Object} Object with modules array and customModules array
@ -363,7 +432,7 @@ class ModuleManager {
await this.copyModuleWithFiltering(sourcePath, targetPath, fileTrackingCallback, options.moduleConfig);
// Compile any .agent.yaml files to .md format
await this.compileModuleAgents(sourcePath, targetPath, moduleName, bmadDir);
await this.compileModuleAgents(sourcePath, targetPath, moduleName, bmadDir, options.installer);
// Process agent files to inject activation block
await this.processAgentFiles(targetPath, moduleName);
@ -409,7 +478,7 @@ class ModuleManager {
await this.syncModule(sourcePath, targetPath);
// Recompile agents (#1133)
await this.compileModuleAgents(sourcePath, targetPath, moduleName, bmadDir);
await this.compileModuleAgents(sourcePath, targetPath, moduleName, bmadDir, options.installer);
await this.processAgentFiles(targetPath, moduleName);
}
@ -495,29 +564,21 @@ class ModuleManager {
// Get all files in source
const sourceFiles = await this.getFileList(sourcePath);
// Game development files to conditionally exclude
const gameDevFiles = [
'agents/game-architect.agent.yaml',
'agents/game-designer.agent.yaml',
'agents/game-dev.agent.yaml',
'workflows/1-analysis/brainstorm-game',
'workflows/1-analysis/game-brief',
'workflows/2-plan-workflows/gdd',
];
for (const file of sourceFiles) {
// Skip sub-modules directory - these are IDE-specific and handled separately
if (file.startsWith('sub-modules/')) {
continue;
}
// Skip sidecar directories - they are handled separately during agent compilation
if (
path
.dirname(file)
.split('/')
.some((dir) => dir.toLowerCase().includes('sidecar'))
) {
// Only skip sidecar directories - they are handled separately during agent compilation
// But still allow other files in agent directories
const isInAgentDirectory = file.startsWith('agents/');
const isInSidecarDirectory = path
.dirname(file)
.split('/')
.some((dir) => dir.toLowerCase().endsWith('-sidecar'));
if (isInSidecarDirectory) {
continue;
}
@ -527,8 +588,7 @@ class ModuleManager {
}
// Skip config.yaml templates - we'll generate clean ones with actual values
// Also skip custom.yaml files - their values will be merged into core config
if (file === 'config.yaml' || file.endsWith('/config.yaml') || file === 'custom.yaml' || file.endsWith('/custom.yaml')) {
if (file === 'config.yaml' || file.endsWith('/config.yaml')) {
continue;
}
@ -537,25 +597,6 @@ class ModuleManager {
continue;
}
// Skip user documentation if install_user_docs is false
if (moduleConfig.install_user_docs === false && (file.startsWith('docs/') || file.startsWith('docs\\'))) {
console.log(chalk.dim(` Skipping user documentation: ${file}`));
continue;
}
// Skip game development content if include_game_planning is false
if (moduleConfig.include_game_planning === false) {
const shouldSkipGameDev = gameDevFiles.some((gamePath) => {
// Check if file path starts with or is within any game dev directory
return file === gamePath || file.startsWith(gamePath + '/') || file.startsWith(gamePath + '\\');
});
if (shouldSkipGameDev) {
console.log(chalk.dim(` Skipping game dev content: ${file}`));
continue;
}
}
const sourceFile = path.join(sourcePath, file);
const targetFile = path.join(targetPath, file);
@ -685,8 +726,9 @@ class ModuleManager {
* @param {string} targetPath - Target module path
* @param {string} moduleName - Module name
* @param {string} bmadDir - BMAD installation directory
* @param {Object} installer - Installer instance for file tracking
*/
async compileModuleAgents(sourcePath, targetPath, moduleName, bmadDir) {
async compileModuleAgents(sourcePath, targetPath, moduleName, bmadDir, installer = null) {
const sourceAgentsPath = path.join(sourcePath, 'agents');
const targetAgentsPath = path.join(targetPath, 'agents');
const cfgAgentsDir = path.join(bmadDir, '_config', 'agents');
@ -785,16 +827,6 @@ class ModuleManager {
}
}
// Load core config to get bmad_memory
const coreConfigPath = path.join(bmadDir, 'bmb', 'config.yaml');
let coreConfig = {};
if (await fs.pathExists(coreConfigPath)) {
const yaml = require('yaml');
const coreConfigContent = await fs.readFile(coreConfigPath, 'utf8');
coreConfig = yaml.parse(coreConfigContent);
}
// Check if agent has sidecar
let hasSidecar = false;
try {
@ -805,14 +837,48 @@ class ModuleManager {
}
// Compile with customizations if any
const { xml } = await compileAgent(yamlContent, answers, agentName, relativePath, { config: coreConfig });
const { xml } = await compileAgent(yamlContent, answers, agentName, relativePath, { config: this.coreConfig || {} });
// Replace _bmad placeholder if needed
if (xml.includes('_bmad') && this.bmadFolderName) {
const processedXml = xml.replaceAll('_bmad', this.bmadFolderName);
await fs.writeFile(targetMdPath, processedXml, 'utf8');
} else {
await fs.writeFile(targetMdPath, xml, 'utf8');
// Write the compiled agent
await fs.writeFile(targetMdPath, xml, 'utf8');
// Handle sidecar copying if present
if (hasSidecar) {
// Get the agent's directory to look for sidecar
const agentDir = path.dirname(agentFile);
const sidecarDirName = `${agentName}-sidecar`;
const sourceSidecarPath = path.join(agentDir, sidecarDirName);
// Check if sidecar directory exists
if (await fs.pathExists(sourceSidecarPath)) {
// Memory is always in _bmad/_memory
const bmadMemoryPath = path.join(bmadDir, '_memory');
// Determine if this is an update (by checking if agent already exists)
const isUpdate = await fs.pathExists(targetMdPath);
// Copy sidecar to memory location with update-safe handling
const copiedFiles = await this.copySidecarToMemory(sourceSidecarPath, agentName, bmadMemoryPath, isUpdate, bmadDir, installer);
if (process.env.BMAD_VERBOSE_INSTALL === 'true' && copiedFiles.length > 0) {
console.log(chalk.dim(` Sidecar files processed: ${copiedFiles.length} files`));
}
} else if (process.env.BMAD_VERBOSE_INSTALL === 'true') {
console.log(chalk.yellow(` Warning: Agent marked as having sidecar but ${sidecarDirName} directory not found`));
}
}
// Copy any non-sidecar files from agent directory (e.g., foo.md)
const agentDir = path.dirname(agentFile);
const agentEntries = await fs.readdir(agentDir, { withFileTypes: true });
for (const entry of agentEntries) {
if (entry.isFile() && !entry.name.endsWith('.agent.yaml') && !entry.name.endsWith('.md')) {
// Copy additional files (like foo.md) to the agent target directory
const sourceFile = path.join(agentDir, entry.name);
const targetFile = path.join(targetDir, entry.name);
await this.copyFileWithPlaceholderReplacement(sourceFile, targetFile);
}
}
// Only show compilation details in verbose mode

View File

@ -333,18 +333,12 @@ async function compileAgent(yamlContent, answers = {}, agentName = '', targetPat
finalAnswers = { ...defaults, ...answers };
}
// Add bmad_memory to answers if provided in config
if (options.config && options.config.bmad_memory) {
finalAnswers.bmad_memory = options.config.bmad_memory;
}
// Process templates with answers
const processedYaml = processAgentYaml(agentYaml, finalAnswers);
// Strip install_config from output
const cleanYaml = stripInstallConfig(processedYaml);
// Replace {bmad_memory} in XML content
let xml = await compileToXml(cleanYaml, agentName, targetPath);
// Ensure xml is a string before attempting replaceAll
@ -352,10 +346,6 @@ async function compileAgent(yamlContent, answers = {}, agentName = '', targetPat
throw new TypeError('compileToXml did not return a string');
}
if (finalAnswers.bmad_memory) {
xml = xml.replaceAll('{bmad_memory}', finalAnswers.bmad_memory);
}
return {
xml,
metadata: cleanYaml.agent.metadata,