fix bmb workflow paths

This commit is contained in:
Brian Madison
2025-12-10 20:50:24 +09:00
parent 45a97b070a
commit 446a0359ab
336 changed files with 1414 additions and 1509 deletions

View File

@@ -105,7 +105,7 @@ The installer is a multi-stage system that handles agent compilation, IDE integr
- Resolve module dependencies (4-pass system)
3. Install Core + Modules
- Copy files to {target}/{bmad_folder}/
- Copy files to {target}/.bmad/
- Compile agents: YAML → Markdown/XML (forWebBundle: false)
- Merge customize.yaml files if they exist
- Inject activation blocks based on agent capabilities
@@ -131,7 +131,7 @@ The installer is a multi-stage system that handles agent compilation, IDE integr
```
{target}/
├── {bmad_folder}/
├── .bmad/
│ ├── core/ # Always installed
│ ├── {module}/ # Selected modules
│ │ ├── agents/ # Compiled .md files
@@ -239,7 +239,7 @@ Platform specifics are **IDE+module combination hooks** that execute custom logi
### Manifest System
The installer generates **5 manifest files** in `{target}/{bmad_folder}/_cfg/`:
The installer generates **5 manifest files** in `{target}/.bmad/_cfg/`:
**1. Installation Manifest** (`manifest.yaml`)
@@ -428,7 +428,7 @@ agent:
identity: 'You are an experienced PM...'
menu:
- trigger: '*create-brief'
workflow: '{project-root}/{bmad_folder}/bmm/workflows/.../workflow.yaml'
workflow: '{project-root}/.bmad/bmm/workflows/.../workflow.yaml'
```
### Output: IDE (Markdown with XML)
@@ -441,7 +441,7 @@ agent:
```xml
<agent id="..." name="PM">
<activation critical="MANDATORY">
<step n="2">Load {project-root}/{bmad_folder}/bmm/config.yaml at runtime</step>
<step n="2">Load {project-root}/.bmad/bmm/config.yaml at runtime</step>
...
</activation>
<persona>...</persona>
@@ -533,20 +533,20 @@ src/utility/models/fragments/
## Key Differences: Installation vs Bundling
| Aspect | Installation (IDE) | Bundling (Web) |
| ----------------------- | ------------------------------------ | --------------------------------- |
| **Trigger** | `npm run install:bmad` | `npm run bundle` |
| **Entry Point** | `commands/install.js` | `bundlers/bundle-web.js` |
| **Compiler Flag** | `forWebBundle: false` | `forWebBundle: true` |
| **Output Format** | Markdown `.md` | Standalone XML `.xml` |
| **Output Location** | `{target}/{bmad_folder}/` + IDE dirs | `web-bundles/` |
| **Customization** | Merges `customize.yaml` | Base agents only |
| **Dependencies** | Referenced by path | Bundled inline (CDATA) |
| **Activation Fragment** | `activation-steps.xml` | `web-bundle-activation-steps.xml` |
| **Filesystem Access** | Required | Not needed |
| **Build Metadata** | Included (hash) | Excluded |
| **Path Format** | `{project-root}` placeholders | Stripped, wrapped as `<file>` |
| **Use Case** | Local IDE development | Web deployment |
| Aspect | Installation (IDE) | Bundling (Web) |
| ----------------------- | ----------------------------- | --------------------------------- |
| **Trigger** | `npm run install:bmad` | `npm run bundle` |
| **Entry Point** | `commands/install.js` | `bundlers/bundle-web.js` |
| **Compiler Flag** | `forWebBundle: false` | `forWebBundle: true` |
| **Output Format** | Markdown `.md` | Standalone XML `.xml` |
| **Output Location** | `{target}/.bmad/` + IDE dirs | `web-bundles/` |
| **Customization** | Merges `customize.yaml` | Base agents only |
| **Dependencies** | Referenced by path | Bundled inline (CDATA) |
| **Activation Fragment** | `activation-steps.xml` | `web-bundle-activation-steps.xml` |
| **Filesystem Access** | Required | Not needed |
| **Build Metadata** | Included (hash) | Excluded |
| **Path Format** | `{project-root}` placeholders | Stripped, wrapped as `<file>` |
| **Use Case** | Local IDE development | Web deployment |
**Activation Differences**:

View File

@@ -29,7 +29,7 @@ class WebBundler {
// Temporary directory for generated manifests
this.tempDir = path.join(process.cwd(), '.bundler-temp');
this.tempManifestDir = path.join(this.tempDir, 'bmad', '_cfg');
this.tempManifestDir = path.join(this.tempDir, '.bmad', '_cfg');
// Bundle statistics
this.stats = {
@@ -531,9 +531,9 @@ class WebBundler {
}
// Parse paths to extract module and workflow location
// Support both {project-root}/bmad/... and {project-root}/{bmad_folder}/... patterns
const sourceMatch = sourceWorkflowPath.match(/\{project-root\}\/(?:\{bmad_folder\}|bmad)\/([^/]+)\/workflows\/(.+)/);
const installMatch = installWorkflowPath.match(/\{project-root\}\/(?:\{bmad_folder\}|bmad)\/([^/]+)\/workflows\/(.+)/);
// Support both {project-root}/bmad/... and {project-root}/.bmad/... patterns
const sourceMatch = sourceWorkflowPath.match(/\{project-root\}\/(?:\.?bmad)\/([^/]+)\/workflows\/(.+)/);
const installMatch = installWorkflowPath.match(/\{project-root\}\/(?:\.?bmad)\/([^/]+)\/workflows\/(.+)/);
if (!sourceMatch || !installMatch) {
continue;
@@ -584,9 +584,9 @@ class WebBundler {
let yamlContent = await fs.readFile(workflowYamlPath, 'utf8');
// Replace config_source with new module reference
// Support both old format (bmad) and new format ({bmad_folder})
const configSourcePattern = /config_source:\s*["']?\{project-root\}\/(?:\{bmad_folder\}|bmad)\/[^/]+\/config\.yaml["']?/g;
const newConfigSource = `config_source: "{project-root}/{bmad_folder}/${newModuleName}/config.yaml"`;
// Support both old format (bmad) and new format (.bmad)
const configSourcePattern = /config_source:\s*["']?\{project-root\}\/(?:\.?bmad)\/[^/]+\/config\.yaml["']?/g;
const newConfigSource = `config_source: "{project-root}/.bmad/${newModuleName}/config.yaml"`;
const updatedYaml = yamlContent.replaceAll(configSourcePattern, newConfigSource);
await fs.writeFile(workflowYamlPath, updatedYaml, 'utf8');
@@ -723,7 +723,7 @@ class WebBundler {
/tools="([^"]+)"/g,
/knowledge="([^"]+)"/g,
/{project-root}\/([^"'\s<>]+)/g, // Legacy {project-root} paths
/\bbmad\/([^"'\s<>]+)/g, // Direct bmad/ paths (after {bmad_folder} replacement)
/\bbmad\/([^"'\s<>]+)/g, // Direct bmad/ paths (after .bmad replacement)
];
for (const pattern of patterns) {
@@ -733,8 +733,8 @@ class WebBundler {
let filePath = match[1];
// Remove {project-root} prefix if present
filePath = filePath.replace(/^{project-root}\//, '');
// Remove {bmad_folder} prefix if present (should be rare, mostly replaced already)
filePath = filePath.replace(/^{bmad_folder}\//, 'bmad/');
// Remove .bmad prefix if present (should be rare, mostly replaced already)
filePath = filePath.replace(/^.bmad\//, 'bmad/');
// For bmad/ pattern, prepend 'bmad/' since it was captured without it
if (pattern.source.includes(String.raw`\bbmad\/`)) {
@@ -760,8 +760,8 @@ class WebBundler {
while ((match = pattern.exec(xml)) !== null) {
let workflowPath = match[1];
workflowPath = workflowPath.replace(/^{project-root}\//, '');
// Remove {bmad_folder} prefix if present and replace with bmad
workflowPath = workflowPath.replace(/^{bmad_folder}\//, 'bmad/');
// Remove .bmad prefix if present and replace with bmad
workflowPath = workflowPath.replace(/^.bmad\//, 'bmad/');
// Skip obvious placeholder/example paths
if (workflowPath && workflowPath.endsWith('.yaml') && !workflowPath.includes('path/to/') && !workflowPath.includes('example')) {
@@ -851,7 +851,7 @@ class WebBundler {
if (deps) {
for (const dep of deps) {
let depPath = dep.replaceAll(/['"]/g, '').replace(/^{project-root}\//, '');
depPath = depPath.replace(/^{bmad_folder}\//, 'bmad/');
depPath = depPath.replace(/^.bmad\//, 'bmad/');
if (depPath && !processed.has(depPath)) {
await this.processFileDependency(depPath, dependencies, processed, moduleName, warnings);
}
@@ -865,7 +865,7 @@ class WebBundler {
if (templates) {
for (const template of templates) {
let templatePath = template.replaceAll(/['"]/g, '').replace(/^{project-root}\//, '');
templatePath = templatePath.replace(/^{bmad_folder}\//, 'bmad/');
templatePath = templatePath.replace(/^.bmad\//, 'bmad/');
if (templatePath && !processed.has(templatePath)) {
await this.processFileDependency(templatePath, dependencies, processed, moduleName, warnings);
}
@@ -1053,13 +1053,13 @@ class WebBundler {
bundleYamlContent = yamlContent;
}
// Process {project-root} and {bmad_folder} references in the YAML content
// Process {project-root} and .bmad references in the YAML content
bundleYamlContent = this.processProjectRootReferences(bundleYamlContent);
// Include the YAML file with only web_bundle content, wrapped in XML
// Process the workflow path to create a clean ID
let yamlId = workflowPath.replace(/^{project-root}\//, '');
yamlId = yamlId.replace(/^{bmad_folder}\//, 'bmad/');
yamlId = yamlId.replace(/^.bmad\//, 'bmad/');
const wrappedYaml = this.wrapContentInXml(bundleYamlContent, yamlId, 'yaml');
dependencies.set(yamlId, wrappedYaml);
@@ -1078,7 +1078,7 @@ class WebBundler {
for (const bundleFilePath of bundleFiles) {
// Process the file path to create a clean ID for checking if already processed
let cleanFilePath = bundleFilePath.replace(/^{project-root}\//, '');
cleanFilePath = cleanFilePath.replace(/^{bmad_folder}\//, 'bmad/');
cleanFilePath = cleanFilePath.replace(/^.bmad\//, 'bmad/');
if (processed.has(cleanFilePath)) {
continue;
@@ -1087,7 +1087,7 @@ class WebBundler {
const bundleActualPath = this.resolveFilePath(bundleFilePath, moduleName);
if (!bundleActualPath || !(await fs.pathExists(bundleActualPath))) {
// Use the cleaned path in warnings (with {bmad_folder} replaced)
// Use the cleaned path in warnings (with .bmad replaced)
warnings.push(cleanFilePath);
continue;
}
@@ -1136,7 +1136,7 @@ class WebBundler {
}
let fileContent = await fs.readFile(actualPath, 'utf8');
// Process {project-root} and {bmad_folder} references
// Process {project-root} and .bmad references
fileContent = this.processProjectRootReferences(fileContent);
const wrappedContent = this.wrapContentInXml(fileContent, coreWorkflowPath, 'xml');
dependencies.set(coreWorkflowPath, wrappedContent);
@@ -1162,7 +1162,7 @@ class WebBundler {
}
let fileContent = await fs.readFile(actualPath, 'utf8');
// Process {project-root} and {bmad_folder} references
// Process {project-root} and .bmad references
fileContent = this.processProjectRootReferences(fileContent);
const fileExt = path.extname(actualPath).toLowerCase().replace('.', '');
const wrappedContent = this.wrapContentInXml(fileContent, filePath, fileExt);
@@ -1196,8 +1196,8 @@ class WebBundler {
async processWildcardDependency(pattern, dependencies, processed, moduleName, warnings = []) {
// Remove {project-root} prefix
pattern = pattern.replace(/^{project-root}\//, '');
// Replace {bmad_folder} with bmad
pattern = pattern.replace(/^{bmad_folder}\//, 'bmad/');
// Replace .bmad with bmad
pattern = pattern.replace(/^.bmad\//, 'bmad/');
// Get directory and file pattern
const lastSlash = pattern.lastIndexOf('/');
@@ -1265,9 +1265,6 @@ class WebBundler {
resolveFilePath(filePath, moduleName) {
// Remove {project-root} prefix
filePath = filePath.replace(/^{project-root}\//, '');
// Replace {bmad_folder} with bmad
filePath = filePath.replace(/^{bmad_folder}\//, 'bmad/');
filePath = filePath.replace(/^{bmad_folder}$/, 'bmad');
// Check temp directory first for _cfg files
if (filePath.startsWith('bmad/_cfg/')) {
@@ -1278,11 +1275,6 @@ class WebBundler {
}
}
// Handle different path patterns for bmad files
// bmad/cis/tasks/brain-session.md -> src/modules/cis/tasks/brain-session.md
// bmad/core/tasks/create-doc.md -> src/core/tasks/create-doc.md
// bmad/bmm/templates/brief.md -> src/modules/bmm/templates/brief.md
let actualPath = filePath;
if (filePath.startsWith('bmad/')) {
@@ -1334,15 +1326,13 @@ class WebBundler {
}
/**
* Process and remove {project-root} references and replace {bmad_folder} with bmad
* Process and remove {project-root} references
*/
processProjectRootReferences(content) {
// Remove {project-root}/ prefix (with slash)
content = content.replaceAll('{project-root}/', '');
// Also remove {project-root} without slash
content = content.replaceAll('{project-root}', '');
// Replace {bmad_folder} with bmad
content = content.replaceAll('{bmad_folder}', 'bmad');
return content;
}

View File

@@ -80,7 +80,7 @@ module.exports = {
*/
async function buildAgent(projectDir, agentName) {
// First check standalone agents in bmad/agents/{agentname}/
const standaloneAgentDir = path.join(projectDir, 'bmad', 'agents', agentName);
const standaloneAgentDir = path.join(projectDir, '.bmad', 'agents', agentName);
let standaloneYamlPath = path.join(standaloneAgentDir, `${agentName}.agent.yaml`);
// If exact match doesn't exist, look for any .agent.yaml file in the directory
@@ -99,7 +99,7 @@ async function buildAgent(projectDir, agentName) {
// Build the standalone agent
console.log(chalk.cyan(` Building standalone agent ${agentName}...`));
const customizePath = path.join(projectDir, 'bmad', '_cfg', 'agents', `${agentName}.customize.yaml`);
const customizePath = path.join(projectDir, '.bmad', '_cfg', 'agents', `${agentName}.customize.yaml`);
const customizeExists = await fs.pathExists(customizePath);
await builder.buildAgent(standaloneYamlPath, customizeExists ? customizePath : null, outputPath, { includeMetadata: true });
@@ -109,7 +109,7 @@ async function buildAgent(projectDir, agentName) {
}
// Find the agent YAML file in .claude/commands/bmad/
const bmadCommandsDir = path.join(projectDir, '.claude', 'commands', 'bmad');
const bmadCommandsDir = path.join(projectDir, '.claude', 'commands', '.bmad');
// Search all module directories for the agent
const modules = await fs.readdir(bmadCommandsDir);
@@ -149,7 +149,7 @@ async function buildAllAgents(projectDir) {
let builtCount = 0;
// First, build standalone agents in bmad/agents/
const standaloneAgentsDir = path.join(projectDir, 'bmad', 'agents');
const standaloneAgentsDir = path.join(projectDir, '.bmad', 'agents');
if (await fs.pathExists(standaloneAgentsDir)) {
console.log(chalk.cyan('\nBuilding standalone agents...'));
const agentDirs = await fs.readdir(standaloneAgentsDir);
@@ -177,7 +177,7 @@ async function buildAllAgents(projectDir) {
console.log(chalk.cyan(` Building standalone agent ${agentName}...`));
const customizePath = path.join(projectDir, 'bmad', '_cfg', 'agents', `${agentName}.customize.yaml`);
const customizePath = path.join(projectDir, '.bmad', '_cfg', 'agents', `${agentName}.customize.yaml`);
const customizeExists = await fs.pathExists(customizePath);
await builder.buildAgent(agentYamlPath, customizeExists ? customizePath : null, outputPath, { includeMetadata: true });

View File

@@ -696,15 +696,6 @@ class ConfigCollector {
}
}
// Special handling for bmad_folder: detect existing folder name
if (moduleName === 'core' && key === 'bmad_folder' && !existingValue && this.currentProjectDir) {
// Try to detect the existing BMAD folder name
const detectedFolder = await this.detectExistingBmadFolder(this.currentProjectDir);
if (detectedFolder) {
existingValue = detectedFolder;
}
}
// Special handling for user_name: default to system user
if (moduleName === 'core' && key === 'user_name' && !existingValue) {
item.default = this.getDefaultUsername();

View File

@@ -14,7 +14,7 @@
* @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_folder}), module dependency resolution
* @patterns Injection point processing (AgentVibes), placeholder replacement (.bmad), module dependency resolution
* @related GitHub AgentVibes#34 (injection points), ui.js (user prompts), copyFileWithPlaceholderReplacement()
*/
@@ -67,7 +67,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 +89,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_folder} 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')
@@ -105,11 +105,6 @@ class Installer {
* @calledby installCore(), installModule(), IDE installers during file vendoring
* @calls processTTSInjectionPoints(), fs.readFile(), fs.writeFile(), fs.copy()
*
* AI NOTE: This is the core transformation pipeline for ALL BMAD installation file copies.
* It performs two transformations in sequence:
* 1. {bmad_folder} → user's custom folder name (e.g., ".bmad" or "bmad")
* 2. <!-- TTS_INJECTION:* --> → TTS bash calls (if enabled) OR stripped (if disabled)
*
* The injection point processing enables loose coupling between BMAD and TTS providers:
* - BMAD source contains injection markers (not actual TTS code)
* - At install-time, markers are replaced OR removed based on user preference
@@ -140,16 +135,6 @@ class Installer {
// Read the file content
let content = await fs.readFile(sourcePath, 'utf8');
// Replace {bmad_folder} placeholder with actual folder name
if (content.includes('{bmad_folder}')) {
content = content.replaceAll('{bmad_folder}', bmadFolderName);
}
// Replace escape sequence {*bmad_folder*} with literal {bmad_folder}
if (content.includes('{*bmad_folder*}')) {
content = content.replaceAll('{*bmad_folder*}', '{bmad_folder}');
}
// Process AgentVibes injection points (pass targetPath for tracking)
content = this.processTTSInjectionPoints(content, targetPath);
@@ -487,8 +472,8 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
});
}
// Get bmad_folder from config (default to 'bmad' for backwards compatibility)
const bmadFolderName = moduleConfigs.core && moduleConfigs.core.bmad_folder ? moduleConfigs.core.bmad_folder : '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
@@ -507,7 +492,6 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
// Resolve target directory (path.resolve handles platform differences)
const projectDir = path.resolve(config.directory);
// Check if bmad_folder has changed from existing installation (only if project dir exists)
let existingBmadDir = null;
let existingBmadFolderName = null;
@@ -516,54 +500,6 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
existingBmadFolderName = path.basename(existingBmadDir);
}
const targetBmadDir = path.join(projectDir, bmadFolderName);
// If bmad_folder changed during update/upgrade, back up old folder and do fresh install
if (existingBmadDir && (await fs.pathExists(existingBmadDir)) && existingBmadFolderName !== bmadFolderName) {
spinner.stop();
console.log(chalk.yellow(`\n⚠️ bmad_folder has changed: ${existingBmadFolderName}${bmadFolderName}`));
console.log(chalk.yellow('This will result in a fresh installation to the new folder.'));
const inquirer = require('inquirer');
const { confirmFreshInstall } = await inquirer.prompt([
{
type: 'confirm',
name: 'confirmFreshInstall',
message: chalk.cyan('Proceed with fresh install? (Your old folder will be backed up)'),
default: true,
},
]);
if (!confirmFreshInstall) {
console.log(chalk.yellow('Installation cancelled.'));
return { success: false, cancelled: true };
}
spinner.start('Backing up existing installation...');
// Find a unique backup name
let backupDir = `${existingBmadDir}-bak`;
let counter = 1;
while (await fs.pathExists(backupDir)) {
backupDir = `${existingBmadDir}-bak-${counter}`;
counter++;
}
// Rename the old folder to backup
await fs.move(existingBmadDir, backupDir);
spinner.succeed(`Backed up ${existingBmadFolderName}${path.basename(backupDir)}`);
console.log(chalk.cyan('\n📋 Important:'));
console.log(chalk.dim(` - Your old installation has been backed up to: ${path.basename(backupDir)}`));
console.log(chalk.dim(` - If you had custom agents or configurations, copy them from:`));
console.log(chalk.dim(` ${path.basename(backupDir)}/_cfg/`));
console.log(chalk.dim(` - To the new location:`));
console.log(chalk.dim(` ${bmadFolderName}/_cfg/`));
console.log('');
spinner.start('Starting fresh installation...');
}
// Create a project directory if it doesn't exist (user already confirmed)
if (!(await fs.pathExists(projectDir))) {
spinner.text = 'Creating installation directory...';
@@ -1932,8 +1868,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_folder} with actual folder name
xmlContent = xmlContent.replaceAll('{bmad_folder}', 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 || {};
@@ -1980,7 +1916,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_folder}', this.bmadFolderName || 'bmad');
.replaceAll('.bmad', this.bmadFolderName || 'bmad');
// Create sidecar directory for this agent
const agentSidecarDir = path.join(resolvedSidecarFolder, agentName);
@@ -2674,7 +2610,6 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
lastModified: new Date().toISOString(),
};
// Check if bmad_folder has changed
const existingBmadFolderName = path.basename(bmadDir);
const newBmadFolderName = this.configCollector.collectedConfig.core?.bmad_folder || existingBmadFolderName;
@@ -3272,7 +3207,7 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
const agentSidecarFolder = config.coreConfig?.agent_sidecar_folder;
// Resolve path variables
const resolvedSidecarFolder = agentSidecarFolder.replaceAll('{project-root}', projectDir).replaceAll('{bmad_folder}', bmadDir);
const resolvedSidecarFolder = agentSidecarFolder.replaceAll('{project-root}', projectDir).replaceAll('.bmad', bmadDir);
// Create sidecar directory for this agent
const agentSidecarDir = path.join(resolvedSidecarFolder, finalAgentName);

View File

@@ -23,7 +23,7 @@ class ManifestGenerator {
/**
* Generate all manifests for the installation
* @param {string} bmadDir - BMAD installation directory
* @param {string} bmadDir - .bmad
* @param {Array} selectedModules - Selected modules for installation
* @param {Array} installedFiles - All installed files (optional, for hash tracking)
*/

View File

@@ -255,7 +255,6 @@ class CustomHandler {
let content = await fs.readFile(sourcePath, 'utf8');
// Replace placeholders
content = content.replaceAll('{bmad_folder}', config.bmad_folder || 'bmad');
content = content.replaceAll('{user_name}', config.user_name || 'User');
content = content.replaceAll('{communication_language}', config.communication_language || 'English');
content = content.replaceAll('{output_folder}', config.output_folder || 'docs');
@@ -321,7 +320,6 @@ class CustomHandler {
if (await fs.pathExists(genericTemplatePath)) {
// Copy with placeholder replacement
let templateContent = await fs.readFile(genericTemplatePath, 'utf8');
templateContent = templateContent.replaceAll('{bmad_folder}', config.bmad_folder || 'bmad');
await fs.writeFile(customizePath, templateContent, 'utf8');
console.log(chalk.dim(` Created customize: custom-${agentName}.customize.yaml`));
}
@@ -332,7 +330,6 @@ class CustomHandler {
// Replace placeholders in the compiled content
let processedXml = xml;
processedXml = processedXml.replaceAll('{bmad_folder}', config.bmad_folder || 'bmad');
processedXml = processedXml.replaceAll('{user_name}', config.user_name || 'User');
processedXml = processedXml.replaceAll('{communication_language}', config.communication_language || 'English');
processedXml = processedXml.replaceAll('{output_folder}', config.output_folder || 'docs');
@@ -358,7 +355,7 @@ class CustomHandler {
const projectDir = path.dirname(bmadDir);
const resolvedSidecarFolder = config.agent_sidecar_folder
.replaceAll('{project-root}', projectDir)
.replaceAll('{bmad_folder}', path.basename(bmadDir));
.replaceAll('.bmad', path.basename(bmadDir));
// Create sidecar directory for this agent
const agentSidecarDir = path.join(resolvedSidecarFolder, agentName);

View File

@@ -527,26 +527,26 @@ class BaseIdeSetup {
}
/**
* Write file with content (replaces {bmad_folder} placeholder)
* Write file with content (replaces .bmad placeholder)
* @param {string} filePath - File path
* @param {string} content - File content
*/
async writeFile(filePath, content) {
// Replace {bmad_folder} placeholder if present
if (typeof content === 'string' && content.includes('{bmad_folder}')) {
content = content.replaceAll('{bmad_folder}', this.bmadFolderName);
// Replace .bmad placeholder if present
if (typeof content === 'string' && content.includes('.bmad')) {
content = content.replaceAll('.bmad', this.bmadFolderName);
}
// Replace escape sequence {*bmad_folder*} with literal {bmad_folder}
if (typeof content === 'string' && content.includes('{*bmad_folder*}')) {
content = content.replaceAll('{*bmad_folder*}', '{bmad_folder}');
// 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_folder} 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 +563,14 @@ class BaseIdeSetup {
// Read the file content
let content = await fs.readFile(source, 'utf8');
// Replace {bmad_folder} placeholder with actual folder name
if (content.includes('{bmad_folder}')) {
content = content.replaceAll('{bmad_folder}', this.bmadFolderName);
// Replace .bmad placeholder with actual folder name
if (content.includes('.bmad')) {
content = content.replaceAll('.bmad', this.bmadFolderName);
}
// Replace escape sequence {*bmad_folder*} with literal {bmad_folder}
if (content.includes('{*bmad_folder*}')) {
content = content.replaceAll('{*bmad_folder*}', '{bmad_folder}');
// Replace escape sequence .bmad with literal .bmad
if (content.includes('.bmad')) {
content = content.replaceAll('.bmad', '.bmad');
}
// Write to dest with replaced content

View File

@@ -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_folder*}}', '{bmad_folder}')
.replaceAll('{{bmad_folder}}', 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_folder*}}', '{bmad_folder}')
.replaceAll('{{bmad_folder}}', this.bmadFolderName)
.replaceAll('{.bmad}', '.bmad')
.replaceAll('{.bmad}', this.bmadFolderName)
.replaceAll('{{module}}', task.module)
.replaceAll('{{filename}}', task.filename);

View File

@@ -65,8 +65,8 @@ class AgentCommandGenerator {
.replaceAll('{{module}}', agent.module)
.replaceAll('{{path}}', agentPathInModule)
.replaceAll('{{description}}', agent.description || `${agent.name} agent`)
.replaceAll('{bmad_folder}', this.bmadFolderName)
.replaceAll('{*bmad_folder*}', '{bmad_folder}');
.replaceAll('.bmad', this.bmadFolderName)
.replaceAll('.bmad', '.bmad');
}
/**

View File

@@ -109,7 +109,7 @@ class WorkflowCommandGenerator {
// Convert source path to installed path
// From: /Users/.../src/modules/bmm/workflows/.../workflow.yaml
// To: {project-root}/{bmad_folder}/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_folder}', this.bmadFolderName)
.replaceAll('{*bmad_folder*}', '{bmad_folder}');
.replaceAll('.bmad', this.bmadFolderName)
.replaceAll('.bmad', '.bmad');
}
/**

View File

@@ -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/{{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

View File

@@ -3,12 +3,12 @@ prompt = """
CRITICAL: You are now the BMad '{{title}}' agent.
PRE-FLIGHT CHECKLIST:
1. [ ] IMMEDIATE ACTION: Load and parse @{{bmad_folder}}/{{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_folder}}/{{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_folder}}/{{module}}/agents/{{name}}.md
AGENT DEFINITION: @{.bmad}/{{module}}/agents/{{name}}.md
"""

View File

@@ -3,10 +3,10 @@ prompt = """
Execute the following BMad Method task workflow:
PRE-FLIGHT CHECKLIST:
1. [ ] IMMEDIATE ACTION: Load and parse @{{bmad_folder}}/{{module}}/config.yaml.
2. [ ] IMMEDIATE ACTION: Read and load the task definition at @{{bmad_folder}}/{{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_folder}}/{{module}}/tasks/{{filename}}
TASK DEFINITION: @{.bmad}/{{module}}/tasks/{{filename}}
"""

View File

@@ -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_folder}/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

View File

@@ -47,7 +47,7 @@ class ModuleManager {
}
/**
* Copy a file and replace {bmad_folder} 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_folder*} with literal {bmad_folder}
if (content.includes('{*bmad_folder*}')) {
content = content.replaceAll('{*bmad_folder*}', '{bmad_folder}');
// Replace escape sequence .bmad with literal .bmad
if (content.includes('.bmad')) {
content = content.replaceAll('.bmad', '.bmad');
}
// Replace {bmad_folder} placeholder with actual folder name
if (content.includes('{bmad_folder}')) {
content = content.replaceAll('{bmad_folder}', 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_folder*}', '{bmad_folder}');
yamlContent = yamlContent.replaceAll('{bmad_folder}', this.bmadFolderName);
yamlContent = yamlContent.replaceAll('.bmad', '.bmad');
yamlContent = yamlContent.replaceAll('.bmad', this.bmadFolderName);
try {
// First check if web_bundle exists by parsing
@@ -853,9 +853,9 @@ class ModuleManager {
// Compile with customizations if any
const { xml } = compileAgent(yamlContent, {}, agentName, relativePath, { config: this.coreConfig });
// Replace {bmad_folder} placeholder if needed
if (xml.includes('{bmad_folder}') && this.bmadFolderName) {
const processedXml = xml.replaceAll('{bmad_folder}', 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_folder}', path.basename(bmadDir));
.replaceAll('.bmad', path.basename(bmadDir));
// Create sidecar directory for this agent
const agentSidecarDir = path.join(resolvedSidecarFolder, agentName);
@@ -1030,10 +1030,10 @@ class ModuleManager {
const installWorkflowPath = item['workflow-install']; // Where to copy TO
// Parse SOURCE workflow path
// Handle both {bmad_folder} placeholder and hardcoded 'bmad'
// Example: {project-root}/{bmad_folder}/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_folder\}|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 +1042,9 @@ class ModuleManager {
const [, sourceModule, sourceWorkflowSubPath] = sourceMatch;
// Parse INSTALL workflow path
// Handle both {bmad_folder} placeholder and hardcoded 'bmad'
// Example: {project-root}/{bmad_folder}/bmgd/workflows/4-production/create-story/workflow.yaml
const installMatch = installWorkflowPath.match(/\{project-root\}\/(?:\{bmad_folder\}|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 +1096,9 @@ class ModuleManager {
async updateWorkflowConfigSource(workflowYamlPath, newModuleName) {
let yamlContent = await fs.readFile(workflowYamlPath, 'utf8');
// Replace config_source: "{project-root}/{bmad_folder}/OLD_MODULE/config.yaml"
// with config_source: "{project-root}/{bmad_folder}/NEW_MODULE/config.yaml"
// Note: At this point {bmad_folder} 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"`;

