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
|
||||
- `{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
|
||||
- `{user_name}` - User's name from config
|
||||
- `{communication_language}` - Language preference
|
||||
|
|
|
|||
|
|
@ -196,7 +196,7 @@ critical_actions:
|
|||
- **Memory integration** - Past context becomes part of current session
|
||||
- **Protocol adherence** - Ensures consistent behavior
|
||||
|
||||
### {agent-folder} Variable
|
||||
### {agent_sidecar_folder} Variable
|
||||
|
||||
Special variable resolved during installation:
|
||||
|
||||
|
|
@ -313,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 {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
|
||||
5. **Reference past naturally** - Don't dump memory, weave it into conversation
|
||||
6. **Separate concerns** - Memories, instructions, knowledge in distinct files
|
||||
|
|
@ -356,8 +356,8 @@ identity: |
|
|||
- [ ] Sidecar folder structure created and populated
|
||||
- [ ] memories.md has clear section structure
|
||||
- [ ] instructions.md contains core directives
|
||||
- [ ] Menu actions reference {agent-folder} correctly
|
||||
- [ ] File paths use {agent-folder} variable
|
||||
- [ ] Menu actions reference {agent_sidecar_folder} correctly
|
||||
- [ ] File paths use {agent_sidecar_folder} variable
|
||||
- [ ] Install config personalizes sidecar references
|
||||
- [ ] Agent folder named consistently: `{agent-name}/`
|
||||
- [ ] 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/`
|
||||
|
||||
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
|
||||
- `{custom_module}` resolves to custom/src/modules
|
||||
- `{module}` is your module code/name
|
||||
|
|
|
|||
|
|
@ -245,12 +245,20 @@ module.exports = {
|
|||
// Load agent configuration
|
||||
const agentConfig = loadAgentConfig(selectedAgent.yamlFile);
|
||||
|
||||
// Check if agent has sidecar
|
||||
if (agentConfig.metadata.hasSidecar) {
|
||||
selectedAgent.hasSidecar = true;
|
||||
}
|
||||
|
||||
if (agentConfig.metadata.name) {
|
||||
console.log(chalk.dim(`Agent Name: ${agentConfig.metadata.name}`));
|
||||
}
|
||||
if (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)
|
||||
const agentType = selectedAgent.name; // e.g., "commit-poet"
|
||||
|
|
@ -508,12 +516,22 @@ module.exports = {
|
|||
const compiledPath = path.join(agentTargetDir, compiledFileName);
|
||||
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
|
||||
const { xml, metadata, processedYaml } = compileAgent(
|
||||
fs.readFileSync(selectedAgent.yamlFile, 'utf8'),
|
||||
answers,
|
||||
finalAgentName,
|
||||
relativePath,
|
||||
{ config: coreConfig },
|
||||
);
|
||||
|
||||
// Write compiled XML (.md) with custom name
|
||||
|
|
@ -527,12 +545,31 @@ module.exports = {
|
|||
sidecarCopied: false,
|
||||
};
|
||||
|
||||
// Copy sidecar files for expert agents
|
||||
if (selectedAgent.hasSidecar && selectedAgent.type === 'expert') {
|
||||
const { copySidecarFiles } = require('../lib/agent/installer');
|
||||
const sidecarFiles = copySidecarFiles(selectedAgent.path, agentTargetDir, selectedAgent.yamlFile);
|
||||
// Handle sidecar files for agents with hasSidecar flag
|
||||
if (selectedAgent.hasSidecar === true && selectedAgent.type === 'expert') {
|
||||
const { copyAgentSidecarFiles } = require('../lib/agent/installer');
|
||||
|
||||
// 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.sidecarFiles = sidecarFiles;
|
||||
result.sidecarDir = agentSidecarDir;
|
||||
|
||||
console.log(chalk.dim(` Sidecar copied to: ${agentSidecarDir}`));
|
||||
}
|
||||
|
||||
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 { ManifestGenerator } = require('./manifest-generator');
|
||||
const { IdeConfigManager } = require('./ide-config-manager');
|
||||
const { replaceAgentSidecarFolders } = require('./post-install-sidecar-replacement');
|
||||
|
||||
class Installer {
|
||||
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
|
||||
const { UI } = require('../../../lib/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
|
||||
// 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)
|
||||
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
|
||||
const content = xmlContent.endsWith('\n') ? xmlContent : xmlContent + '\n';
|
||||
await fs.writeFile(mdPath, content, 'utf8');
|
||||
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
|
||||
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
|
||||
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
|
||||
// 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)
|
||||
xmlContent = this.processTTSInjectionPoints(xmlContent, targetMdPath);
|
||||
|
||||
|
|
@ -2532,6 +2615,7 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
|
|||
agentConfig.defaults || {},
|
||||
finalAgentName,
|
||||
relativePath,
|
||||
{ config: config.coreConfig },
|
||||
);
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
// Copy sidecar files if expert agent
|
||||
if (agent.hasSidecar && agent.type === 'expert') {
|
||||
const { copySidecarFiles } = require('../../../lib/agent/installer');
|
||||
copySidecarFiles(agent.path, agentTargetDir, agent.yamlFile);
|
||||
// Copy sidecar files for agents with hasSidecar flag
|
||||
if (agentConfig.hasSidecar === true && agent.type === 'expert') {
|
||||
const { copyAgentSidecarFiles } = require('../../../lib/agent/installer');
|
||||
|
||||
// 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
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
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({
|
||||
type: 'agent-launcher',
|
||||
module: agent.module,
|
||||
name: agent.name,
|
||||
relativePath: path.join(agent.module, 'agents', `${agent.name}.md`),
|
||||
relativePath: path.join(agent.module, 'agents', agentPathInModule),
|
||||
content: launcherContent,
|
||||
sourcePath: agent.path,
|
||||
});
|
||||
|
|
@ -56,9 +58,12 @@ class AgentCommandGenerator {
|
|||
const template = await fs.readFile(this.templatePath, 'utf8');
|
||||
|
||||
// Replace template variables
|
||||
// Use relativePath if available (for nested agents), otherwise just name with .md
|
||||
const agentPathInModule = agent.relativePath || `${agent.name}.md`;
|
||||
return template
|
||||
.replaceAll('{{name}}', agent.name)
|
||||
.replaceAll('{{module}}', agent.module)
|
||||
.replaceAll('{{path}}', agentPathInModule)
|
||||
.replaceAll('{{description}}', agent.description || `${agent.name} agent`)
|
||||
.replaceAll('{bmad_folder}', this.bmadFolderName)
|
||||
.replaceAll('{*bmad_folder*}', '{bmad_folder}');
|
||||
|
|
|
|||
|
|
@ -76,7 +76,7 @@ async function getTasksFromBmad(bmadDir, selectedModules = []) {
|
|||
return tasks;
|
||||
}
|
||||
|
||||
async function getAgentsFromDir(dirPath, moduleName) {
|
||||
async function getAgentsFromDir(dirPath, moduleName, relativePath = '') {
|
||||
const agents = [];
|
||||
|
||||
if (!(await fs.pathExists(dirPath))) {
|
||||
|
|
@ -87,10 +87,11 @@ async function getAgentsFromDir(dirPath, moduleName) {
|
|||
|
||||
for (const entry of entries) {
|
||||
const fullPath = path.join(dirPath, entry.name);
|
||||
const newRelativePath = relativePath ? `${relativePath}/${entry.name}` : entry.name;
|
||||
|
||||
if (entry.isDirectory()) {
|
||||
// Recurse into subdirectories
|
||||
const subDirAgents = await getAgentsFromDir(fullPath, moduleName);
|
||||
const subDirAgents = await getAgentsFromDir(fullPath, moduleName, newRelativePath);
|
||||
agents.push(...subDirAgents);
|
||||
} else if (entry.name.endsWith('.md')) {
|
||||
// Skip README files and other non-agent files
|
||||
|
|
@ -117,6 +118,7 @@ async function getAgentsFromDir(dirPath, moduleName) {
|
|||
path: fullPath,
|
||||
name: entry.name.replace('.md', ''),
|
||||
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.
|
||||
|
||||
<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
|
||||
3. Execute ALL activation steps exactly as written in the agent file
|
||||
4. Follow the agent's persona and menu system precisely
|
||||
|
|
|
|||
|
|
@ -484,6 +484,16 @@ class ModuleManager {
|
|||
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
|
||||
if (file.startsWith('_module-installer/')) {
|
||||
continue;
|
||||
|
|
@ -697,13 +707,58 @@ class ModuleManager {
|
|||
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
|
||||
const { xml } = compileAgent(yamlContent, customizedFields, agentName, relativePath);
|
||||
const { xml } = compileAgent(yamlContent, {}, agentName, relativePath, { config: coreConfig });
|
||||
|
||||
// Write the compiled MD file
|
||||
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) {
|
||||
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 {string} agentName - Optional final agent name (user's custom persona name)
|
||||
* @param {string} targetPath - Optional target path for agent ID
|
||||
* @param {Object} options - Additional options including config
|
||||
* @returns {Object} { xml: string, metadata: Object }
|
||||
*/
|
||||
function compileAgent(yamlContent, answers = {}, agentName = '', targetPath = '') {
|
||||
function compileAgent(yamlContent, answers = {}, agentName = '', targetPath = '', options = {}) {
|
||||
// Parse YAML
|
||||
const agentYaml = yaml.parse(yamlContent);
|
||||
|
||||
|
|
@ -466,14 +467,22 @@ function compileAgent(yamlContent, answers = {}, agentName = '', targetPath = ''
|
|||
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
|
||||
const processedYaml = processAgentYaml(agentYaml, finalAnswers);
|
||||
|
||||
// Strip install_config from output
|
||||
const cleanYaml = stripInstallConfig(processedYaml);
|
||||
|
||||
// Compile to XML
|
||||
const xml = compileToXml(cleanYaml, agentName, targetPath);
|
||||
// Replace {agent_sidecar_folder} in XML content
|
||||
let xml = compileToXml(cleanYaml, agentName, targetPath);
|
||||
if (finalAnswers.agent_sidecar_folder) {
|
||||
xml = xml.replaceAll('{agent_sidecar_folder}', finalAnswers.agent_sidecar_folder);
|
||||
}
|
||||
|
||||
return {
|
||||
xml,
|
||||
|
|
|
|||
|
|
@ -93,7 +93,6 @@ function discoverAgents(searchPath) {
|
|||
name: agentName,
|
||||
path: fullPath,
|
||||
yamlFile: agentYamlPath,
|
||||
hasSidecar: true,
|
||||
relativePath: agentRelativePath,
|
||||
});
|
||||
}
|
||||
|
|
@ -127,12 +126,15 @@ function loadAgentConfig(yamlPath) {
|
|||
// These take precedence over defaults
|
||||
const savedAnswers = agentYaml?.saved_answers || {};
|
||||
|
||||
const metadata = agentYaml?.agent?.metadata || {};
|
||||
|
||||
return {
|
||||
yamlContent: content,
|
||||
agentYaml,
|
||||
installConfig,
|
||||
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} answers - User answers for install_config
|
||||
* @param {string} targetPath - Target installation directory
|
||||
* @param {Object} options - Additional options including config
|
||||
* @returns {Object} Installation result
|
||||
*/
|
||||
function installAgent(agentInfo, answers, targetPath) {
|
||||
function installAgent(agentInfo, answers, targetPath, options = {}) {
|
||||
// Compile the agent
|
||||
const { xml, metadata, processedYaml } = compileAgent(fs.readFileSync(agentInfo.yamlFile, 'utf8'), answers);
|
||||
|
||||
|
|
@ -261,11 +264,27 @@ function installAgent(agentInfo, answers, targetPath) {
|
|||
sidecarCopied: false,
|
||||
};
|
||||
|
||||
// Copy sidecar files for expert agents
|
||||
if (agentInfo.hasSidecar && agentInfo.type === 'expert') {
|
||||
const sidecarFiles = copySidecarFiles(agentInfo.path, agentTargetDir, agentInfo.yamlFile);
|
||||
// Handle sidecar files for agents with hasSidecar flag
|
||||
if (agentInfo.hasSidecar === true && agentInfo.type === 'expert') {
|
||||
// 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.sidecarFiles = sidecarFiles;
|
||||
result.sidecarDir = agentSidecarDir;
|
||||
}
|
||||
|
||||
return result;
|
||||
|
|
@ -309,6 +328,50 @@ function copySidecarFiles(sourceDir, targetDir, excludeYaml) {
|
|||
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
|
||||
* @param {string} compiledContent - Compiled XML content
|
||||
|
|
@ -745,6 +808,7 @@ module.exports = {
|
|||
promptInstallQuestions,
|
||||
installAgent,
|
||||
copySidecarFiles,
|
||||
copyAgentSidecarFiles,
|
||||
updateAgentId,
|
||||
detectBmadProject,
|
||||
addToManifest,
|
||||
|
|
|
|||
Loading…
Reference in New Issue