mirror of
https://github.com/bmadcode/BMAD-METHOD.git
synced 2025-12-17 09:45:25 +00:00
memory location is non configurable _bmad/_memory for sidecar content
This commit is contained in:
parent
59e4cc7b82
commit
32615afaf9
@ -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
|
||||
|
||||
@ -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}"
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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`
|
||||
|
||||
@ -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?"
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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`
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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**:
|
||||
|
||||
@ -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?"
|
||||
|
||||
@ -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?"
|
||||
|
||||
@ -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 -->
|
||||
|
||||
@ -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 -->
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user