sidecar content goes to custom core config location
This commit is contained in:
parent
ba2c81263b
commit
1697a45376
|
|
@ -375,7 +375,7 @@ exec: "../../../core/tasks/validate.xml"
|
||||||
|
|
||||||
- `{project-root}` - Project root directory
|
- `{project-root}` - Project root directory
|
||||||
- `{bmad_folder}` - BMAD installation folder
|
- `{bmad_folder}` - BMAD installation folder
|
||||||
- `{agent-folder}` - Agent installation directory (Expert agents)
|
- `{agent_sidecar_folder}` - Agent installation directory (Expert agents)
|
||||||
- `{output_folder}` - Document output location
|
- `{output_folder}` - Document output location
|
||||||
- `{user_name}` - User's name from config
|
- `{user_name}` - User's name from config
|
||||||
- `{communication_language}` - Language preference
|
- `{communication_language}` - Language preference
|
||||||
|
|
|
||||||
|
|
@ -196,7 +196,7 @@ critical_actions:
|
||||||
- **Memory integration** - Past context becomes part of current session
|
- **Memory integration** - Past context becomes part of current session
|
||||||
- **Protocol adherence** - Ensures consistent behavior
|
- **Protocol adherence** - Ensures consistent behavior
|
||||||
|
|
||||||
### {agent-folder} Variable
|
### {agent_sidecar_folder} Variable
|
||||||
|
|
||||||
Special variable resolved during installation:
|
Special variable resolved during installation:
|
||||||
|
|
||||||
|
|
@ -313,7 +313,7 @@ critical_actions:
|
||||||
|
|
||||||
1. **Load sidecar files in critical_actions** - Must be explicit and MANDATORY
|
1. **Load sidecar files in critical_actions** - Must be explicit and MANDATORY
|
||||||
2. **Enforce domain restrictions** - Clear boundaries prevent scope creep
|
2. **Enforce domain restrictions** - Clear boundaries prevent scope creep
|
||||||
3. **Use {agent-folder} paths** - Portable across installations
|
3. **Use {agent_sidecar_folder} paths** - Portable across installations
|
||||||
4. **Design for memory growth** - Structure sidecar files for accumulation
|
4. **Design for memory growth** - Structure sidecar files for accumulation
|
||||||
5. **Reference past naturally** - Don't dump memory, weave it into conversation
|
5. **Reference past naturally** - Don't dump memory, weave it into conversation
|
||||||
6. **Separate concerns** - Memories, instructions, knowledge in distinct files
|
6. **Separate concerns** - Memories, instructions, knowledge in distinct files
|
||||||
|
|
@ -356,8 +356,8 @@ identity: |
|
||||||
- [ ] Sidecar folder structure created and populated
|
- [ ] Sidecar folder structure created and populated
|
||||||
- [ ] memories.md has clear section structure
|
- [ ] memories.md has clear section structure
|
||||||
- [ ] instructions.md contains core directives
|
- [ ] instructions.md contains core directives
|
||||||
- [ ] Menu actions reference {agent-folder} correctly
|
- [ ] Menu actions reference {agent_sidecar_folder} correctly
|
||||||
- [ ] File paths use {agent-folder} variable
|
- [ ] File paths use {agent_sidecar_folder} variable
|
||||||
- [ ] Install config personalizes sidecar references
|
- [ ] Install config personalizes sidecar references
|
||||||
- [ ] Agent folder named consistently: `{agent-name}/`
|
- [ ] Agent folder named consistently: `{agent-name}/`
|
||||||
- [ ] YAML file named: `{agent-name}.agent.yaml`
|
- [ ] YAML file named: `{agent-name}.agent.yaml`
|
||||||
|
|
|
||||||
|
|
@ -170,7 +170,7 @@ Expert agents support three types of menu actions:
|
||||||
- Sidecar folders go in: `{custom_module_location}/{module_name}/agents/[agent-name]-sidecar/`
|
- Sidecar folders go in: `{custom_module_location}/{module_name}/agents/[agent-name]-sidecar/`
|
||||||
|
|
||||||
2. **Variable Usage**:
|
2. **Variable Usage**:
|
||||||
- `{agent-folder}` resolves to the agents folder within your module
|
- `{agent_sidecar_folder}` resolves to the agents sidecar folder destination after installation
|
||||||
- `{bmad_folder}` resolves to .bmad
|
- `{bmad_folder}` resolves to .bmad
|
||||||
- `{custom_module}` resolves to custom/src/modules
|
- `{custom_module}` resolves to custom/src/modules
|
||||||
- `{module}` is your module code/name
|
- `{module}` is your module code/name
|
||||||
|
|
|
||||||
|
|
@ -245,12 +245,20 @@ module.exports = {
|
||||||
// Load agent configuration
|
// Load agent configuration
|
||||||
const agentConfig = loadAgentConfig(selectedAgent.yamlFile);
|
const agentConfig = loadAgentConfig(selectedAgent.yamlFile);
|
||||||
|
|
||||||
|
// Check if agent has sidecar
|
||||||
|
if (agentConfig.metadata.hasSidecar) {
|
||||||
|
selectedAgent.hasSidecar = true;
|
||||||
|
}
|
||||||
|
|
||||||
if (agentConfig.metadata.name) {
|
if (agentConfig.metadata.name) {
|
||||||
console.log(chalk.dim(`Agent Name: ${agentConfig.metadata.name}`));
|
console.log(chalk.dim(`Agent Name: ${agentConfig.metadata.name}`));
|
||||||
}
|
}
|
||||||
if (agentConfig.metadata.title) {
|
if (agentConfig.metadata.title) {
|
||||||
console.log(chalk.dim(`Title: ${agentConfig.metadata.title}`));
|
console.log(chalk.dim(`Title: ${agentConfig.metadata.title}`));
|
||||||
}
|
}
|
||||||
|
if (agentConfig.metadata.hasSidecar) {
|
||||||
|
console.log(chalk.dim(`Sidecar: Yes`));
|
||||||
|
}
|
||||||
|
|
||||||
// Get the agent type (source name)
|
// Get the agent type (source name)
|
||||||
const agentType = selectedAgent.name; // e.g., "commit-poet"
|
const agentType = selectedAgent.name; // e.g., "commit-poet"
|
||||||
|
|
@ -508,12 +516,22 @@ module.exports = {
|
||||||
const compiledPath = path.join(agentTargetDir, compiledFileName);
|
const compiledPath = path.join(agentTargetDir, compiledFileName);
|
||||||
const relativePath = path.relative(projectRoot, compiledPath);
|
const relativePath = path.relative(projectRoot, compiledPath);
|
||||||
|
|
||||||
|
// Read core config to get agent_sidecar_folder
|
||||||
|
const coreConfigPath = path.join(config.bmadFolder, 'bmb', 'config.yaml');
|
||||||
|
let coreConfig = {};
|
||||||
|
if (fs.existsSync(coreConfigPath)) {
|
||||||
|
const yamlLib = require('yaml');
|
||||||
|
const content = fs.readFileSync(coreConfigPath, 'utf8');
|
||||||
|
coreConfig = yamlLib.parse(content);
|
||||||
|
}
|
||||||
|
|
||||||
// Compile with proper name and path
|
// Compile with proper name and path
|
||||||
const { xml, metadata, processedYaml } = compileAgent(
|
const { xml, metadata, processedYaml } = compileAgent(
|
||||||
fs.readFileSync(selectedAgent.yamlFile, 'utf8'),
|
fs.readFileSync(selectedAgent.yamlFile, 'utf8'),
|
||||||
answers,
|
answers,
|
||||||
finalAgentName,
|
finalAgentName,
|
||||||
relativePath,
|
relativePath,
|
||||||
|
{ config: coreConfig },
|
||||||
);
|
);
|
||||||
|
|
||||||
// Write compiled XML (.md) with custom name
|
// Write compiled XML (.md) with custom name
|
||||||
|
|
@ -527,12 +545,31 @@ module.exports = {
|
||||||
sidecarCopied: false,
|
sidecarCopied: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Copy sidecar files for expert agents
|
// Handle sidecar files for agents with hasSidecar flag
|
||||||
if (selectedAgent.hasSidecar && selectedAgent.type === 'expert') {
|
if (selectedAgent.hasSidecar === true && selectedAgent.type === 'expert') {
|
||||||
const { copySidecarFiles } = require('../lib/agent/installer');
|
const { copyAgentSidecarFiles } = require('../lib/agent/installer');
|
||||||
const sidecarFiles = copySidecarFiles(selectedAgent.path, agentTargetDir, selectedAgent.yamlFile);
|
|
||||||
|
// Get agent sidecar folder from config or use default
|
||||||
|
const agentSidecarFolder = coreConfig?.agent_sidecar_folder || '{project-root}/.myagent-data';
|
||||||
|
|
||||||
|
// Resolve path variables
|
||||||
|
const resolvedSidecarFolder = agentSidecarFolder
|
||||||
|
.replaceAll('{project-root}', projectRoot)
|
||||||
|
.replaceAll('{bmad_folder}', config.bmadFolder);
|
||||||
|
|
||||||
|
// Create sidecar directory for this agent
|
||||||
|
const agentSidecarDir = path.join(resolvedSidecarFolder, finalAgentName);
|
||||||
|
if (!fs.existsSync(agentSidecarDir)) {
|
||||||
|
fs.mkdirSync(agentSidecarDir, { recursive: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find and copy sidecar folder
|
||||||
|
const sidecarFiles = copyAgentSidecarFiles(selectedAgent.path, agentSidecarDir, selectedAgent.yamlFile);
|
||||||
result.sidecarCopied = true;
|
result.sidecarCopied = true;
|
||||||
result.sidecarFiles = sidecarFiles;
|
result.sidecarFiles = sidecarFiles;
|
||||||
|
result.sidecarDir = agentSidecarDir;
|
||||||
|
|
||||||
|
console.log(chalk.dim(` Sidecar copied to: ${agentSidecarDir}`));
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(chalk.green('\n✨ Agent installed successfully!'));
|
console.log(chalk.green('\n✨ Agent installed successfully!'));
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,7 @@ const { AgentPartyGenerator } = require('../../../lib/agent-party-generator');
|
||||||
const { CLIUtils } = require('../../../lib/cli-utils');
|
const { CLIUtils } = require('../../../lib/cli-utils');
|
||||||
const { ManifestGenerator } = require('./manifest-generator');
|
const { ManifestGenerator } = require('./manifest-generator');
|
||||||
const { IdeConfigManager } = require('./ide-config-manager');
|
const { IdeConfigManager } = require('./ide-config-manager');
|
||||||
|
const { replaceAgentSidecarFolders } = require('./post-install-sidecar-replacement');
|
||||||
|
|
||||||
class Installer {
|
class Installer {
|
||||||
constructor() {
|
constructor() {
|
||||||
|
|
@ -1024,6 +1025,20 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Replace {agent_sidecar_folder} placeholders in all agent files
|
||||||
|
console.log(chalk.dim('\n Configuring agent sidecar folders...'));
|
||||||
|
const sidecarResults = await replaceAgentSidecarFolders(bmadDir);
|
||||||
|
|
||||||
|
if (sidecarResults.filesReplaced > 0) {
|
||||||
|
console.log(
|
||||||
|
chalk.green(
|
||||||
|
` ✓ Updated ${sidecarResults.filesReplaced} agent file(s) with ${sidecarResults.totalReplacements} sidecar reference(s)`,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
console.log(chalk.dim(' No agent sidecar references found'));
|
||||||
|
}
|
||||||
|
|
||||||
// Display completion message
|
// Display completion message
|
||||||
const { UI } = require('../../../lib/ui');
|
const { UI } = require('../../../lib/ui');
|
||||||
const ui = new UI();
|
const ui = new UI();
|
||||||
|
|
@ -1529,18 +1544,71 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
|
||||||
// DO NOT replace {project-root} - LLMs understand this placeholder at runtime
|
// DO NOT replace {project-root} - LLMs understand this placeholder at runtime
|
||||||
// const processedContent = xmlContent.replaceAll('{project-root}', projectDir);
|
// const processedContent = xmlContent.replaceAll('{project-root}', projectDir);
|
||||||
|
|
||||||
|
// Replace {agent_sidecar_folder} if configured
|
||||||
|
const coreConfig = this.configCollector.collectedConfig.core || {};
|
||||||
|
if (coreConfig.agent_sidecar_folder && xmlContent.includes('{agent_sidecar_folder}')) {
|
||||||
|
xmlContent = xmlContent.replaceAll('{agent_sidecar_folder}', coreConfig.agent_sidecar_folder);
|
||||||
|
}
|
||||||
|
|
||||||
// Process TTS injection points (pass targetPath for tracking)
|
// Process TTS injection points (pass targetPath for tracking)
|
||||||
xmlContent = this.processTTSInjectionPoints(xmlContent, mdPath);
|
xmlContent = this.processTTSInjectionPoints(xmlContent, mdPath);
|
||||||
|
|
||||||
|
// Check if agent has sidecar and copy it
|
||||||
|
let agentYamlContent = null;
|
||||||
|
let hasSidecar = false;
|
||||||
|
|
||||||
|
try {
|
||||||
|
agentYamlContent = await fs.readFile(yamlPath, 'utf8');
|
||||||
|
const yamlLib = require('yaml');
|
||||||
|
const agentYaml = yamlLib.parse(agentYamlContent);
|
||||||
|
hasSidecar = agentYaml?.agent?.metadata?.hasSidecar === true;
|
||||||
|
} catch {
|
||||||
|
// Continue without sidecar processing
|
||||||
|
}
|
||||||
|
|
||||||
// Write the built .md file to bmad/{module}/agents/ with POSIX-compliant final newline
|
// Write the built .md file to bmad/{module}/agents/ with POSIX-compliant final newline
|
||||||
const content = xmlContent.endsWith('\n') ? xmlContent : xmlContent + '\n';
|
const content = xmlContent.endsWith('\n') ? xmlContent : xmlContent + '\n';
|
||||||
await fs.writeFile(mdPath, content, 'utf8');
|
await fs.writeFile(mdPath, content, 'utf8');
|
||||||
this.installedFiles.push(mdPath);
|
this.installedFiles.push(mdPath);
|
||||||
|
|
||||||
|
// Copy sidecar files if agent has hasSidecar flag
|
||||||
|
if (hasSidecar) {
|
||||||
|
const { copyAgentSidecarFiles } = require('../../../lib/agent/installer');
|
||||||
|
|
||||||
|
// Get agent sidecar folder from core config
|
||||||
|
const coreConfigPath = path.join(bmadDir, 'bmb', 'config.yaml');
|
||||||
|
let agentSidecarFolder = '{project-root}/.myagent-data';
|
||||||
|
|
||||||
|
if (await fs.pathExists(coreConfigPath)) {
|
||||||
|
const yamlLib = require('yaml');
|
||||||
|
const coreConfigContent = await fs.readFile(coreConfigPath, 'utf8');
|
||||||
|
const coreConfig = yamlLib.parse(coreConfigContent);
|
||||||
|
agentSidecarFolder = coreConfig.agent_sidecar_folder || agentSidecarFolder;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resolve path variables
|
||||||
|
const resolvedSidecarFolder = agentSidecarFolder
|
||||||
|
.replaceAll('{project-root}', projectDir)
|
||||||
|
.replaceAll('{bmad_folder}', this.bmadFolderName || 'bmad');
|
||||||
|
|
||||||
|
// Create sidecar directory for this agent
|
||||||
|
const agentSidecarDir = path.join(resolvedSidecarFolder, agentName);
|
||||||
|
await fs.ensureDir(agentSidecarDir);
|
||||||
|
|
||||||
|
// Find and copy sidecar folder from source module
|
||||||
|
const sourceModulePath = getSourcePath(`modules/${moduleName}`);
|
||||||
|
const sourceAgentPath = path.join(sourceModulePath, 'agents');
|
||||||
|
|
||||||
|
// Copy sidecar files
|
||||||
|
const sidecarFiles = copyAgentSidecarFiles(sourceAgentPath, agentSidecarDir, yamlPath);
|
||||||
|
|
||||||
|
console.log(chalk.dim(` Copied sidecar to: ${agentSidecarDir}`));
|
||||||
|
}
|
||||||
|
|
||||||
// Remove the source YAML file - we can regenerate from installer source if needed
|
// Remove the source YAML file - we can regenerate from installer source if needed
|
||||||
await fs.remove(yamlPath);
|
await fs.remove(yamlPath);
|
||||||
|
|
||||||
console.log(chalk.dim(` Built agent: ${agentName}.md`));
|
console.log(chalk.dim(` Built agent: ${agentName}.md${hasSidecar ? ' (with sidecar)' : ''}`));
|
||||||
}
|
}
|
||||||
// Handle legacy .md agents - inject activation if needed
|
// Handle legacy .md agents - inject activation if needed
|
||||||
else if (agentFile.endsWith('.md')) {
|
else if (agentFile.endsWith('.md')) {
|
||||||
|
|
@ -1731,6 +1799,21 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
|
||||||
// DO NOT replace {project-root} - LLMs understand this placeholder at runtime
|
// DO NOT replace {project-root} - LLMs understand this placeholder at runtime
|
||||||
// const processedContent = xmlContent.replaceAll('{project-root}', projectDir);
|
// const processedContent = xmlContent.replaceAll('{project-root}', projectDir);
|
||||||
|
|
||||||
|
// Replace {agent_sidecar_folder} if configured
|
||||||
|
const coreConfigPath = path.join(bmadDir, 'bmb', 'config.yaml');
|
||||||
|
let agentSidecarFolder = null;
|
||||||
|
|
||||||
|
if (await fs.pathExists(coreConfigPath)) {
|
||||||
|
const yamlLib = require('yaml');
|
||||||
|
const coreConfigContent = await fs.readFile(coreConfigPath, 'utf8');
|
||||||
|
const coreConfig = yamlLib.parse(coreConfigContent);
|
||||||
|
agentSidecarFolder = coreConfig.agent_sidecar_folder;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (agentSidecarFolder && xmlContent.includes('{agent_sidecar_folder}')) {
|
||||||
|
xmlContent = xmlContent.replaceAll('{agent_sidecar_folder}', agentSidecarFolder);
|
||||||
|
}
|
||||||
|
|
||||||
// Process TTS injection points (pass targetPath for tracking)
|
// Process TTS injection points (pass targetPath for tracking)
|
||||||
xmlContent = this.processTTSInjectionPoints(xmlContent, targetMdPath);
|
xmlContent = this.processTTSInjectionPoints(xmlContent, targetMdPath);
|
||||||
|
|
||||||
|
|
@ -2532,6 +2615,7 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
|
||||||
agentConfig.defaults || {},
|
agentConfig.defaults || {},
|
||||||
finalAgentName,
|
finalAgentName,
|
||||||
relativePath,
|
relativePath,
|
||||||
|
{ config: config.coreConfig },
|
||||||
);
|
);
|
||||||
|
|
||||||
// Write compiled agent
|
// Write compiled agent
|
||||||
|
|
@ -2547,10 +2631,22 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
|
||||||
await fs.copy(agent.yamlFile, backupYamlPath);
|
await fs.copy(agent.yamlFile, backupYamlPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Copy sidecar files if expert agent
|
// Copy sidecar files for agents with hasSidecar flag
|
||||||
if (agent.hasSidecar && agent.type === 'expert') {
|
if (agentConfig.hasSidecar === true && agent.type === 'expert') {
|
||||||
const { copySidecarFiles } = require('../../../lib/agent/installer');
|
const { copyAgentSidecarFiles } = require('../../../lib/agent/installer');
|
||||||
copySidecarFiles(agent.path, agentTargetDir, agent.yamlFile);
|
|
||||||
|
// Get agent sidecar folder from config or use default
|
||||||
|
const agentSidecarFolder = config.coreConfig?.agent_sidecar_folder || '{project-root}/.myagent-data';
|
||||||
|
|
||||||
|
// Resolve path variables
|
||||||
|
const resolvedSidecarFolder = agentSidecarFolder.replaceAll('{project-root}', projectDir).replaceAll('{bmad_folder}', bmadDir);
|
||||||
|
|
||||||
|
// Create sidecar directory for this agent
|
||||||
|
const agentSidecarDir = path.join(resolvedSidecarFolder, finalAgentName);
|
||||||
|
await fs.ensureDir(agentSidecarDir);
|
||||||
|
|
||||||
|
// Find and copy sidecar folder
|
||||||
|
const sidecarFiles = copyAgentSidecarFiles(agent.path, agentSidecarDir, agent.yamlFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update manifest CSV
|
// Update manifest CSV
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,79 @@
|
||||||
|
/**
|
||||||
|
* Post-installation sidecar folder replacement utility
|
||||||
|
* Replaces {agent_sidecar_folder} placeholders in all installed agents
|
||||||
|
*/
|
||||||
|
|
||||||
|
const fs = require('fs-extra');
|
||||||
|
const path = require('node:path');
|
||||||
|
const yaml = require('yaml');
|
||||||
|
const glob = require('glob');
|
||||||
|
const chalk = require('chalk');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replace {agent_sidecar_folder} placeholders in all agent files
|
||||||
|
* @param {string} bmadDir - Path to .bmad directory
|
||||||
|
* @returns {Object} Statistics about replacements made
|
||||||
|
*/
|
||||||
|
async function replaceAgentSidecarFolders(bmadDir) {
|
||||||
|
const results = {
|
||||||
|
filesScanned: 0,
|
||||||
|
filesReplaced: 0,
|
||||||
|
totalReplacements: 0,
|
||||||
|
errors: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Load core config to get agent_sidecar_folder value
|
||||||
|
const coreConfigPath = path.join(bmadDir, 'bmb', 'config.yaml');
|
||||||
|
|
||||||
|
if (!(await fs.pathExists(coreConfigPath))) {
|
||||||
|
throw new Error(`Core config not found at ${coreConfigPath}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const coreConfigContent = await fs.readFile(coreConfigPath, 'utf8');
|
||||||
|
const coreConfig = yaml.parse(coreConfigContent);
|
||||||
|
const agentSidecarFolder = coreConfig.agent_sidecar_folder || '{project-root}/.myagent-data';
|
||||||
|
|
||||||
|
// Use the literal value from config, don't resolve the placeholders
|
||||||
|
console.log(chalk.dim(`\n Replacing {agent_sidecar_folder} with: ${agentSidecarFolder}`));
|
||||||
|
|
||||||
|
// Find all agent .md files
|
||||||
|
const agentPattern = path.join(bmadDir, '**/*.md');
|
||||||
|
const agentFiles = glob.sync(agentPattern);
|
||||||
|
|
||||||
|
for (const agentFile of agentFiles) {
|
||||||
|
results.filesScanned++;
|
||||||
|
|
||||||
|
try {
|
||||||
|
let content = await fs.readFile(agentFile, 'utf8');
|
||||||
|
|
||||||
|
// Check if file contains {agent_sidecar_folder}
|
||||||
|
if (content.includes('{agent_sidecar_folder}')) {
|
||||||
|
// Replace all occurrences
|
||||||
|
const originalContent = content;
|
||||||
|
content = content.replaceAll('{agent_sidecar_folder}', agentSidecarFolder);
|
||||||
|
|
||||||
|
// Only write if content changed
|
||||||
|
if (content !== originalContent) {
|
||||||
|
await fs.writeFile(agentFile, content, 'utf8');
|
||||||
|
|
||||||
|
const replacementCount = (originalContent.match(/{agent_sidecar_folder}/g) || []).length;
|
||||||
|
results.filesReplaced++;
|
||||||
|
results.totalReplacements += replacementCount;
|
||||||
|
|
||||||
|
console.log(chalk.dim(` ✓ Replaced ${replacementCount} occurrence(s) in ${path.relative(bmadDir, agentFile)}`));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
results.errors.push(`Error processing ${agentFile}: ${error.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return results;
|
||||||
|
} catch (error) {
|
||||||
|
results.errors.push(`Fatal error: ${error.message}`);
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { replaceAgentSidecarFolders };
|
||||||
|
|
@ -28,11 +28,13 @@ class AgentCommandGenerator {
|
||||||
|
|
||||||
for (const agent of agents) {
|
for (const agent of agents) {
|
||||||
const launcherContent = await this.generateLauncherContent(agent);
|
const launcherContent = await this.generateLauncherContent(agent);
|
||||||
|
// Use relativePath if available (for nested agents), otherwise just name with .md
|
||||||
|
const agentPathInModule = agent.relativePath || `${agent.name}.md`;
|
||||||
artifacts.push({
|
artifacts.push({
|
||||||
type: 'agent-launcher',
|
type: 'agent-launcher',
|
||||||
module: agent.module,
|
module: agent.module,
|
||||||
name: agent.name,
|
name: agent.name,
|
||||||
relativePath: path.join(agent.module, 'agents', `${agent.name}.md`),
|
relativePath: path.join(agent.module, 'agents', agentPathInModule),
|
||||||
content: launcherContent,
|
content: launcherContent,
|
||||||
sourcePath: agent.path,
|
sourcePath: agent.path,
|
||||||
});
|
});
|
||||||
|
|
@ -56,9 +58,12 @@ class AgentCommandGenerator {
|
||||||
const template = await fs.readFile(this.templatePath, 'utf8');
|
const template = await fs.readFile(this.templatePath, 'utf8');
|
||||||
|
|
||||||
// Replace template variables
|
// Replace template variables
|
||||||
|
// Use relativePath if available (for nested agents), otherwise just name with .md
|
||||||
|
const agentPathInModule = agent.relativePath || `${agent.name}.md`;
|
||||||
return template
|
return template
|
||||||
.replaceAll('{{name}}', agent.name)
|
.replaceAll('{{name}}', agent.name)
|
||||||
.replaceAll('{{module}}', agent.module)
|
.replaceAll('{{module}}', agent.module)
|
||||||
|
.replaceAll('{{path}}', agentPathInModule)
|
||||||
.replaceAll('{{description}}', agent.description || `${agent.name} agent`)
|
.replaceAll('{{description}}', agent.description || `${agent.name} agent`)
|
||||||
.replaceAll('{bmad_folder}', this.bmadFolderName)
|
.replaceAll('{bmad_folder}', this.bmadFolderName)
|
||||||
.replaceAll('{*bmad_folder*}', '{bmad_folder}');
|
.replaceAll('{*bmad_folder*}', '{bmad_folder}');
|
||||||
|
|
|
||||||
|
|
@ -76,7 +76,7 @@ async function getTasksFromBmad(bmadDir, selectedModules = []) {
|
||||||
return tasks;
|
return tasks;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getAgentsFromDir(dirPath, moduleName) {
|
async function getAgentsFromDir(dirPath, moduleName, relativePath = '') {
|
||||||
const agents = [];
|
const agents = [];
|
||||||
|
|
||||||
if (!(await fs.pathExists(dirPath))) {
|
if (!(await fs.pathExists(dirPath))) {
|
||||||
|
|
@ -87,10 +87,11 @@ async function getAgentsFromDir(dirPath, moduleName) {
|
||||||
|
|
||||||
for (const entry of entries) {
|
for (const entry of entries) {
|
||||||
const fullPath = path.join(dirPath, entry.name);
|
const fullPath = path.join(dirPath, entry.name);
|
||||||
|
const newRelativePath = relativePath ? `${relativePath}/${entry.name}` : entry.name;
|
||||||
|
|
||||||
if (entry.isDirectory()) {
|
if (entry.isDirectory()) {
|
||||||
// Recurse into subdirectories
|
// Recurse into subdirectories
|
||||||
const subDirAgents = await getAgentsFromDir(fullPath, moduleName);
|
const subDirAgents = await getAgentsFromDir(fullPath, moduleName, newRelativePath);
|
||||||
agents.push(...subDirAgents);
|
agents.push(...subDirAgents);
|
||||||
} else if (entry.name.endsWith('.md')) {
|
} else if (entry.name.endsWith('.md')) {
|
||||||
// Skip README files and other non-agent files
|
// Skip README files and other non-agent files
|
||||||
|
|
@ -117,6 +118,7 @@ async function getAgentsFromDir(dirPath, moduleName) {
|
||||||
path: fullPath,
|
path: fullPath,
|
||||||
name: entry.name.replace('.md', ''),
|
name: entry.name.replace('.md', ''),
|
||||||
module: moduleName,
|
module: moduleName,
|
||||||
|
relativePath: newRelativePath, // Keep the .md extension for the full path
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ description: '{{description}}'
|
||||||
You must fully embody this agent's persona and follow all activation instructions exactly as specified. NEVER break character until given an exit command.
|
You must fully embody this agent's persona and follow all activation instructions exactly as specified. NEVER break character until given an exit command.
|
||||||
|
|
||||||
<agent-activation CRITICAL="TRUE">
|
<agent-activation CRITICAL="TRUE">
|
||||||
1. LOAD the FULL agent file from @{bmad_folder}/{{module}}/agents/{{name}}.md
|
1. LOAD the FULL agent file from @{bmad_folder}/{{module}}/agents/{{path}}
|
||||||
2. READ its entire contents - this contains the complete agent persona, menu, and instructions
|
2. READ its entire contents - this contains the complete agent persona, menu, and instructions
|
||||||
3. Execute ALL activation steps exactly as written in the agent file
|
3. Execute ALL activation steps exactly as written in the agent file
|
||||||
4. Follow the agent's persona and menu system precisely
|
4. Follow the agent's persona and menu system precisely
|
||||||
|
|
|
||||||
|
|
@ -484,6 +484,16 @@ class ModuleManager {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Skip sidecar directories - they are handled separately during agent compilation
|
||||||
|
if (
|
||||||
|
path
|
||||||
|
.dirname(file)
|
||||||
|
.split('/')
|
||||||
|
.some((dir) => dir.toLowerCase().includes('sidecar'))
|
||||||
|
) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
// Skip _module-installer directory - it's only needed at install time
|
// Skip _module-installer directory - it's only needed at install time
|
||||||
if (file.startsWith('_module-installer/')) {
|
if (file.startsWith('_module-installer/')) {
|
||||||
continue;
|
continue;
|
||||||
|
|
@ -697,13 +707,58 @@ class ModuleManager {
|
||||||
customizedFields = customizeData.customized_fields || [];
|
customizedFields = customizeData.customized_fields || [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Load core config to get agent_sidecar_folder
|
||||||
|
const coreConfigPath = path.join(bmadDir, 'bmb', 'config.yaml');
|
||||||
|
let coreConfig = {};
|
||||||
|
|
||||||
|
if (await fs.pathExists(coreConfigPath)) {
|
||||||
|
const yamlLib = require('yaml');
|
||||||
|
const coreConfigContent = await fs.readFile(coreConfigPath, 'utf8');
|
||||||
|
coreConfig = yamlLib.parse(coreConfigContent);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if agent has sidecar
|
||||||
|
let hasSidecar = false;
|
||||||
|
try {
|
||||||
|
const yamlLib = require('yaml');
|
||||||
|
const agentYaml = yamlLib.parse(yamlContent);
|
||||||
|
hasSidecar = agentYaml?.agent?.metadata?.hasSidecar === true;
|
||||||
|
} catch {
|
||||||
|
// Continue without sidecar processing
|
||||||
|
}
|
||||||
|
|
||||||
// Compile with customizations if any
|
// Compile with customizations if any
|
||||||
const { xml } = compileAgent(yamlContent, customizedFields, agentName, relativePath);
|
const { xml } = compileAgent(yamlContent, {}, agentName, relativePath, { config: coreConfig });
|
||||||
|
|
||||||
// Write the compiled MD file
|
// Write the compiled MD file
|
||||||
await fs.writeFile(targetMdPath, xml, 'utf8');
|
await fs.writeFile(targetMdPath, xml, 'utf8');
|
||||||
|
|
||||||
console.log(chalk.dim(` Compiled agent: ${agentName} -> ${path.relative(targetPath, targetMdPath)}`));
|
// Copy sidecar files if agent has hasSidecar flag
|
||||||
|
if (hasSidecar) {
|
||||||
|
const { copyAgentSidecarFiles } = require('../../../lib/agent/installer');
|
||||||
|
|
||||||
|
// Get agent sidecar folder from core config or use default
|
||||||
|
const agentSidecarFolder = coreConfig.agent_sidecar_folder || '{project-root}/.myagent-data';
|
||||||
|
|
||||||
|
// Resolve path variables
|
||||||
|
const projectDir = path.dirname(bmadDir);
|
||||||
|
const resolvedSidecarFolder = agentSidecarFolder
|
||||||
|
.replaceAll('{project-root}', projectDir)
|
||||||
|
.replaceAll('{bmad_folder}', path.basename(bmadDir));
|
||||||
|
|
||||||
|
// Create sidecar directory for this agent
|
||||||
|
const agentSidecarDir = path.join(resolvedSidecarFolder, agentName);
|
||||||
|
await fs.ensureDir(agentSidecarDir);
|
||||||
|
|
||||||
|
// Copy sidecar files
|
||||||
|
const sidecarFiles = copyAgentSidecarFiles(path.dirname(sourceYamlPath), agentSidecarDir, sourceYamlPath);
|
||||||
|
|
||||||
|
console.log(chalk.dim(` Copied sidecar to: ${agentSidecarDir}`));
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
chalk.dim(` Compiled agent: ${agentName} -> ${path.relative(targetPath, targetMdPath)}${hasSidecar ? ' (with sidecar)' : ''}`),
|
||||||
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn(chalk.yellow(` Failed to compile agent ${agentName}:`, error.message));
|
console.warn(chalk.yellow(` Failed to compile agent ${agentName}:`, error.message));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -438,9 +438,10 @@ function compileToXml(agentYaml, agentName = '', targetPath = '') {
|
||||||
* @param {Object} answers - Answers from install_config questions (or defaults)
|
* @param {Object} answers - Answers from install_config questions (or defaults)
|
||||||
* @param {string} agentName - Optional final agent name (user's custom persona name)
|
* @param {string} agentName - Optional final agent name (user's custom persona name)
|
||||||
* @param {string} targetPath - Optional target path for agent ID
|
* @param {string} targetPath - Optional target path for agent ID
|
||||||
|
* @param {Object} options - Additional options including config
|
||||||
* @returns {Object} { xml: string, metadata: Object }
|
* @returns {Object} { xml: string, metadata: Object }
|
||||||
*/
|
*/
|
||||||
function compileAgent(yamlContent, answers = {}, agentName = '', targetPath = '') {
|
function compileAgent(yamlContent, answers = {}, agentName = '', targetPath = '', options = {}) {
|
||||||
// Parse YAML
|
// Parse YAML
|
||||||
const agentYaml = yaml.parse(yamlContent);
|
const agentYaml = yaml.parse(yamlContent);
|
||||||
|
|
||||||
|
|
@ -466,14 +467,22 @@ function compileAgent(yamlContent, answers = {}, agentName = '', targetPath = ''
|
||||||
finalAnswers = { ...defaults, ...answers };
|
finalAnswers = { ...defaults, ...answers };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add agent_sidecar_folder to answers if provided in config
|
||||||
|
if (options.config && options.config.agent_sidecar_folder) {
|
||||||
|
finalAnswers.agent_sidecar_folder = options.config.agent_sidecar_folder;
|
||||||
|
}
|
||||||
|
|
||||||
// Process templates with answers
|
// Process templates with answers
|
||||||
const processedYaml = processAgentYaml(agentYaml, finalAnswers);
|
const processedYaml = processAgentYaml(agentYaml, finalAnswers);
|
||||||
|
|
||||||
// Strip install_config from output
|
// Strip install_config from output
|
||||||
const cleanYaml = stripInstallConfig(processedYaml);
|
const cleanYaml = stripInstallConfig(processedYaml);
|
||||||
|
|
||||||
// Compile to XML
|
// Replace {agent_sidecar_folder} in XML content
|
||||||
const xml = compileToXml(cleanYaml, agentName, targetPath);
|
let xml = compileToXml(cleanYaml, agentName, targetPath);
|
||||||
|
if (finalAnswers.agent_sidecar_folder) {
|
||||||
|
xml = xml.replaceAll('{agent_sidecar_folder}', finalAnswers.agent_sidecar_folder);
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
xml,
|
xml,
|
||||||
|
|
|
||||||
|
|
@ -93,7 +93,6 @@ function discoverAgents(searchPath) {
|
||||||
name: agentName,
|
name: agentName,
|
||||||
path: fullPath,
|
path: fullPath,
|
||||||
yamlFile: agentYamlPath,
|
yamlFile: agentYamlPath,
|
||||||
hasSidecar: true,
|
|
||||||
relativePath: agentRelativePath,
|
relativePath: agentRelativePath,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -127,12 +126,15 @@ function loadAgentConfig(yamlPath) {
|
||||||
// These take precedence over defaults
|
// These take precedence over defaults
|
||||||
const savedAnswers = agentYaml?.saved_answers || {};
|
const savedAnswers = agentYaml?.saved_answers || {};
|
||||||
|
|
||||||
|
const metadata = agentYaml?.agent?.metadata || {};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
yamlContent: content,
|
yamlContent: content,
|
||||||
agentYaml,
|
agentYaml,
|
||||||
installConfig,
|
installConfig,
|
||||||
defaults: { ...defaults, ...savedAnswers }, // saved_answers override defaults
|
defaults: { ...defaults, ...savedAnswers }, // saved_answers override defaults
|
||||||
metadata: agentYaml?.agent?.metadata || {},
|
metadata,
|
||||||
|
hasSidecar: metadata.hasSidecar === true,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -232,9 +234,10 @@ async function promptInstallQuestions(installConfig, defaults, presetAnswers = {
|
||||||
* @param {Object} agentInfo - Agent discovery info
|
* @param {Object} agentInfo - Agent discovery info
|
||||||
* @param {Object} answers - User answers for install_config
|
* @param {Object} answers - User answers for install_config
|
||||||
* @param {string} targetPath - Target installation directory
|
* @param {string} targetPath - Target installation directory
|
||||||
|
* @param {Object} options - Additional options including config
|
||||||
* @returns {Object} Installation result
|
* @returns {Object} Installation result
|
||||||
*/
|
*/
|
||||||
function installAgent(agentInfo, answers, targetPath) {
|
function installAgent(agentInfo, answers, targetPath, options = {}) {
|
||||||
// Compile the agent
|
// Compile the agent
|
||||||
const { xml, metadata, processedYaml } = compileAgent(fs.readFileSync(agentInfo.yamlFile, 'utf8'), answers);
|
const { xml, metadata, processedYaml } = compileAgent(fs.readFileSync(agentInfo.yamlFile, 'utf8'), answers);
|
||||||
|
|
||||||
|
|
@ -261,11 +264,27 @@ function installAgent(agentInfo, answers, targetPath) {
|
||||||
sidecarCopied: false,
|
sidecarCopied: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Copy sidecar files for expert agents
|
// Handle sidecar files for agents with hasSidecar flag
|
||||||
if (agentInfo.hasSidecar && agentInfo.type === 'expert') {
|
if (agentInfo.hasSidecar === true && agentInfo.type === 'expert') {
|
||||||
const sidecarFiles = copySidecarFiles(agentInfo.path, agentTargetDir, agentInfo.yamlFile);
|
// Get agent sidecar folder from config or use default
|
||||||
|
const agentSidecarFolder = options.config?.agent_sidecar_folder || '{project-root}/.myagent-data';
|
||||||
|
|
||||||
|
// Resolve path variables
|
||||||
|
const resolvedSidecarFolder = agentSidecarFolder
|
||||||
|
.replaceAll('{project-root}', options.projectRoot || process.cwd())
|
||||||
|
.replaceAll('{bmad_folder}', options.bmadFolder || '.bmad');
|
||||||
|
|
||||||
|
// Create sidecar directory for this agent
|
||||||
|
const agentSidecarDir = path.join(resolvedSidecarFolder, agentFolderName);
|
||||||
|
if (!fs.existsSync(agentSidecarDir)) {
|
||||||
|
fs.mkdirSync(agentSidecarDir, { recursive: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find and copy sidecar folder
|
||||||
|
const sidecarFiles = copyAgentSidecarFiles(agentInfo.path, agentSidecarDir, agentInfo.yamlFile);
|
||||||
result.sidecarCopied = true;
|
result.sidecarCopied = true;
|
||||||
result.sidecarFiles = sidecarFiles;
|
result.sidecarFiles = sidecarFiles;
|
||||||
|
result.sidecarDir = agentSidecarDir;
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
|
@ -309,6 +328,50 @@ function copySidecarFiles(sourceDir, targetDir, excludeYaml) {
|
||||||
return copied;
|
return copied;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find and copy agent sidecar folders
|
||||||
|
* @param {string} sourceDir - Source agent directory
|
||||||
|
* @param {string} targetSidecarDir - Target sidecar directory for the agent
|
||||||
|
* @param {string} excludeYaml - The .agent.yaml file to exclude
|
||||||
|
* @returns {Array} List of copied files
|
||||||
|
*/
|
||||||
|
function copyAgentSidecarFiles(sourceDir, targetSidecarDir, excludeYaml) {
|
||||||
|
const copied = [];
|
||||||
|
|
||||||
|
// Find folders with "sidecar" in the name
|
||||||
|
const entries = fs.readdirSync(sourceDir, { withFileTypes: true });
|
||||||
|
|
||||||
|
for (const entry of entries) {
|
||||||
|
if (entry.isDirectory() && entry.name.toLowerCase().includes('sidecar')) {
|
||||||
|
const sidecarSourcePath = path.join(sourceDir, entry.name);
|
||||||
|
|
||||||
|
// Recursively copy the sidecar folder contents
|
||||||
|
function copySidecarDir(src, dest) {
|
||||||
|
if (!fs.existsSync(dest)) {
|
||||||
|
fs.mkdirSync(dest, { recursive: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
const sidecarEntries = fs.readdirSync(src, { withFileTypes: true });
|
||||||
|
for (const sidecarEntry of sidecarEntries) {
|
||||||
|
const srcPath = path.join(src, sidecarEntry.name);
|
||||||
|
const destPath = path.join(dest, sidecarEntry.name);
|
||||||
|
|
||||||
|
if (sidecarEntry.isDirectory()) {
|
||||||
|
copySidecarDir(srcPath, destPath);
|
||||||
|
} else {
|
||||||
|
fs.copyFileSync(srcPath, destPath);
|
||||||
|
copied.push(destPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
copySidecarDir(sidecarSourcePath, targetSidecarDir);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return copied;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update agent metadata ID to reflect installed location
|
* Update agent metadata ID to reflect installed location
|
||||||
* @param {string} compiledContent - Compiled XML content
|
* @param {string} compiledContent - Compiled XML content
|
||||||
|
|
@ -745,6 +808,7 @@ module.exports = {
|
||||||
promptInstallQuestions,
|
promptInstallQuestions,
|
||||||
installAgent,
|
installAgent,
|
||||||
copySidecarFiles,
|
copySidecarFiles,
|
||||||
|
copyAgentSidecarFiles,
|
||||||
updateAgentId,
|
updateAgentId,
|
||||||
detectBmadProject,
|
detectBmadProject,
|
||||||
addToManifest,
|
addToManifest,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue