mirror of
https://github.com/bmadcode/BMAD-METHOD.git
synced 2025-12-29 16:14:59 +00:00
memory location is non configurable _bmad/_memory for sidecar content
This commit is contained in:
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user