View File

@@ -59,7 +59,7 @@ function buildSimpleActivation(criticalActions = [], menuItems = [], deploymentT
// Standard steps
activation += ` <step n="${stepNum++}">Load persona from this current agent file (already in context)</step>\n`;
activation += ` <step n="${stepNum++}">Load and read {project-root}/{bmad_folder}/core/config.yaml to get {user_name}, {communication_language}, {output_folder}</step>\n`;
activation += ` <step n="${stepNum++}">Load and read {project-root}/.bmad/core/config.yaml to get {user_name}, {communication_language}, {output_folder}</step>\n`;
activation += ` <step n="${stepNum++}">Remember: user's name is {user_name}</step>\n`;
// Agent-specific steps from critical_actions
@@ -119,7 +119,7 @@ function buildSimpleActivation(criticalActions = [], menuItems = [], deploymentT
if (usedHandlers.has('workflow')) {
activation += ` <handler type="workflow">
When menu item has: workflow="path/to/workflow.yaml"
1. CRITICAL: Always LOAD {project-root}/{bmad_folder}/core/tasks/workflow.xml
1. CRITICAL: Always LOAD {project-root}/.bmad/core/tasks/workflow.xml
2. Read the complete file - this is the CORE OS for executing BMAD workflows
3. Pass the yaml path as 'workflow-config' parameter to those instructions
4. Execute workflow.xml instructions precisely following all steps
@@ -150,7 +150,7 @@ function buildSimpleActivation(criticalActions = [], menuItems = [], deploymentT
if (usedHandlers.has('validate-workflow')) {
activation += ` <handler type="validate-workflow">
When menu item has: validate-workflow="path/to/workflow.yaml"
1. CRITICAL: Always LOAD {project-root}/{bmad_folder}/core/tasks/validate-workflow.xml
1. CRITICAL: Always LOAD {project-root}/.bmad/core/tasks/validate-workflow.xml
2. Read the complete file - this is the CORE OS for validating BMAD workflows
3. Pass the workflow.yaml path as 'workflow' parameter to those instructions
4. Pass any checklist.md from the workflow location as 'checklist' parameter if available

View File

@@ -273,7 +273,7 @@ function installAgent(agentInfo, answers, targetPath, options = {}) {
// Resolve path variables
const resolvedSidecarFolder = agentSidecarFolder
.replaceAll('{project-root}', options.projectRoot || process.cwd())
.replaceAll('{bmad_folder}', options.bmadFolder || '.bmad');
.replaceAll('.bmad', options.bmadFolder || '.bmad');
// Create sidecar directory for this agent
const agentSidecarDir = path.join(resolvedSidecarFolder, agentFolderName);
@@ -407,7 +407,7 @@ function detectBmadProject(targetPath) {
// Walk up directory tree looking for BMAD installation
while (checkPath !== root) {
const possibleNames = ['.bmad', 'bmad'];
const possibleNames = ['.bmad'];
for (const name of possibleNames) {
const bmadFolder = path.join(checkPath, name);
const cfgFolder = path.join(bmadFolder, '_cfg');

View File

@@ -136,7 +136,7 @@ class UI {
// Create the bmad directory based on core config
const path = require('node:path');
const fs = require('fs-extra');
const bmadFolderName = coreConfig.bmad_folder || 'bmad';
const bmadFolderName = '.bmad';
const bmadDir = path.join(confirmedDirectory, bmadFolderName);
await fs.ensureDir(bmadDir);
@@ -1082,7 +1082,7 @@ class UI {
* @calls checkAgentVibesInstalled(), inquirer.prompt(), chalk.green/yellow/dim()
*
* AI NOTE: This prompt is strategically positioned in installation flow:
* - AFTER core config (bmad_folder, user_name, etc)
* - AFTER core config (user_name, etc)
* - BEFORE IDE selection (which can hang on Windows/PowerShell)
*
* Flow Logic:
@@ -1210,129 +1210,134 @@ class UI {
*/
async promptCustomContentForExisting() {
try {
CLIUtils.displaySection('Custom Content', 'Add new custom agents, workflows, or modules to your installation');
// Skip custom content installation - always return false
return { hasCustomContent: false };
const { hasCustomContent } = await inquirer.prompt([
{
type: 'list',
name: 'hasCustomContent',
message: 'Do you want to add or update custom content?',
choices: [
{
name: 'No, continue with current installation only',
value: false,
},
{
name: 'Yes, I have custom content to add or update',
value: true,
},
],
default: false,
},
]);
// TODO: Custom content installation temporarily disabled
// CLIUtils.displaySection('Custom Content', 'Add new custom agents, workflows, or modules to your installation');
if (!hasCustomContent) {
return { hasCustomContent: false };
}
// const { hasCustomContent } = await inquirer.prompt([
// {
// type: 'list',
// name: 'hasCustomContent',
// message: 'Do you want to add or update custom content?',
// choices: [
// {
// name: 'No, continue with current installation only',
// value: false,
// },
// {
// name: 'Yes, I have custom content to add or update',
// value: true,
// },
// ],
// default: false,
// },
// ]);
// Get directory path
const { customPath } = await inquirer.prompt([
{
type: 'input',
name: 'customPath',
message: 'Enter directory to search for custom content (will scan subfolders):',
default: process.cwd(),
validate: async (input) => {
if (!input || input.trim() === '') {
return 'Please enter a directory path';
}
// if (!hasCustomContent) {
// return { hasCustomContent: false };
// }
// Normalize and check if path exists
const expandedPath = CLIUtils.expandPath(input.trim());
const pathExists = await fs.pathExists(expandedPath);
if (!pathExists) {
return 'Directory does not exist';
}
// TODO: Custom content installation temporarily disabled
// // Get directory path
// const { customPath } = await inquirer.prompt([
// {
// type: 'input',
// name: 'customPath',
// message: 'Enter directory to search for custom content (will scan subfolders):',
// default: process.cwd(),
// validate: async (input) => {
// if (!input || input.trim() === '') {
// return 'Please enter a directory path';
// }
// Check if it's actually a directory
const stats = await fs.stat(expandedPath);
if (!stats.isDirectory()) {
return 'Path must be a directory';
}
// // Normalize and check if path exists
// const expandedPath = CLIUtils.expandPath(input.trim());
// const pathExists = await fs.pathExists(expandedPath);
// if (!pathExists) {
// return 'Directory does not exist';
// }
return true;
},
transformer: (input) => {
return CLIUtils.expandPath(input);
},
},
]);
// // Check if it's actually a directory
// const stats = await fs.stat(expandedPath);
// if (!stats.isDirectory()) {
// return 'Path must be a directory';
// }
const resolvedPath = CLIUtils.expandPath(customPath);
// return true;
// },
// transformer: (input) => {
// return CLIUtils.expandPath(input);
// },
// },
// ]);
// Find custom content
const customHandler = new CustomHandler();
const customFiles = await customHandler.findCustomContent(resolvedPath);
// const resolvedPath = CLIUtils.expandPath(customPath);
if (customFiles.length === 0) {
console.log(chalk.yellow(`\nNo custom content found in ${resolvedPath}`));
// // Find custom content
// const customHandler = new CustomHandler();
// const customFiles = await customHandler.findCustomContent(resolvedPath);
const { tryDifferent } = await inquirer.prompt([
{
type: 'confirm',
name: 'tryDifferent',
message: 'Try a different directory?',
default: true,
},
]);
// if (customFiles.length === 0) {
// console.log(chalk.yellow(`\nNo custom content found in ${resolvedPath}`));
if (tryDifferent) {
return await this.promptCustomContentForExisting();
}
// const { tryDifferent } = await inquirer.prompt([
// {
// type: 'confirm',
// name: 'tryDifferent',
// message: 'Try a different directory?',
// default: true,
// },
// ]);
return { hasCustomContent: false };
}
// if (tryDifferent) {
// return await this.promptCustomContentForExisting();
// }
// Display found items
console.log(chalk.cyan(`\nFound ${customFiles.length} custom content file(s):`));
const customContentItems = [];
// return { hasCustomContent: false };
// }
for (const customFile of customFiles) {
const customInfo = await customHandler.getCustomInfo(customFile);
if (customInfo) {
customContentItems.push({
name: `${chalk.cyan('✓')} ${customInfo.name} ${chalk.gray(`(${customInfo.relativePath})`)}`,
value: `__CUSTOM_CONTENT__${customFile}`,
checked: true,
});
}
}
// // Display found items
// console.log(chalk.cyan(`\nFound ${customFiles.length} custom content file(s):`));
// const customContentItems = [];
// Add option to keep existing custom content
console.log(chalk.yellow('\nExisting custom modules will be preserved unless you remove them'));
// for (const customFile of customFiles) {
// const customInfo = await customHandler.getCustomInfo(customFile);
// if (customInfo) {
// customContentItems.push({
// name: `${chalk.cyan('✓')} ${customInfo.name} ${chalk.gray(`(${customInfo.relativePath})`)}`,
// value: `__CUSTOM_CONTENT__${customFile}`,
// checked: true,
// });
// }
// }
const { selectedFiles } = await inquirer.prompt([
{
type: 'checkbox',
name: 'selectedFiles',
message: 'Select custom content to add:',
choices: customContentItems,
pageSize: 15,
validate: (answer) => {
if (answer.length === 0) {
return 'You must select at least one item';
}
return true;
},
},
]);
// // Add option to keep existing custom content
// console.log(chalk.yellow('\nExisting custom modules will be preserved unless you remove them'));
return {
hasCustomContent: true,
customPath: resolvedPath,
selected: true,
selectedFiles: selectedFiles,
};
// const { selectedFiles } = await inquirer.prompt([
// {
// type: 'checkbox',
// name: 'selectedFiles',
// message: 'Select custom content to add:',
// choices: customContentItems,
// pageSize: 15,
// validate: (answer) => {
// if (answer.length === 0) {
// return 'You must select at least one item';
// }
// return true;
// },
// },
// ]);
// return {
// hasCustomContent: true,
// customPath: resolvedPath,
// selected: true,
// selectedFiles: selectedFiles,
// };
} catch (error) {
console.error(chalk.red('Error configuring custom content:'), error);
return { hasCustomContent: false };

View File

@@ -3,17 +3,16 @@ const { ManifestGenerator } = require('./installers/lib/core/manifest-generator'
async function regenerateManifests() {
const generator = new ManifestGenerator();
const targetDir = process.argv[2] || 'z1';
const bmadDir = path.join(process.cwd(), targetDir, 'bmad');
const targetDir = process.argv[2];
// List of modules to include in manifests
const selectedModules = ['bmb', 'bmm', 'cis'];
console.log('Regenerating manifests with relative paths...');
console.log('Target directory:', bmadDir);
console.log('Target directory: .bmad');
try {
const result = await generator.generateManifests(bmadDir, selectedModules, [], { ides: [] });
const result = await generator.generateManifests('.bmad', selectedModules, [], { ides: [] });
console.log('✓ Manifests generated successfully:');
console.log(` - ${result.workflows} workflows`);
console.log(` - ${result.agents} agents`);

View File

@@ -12,7 +12,7 @@ const chalk = require('chalk');
* Find BMAD directory in project
*/
function findBmadDir(projectDir = process.cwd()) {
const possibleNames = ['bmad', '.bmad'];
const possibleNames = ['.bmad'];
for (const name of possibleNames) {
const bmadDir = path.join(projectDir, name);