mirror of
https://github.com/bmadcode/BMAD-METHOD.git
synced 2025-12-29 16:14:59 +00:00
folder rename from .bmad to _bmad
This commit is contained in:
@@ -135,7 +135,7 @@ class Detector {
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect legacy installation (.bmad-method, .bmm, .cis)
|
||||
* Detect legacy installation (_bmad-method, .bmm, .cis)
|
||||
* @param {string} projectDir - Project directory to check
|
||||
* @returns {Object} Legacy installation details
|
||||
*/
|
||||
@@ -147,8 +147,8 @@ class Detector {
|
||||
paths: [],
|
||||
};
|
||||
|
||||
// Check for legacy core (.bmad-method)
|
||||
const legacyCorePath = path.join(projectDir, '.bmad-method');
|
||||
// Check for legacy core (_bmad-method)
|
||||
const legacyCorePath = path.join(projectDir, '_bmad-method');
|
||||
if (await fs.pathExists(legacyCorePath)) {
|
||||
result.hasLegacy = true;
|
||||
result.legacyCore = true;
|
||||
@@ -161,7 +161,7 @@ class Detector {
|
||||
if (
|
||||
entry.isDirectory() &&
|
||||
entry.name.startsWith('.') &&
|
||||
entry.name !== '.bmad-method' &&
|
||||
entry.name !== '_bmad-method' &&
|
||||
!entry.name.startsWith('.git') &&
|
||||
!entry.name.startsWith('.vscode') &&
|
||||
!entry.name.startsWith('.idea')
|
||||
@@ -204,7 +204,7 @@ class Detector {
|
||||
|
||||
/**
|
||||
* Detect legacy BMAD v4 footprints (case-sensitive path checks)
|
||||
* V4 used .bmad-method as default folder name
|
||||
* V4 used _bmad-method as default folder name
|
||||
* V6+ uses configurable folder names and ALWAYS has _cfg/manifest.yaml with installation.version
|
||||
* @param {string} projectDir - Project directory to check
|
||||
* @returns {{ hasLegacyV4: boolean, offenders: string[] }}
|
||||
|
||||
@@ -1,23 +1,3 @@
|
||||
/**
|
||||
* File: tools/cli/installers/lib/core/installer.js
|
||||
*
|
||||
* BMAD Method - Business Model Agile Development Method
|
||||
* Repository: https://github.com/paulpreibisch/BMAD-METHOD
|
||||
*
|
||||
* Copyright (c) 2025 Paul Preibisch
|
||||
* Licensed under the Apache License, Version 2.0
|
||||
*
|
||||
* ---
|
||||
*
|
||||
* @fileoverview Core BMAD installation orchestrator with AgentVibes injection point support
|
||||
* @context Manages complete BMAD installation flow including core agents, modules, IDE configs, and optional TTS integration
|
||||
* @architecture Orchestrator pattern - coordinates Detector, ModuleManager, IdeManager, and file operations to build complete BMAD installation
|
||||
* @dependencies fs-extra, ora, chalk, detector.js, module-manager.js, ide-manager.js, config.js
|
||||
* @entrypoints Called by install.js command via installer.install(config)
|
||||
* @patterns Injection point processing (AgentVibes), placeholder replacement (.bmad), module dependency resolution
|
||||
* @related GitHub AgentVibes#34 (injection points), ui.js (user prompts), copyFileWithPlaceholderReplacement()
|
||||
*/
|
||||
|
||||
const path = require('node:path');
|
||||
const fs = require('fs-extra');
|
||||
const chalk = require('chalk');
|
||||
@@ -32,13 +12,11 @@ const { Config } = require('../../../lib/config');
|
||||
const { XmlHandler } = require('../../../lib/xml-handler');
|
||||
const { DependencyResolver } = require('./dependency-resolver');
|
||||
const { ConfigCollector } = require('./config-collector');
|
||||
// processInstallation no longer needed - LLMs understand {project-root}
|
||||
const { getProjectRoot, getSourcePath, getModulePath } = require('../../../lib/project-root');
|
||||
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');
|
||||
const { CustomHandler } = require('../custom/handler');
|
||||
|
||||
class Installer {
|
||||
@@ -67,7 +45,7 @@ class Installer {
|
||||
// Check if project directory exists
|
||||
if (!(await fs.pathExists(projectDir))) {
|
||||
// Project doesn't exist yet, return default
|
||||
return path.join(projectDir, '.bmad');
|
||||
return path.join(projectDir, '_bmad');
|
||||
}
|
||||
|
||||
// V6+ strategy: Look for ANY directory with _cfg/manifest.yaml
|
||||
@@ -89,13 +67,13 @@ class Installer {
|
||||
|
||||
// No V6+ installation found, return default
|
||||
// This will be used for new installations
|
||||
return path.join(projectDir, '.bmad');
|
||||
return path.join(projectDir, '_bmad');
|
||||
}
|
||||
|
||||
/**
|
||||
* @function copyFileWithPlaceholderReplacement
|
||||
* @intent Copy files from BMAD source to installation directory with dynamic content transformation
|
||||
* @why Enables installation-time customization: .bmad replacement + optional AgentVibes TTS injection
|
||||
* @why Enables installation-time customization: _bmad replacement + optional AgentVibes TTS injection
|
||||
* @param {string} sourcePath - Absolute path to source file in BMAD repository
|
||||
* @param {string} targetPath - Absolute path to destination file in user's project
|
||||
* @param {string} bmadFolderName - User's chosen bmad folder name (default: 'bmad')
|
||||
@@ -472,8 +450,8 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
|
||||
});
|
||||
}
|
||||
|
||||
// Always use .bmad as the folder name
|
||||
const bmadFolderName = '.bmad';
|
||||
// Always use _bmad as the folder name
|
||||
const bmadFolderName = '_bmad';
|
||||
this.bmadFolderName = bmadFolderName; // Store for use in other methods
|
||||
|
||||
// Store AgentVibes configuration for injection point processing
|
||||
@@ -602,7 +580,7 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
|
||||
|
||||
// If there are custom files, back them up temporarily
|
||||
if (customFiles.length > 0) {
|
||||
const tempBackupDir = path.join(projectDir, '.bmad-custom-backup-temp');
|
||||
const tempBackupDir = path.join(projectDir, '_bmad-custom-backup-temp');
|
||||
await fs.ensureDir(tempBackupDir);
|
||||
|
||||
spinner.start(`Backing up ${customFiles.length} custom files...`);
|
||||
@@ -619,7 +597,7 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
|
||||
|
||||
// For modified files, back them up to temp directory (will be restored as .bak files after install)
|
||||
if (modifiedFiles.length > 0) {
|
||||
const tempModifiedBackupDir = path.join(projectDir, '.bmad-modified-backup-temp');
|
||||
const tempModifiedBackupDir = path.join(projectDir, '_bmad-modified-backup-temp');
|
||||
await fs.ensureDir(tempModifiedBackupDir);
|
||||
|
||||
console.log(chalk.yellow(`\nDEBUG: Backing up ${modifiedFiles.length} modified files to temp location`));
|
||||
@@ -653,7 +631,7 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
|
||||
|
||||
// Back up custom files
|
||||
if (customFiles.length > 0) {
|
||||
const tempBackupDir = path.join(projectDir, '.bmad-custom-backup-temp');
|
||||
const tempBackupDir = path.join(projectDir, '_bmad-custom-backup-temp');
|
||||
await fs.ensureDir(tempBackupDir);
|
||||
|
||||
spinner.start(`Backing up ${customFiles.length} custom files...`);
|
||||
@@ -669,7 +647,7 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
|
||||
|
||||
// Back up modified files
|
||||
if (modifiedFiles.length > 0) {
|
||||
const tempModifiedBackupDir = path.join(projectDir, '.bmad-modified-backup-temp');
|
||||
const tempModifiedBackupDir = path.join(projectDir, '_bmad-modified-backup-temp');
|
||||
await fs.ensureDir(tempModifiedBackupDir);
|
||||
|
||||
spinner.start(`Backing up ${modifiedFiles.length} modified files...`);
|
||||
@@ -1316,29 +1294,6 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
|
||||
console.log(chalk.dim('Review the .bak files to see your changes and merge if needed.\n'));
|
||||
}
|
||||
|
||||
// Reinstall custom agents from _cfg/custom/agents/ sources
|
||||
const customAgentResults = await this.reinstallCustomAgents(projectDir, bmadDir);
|
||||
if (customAgentResults.count > 0) {
|
||||
console.log(chalk.green(`\n✓ Reinstalled ${customAgentResults.count} custom agent${customAgentResults.count > 1 ? 's' : ''}`));
|
||||
for (const agent of customAgentResults.agents) {
|
||||
console.log(chalk.dim(` - ${agent}`));
|
||||
}
|
||||
}
|
||||
|
||||
// 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();
|
||||
@@ -1852,7 +1807,7 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
|
||||
|
||||
// Create customize template if it doesn't exist
|
||||
if (!(await fs.pathExists(customizePath))) {
|
||||
const genericTemplatePath = getSourcePath('utility', 'templates', 'agent.customize.template.yaml');
|
||||
const genericTemplatePath = getSourcePath('utility', 'agent-components', 'agent.customize.template.yaml');
|
||||
if (await fs.pathExists(genericTemplatePath)) {
|
||||
await this.copyFileWithPlaceholderReplacement(genericTemplatePath, customizePath, this.bmadFolderName || 'bmad');
|
||||
console.log(chalk.dim(` Created customize: ${moduleName}-${agentName}.customize.yaml`));
|
||||
@@ -1868,8 +1823,8 @@ 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 .bmad with actual folder name
|
||||
xmlContent = xmlContent.replaceAll('.bmad', this.bmadFolderName || 'bmad');
|
||||
// Replace _bmad with actual folder name
|
||||
xmlContent = xmlContent.replaceAll('_bmad', this.bmadFolderName || 'bmad');
|
||||
|
||||
// Replace {agent_sidecar_folder} if configured
|
||||
const coreConfig = this.configCollector.collectedConfig.core || {};
|
||||
@@ -1916,7 +1871,7 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
|
||||
// Resolve path variables
|
||||
const resolvedSidecarFolder = agentSidecarFolder
|
||||
.replaceAll('{project-root}', projectDir)
|
||||
.replaceAll('.bmad', this.bmadFolderName || 'bmad');
|
||||
.replaceAll('_bmad', this.bmadFolderName || 'bmad');
|
||||
|
||||
// Create sidecar directory for this agent
|
||||
const agentSidecarDir = path.join(resolvedSidecarFolder, agentName);
|
||||
@@ -1942,20 +1897,6 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
|
||||
|
||||
console.log(chalk.dim(` Built agent: ${agentName}.md${hasSidecar ? ' (with sidecar)' : ''}`));
|
||||
}
|
||||
// Handle legacy .md agents - inject activation if needed
|
||||
else if (agentFile.endsWith('.md')) {
|
||||
const agentPath = path.join(agentsPath, agentFile);
|
||||
let content = await fs.readFile(agentPath, 'utf8');
|
||||
|
||||
// Check if content has agent XML and no activation block
|
||||
if (content.includes('<agent') && !content.includes('<activation')) {
|
||||
// Inject the activation block using XML handler
|
||||
content = this.xmlHandler.injectActivationSimple(content);
|
||||
// Ensure POSIX-compliant final newline
|
||||
const finalContent = content.endsWith('\n') ? content : content + '\n';
|
||||
await fs.writeFile(agentPath, finalContent, 'utf8');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2169,23 +2110,18 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
|
||||
* @returns {Object} Compilation results
|
||||
*/
|
||||
async compileAgents(config) {
|
||||
const ora = require('ora');
|
||||
const spinner = ora('Starting agent compilation...').start();
|
||||
|
||||
try {
|
||||
const projectDir = path.resolve(config.directory);
|
||||
const bmadDir = await this.findBmadDir(projectDir);
|
||||
|
||||
// Check if bmad directory exists
|
||||
if (!(await fs.pathExists(bmadDir))) {
|
||||
spinner.fail('No BMAD installation found');
|
||||
throw new Error(`BMAD not installed at ${bmadDir}`);
|
||||
}
|
||||
|
||||
// Check for custom modules with missing sources
|
||||
const manifest = await this.manifest.read(bmadDir);
|
||||
if (manifest && manifest.customModules && manifest.customModules.length > 0) {
|
||||
spinner.stop();
|
||||
console.log(chalk.yellow('\nChecking custom module sources before compilation...'));
|
||||
|
||||
const customModuleSources = new Map();
|
||||
@@ -2196,15 +2132,12 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
|
||||
const projectRoot = getProjectRoot();
|
||||
const installedModules = manifest.modules || [];
|
||||
await this.handleMissingCustomSources(customModuleSources, bmadDir, projectRoot, 'compile-agents', installedModules);
|
||||
|
||||
spinner.start('Rebuilding agent files...');
|
||||
}
|
||||
|
||||
let agentCount = 0;
|
||||
let taskCount = 0;
|
||||
|
||||
// Process all modules in bmad directory
|
||||
spinner.text = 'Rebuilding agent files...';
|
||||
const entries = await fs.readdir(bmadDir, { withFileTypes: true });
|
||||
|
||||
for (const entry of entries) {
|
||||
@@ -2213,7 +2146,6 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
|
||||
|
||||
// Special handling for standalone agents in bmad/agents/ directory
|
||||
if (entry.name === 'agents') {
|
||||
spinner.text = 'Building standalone agents...';
|
||||
await this.buildStandaloneAgents(bmadDir, projectDir);
|
||||
|
||||
// Count standalone agents
|
||||
@@ -2245,16 +2177,6 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
|
||||
}
|
||||
}
|
||||
|
||||
// Reinstall custom agents from _cfg/custom/agents/ sources
|
||||
spinner.start('Rebuilding custom agents...');
|
||||
const customAgentResults = await this.reinstallCustomAgents(projectDir, bmadDir);
|
||||
if (customAgentResults.count > 0) {
|
||||
spinner.succeed(`Rebuilt ${customAgentResults.count} custom agent${customAgentResults.count > 1 ? 's' : ''}`);
|
||||
agentCount += customAgentResults.count;
|
||||
} else {
|
||||
spinner.succeed('No custom agents found to rebuild');
|
||||
}
|
||||
|
||||
// Skip full manifest regeneration during compileAgents to preserve custom agents
|
||||
// Custom agents are already added to manifests during individual installation
|
||||
// Only regenerate YAML manifest for IDE updates if needed
|
||||
@@ -2269,36 +2191,20 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
|
||||
|
||||
// Update IDE configurations using the existing IDE list from manifest
|
||||
if (existingIdes && existingIdes.length > 0) {
|
||||
spinner.start('Updating IDE configurations...');
|
||||
|
||||
for (const ide of existingIdes) {
|
||||
spinner.text = `Updating ${ide}...`;
|
||||
|
||||
// Stop spinner before IDE setup to prevent blocking any potential prompts
|
||||
// However, we pass _alreadyConfigured to skip all prompts during compile
|
||||
spinner.stop();
|
||||
|
||||
await this.ideManager.setup(ide, projectDir, bmadDir, {
|
||||
selectedModules: installedModules,
|
||||
skipModuleInstall: true, // Skip module installation, just update IDE files
|
||||
verbose: config.verbose,
|
||||
preCollectedConfig: { _alreadyConfigured: true }, // Skip all interactive prompts during compile
|
||||
});
|
||||
|
||||
// Restart spinner for next IDE
|
||||
if (existingIdes.indexOf(ide) < existingIdes.length - 1) {
|
||||
spinner.start('Updating IDE configurations...');
|
||||
}
|
||||
}
|
||||
|
||||
console.log(chalk.green('✓ IDE configurations updated'));
|
||||
} else {
|
||||
console.log(chalk.yellow('⚠️ No IDEs configured. Skipping IDE update.'));
|
||||
}
|
||||
|
||||
return { agentCount, taskCount };
|
||||
} catch (error) {
|
||||
spinner.fail('Compilation failed');
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
@@ -2610,19 +2516,6 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
|
||||
lastModified: new Date().toISOString(),
|
||||
};
|
||||
|
||||
const existingBmadFolderName = path.basename(bmadDir);
|
||||
const newBmadFolderName = this.configCollector.collectedConfig.core?.bmad_folder || existingBmadFolderName;
|
||||
|
||||
if (existingBmadFolderName === newBmadFolderName) {
|
||||
// Normal quick update - start the spinner
|
||||
console.log(chalk.cyan('Updating BMAD installation...'));
|
||||
} else {
|
||||
// Folder name has changed - stop spinner and let install() handle it
|
||||
spinner.stop();
|
||||
console.log(chalk.yellow(`\n⚠️ Folder name will change: ${existingBmadFolderName} → ${newBmadFolderName}`));
|
||||
console.log(chalk.yellow('The installer will handle the folder migration.\n'));
|
||||
}
|
||||
|
||||
// Build the config object for the installer
|
||||
const installConfig = {
|
||||
directory: projectDir,
|
||||
@@ -2690,14 +2583,14 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
|
||||
console.log(chalk.yellow.bold('\n⚠️ Legacy BMAD v4 detected'));
|
||||
console.log(chalk.dim('The installer found legacy artefacts in your project.\n'));
|
||||
|
||||
// Separate .bmad* folders (auto-backup) from other offending paths (manual cleanup)
|
||||
// Separate _bmad* folders (auto-backup) from other offending paths (manual cleanup)
|
||||
const bmadFolders = legacyV4.offenders.filter((p) => {
|
||||
const name = path.basename(p);
|
||||
return name.startsWith('.bmad'); // Only dot-prefixed folders get auto-backed up
|
||||
return name.startsWith('_bmad'); // Only dot-prefixed folders get auto-backed up
|
||||
});
|
||||
const otherOffenders = legacyV4.offenders.filter((p) => {
|
||||
const name = path.basename(p);
|
||||
return !name.startsWith('.bmad'); // Everything else is manual cleanup
|
||||
return !name.startsWith('_bmad'); // Everything else is manual cleanup
|
||||
});
|
||||
|
||||
const inquirer = require('inquirer');
|
||||
@@ -2730,7 +2623,7 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
|
||||
}
|
||||
}
|
||||
|
||||
// Handle .bmad* folders with automatic backup
|
||||
// Handle _bmad* folders with automatic backup
|
||||
if (bmadFolders.length > 0) {
|
||||
console.log(chalk.cyan('The following legacy folders will be moved to v4-backup:'));
|
||||
for (const p of bmadFolders) console.log(chalk.dim(` - ${p}`));
|
||||
@@ -2846,7 +2739,7 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
|
||||
if (fileEntry.path) {
|
||||
// Paths are relative to bmadDir. Legacy manifests incorrectly prefixed 'bmad/' -
|
||||
// strip it if present. This is safe because no real path inside bmadDir would
|
||||
// start with 'bmad/' (you'd never have .bmad/bmad/... as an actual structure).
|
||||
// start with 'bmad/' (you'd never have _bmad/bmad/... as an actual structure).
|
||||
const relativePath = fileEntry.path.startsWith('bmad/') ? fileEntry.path.slice(5) : fileEntry.path;
|
||||
const absolutePath = path.join(bmadDir, relativePath);
|
||||
installedFilesMap.set(path.normalize(absolutePath), {
|
||||
@@ -3099,165 +2992,6 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
|
||||
return nodes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reinstall custom agents from backup and source locations
|
||||
* This preserves custom agents across quick updates/reinstalls
|
||||
* @param {string} projectDir - Project directory
|
||||
* @param {string} bmadDir - BMAD installation directory
|
||||
* @returns {Object} Result with count and agent names
|
||||
*/
|
||||
async reinstallCustomAgents(projectDir, bmadDir) {
|
||||
const {
|
||||
discoverAgents,
|
||||
loadAgentConfig,
|
||||
extractManifestData,
|
||||
addToManifest,
|
||||
createIdeSlashCommands,
|
||||
updateManifestYaml,
|
||||
} = require('../../../lib/agent/installer');
|
||||
const { compileAgent } = require('../../../lib/agent/compiler');
|
||||
|
||||
const results = { count: 0, agents: [] };
|
||||
|
||||
// Check multiple locations for custom agents
|
||||
const sourceLocations = [
|
||||
path.join(bmadDir, '_cfg', 'custom', 'agents'), // Backup location
|
||||
path.join(bmadDir, 'custom', 'src', 'agents'), // BMAD folder source location
|
||||
path.join(projectDir, 'custom', 'src', 'agents'), // Project root source location
|
||||
];
|
||||
|
||||
let foundAgents = [];
|
||||
let processedAgents = new Set(); // Track to avoid duplicates
|
||||
|
||||
// Discover agents from all locations
|
||||
for (const location of sourceLocations) {
|
||||
if (await fs.pathExists(location)) {
|
||||
const agents = discoverAgents(location);
|
||||
// Only add agents we haven't processed yet
|
||||
const newAgents = agents.filter((agent) => !processedAgents.has(agent.name));
|
||||
foundAgents.push(...newAgents);
|
||||
for (const agent of newAgents) processedAgents.add(agent.name);
|
||||
}
|
||||
}
|
||||
|
||||
if (foundAgents.length === 0) {
|
||||
return results;
|
||||
}
|
||||
|
||||
try {
|
||||
const customAgentsDir = path.join(bmadDir, 'custom', 'agents');
|
||||
await fs.ensureDir(customAgentsDir);
|
||||
|
||||
const manifestFile = path.join(bmadDir, '_cfg', 'agent-manifest.csv');
|
||||
const manifestYamlFile = path.join(bmadDir, '_cfg', 'manifest.yaml');
|
||||
|
||||
for (const agent of foundAgents) {
|
||||
try {
|
||||
const agentConfig = loadAgentConfig(agent.yamlFile);
|
||||
const finalAgentName = agent.name; // Already named correctly from save
|
||||
|
||||
// Determine agent type from the name (e.g., "fred-commit-poet" → "commit-poet")
|
||||
let agentType = finalAgentName;
|
||||
const parts = finalAgentName.split('-');
|
||||
if (parts.length >= 2) {
|
||||
// Try to extract type (last part or last two parts)
|
||||
// For "fred-commit-poet", we want "commit-poet"
|
||||
// This is heuristic - could be improved with metadata storage
|
||||
agentType = parts.slice(-2).join('-'); // Take last 2 parts as type
|
||||
}
|
||||
|
||||
// Create target directory - use relative path if agent is in a subdirectory
|
||||
const agentTargetDir = agent.relativePath
|
||||
? path.join(customAgentsDir, agent.relativePath)
|
||||
: path.join(customAgentsDir, finalAgentName);
|
||||
await fs.ensureDir(agentTargetDir);
|
||||
|
||||
// Calculate paths
|
||||
const compiledFileName = `${finalAgentName}.md`;
|
||||
const compiledPath = path.join(agentTargetDir, compiledFileName);
|
||||
const relativePath = path.relative(projectDir, compiledPath);
|
||||
|
||||
// Compile with embedded defaults (answers are already in defaults section)
|
||||
const { xml, metadata } = compileAgent(
|
||||
await fs.readFile(agent.yamlFile, 'utf8'),
|
||||
agentConfig.defaults || {},
|
||||
finalAgentName,
|
||||
relativePath,
|
||||
{ config: config.coreConfig },
|
||||
);
|
||||
|
||||
// Write compiled agent
|
||||
await fs.writeFile(compiledPath, xml, 'utf8');
|
||||
|
||||
// Backup source YAML to _cfg/custom/agents if not already there
|
||||
const cfgAgentsBackupDir = path.join(bmadDir, '_cfg', 'custom', 'agents');
|
||||
await fs.ensureDir(cfgAgentsBackupDir);
|
||||
const backupYamlPath = path.join(cfgAgentsBackupDir, `${finalAgentName}.agent.yaml`);
|
||||
|
||||
// Only backup if source is not already in backup location
|
||||
if (agent.yamlFile !== backupYamlPath) {
|
||||
await fs.copy(agent.yamlFile, backupYamlPath);
|
||||
}
|
||||
|
||||
// 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;
|
||||
|
||||
// Resolve path variables
|
||||
const resolvedSidecarFolder = agentSidecarFolder.replaceAll('{project-root}', projectDir).replaceAll('.bmad', bmadDir);
|
||||
|
||||
// Create sidecar directory for this agent
|
||||
const agentSidecarDir = path.join(resolvedSidecarFolder, finalAgentName);
|
||||
await fs.ensureDir(agentSidecarDir);
|
||||
|
||||
// Copy sidecar files (preserve existing, add new)
|
||||
const sidecarResult = copyAgentSidecarFiles(agent.path, agentSidecarDir, agent.yamlFile);
|
||||
|
||||
if (sidecarResult.copied.length > 0 || sidecarResult.preserved.length > 0) {
|
||||
console.log(chalk.dim(` Sidecar: ${sidecarResult.copied.length} new, ${sidecarResult.preserved.length} preserved`));
|
||||
}
|
||||
}
|
||||
|
||||
// Update manifest CSV
|
||||
if (await fs.pathExists(manifestFile)) {
|
||||
// Preserve YAML metadata for persona name, but override id for filename
|
||||
const manifestMetadata = {
|
||||
...metadata,
|
||||
id: relativePath, // Use the compiled agent path for id
|
||||
name: metadata.name || finalAgentName, // Use YAML metadata.name (persona name) or fallback
|
||||
title: metadata.title, // Use YAML title
|
||||
icon: metadata.icon, // Use YAML icon
|
||||
};
|
||||
const manifestData = extractManifestData(xml, manifestMetadata, relativePath, 'custom');
|
||||
manifestData.name = finalAgentName; // Use filename for the name field
|
||||
manifestData.path = relativePath;
|
||||
addToManifest(manifestFile, manifestData);
|
||||
}
|
||||
|
||||
// Create IDE slash commands (async function)
|
||||
await createIdeSlashCommands(projectDir, finalAgentName, relativePath, metadata);
|
||||
|
||||
// Update manifest.yaml
|
||||
if (await fs.pathExists(manifestYamlFile)) {
|
||||
updateManifestYaml(manifestYamlFile, finalAgentName, agentType);
|
||||
}
|
||||
|
||||
results.count++;
|
||||
results.agents.push(finalAgentName);
|
||||
} catch (agentError) {
|
||||
console.log(chalk.yellow(` ⚠️ Failed to reinstall ${agent.name}: ${agentError.message}`));
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(chalk.yellow(` ⚠️ Error reinstalling custom agents: ${error.message}`));
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy IDE-specific documentation to BMAD docs
|
||||
* @param {Array} ides - List of selected IDEs
|
||||
|
||||
@@ -23,7 +23,7 @@ class ManifestGenerator {
|
||||
|
||||
/**
|
||||
* Generate all manifests for the installation
|
||||
* @param {string} bmadDir - .bmad
|
||||
* @param {string} bmadDir - _bmad
|
||||
* @param {Array} selectedModules - Selected modules for installation
|
||||
* @param {Array} installedFiles - All installed files (optional, for hash tracking)
|
||||
*/
|
||||
@@ -47,7 +47,7 @@ class ManifestGenerator {
|
||||
// But all modules should be included in the final manifest
|
||||
this.preservedModules = [...new Set([...preservedModules, ...selectedModules, ...installedModules])]; // Include all installed modules
|
||||
this.bmadDir = bmadDir;
|
||||
this.bmadFolderName = path.basename(bmadDir); // Get the actual folder name (e.g., '.bmad' or 'bmad')
|
||||
this.bmadFolderName = path.basename(bmadDir); // Get the actual folder name (e.g., '_bmad' or 'bmad')
|
||||
this.allInstalledFiles = installedFiles;
|
||||
|
||||
if (!Object.prototype.hasOwnProperty.call(options, 'ides')) {
|
||||
|
||||
@@ -1,79 +0,0 @@
|
||||
/**
|
||||
* 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;
|
||||
|
||||
// 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 };
|
||||
@@ -316,7 +316,7 @@ class CustomHandler {
|
||||
// Create customize template if it doesn't exist
|
||||
if (!(await fs.pathExists(customizePath))) {
|
||||
const { getSourcePath } = require('../../../lib/project-root');
|
||||
const genericTemplatePath = getSourcePath('utility', 'templates', 'agent.customize.template.yaml');
|
||||
const genericTemplatePath = getSourcePath('utility', 'agent-components', 'agent.customize.template.yaml');
|
||||
if (await fs.pathExists(genericTemplatePath)) {
|
||||
// Copy with placeholder replacement
|
||||
let templateContent = await fs.readFile(genericTemplatePath, 'utf8');
|
||||
@@ -355,7 +355,7 @@ class CustomHandler {
|
||||
const projectDir = path.dirname(bmadDir);
|
||||
const resolvedSidecarFolder = config.agent_sidecar_folder
|
||||
.replaceAll('{project-root}', projectDir)
|
||||
.replaceAll('.bmad', path.basename(bmadDir));
|
||||
.replaceAll('_bmad', path.basename(bmadDir));
|
||||
|
||||
// Create sidecar directory for this agent
|
||||
const agentSidecarDir = path.join(resolvedSidecarFolder, agentName);
|
||||
|
||||
@@ -31,18 +31,11 @@ class BaseIdeSetup {
|
||||
|
||||
/**
|
||||
* Get the agent command activation header from the central template
|
||||
* @returns {string} The activation header text (without XML tags)
|
||||
* @returns {string} The activation header text
|
||||
*/
|
||||
async getAgentCommandHeader() {
|
||||
const headerPath = path.join(getSourcePath(), 'src', 'utility', 'models', 'agent-command-header.md');
|
||||
try {
|
||||
const content = await fs.readFile(headerPath, 'utf8');
|
||||
// Strip the <critical> tags to get plain text
|
||||
return content.replaceAll(/<critical>|<\/critical>/g, '').trim();
|
||||
} catch {
|
||||
// Fallback if file doesn't exist
|
||||
return "You must fully embody this agent's persona and follow all activation instructions, steps and rules exactly as specified. NEVER break character until given an exit command.";
|
||||
}
|
||||
const headerPath = path.join(getSourcePath(), 'src', 'utility', 'agent-components', 'agent-command-header.md');
|
||||
return await fs.readFile(headerPath, 'utf8');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -527,26 +520,26 @@ class BaseIdeSetup {
|
||||
}
|
||||
|
||||
/**
|
||||
* Write file with content (replaces .bmad placeholder)
|
||||
* Write file with content (replaces _bmad placeholder)
|
||||
* @param {string} filePath - File path
|
||||
* @param {string} content - File content
|
||||
*/
|
||||
async writeFile(filePath, content) {
|
||||
// Replace .bmad placeholder if present
|
||||
if (typeof content === 'string' && content.includes('.bmad')) {
|
||||
content = content.replaceAll('.bmad', this.bmadFolderName);
|
||||
// Replace _bmad placeholder if present
|
||||
if (typeof content === 'string' && content.includes('_bmad')) {
|
||||
content = content.replaceAll('_bmad', this.bmadFolderName);
|
||||
}
|
||||
|
||||
// Replace escape sequence .bmad with literal .bmad
|
||||
if (typeof content === 'string' && content.includes('.bmad')) {
|
||||
content = content.replaceAll('.bmad', '.bmad');
|
||||
// Replace escape sequence _bmad with literal _bmad
|
||||
if (typeof content === 'string' && content.includes('_bmad')) {
|
||||
content = content.replaceAll('_bmad', '_bmad');
|
||||
}
|
||||
await this.ensureDir(path.dirname(filePath));
|
||||
await fs.writeFile(filePath, content, 'utf8');
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy file from source to destination (replaces .bmad placeholder in text files)
|
||||
* Copy file from source to destination (replaces _bmad placeholder in text files)
|
||||
* @param {string} source - Source file path
|
||||
* @param {string} dest - Destination file path
|
||||
*/
|
||||
@@ -563,14 +556,14 @@ class BaseIdeSetup {
|
||||
// Read the file content
|
||||
let content = await fs.readFile(source, 'utf8');
|
||||
|
||||
// Replace .bmad placeholder with actual folder name
|
||||
if (content.includes('.bmad')) {
|
||||
content = content.replaceAll('.bmad', this.bmadFolderName);
|
||||
// Replace _bmad placeholder with actual folder name
|
||||
if (content.includes('_bmad')) {
|
||||
content = content.replaceAll('_bmad', this.bmadFolderName);
|
||||
}
|
||||
|
||||
// Replace escape sequence .bmad with literal .bmad
|
||||
if (content.includes('.bmad')) {
|
||||
content = content.replaceAll('.bmad', '.bmad');
|
||||
// Replace escape sequence _bmad with literal _bmad
|
||||
if (content.includes('_bmad')) {
|
||||
content = content.replaceAll('_bmad', '_bmad');
|
||||
}
|
||||
|
||||
// Write to dest with replaced content
|
||||
|
||||
@@ -119,7 +119,7 @@ class AntigravitySetup extends BaseIdeSetup {
|
||||
await this.ensureDir(bmadWorkflowsDir);
|
||||
|
||||
// Generate agent launchers using AgentCommandGenerator
|
||||
// This creates small launcher files that reference the actual agents in .bmad/
|
||||
// This creates small launcher files that reference the actual agents in _bmad/
|
||||
const agentGen = new AgentCommandGenerator(this.bmadFolderName);
|
||||
const { artifacts: agentArtifacts, counts: agentCounts } = await agentGen.collectAgentArtifacts(bmadDir, options.selectedModules || []);
|
||||
|
||||
|
||||
@@ -118,7 +118,7 @@ class ClaudeCodeSetup extends BaseIdeSetup {
|
||||
await this.ensureDir(bmadCommandsDir);
|
||||
|
||||
// Generate agent launchers using AgentCommandGenerator
|
||||
// This creates small launcher files that reference the actual agents in .bmad/
|
||||
// This creates small launcher files that reference the actual agents in _bmad/
|
||||
const agentGen = new AgentCommandGenerator(this.bmadFolderName);
|
||||
const { artifacts: agentArtifacts, counts: agentCounts } = await agentGen.collectAgentArtifacts(bmadDir, options.selectedModules || []);
|
||||
|
||||
|
||||
@@ -265,8 +265,8 @@ class CodexSetup extends BaseIdeSetup {
|
||||
'',
|
||||
chalk.white(' /prompts installed globally to your HOME DIRECTORY.'),
|
||||
'',
|
||||
chalk.yellow(' ⚠️ These prompts reference a specific .bmad path'),
|
||||
chalk.dim(" To use with other projects, you'd need to copy the .bmad dir"),
|
||||
chalk.yellow(' ⚠️ These prompts reference a specific _bmad path'),
|
||||
chalk.dim(" To use with other projects, you'd need to copy the _bmad dir"),
|
||||
'',
|
||||
chalk.green(' ✓ You can now use /commands in Codex CLI'),
|
||||
chalk.dim(' Example: /bmad-bmm-agents-pm'),
|
||||
|
||||
@@ -174,8 +174,8 @@ ${contentWithoutFrontmatter}
|
||||
// Note: {user_name} and other {config_values} are left as-is for runtime substitution by Gemini
|
||||
const tomlContent = template
|
||||
.replaceAll('{{title}}', title)
|
||||
.replaceAll('{.bmad}', '.bmad')
|
||||
.replaceAll('{.bmad}', this.bmadFolderName)
|
||||
.replaceAll('{_bmad}', '_bmad')
|
||||
.replaceAll('{_bmad}', this.bmadFolderName)
|
||||
.replaceAll('{{module}}', agent.module)
|
||||
.replaceAll('{{name}}', agent.name);
|
||||
|
||||
@@ -196,8 +196,8 @@ ${contentWithoutFrontmatter}
|
||||
// Replace template variables
|
||||
const tomlContent = template
|
||||
.replaceAll('{{taskName}}', taskName)
|
||||
.replaceAll('{.bmad}', '.bmad')
|
||||
.replaceAll('{.bmad}', this.bmadFolderName)
|
||||
.replaceAll('{_bmad}', '_bmad')
|
||||
.replaceAll('{_bmad}', this.bmadFolderName)
|
||||
.replaceAll('{{module}}', task.module)
|
||||
.replaceAll('{{filename}}', task.filename);
|
||||
|
||||
|
||||
@@ -45,11 +45,11 @@ class RooSetup extends BaseIdeSetup {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Read the actual agent file from .bmad for metadata extraction (installed agents are .md files)
|
||||
// Read the actual agent file from _bmad for metadata extraction (installed agents are .md files)
|
||||
const agentPath = path.join(bmadDir, artifact.module, 'agents', `${artifact.name}.md`);
|
||||
const content = await this.readFile(agentPath);
|
||||
|
||||
// Create command file that references the actual .bmad agent
|
||||
// Create command file that references the actual _bmad agent
|
||||
await this.createCommandFile({ module: artifact.module, name: artifact.name, path: agentPath }, content, commandPath, projectDir);
|
||||
|
||||
addedCount++;
|
||||
|
||||
@@ -65,8 +65,8 @@ class AgentCommandGenerator {
|
||||
.replaceAll('{{module}}', agent.module)
|
||||
.replaceAll('{{path}}', agentPathInModule)
|
||||
.replaceAll('{{description}}', agent.description || `${agent.name} agent`)
|
||||
.replaceAll('.bmad', this.bmadFolderName)
|
||||
.replaceAll('.bmad', '.bmad');
|
||||
.replaceAll('_bmad', this.bmadFolderName)
|
||||
.replaceAll('_bmad', '_bmad');
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -109,7 +109,7 @@ class WorkflowCommandGenerator {
|
||||
|
||||
// Convert source path to installed path
|
||||
// From: /Users/.../src/modules/bmm/workflows/.../workflow.yaml
|
||||
// To: {project-root}/.bmad/bmm/workflows/.../workflow.yaml
|
||||
// To: {project-root}/_bmad/bmm/workflows/.../workflow.yaml
|
||||
let workflowPath = workflow.path;
|
||||
|
||||
// Extract the relative path from source
|
||||
@@ -131,8 +131,8 @@ class WorkflowCommandGenerator {
|
||||
.replaceAll('{{module}}', workflow.module)
|
||||
.replaceAll('{{description}}', workflow.description)
|
||||
.replaceAll('{{workflow_path}}', workflowPath)
|
||||
.replaceAll('.bmad', this.bmadFolderName)
|
||||
.replaceAll('.bmad', '.bmad');
|
||||
.replaceAll('_bmad', this.bmadFolderName)
|
||||
.replaceAll('_bmad', '_bmad');
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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/{{module}}/agents/{{path}}
|
||||
1. LOAD the FULL agent file from @_bmad/{{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
|
||||
|
||||
@@ -3,12 +3,12 @@ prompt = """
|
||||
CRITICAL: You are now the BMad '{{title}}' agent.
|
||||
|
||||
PRE-FLIGHT CHECKLIST:
|
||||
1. [ ] IMMEDIATE ACTION: Load and parse @{.bmad}/{{module}}/config.yaml - store ALL config values in memory for use throughout the session.
|
||||
2. [ ] IMMEDIATE ACTION: Read and internalize the full agent definition at @{.bmad}/{{module}}/agents/{{name}}.md.
|
||||
1. [ ] IMMEDIATE ACTION: Load and parse @{_bmad}/{{module}}/config.yaml - store ALL config values in memory for use throughout the session.
|
||||
2. [ ] IMMEDIATE ACTION: Read and internalize the full agent definition at @{_bmad}/{{module}}/agents/{{name}}.md.
|
||||
3. [ ] CONFIRM: The user's name from config is {user_name}.
|
||||
|
||||
Only after all checks are complete, greet the user by name and display the menu.
|
||||
Acknowledge this checklist is complete in your first response.
|
||||
|
||||
AGENT DEFINITION: @{.bmad}/{{module}}/agents/{{name}}.md
|
||||
AGENT DEFINITION: @{_bmad}/{{module}}/agents/{{name}}.md
|
||||
"""
|
||||
|
||||
@@ -3,10 +3,10 @@ prompt = """
|
||||
Execute the following BMad Method task workflow:
|
||||
|
||||
PRE-FLIGHT CHECKLIST:
|
||||
1. [ ] IMMEDIATE ACTION: Load and parse @{.bmad}/{{module}}/config.yaml.
|
||||
2. [ ] IMMEDIATE ACTION: Read and load the task definition at @{.bmad}/{{module}}/tasks/{{filename}}.
|
||||
1. [ ] IMMEDIATE ACTION: Load and parse @{_bmad}/{{module}}/config.yaml.
|
||||
2. [ ] IMMEDIATE ACTION: Read and load the task definition at @{_bmad}/{{module}}/tasks/{{filename}}.
|
||||
|
||||
Follow all instructions and complete the task as defined.
|
||||
|
||||
TASK DEFINITION: @{.bmad}/{{module}}/tasks/{{filename}}
|
||||
TASK DEFINITION: @{_bmad}/{{module}}/tasks/{{filename}}
|
||||
"""
|
||||
|
||||
@@ -5,7 +5,7 @@ description: '{{description}}'
|
||||
IT IS CRITICAL THAT YOU FOLLOW THESE STEPS - while staying in character as the current agent persona you may have loaded:
|
||||
|
||||
<steps CRITICAL="TRUE">
|
||||
1. Always LOAD the FULL @.bmad/core/tasks/workflow.xml
|
||||
1. Always LOAD the FULL @_bmad/core/tasks/workflow.xml
|
||||
2. READ its entire contents - this is the CORE OS for EXECUTING the specific workflow-config @{{workflow_path}}
|
||||
3. Pass the yaml path {{workflow_path}} as 'workflow-config' parameter to the workflow.xml instructions
|
||||
4. Follow workflow.xml instructions EXACTLY as written to process and follow the specific workflow config and its instructions
|
||||
|
||||
@@ -47,7 +47,7 @@ class ModuleManager {
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy a file and replace .bmad placeholder with actual folder name
|
||||
* Copy a file and replace _bmad placeholder with actual folder name
|
||||
* @param {string} sourcePath - Source file path
|
||||
* @param {string} targetPath - Target file path
|
||||
*/
|
||||
@@ -62,14 +62,14 @@ class ModuleManager {
|
||||
// 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 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);
|
||||
// Replace _bmad placeholder with actual folder name
|
||||
if (content.includes('_bmad')) {
|
||||
content = content.replaceAll('_bmad', this.bmadFolderName);
|
||||
}
|
||||
|
||||
// Write to target with replaced content
|
||||
@@ -695,8 +695,8 @@ class ModuleManager {
|
||||
|
||||
// IMPORTANT: Replace escape sequence and placeholder BEFORE parsing YAML
|
||||
// Otherwise parsing will fail on the placeholder
|
||||
yamlContent = yamlContent.replaceAll('.bmad', '.bmad');
|
||||
yamlContent = yamlContent.replaceAll('.bmad', this.bmadFolderName);
|
||||
yamlContent = yamlContent.replaceAll('_bmad', '_bmad');
|
||||
yamlContent = yamlContent.replaceAll('_bmad', this.bmadFolderName);
|
||||
|
||||
try {
|
||||
// First check if web_bundle exists by parsing
|
||||
@@ -815,7 +815,7 @@ class ModuleManager {
|
||||
// Create customize template if it doesn't exist
|
||||
if (!(await fs.pathExists(customizePath))) {
|
||||
const { getSourcePath } = require('../../../lib/project-root');
|
||||
const genericTemplatePath = getSourcePath('utility', 'templates', 'agent.customize.template.yaml');
|
||||
const genericTemplatePath = getSourcePath('utility', 'agent-components', 'agent.customize.template.yaml');
|
||||
if (await fs.pathExists(genericTemplatePath)) {
|
||||
await this.copyFileWithPlaceholderReplacement(genericTemplatePath, customizePath);
|
||||
console.log(chalk.dim(` Created customize: ${moduleName}-${agentName}.customize.yaml`));
|
||||
@@ -853,9 +853,9 @@ class ModuleManager {
|
||||
// Compile with customizations if any
|
||||
const { xml } = compileAgent(yamlContent, {}, agentName, relativePath, { config: this.coreConfig });
|
||||
|
||||
// Replace .bmad placeholder if needed
|
||||
if (xml.includes('.bmad') && this.bmadFolderName) {
|
||||
const processedXml = xml.replaceAll('.bmad', this.bmadFolderName);
|
||||
// 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');
|
||||
@@ -872,7 +872,7 @@ class ModuleManager {
|
||||
const projectDir = path.dirname(bmadDir);
|
||||
const resolvedSidecarFolder = agentSidecarFolder
|
||||
.replaceAll('{project-root}', projectDir)
|
||||
.replaceAll('.bmad', path.basename(bmadDir));
|
||||
.replaceAll('_bmad', path.basename(bmadDir));
|
||||
|
||||
// Create sidecar directory for this agent
|
||||
const agentSidecarDir = path.join(resolvedSidecarFolder, agentName);
|
||||
@@ -931,28 +931,23 @@ class ModuleManager {
|
||||
* @param {string} moduleName - Module name
|
||||
*/
|
||||
async processAgentFiles(modulePath, moduleName) {
|
||||
const agentsPath = path.join(modulePath, 'agents');
|
||||
|
||||
// Check if agents directory exists
|
||||
if (!(await fs.pathExists(agentsPath))) {
|
||||
return; // No agents to process
|
||||
}
|
||||
|
||||
// Get all agent MD files recursively
|
||||
const agentFiles = await this.findAgentMdFiles(agentsPath);
|
||||
|
||||
for (const agentFile of agentFiles) {
|
||||
if (!agentFile.endsWith('.md')) continue;
|
||||
|
||||
let content = await fs.readFile(agentFile, 'utf8');
|
||||
|
||||
// Check if content has agent XML and no activation block
|
||||
if (content.includes('<agent') && !content.includes('<activation')) {
|
||||
// Inject the activation block using XML handler
|
||||
content = this.xmlHandler.injectActivationSimple(content);
|
||||
await fs.writeFile(agentFile, content, 'utf8');
|
||||
}
|
||||
}
|
||||
// const agentsPath = path.join(modulePath, 'agents');
|
||||
// // Check if agents directory exists
|
||||
// if (!(await fs.pathExists(agentsPath))) {
|
||||
// return; // No agents to process
|
||||
// }
|
||||
// // Get all agent MD files recursively
|
||||
// const agentFiles = await this.findAgentMdFiles(agentsPath);
|
||||
// for (const agentFile of agentFiles) {
|
||||
// if (!agentFile.endsWith('.md')) continue;
|
||||
// let content = await fs.readFile(agentFile, 'utf8');
|
||||
// // Check if content has agent XML and no activation block
|
||||
// if (content.includes('<agent') && !content.includes('<activation')) {
|
||||
// // Inject the activation block using XML handler
|
||||
// content = this.xmlHandler.injectActivationSimple(content);
|
||||
// await fs.writeFile(agentFile, content, 'utf8');
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1030,10 +1025,10 @@ class ModuleManager {
|
||||
const installWorkflowPath = item['workflow-install']; // Where to copy TO
|
||||
|
||||
// Parse SOURCE workflow path
|
||||
// Handle both .bmad placeholder and hardcoded 'bmad'
|
||||
// Example: {project-root}/.bmad/bmm/workflows/4-implementation/create-story/workflow.yaml
|
||||
// Handle both _bmad placeholder and hardcoded 'bmad'
|
||||
// Example: {project-root}/_bmad/bmm/workflows/4-implementation/create-story/workflow.yaml
|
||||
// Or: {project-root}/bmad/bmm/workflows/4-implementation/create-story/workflow.yaml
|
||||
const sourceMatch = sourceWorkflowPath.match(/\{project-root\}\/(?:\.bmad)\/([^/]+)\/workflows\/(.+)/);
|
||||
const sourceMatch = sourceWorkflowPath.match(/\{project-root\}\/(?:_bmad)\/([^/]+)\/workflows\/(.+)/);
|
||||
if (!sourceMatch) {
|
||||
console.warn(chalk.yellow(` Could not parse workflow path: ${sourceWorkflowPath}`));
|
||||
continue;
|
||||
@@ -1042,9 +1037,9 @@ class ModuleManager {
|
||||
const [, sourceModule, sourceWorkflowSubPath] = sourceMatch;
|
||||
|
||||
// Parse INSTALL workflow path
|
||||
// Handle.bmad
|
||||
// Example: {project-root}/.bmad/bmgd/workflows/4-production/create-story/workflow.yaml
|
||||
const installMatch = installWorkflowPath.match(/\{project-root\}\/(\.bmad)\/([^/]+)\/workflows\/(.+)/);
|
||||
// Handle_bmad
|
||||
// Example: {project-root}/_bmad/bmgd/workflows/4-production/create-story/workflow.yaml
|
||||
const installMatch = installWorkflowPath.match(/\{project-root\}\/(_bmad)\/([^/]+)\/workflows\/(.+)/);
|
||||
if (!installMatch) {
|
||||
console.warn(chalk.yellow(` Could not parse workflow-install path: ${installWorkflowPath}`));
|
||||
continue;
|
||||
@@ -1096,9 +1091,9 @@ class ModuleManager {
|
||||
async updateWorkflowConfigSource(workflowYamlPath, newModuleName) {
|
||||
let yamlContent = await fs.readFile(workflowYamlPath, 'utf8');
|
||||
|
||||
// Replace config_source: "{project-root}/.bmad/OLD_MODULE/config.yaml"
|
||||
// with config_source: "{project-root}/.bmad/NEW_MODULE/config.yaml"
|
||||
// Note: At this point .bmad has already been replaced with actual folder name
|
||||
// Replace config_source: "{project-root}/_bmad/OLD_MODULE/config.yaml"
|
||||
// with config_source: "{project-root}/_bmad/NEW_MODULE/config.yaml"
|
||||
// Note: At this point _bmad has already been replaced with actual folder name
|
||||
const configSourcePattern = /config_source:\s*["']?\{project-root\}\/[^/]+\/[^/]+\/config\.yaml["']?/g;
|
||||
const newConfigSource = `config_source: "{project-root}/${this.bmadFolderName}/${newModuleName}/config.yaml"`;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user