mirror of
https://github.com/bmadcode/BMAD-METHOD.git
synced 2025-12-29 16:14:59 +00:00
fix bmb workflow paths
This commit is contained in:
@@ -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**:
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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 });
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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)
|
||||
*/
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
"""
|
||||
|
||||
@@ -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}}
|
||||
"""
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"`;
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -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 };
|
||||
|
||||
@@ -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`);
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user