folder rename from .bmad to _bmad

This commit is contained in:
Brian Madison
2025-12-13 16:22:34 +08:00
parent 0c873638ab
commit 25c79e3fe5
375 changed files with 1421 additions and 2745 deletions

View File

@@ -1,609 +1,3 @@
# BMad CLI Tool
The BMad CLI handles installation and web bundling for the BMAD-METHOD framework. It compiles YAML agents into two distinct formats: **IDE-integrated agents** (filesystem-aware, customizable) and **web bundles** (self-contained, dependency-embedded).
## Table of Contents
- [BMad CLI Tool](#bmad-cli-tool)
- [Table of Contents](#table-of-contents)
- [Overview](#overview)
- [Commands](#commands)
- [Installation](#installation)
- [Bundling](#bundling)
- [Utilities](#utilities)
- [Installation System](#installation-system)
- [Installation Flow](#installation-flow)
- [IDE Support](#ide-support)
- [Custom Module Configuration](#custom-module-configuration)
- [Platform Specifics](#platform-specifics)
- [Manifest System](#manifest-system)
- [Advanced Features](#advanced-features)
- [Bundling System](#bundling-system)
- [Bundling Flow](#bundling-flow)
- [Agent Compilation](#agent-compilation)
- [Compilation Engine](#compilation-engine)
- [Fragment System](#fragment-system)
- [Input: Agent YAML](#input-agent-yaml)
- [Output: IDE (Markdown with XML)](#output-ide-markdown-with-xml)
- [Architecture](#architecture)
- [Directory Structure](#directory-structure)
- [Fragment Library](#fragment-library)
- [Key Differences: Installation vs Bundling](#key-differences-installation-vs-bundling)
- [Development Workflows](#development-workflows)
- [Testing Compilation](#testing-compilation)
- [Adding New Menu Handlers](#adding-new-menu-handlers)
- [Regenerating Manifests](#regenerating-manifests)
- [Related Documentation](#related-documentation)
- [Support](#support)
---
## Overview
The CLI provides two primary functions:
1. **Installation**: Compiles agents from YAML and installs to IDE environments with full customization support
2. **Bundling**: Packages agents and dependencies into standalone web-ready XML files
Both use the same YAML→XML compilation engine but produce different outputs optimized for their environments.
---
## Commands
### Installation
```bash
# Interactive installation
npm run install:bmad
# Direct CLI usage
node tools/cli/bmad-cli.js install --target /path/to/project --modules bmm,bmb --ides codex
# Flags:
# --target <path> Target project directory
# --modules <list> Comma-separated: bmm, bmb, cis
# --ides <list> Comma-separated IDE codes (see IDE Support)
# --non-interactive Skip all prompts
```
### Bundling
```bash
# Bundle all modules
npm run bundle
# Bundle specific items
node tools/cli/bundlers/bundle-web.js all # Everything
node tools/cli/bundlers/bundle-web.js module bmm # One module
node tools/cli/bundlers/bundle-web.js agent bmm pm # One agent
```
### Utilities
```bash
npm run bmad:status # Installation status
npm run validate:bundles # Validate web bundles
node tools/cli/regenerate-manifests.js # Regenerate agent-manifest.csv files
```
---
## Installation System
The installer is a multi-stage system that handles agent compilation, IDE integration, module configuration, platform-specific behaviors, and manifest generation.
### Installation Flow
```
1. Collect User Input
- Target directory, modules, IDEs
- Custom module configuration (via module.yaml)
2. Pre-Installation
- Validate target, check conflicts, backup existing installations
- Resolve module dependencies (4-pass system)
3. Install Core + Modules
- 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
4. IDE Integration
- Initialize selected IDE handlers
- Generate IDE-specific artifacts (commands/rules/workflows)
- Execute platform-specific hooks (IDE+module combinations)
5. Generate Manifests
- manifest.yaml (installation metadata)
- workflow-manifest.csv (workflow catalog)
- agent-manifest.csv (agent metadata)
- task-manifest.csv (legacy)
- files-manifest.csv (all files with SHA256 hashes)
6. Validate & Finalize
- Verify file integrity, agent compilation, IDE artifacts
- Display summary and next steps
```
**Output Structure**:
```
{target}/
├── .bmad/
│ ├── core/ # Always installed
│ ├── {module}/ # Selected modules
│ │ ├── agents/ # Compiled .md files
│ │ ├── workflows/
│ │ └── config.yaml
│ └── _cfg/ # Manifests
└── .{ide}/ # IDE-specific artifacts
└── ... # Format varies by IDE
```
### IDE Support
The installer supports **15 IDE environments** through a base-derived architecture. Each IDE handler extends `BaseIDE` and implements IDE-specific artifact generation.
**Supported IDEs** (as of v6-alpha):
| Code | Name | Artifact Location |
| ---------------- | ----------------- | ------------------------ |
| `codex` | Claude Code | `.claude/commands/` |
| `claude-code` | Claude Code (alt) | `.claude/commands/` |
| `opencode` | OpenCode | `.opencode` |
| `windsurf` | Windsurf | `.windsurf/workflows/` |
| `cursor` | Cursor | `.cursor/rules/` |
| `cline` | Cline | `.clinerules/workflows/` |
| `github-copilot` | GitHub Copilot | `.github/copilot/` |
| `crush` | Crush | `.crush/` |
| `auggie` | Auggie | `.auggie/` |
| `gemini` | Google Gemini | `.gemini/` |
| `qwen` | Qwen | `.qwen/` |
| `roo` | Roo | `.roo/` |
| `rovo-dev` | Rovo | `.rovodev/` |
| `trae` | Trae | `.trae/` |
| `iflow` | iFlow | `.iflow/` |
| `kilo` | Kilo | `.kilo/` |
**Handler Architecture**:
- Base class: `tools/cli/installers/lib/ide/_base-ide.js`
- Handler implementations: `tools/cli/installers/lib/ide/{ide-code}.js`
- Dynamic discovery: IDE manager scans directory and auto-registers handlers
- Each handler implements: `setup()`, `createArtifacts()`, `cleanup()`, `getAgentsFromBmad()`
**Adding New IDE Support**:
1. Create handler file: `tools/cli/installers/lib/ide/your-ide.js`
2. Extend `BaseIDE`, set `ideCode`, `ideName`, `artifactType`
3. Implement artifact generation methods
4. IDE auto-discovered on next run
### Custom Module Configuration
Modules define interactive configuration menus via `module.yaml` files in their `_module-installer/` directories.
**Config File Location**:
- Core: `src/core/module.yaml`
- Modules: `src/modules/{module}/module.yaml`
**Configuration Types**:
- `select`: Radio button choices
- `multiselect`: Checkboxes
- `input`: Text input with validation
- `confirm`: Yes/no
**Variable Substitution**:
- `{project-root}` → Absolute target path
- `{directory_name}` → Project directory basename
- `{module}` → Current module name
- `{value:config_id}` → Reference another config value
**Config Persistence**:
- Values saved to module's `config.yaml`
- Existing values detected on reinstall
- User prompted: "Use existing or change?"
**Processor**: `tools/cli/installers/lib/core/config-collector.js`
### Platform Specifics
Platform specifics are **IDE+module combination hooks** that execute custom logic when specific IDE and module are installed together.
**Two-Layer Architecture**:
1. **Module-Level**: `src/modules/{module}/_module-installer/platform-specifics/{ide}.js`
- Module provides custom behavior for specific IDEs
- Example: BMM creates subagents when installed with Claude Code
2. **IDE-Level**: Embedded in IDE handler's `createArtifacts()` method
- IDE provides custom handling for specific modules
- Example: Windsurf configures cascade workflows for BMM
**Execution Timing**: After standard installation, before validation
**Common Use Cases**:
- Creating subagent variations (PM-technical, PM-market)
- Configuring IDE-specific workflow integrations
- Adding custom commands or rules based on module features
- Adjusting UI/UX for module-specific patterns
**Platform Registry**: `tools/cli/installers/lib/ide/shared/platform-codes.js`
### Manifest System
The installer generates **5 manifest files** in `{target}/.bmad/_cfg/`:
**1. Installation Manifest** (`manifest.yaml`)
- Installation metadata: version, timestamps, target directory
- Installed modules and versions
- Integrated IDEs and their configurations
- User configuration values
**2. Workflow Manifest** (`workflow-manifest.csv`)
- Columns: module, workflow_path, workflow_name, description, scale_level
- Used by workflow command generators
- RFC 4180 compliant CSV format
**3. Agent Manifest** (`agent-manifest.csv`)
- Columns: module, agent_path, agent_name, role, identity_summary, communication_style, expertise, approach, responsibilities, workflows
- 10-column metadata for each agent
- Used by IDE integrations and documentation
**4. Task Manifest** (`task-manifest.csv`)
- Legacy compatibility (deprecated in v6)
- Columns: module, task_path, task_name, objective, agent
**5. Files Manifest** (`files-manifest.csv`)
- Complete file tracking with SHA256 hashes
- Columns: file_path, file_type, module, hash
- Enables integrity validation and change detection
**Generator**: `tools/cli/installers/lib/core/manifest-generator.js`
**Use Cases**:
- Update detection (compare current vs manifest hashes)
- Workflow command generation for IDEs
- Installation validation and integrity checks
- Rollback capability
### Advanced Features
**Dependency Resolution** (4-Pass System):
- Pass 1: Explicit dependencies from module metadata
- Pass 2: Template references in workflows
- Pass 3: Cross-module workflow/agent references
- Pass 4: Transitive dependencies
**Agent Activation Injection**:
- Detects which handlers each agent uses (workflow, exec, tmpl, data, action)
- Injects only needed handler fragments from `src/utility/models/fragments/`
- Keeps compiled agents lean and purpose-built
**Module Injection System**:
- Conditional content injection based on user config
- Can inject menu items, text blocks, workflow steps
- File: `tools/cli/installers/lib/ide/shared/module-injections.js`
**Conflict Resolution**:
- Detects existing installations
- Options: Update (preserve customizations), Backup (timestamp), Cancel
- Auto-backup to `.bmad-backup-{timestamp}` if selected
**Workflow Command Auto-Generation**:
- Reads workflow-manifest.csv
- Generates IDE commands for each workflow
- IDE-specific formatting (Claude Code .md, Windsurf YAML, etc.)
**Validation & Integrity**:
- Verifies all manifest files exist
- Validates file hashes against files-manifest.csv
- Checks agent compilation completeness
- Confirms IDE artifacts created
---
## Bundling System
Web bundling creates self-contained XML packages with all dependencies embedded for web deployment.
### Bundling Flow
```
1. Discover modules and agents from src/modules/
2. For each agent:
- Compile with YamlXmlBuilder (forWebBundle: true)
- Use web-bundle-activation-steps.xml fragment
- Resolve ALL dependencies recursively:
- Scan menu items for workflow references
- Load workflows → extract web_bundle section
- Find all file references (templates, data, sub-workflows)
- Wrap each in <file id="path"><![CDATA[...]]></file>
- Build consolidated bundle: agent + all deps
3. Output to: web-bundles/{module}/agents/{name}.xml
```
**Key Differences from Installation**:
- No customize.yaml merging (base agents only)
- No metadata (reduces file size)
- All dependencies bundled inline (no filesystem access)
- Uses web-specific activation fragment
- Output: Standalone XML files
**Output Structure**:
```
web-bundles/
├── bmm/
│ ├── agents/
│ │ ├── pm.xml
│ │ ├── architect.xml
│ │ ├── sm.xml
│ │ └── dev.xml
│ └── teams/
│ └── dev-team.xml
├── bmb/
│ └── agents/
│ └── bmad-builder.xml
└── cis/
└── agents/
└── creative-director.xml
```
**Bundler**: `tools/cli/bundlers/web-bundler.js`
---
## Agent Compilation
Both installation and bundling use the same YAML→XML compiler with different configurations.
### Compilation Engine
**Core File**: `tools/cli/lib/yaml-xml-builder.js`
**Process**:
1. Load YAML agent definition
2. Merge with customize.yaml (installation only)
3. Analyze agent to detect required handlers
4. Build activation block:
- IDE: Uses `activation-steps.xml` (filesystem-aware)
- Web: Uses `web-bundle-activation-steps.xml` (bundled files)
5. Convert to XML structure
6. Output as markdown (IDE) or standalone XML (web)
**Key Option Flags**:
- `forWebBundle: true` - Use web activation, omit metadata
- `includeMetadata: true` - Include build hash (IDE only)
- `skipActivation: true` - Omit activation (team bundles)
### Fragment System
Reusable XML fragments in `src/utility/models/fragments/`:
- `activation-steps.xml` - IDE activation (loads config.yaml at runtime)
- `web-bundle-activation-steps.xml` - Web activation (uses bundled files)
- `activation-rules.xml` - Validation rules (IDE only)
- `menu-handlers.xml` - Menu handler wrapper
- `handler-workflow.xml` - Workflow handler
- `handler-exec.xml` - Exec command handler
- `handler-tmpl.xml` - Template handler
- `handler-data.xml` - Data handler
- `handler-action.xml` - Action handler
**Dynamic Injection**: Agent analyzer detects which handlers are used, activation builder injects only those fragments.
### Input: Agent YAML
```yaml
agent:
metadata:
id: 'bmad/bmm/agents/pm.md'
name: 'PM'
title: 'Product Manager'
persona:
role: 'Product Manager'
identity: 'You are an experienced PM...'
menu:
- trigger: '*create-brief'
workflow: '{project-root}/.bmad/bmm/workflows/.../workflow.yaml'
```
### Output: IDE (Markdown with XML)
````markdown
<!-- Powered by BMAD-CORE™ -->
# Product Manager
```xml
<agent id="..." name="PM">
<activation critical="MANDATORY">
<step n="2">Load {project-root}/.bmad/bmm/config.yaml at runtime</step>
...
</activation>
<persona>...</persona>
<menu>...</menu>
</agent>
```
````
````
### Output: Web (Standalone XML)
```xml
<agent id="..." name="PM">
<activation critical="MANDATORY">
<step n="2">All dependencies bundled inline below</step>
...
</activation>
<persona>...</persona>
<menu>...</menu>
<bundled-files>
<file id="bmad/bmm/config.yaml"><![CDATA[...]]></file>
<file id="bmad/bmm/workflows/.../workflow.yaml"><![CDATA[...]]></file>
...
</bundled-files>
</agent>
````
---
## Architecture
### Directory Structure
```
tools/cli/
├── bmad-cli.js # Main CLI entry
├── commands/ # CLI command handlers
│ ├── install.js
│ ├── status.js
│ ├── list.js
│ ├── update.js
│ └── uninstall.js
├── bundlers/ # Web bundling
│ ├── bundle-web.js # CLI entry
│ └── web-bundler.js # WebBundler class
├── installers/
│ └── lib/
│ ├── core/ # Core installer logic
│ │ ├── installer.js
│ │ ├── manifest-generator.js
│ │ ├── manifest.js
│ │ ├── dependency-resolver.js
│ │ ├── config-collector.js
│ │ └── csv-parser.js
│ ├── modules/ # Module processing
│ │ └── manager.js
│ └── ide/ # IDE integrations
│ ├── _base-ide.js
│ ├── {14 IDE handlers}.js
│ ├── manager.js
│ └── shared/
│ ├── bmad-artifacts.js
│ ├── platform-codes.js
│ ├── module-injections.js
│ └── workflow-command-generator.js
├── lib/ # Shared compilation
│ ├── yaml-xml-builder.js # YAML→XML compiler
│ ├── activation-builder.js # Activation generator
│ ├── agent-analyzer.js # Handler detection
│ ├── xml-handler.js # Builder wrapper
│ └── paths.js
├── regenerate-manifests.js
└── test-yaml-builder.js
```
### Fragment Library
```
src/utility/models/fragments/
├── activation-steps.xml
├── web-bundle-activation-steps.xml
├── activation-rules.xml
├── menu-handlers.xml
└── handler-*.xml # 5 handler types
```
---
## 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/` + 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**:
- **IDE**: Loads config.yaml at runtime from filesystem
- **Web**: Accesses bundled content via `<file id>` references
---
## Development Workflows
### Testing Compilation
```bash
# Test YAML→XML compiler
node tools/cli/test-yaml-builder.js
# Test installation
node tools/cli/bmad-cli.js install --target ./test-project --modules bmm --ides codex
# Test bundling
node tools/cli/bundlers/bundle-web.js agent bmm pm
# Validate bundles
npm run validate:bundles
```
### Adding New Menu Handlers
To add a new handler type (e.g., `validate-workflow`):
1. Create fragment: `src/utility/models/fragments/handler-validate-workflow.xml`
2. Update `agent-analyzer.js` to detect the new attribute
3. Update `activation-builder.js` to load/inject the fragment
4. Test with an agent using the handler
### Regenerating Manifests
```bash
# Regenerate agent-manifest.csv for all modules
node tools/cli/regenerate-manifests.js
# Location: src/modules/{module}/agents/agent-manifest.csv
```
---
## Related Documentation
- **Project Guide**: `CLAUDE.md`
- **BMM Workflows**: `src/modules/bmm/workflows/README.md`
- **Module Structure**: `src/modules/bmb/workflows/create-module/module-structure.md`
- **Agent Creation**: `src/modules/bmb/workflows/create-agent/README.md`
---
## Support
- **Issues**: <https://github.com/bmad-code-org/BMAD-METHOD/issues>
- **Discord**: <https://discord.gg/gk8jAdXWmj> (#general-dev, #bugs-issues)
- **YouTube**: <https://www.youtube.com/@BMadCode>
Revised CLI tool docs coming....

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

@@ -135,7 +135,7 @@ class Detector {
}
/**
* Detect legacy installation (.bmad-method, .bmm, .cis)
* Detect legacy installation (_bmad-method, .bmm, .cis)
* @param {string} projectDir - Project directory to check
* @returns {Object} Legacy installation details
*/
@@ -147,8 +147,8 @@ class Detector {
paths: [],
};
// Check for legacy core (.bmad-method)
const legacyCorePath = path.join(projectDir, '.bmad-method');
// Check for legacy core (_bmad-method)
const legacyCorePath = path.join(projectDir, '_bmad-method');
if (await fs.pathExists(legacyCorePath)) {
result.hasLegacy = true;
result.legacyCore = true;
@@ -161,7 +161,7 @@ class Detector {
if (
entry.isDirectory() &&
entry.name.startsWith('.') &&
entry.name !== '.bmad-method' &&
entry.name !== '_bmad-method' &&
!entry.name.startsWith('.git') &&
!entry.name.startsWith('.vscode') &&
!entry.name.startsWith('.idea')
@@ -204,7 +204,7 @@ class Detector {
/**
* Detect legacy BMAD v4 footprints (case-sensitive path checks)
* V4 used .bmad-method as default folder name
* V4 used _bmad-method as default folder name
* V6+ uses configurable folder names and ALWAYS has _cfg/manifest.yaml with installation.version
* @param {string} projectDir - Project directory to check
* @returns {{ hasLegacyV4: boolean, offenders: string[] }}

View File

@@ -1,23 +1,3 @@
/**
* File: tools/cli/installers/lib/core/installer.js
*
* BMAD Method - Business Model Agile Development Method
* Repository: https://github.com/paulpreibisch/BMAD-METHOD
*
* Copyright (c) 2025 Paul Preibisch
* Licensed under the Apache License, Version 2.0
*
* ---
*
* @fileoverview Core BMAD installation orchestrator with AgentVibes injection point support
* @context Manages complete BMAD installation flow including core agents, modules, IDE configs, and optional TTS integration
* @architecture Orchestrator pattern - coordinates Detector, ModuleManager, IdeManager, and file operations to build complete BMAD installation
* @dependencies fs-extra, ora, chalk, detector.js, module-manager.js, ide-manager.js, config.js
* @entrypoints Called by install.js command via installer.install(config)
* @patterns Injection point processing (AgentVibes), placeholder replacement (.bmad), module dependency resolution
* @related GitHub AgentVibes#34 (injection points), ui.js (user prompts), copyFileWithPlaceholderReplacement()
*/
const path = require('node:path');
const fs = require('fs-extra');
const chalk = require('chalk');
@@ -32,13 +12,11 @@ const { Config } = require('../../../lib/config');
const { XmlHandler } = require('../../../lib/xml-handler');
const { DependencyResolver } = require('./dependency-resolver');
const { ConfigCollector } = require('./config-collector');
// processInstallation no longer needed - LLMs understand {project-root}
const { getProjectRoot, getSourcePath, getModulePath } = require('../../../lib/project-root');
const { AgentPartyGenerator } = require('../../../lib/agent-party-generator');
const { CLIUtils } = require('../../../lib/cli-utils');
const { ManifestGenerator } = require('./manifest-generator');
const { IdeConfigManager } = require('./ide-config-manager');
const { replaceAgentSidecarFolders } = require('./post-install-sidecar-replacement');
const { CustomHandler } = require('../custom/handler');
class Installer {
@@ -67,7 +45,7 @@ class Installer {
// Check if project directory exists
if (!(await fs.pathExists(projectDir))) {
// Project doesn't exist yet, return default
return path.join(projectDir, '.bmad');
return path.join(projectDir, '_bmad');
}
// V6+ strategy: Look for ANY directory with _cfg/manifest.yaml
@@ -89,13 +67,13 @@ class Installer {
// No V6+ installation found, return default
// This will be used for new installations
return path.join(projectDir, '.bmad');
return path.join(projectDir, '_bmad');
}
/**
* @function copyFileWithPlaceholderReplacement
* @intent Copy files from BMAD source to installation directory with dynamic content transformation
* @why Enables installation-time customization: .bmad replacement + optional AgentVibes TTS injection
* @why Enables installation-time customization: _bmad replacement + optional AgentVibes TTS injection
* @param {string} sourcePath - Absolute path to source file in BMAD repository
* @param {string} targetPath - Absolute path to destination file in user's project
* @param {string} bmadFolderName - User's chosen bmad folder name (default: 'bmad')
@@ -472,8 +450,8 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
});
}
// Always use .bmad as the folder name
const bmadFolderName = '.bmad';
// Always use _bmad as the folder name
const bmadFolderName = '_bmad';
this.bmadFolderName = bmadFolderName; // Store for use in other methods
// Store AgentVibes configuration for injection point processing
@@ -602,7 +580,7 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
// If there are custom files, back them up temporarily
if (customFiles.length > 0) {
const tempBackupDir = path.join(projectDir, '.bmad-custom-backup-temp');
const tempBackupDir = path.join(projectDir, '_bmad-custom-backup-temp');
await fs.ensureDir(tempBackupDir);
spinner.start(`Backing up ${customFiles.length} custom files...`);
@@ -619,7 +597,7 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
// For modified files, back them up to temp directory (will be restored as .bak files after install)
if (modifiedFiles.length > 0) {
const tempModifiedBackupDir = path.join(projectDir, '.bmad-modified-backup-temp');
const tempModifiedBackupDir = path.join(projectDir, '_bmad-modified-backup-temp');
await fs.ensureDir(tempModifiedBackupDir);
console.log(chalk.yellow(`\nDEBUG: Backing up ${modifiedFiles.length} modified files to temp location`));
@@ -653,7 +631,7 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
// Back up custom files
if (customFiles.length > 0) {
const tempBackupDir = path.join(projectDir, '.bmad-custom-backup-temp');
const tempBackupDir = path.join(projectDir, '_bmad-custom-backup-temp');
await fs.ensureDir(tempBackupDir);
spinner.start(`Backing up ${customFiles.length} custom files...`);
@@ -669,7 +647,7 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
// Back up modified files
if (modifiedFiles.length > 0) {
const tempModifiedBackupDir = path.join(projectDir, '.bmad-modified-backup-temp');
const tempModifiedBackupDir = path.join(projectDir, '_bmad-modified-backup-temp');
await fs.ensureDir(tempModifiedBackupDir);
spinner.start(`Backing up ${modifiedFiles.length} modified files...`);
@@ -1316,29 +1294,6 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
console.log(chalk.dim('Review the .bak files to see your changes and merge if needed.\n'));
}
// Reinstall custom agents from _cfg/custom/agents/ sources
const customAgentResults = await this.reinstallCustomAgents(projectDir, bmadDir);
if (customAgentResults.count > 0) {
console.log(chalk.green(`\n✓ Reinstalled ${customAgentResults.count} custom agent${customAgentResults.count > 1 ? 's' : ''}`));
for (const agent of customAgentResults.agents) {
console.log(chalk.dim(` - ${agent}`));
}
}
// Replace {agent_sidecar_folder} placeholders in all agent files
console.log(chalk.dim('\n Configuring agent sidecar folders...'));
const sidecarResults = await replaceAgentSidecarFolders(bmadDir);
if (sidecarResults.filesReplaced > 0) {
console.log(
chalk.green(
` ✓ Updated ${sidecarResults.filesReplaced} agent file(s) with ${sidecarResults.totalReplacements} sidecar reference(s)`,
),
);
} else {
console.log(chalk.dim(' No agent sidecar references found'));
}
// Display completion message
const { UI } = require('../../../lib/ui');
const ui = new UI();
@@ -1852,7 +1807,7 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
// Create customize template if it doesn't exist
if (!(await fs.pathExists(customizePath))) {
const genericTemplatePath = getSourcePath('utility', 'templates', 'agent.customize.template.yaml');
const genericTemplatePath = getSourcePath('utility', 'agent-components', 'agent.customize.template.yaml');
if (await fs.pathExists(genericTemplatePath)) {
await this.copyFileWithPlaceholderReplacement(genericTemplatePath, customizePath, this.bmadFolderName || 'bmad');
console.log(chalk.dim(` Created customize: ${moduleName}-${agentName}.customize.yaml`));
@@ -1868,8 +1823,8 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
// DO NOT replace {project-root} - LLMs understand this placeholder at runtime
// const processedContent = xmlContent.replaceAll('{project-root}', projectDir);
// Replace .bmad with actual folder name
xmlContent = xmlContent.replaceAll('.bmad', this.bmadFolderName || 'bmad');
// Replace _bmad with actual folder name
xmlContent = xmlContent.replaceAll('_bmad', this.bmadFolderName || 'bmad');
// Replace {agent_sidecar_folder} if configured
const coreConfig = this.configCollector.collectedConfig.core || {};
@@ -1916,7 +1871,7 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
// Resolve path variables
const resolvedSidecarFolder = agentSidecarFolder
.replaceAll('{project-root}', projectDir)
.replaceAll('.bmad', this.bmadFolderName || 'bmad');
.replaceAll('_bmad', this.bmadFolderName || 'bmad');
// Create sidecar directory for this agent
const agentSidecarDir = path.join(resolvedSidecarFolder, agentName);
@@ -1942,20 +1897,6 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
console.log(chalk.dim(` Built agent: ${agentName}.md${hasSidecar ? ' (with sidecar)' : ''}`));
}
// Handle legacy .md agents - inject activation if needed
else if (agentFile.endsWith('.md')) {
const agentPath = path.join(agentsPath, agentFile);
let content = await fs.readFile(agentPath, 'utf8');
// Check if content has agent XML and no activation block
if (content.includes('<agent') && !content.includes('<activation')) {
// Inject the activation block using XML handler
content = this.xmlHandler.injectActivationSimple(content);
// Ensure POSIX-compliant final newline
const finalContent = content.endsWith('\n') ? content : content + '\n';
await fs.writeFile(agentPath, finalContent, 'utf8');
}
}
}
}
@@ -2169,23 +2110,18 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
* @returns {Object} Compilation results
*/
async compileAgents(config) {
const ora = require('ora');
const spinner = ora('Starting agent compilation...').start();
try {
const projectDir = path.resolve(config.directory);
const bmadDir = await this.findBmadDir(projectDir);
// Check if bmad directory exists
if (!(await fs.pathExists(bmadDir))) {
spinner.fail('No BMAD installation found');
throw new Error(`BMAD not installed at ${bmadDir}`);
}
// Check for custom modules with missing sources
const manifest = await this.manifest.read(bmadDir);
if (manifest && manifest.customModules && manifest.customModules.length > 0) {
spinner.stop();
console.log(chalk.yellow('\nChecking custom module sources before compilation...'));
const customModuleSources = new Map();
@@ -2196,15 +2132,12 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
const projectRoot = getProjectRoot();
const installedModules = manifest.modules || [];
await this.handleMissingCustomSources(customModuleSources, bmadDir, projectRoot, 'compile-agents', installedModules);
spinner.start('Rebuilding agent files...');
}
let agentCount = 0;
let taskCount = 0;
// Process all modules in bmad directory
spinner.text = 'Rebuilding agent files...';
const entries = await fs.readdir(bmadDir, { withFileTypes: true });
for (const entry of entries) {
@@ -2213,7 +2146,6 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
// Special handling for standalone agents in bmad/agents/ directory
if (entry.name === 'agents') {
spinner.text = 'Building standalone agents...';
await this.buildStandaloneAgents(bmadDir, projectDir);
// Count standalone agents
@@ -2245,16 +2177,6 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
}
}
// Reinstall custom agents from _cfg/custom/agents/ sources
spinner.start('Rebuilding custom agents...');
const customAgentResults = await this.reinstallCustomAgents(projectDir, bmadDir);
if (customAgentResults.count > 0) {
spinner.succeed(`Rebuilt ${customAgentResults.count} custom agent${customAgentResults.count > 1 ? 's' : ''}`);
agentCount += customAgentResults.count;
} else {
spinner.succeed('No custom agents found to rebuild');
}
// Skip full manifest regeneration during compileAgents to preserve custom agents
// Custom agents are already added to manifests during individual installation
// Only regenerate YAML manifest for IDE updates if needed
@@ -2269,36 +2191,20 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
// Update IDE configurations using the existing IDE list from manifest
if (existingIdes && existingIdes.length > 0) {
spinner.start('Updating IDE configurations...');
for (const ide of existingIdes) {
spinner.text = `Updating ${ide}...`;
// Stop spinner before IDE setup to prevent blocking any potential prompts
// However, we pass _alreadyConfigured to skip all prompts during compile
spinner.stop();
await this.ideManager.setup(ide, projectDir, bmadDir, {
selectedModules: installedModules,
skipModuleInstall: true, // Skip module installation, just update IDE files
verbose: config.verbose,
preCollectedConfig: { _alreadyConfigured: true }, // Skip all interactive prompts during compile
});
// Restart spinner for next IDE
if (existingIdes.indexOf(ide) < existingIdes.length - 1) {
spinner.start('Updating IDE configurations...');
}
}
console.log(chalk.green('✓ IDE configurations updated'));
} else {
console.log(chalk.yellow('⚠️ No IDEs configured. Skipping IDE update.'));
}
return { agentCount, taskCount };
} catch (error) {
spinner.fail('Compilation failed');
throw error;
}
}
@@ -2610,19 +2516,6 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
lastModified: new Date().toISOString(),
};
const existingBmadFolderName = path.basename(bmadDir);
const newBmadFolderName = this.configCollector.collectedConfig.core?.bmad_folder || existingBmadFolderName;
if (existingBmadFolderName === newBmadFolderName) {
// Normal quick update - start the spinner
console.log(chalk.cyan('Updating BMAD installation...'));
} else {
// Folder name has changed - stop spinner and let install() handle it
spinner.stop();
console.log(chalk.yellow(`\n⚠️ Folder name will change: ${existingBmadFolderName}${newBmadFolderName}`));
console.log(chalk.yellow('The installer will handle the folder migration.\n'));
}
// Build the config object for the installer
const installConfig = {
directory: projectDir,
@@ -2690,14 +2583,14 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
console.log(chalk.yellow.bold('\n⚠ Legacy BMAD v4 detected'));
console.log(chalk.dim('The installer found legacy artefacts in your project.\n'));
// Separate .bmad* folders (auto-backup) from other offending paths (manual cleanup)
// Separate _bmad* folders (auto-backup) from other offending paths (manual cleanup)
const bmadFolders = legacyV4.offenders.filter((p) => {
const name = path.basename(p);
return name.startsWith('.bmad'); // Only dot-prefixed folders get auto-backed up
return name.startsWith('_bmad'); // Only dot-prefixed folders get auto-backed up
});
const otherOffenders = legacyV4.offenders.filter((p) => {
const name = path.basename(p);
return !name.startsWith('.bmad'); // Everything else is manual cleanup
return !name.startsWith('_bmad'); // Everything else is manual cleanup
});
const inquirer = require('inquirer');
@@ -2730,7 +2623,7 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
}
}
// Handle .bmad* folders with automatic backup
// Handle _bmad* folders with automatic backup
if (bmadFolders.length > 0) {
console.log(chalk.cyan('The following legacy folders will be moved to v4-backup:'));
for (const p of bmadFolders) console.log(chalk.dim(` - ${p}`));
@@ -2846,7 +2739,7 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
if (fileEntry.path) {
// Paths are relative to bmadDir. Legacy manifests incorrectly prefixed 'bmad/' -
// strip it if present. This is safe because no real path inside bmadDir would
// start with 'bmad/' (you'd never have .bmad/bmad/... as an actual structure).
// start with 'bmad/' (you'd never have _bmad/bmad/... as an actual structure).
const relativePath = fileEntry.path.startsWith('bmad/') ? fileEntry.path.slice(5) : fileEntry.path;
const absolutePath = path.join(bmadDir, relativePath);
installedFilesMap.set(path.normalize(absolutePath), {
@@ -3099,165 +2992,6 @@ If AgentVibes party mode is enabled, immediately trigger TTS with agent's voice:
return nodes;
}
/**
* Reinstall custom agents from backup and source locations
* This preserves custom agents across quick updates/reinstalls
* @param {string} projectDir - Project directory
* @param {string} bmadDir - BMAD installation directory
* @returns {Object} Result with count and agent names
*/
async reinstallCustomAgents(projectDir, bmadDir) {
const {
discoverAgents,
loadAgentConfig,
extractManifestData,
addToManifest,
createIdeSlashCommands,
updateManifestYaml,
} = require('../../../lib/agent/installer');
const { compileAgent } = require('../../../lib/agent/compiler');
const results = { count: 0, agents: [] };
// Check multiple locations for custom agents
const sourceLocations = [
path.join(bmadDir, '_cfg', 'custom', 'agents'), // Backup location
path.join(bmadDir, 'custom', 'src', 'agents'), // BMAD folder source location
path.join(projectDir, 'custom', 'src', 'agents'), // Project root source location
];
let foundAgents = [];
let processedAgents = new Set(); // Track to avoid duplicates
// Discover agents from all locations
for (const location of sourceLocations) {
if (await fs.pathExists(location)) {
const agents = discoverAgents(location);
// Only add agents we haven't processed yet
const newAgents = agents.filter((agent) => !processedAgents.has(agent.name));
foundAgents.push(...newAgents);
for (const agent of newAgents) processedAgents.add(agent.name);
}
}
if (foundAgents.length === 0) {
return results;
}
try {
const customAgentsDir = path.join(bmadDir, 'custom', 'agents');
await fs.ensureDir(customAgentsDir);
const manifestFile = path.join(bmadDir, '_cfg', 'agent-manifest.csv');
const manifestYamlFile = path.join(bmadDir, '_cfg', 'manifest.yaml');
for (const agent of foundAgents) {
try {
const agentConfig = loadAgentConfig(agent.yamlFile);
const finalAgentName = agent.name; // Already named correctly from save
// Determine agent type from the name (e.g., "fred-commit-poet" → "commit-poet")
let agentType = finalAgentName;
const parts = finalAgentName.split('-');
if (parts.length >= 2) {
// Try to extract type (last part or last two parts)
// For "fred-commit-poet", we want "commit-poet"
// This is heuristic - could be improved with metadata storage
agentType = parts.slice(-2).join('-'); // Take last 2 parts as type
}
// Create target directory - use relative path if agent is in a subdirectory
const agentTargetDir = agent.relativePath
? path.join(customAgentsDir, agent.relativePath)
: path.join(customAgentsDir, finalAgentName);
await fs.ensureDir(agentTargetDir);
// Calculate paths
const compiledFileName = `${finalAgentName}.md`;
const compiledPath = path.join(agentTargetDir, compiledFileName);
const relativePath = path.relative(projectDir, compiledPath);
// Compile with embedded defaults (answers are already in defaults section)
const { xml, metadata } = compileAgent(
await fs.readFile(agent.yamlFile, 'utf8'),
agentConfig.defaults || {},
finalAgentName,
relativePath,
{ config: config.coreConfig },
);
// Write compiled agent
await fs.writeFile(compiledPath, xml, 'utf8');
// Backup source YAML to _cfg/custom/agents if not already there
const cfgAgentsBackupDir = path.join(bmadDir, '_cfg', 'custom', 'agents');
await fs.ensureDir(cfgAgentsBackupDir);
const backupYamlPath = path.join(cfgAgentsBackupDir, `${finalAgentName}.agent.yaml`);
// Only backup if source is not already in backup location
if (agent.yamlFile !== backupYamlPath) {
await fs.copy(agent.yamlFile, backupYamlPath);
}
// Copy sidecar files for agents with hasSidecar flag
if (agentConfig.hasSidecar === true && agent.type === 'expert') {
const { copyAgentSidecarFiles } = require('../../../lib/agent/installer');
// Get agent sidecar folder from config or use default
const agentSidecarFolder = config.coreConfig?.agent_sidecar_folder;
// Resolve path variables
const resolvedSidecarFolder = agentSidecarFolder.replaceAll('{project-root}', projectDir).replaceAll('.bmad', bmadDir);
// Create sidecar directory for this agent
const agentSidecarDir = path.join(resolvedSidecarFolder, finalAgentName);
await fs.ensureDir(agentSidecarDir);
// Copy sidecar files (preserve existing, add new)
const sidecarResult = copyAgentSidecarFiles(agent.path, agentSidecarDir, agent.yamlFile);
if (sidecarResult.copied.length > 0 || sidecarResult.preserved.length > 0) {
console.log(chalk.dim(` Sidecar: ${sidecarResult.copied.length} new, ${sidecarResult.preserved.length} preserved`));
}
}
// Update manifest CSV
if (await fs.pathExists(manifestFile)) {
// Preserve YAML metadata for persona name, but override id for filename
const manifestMetadata = {
...metadata,
id: relativePath, // Use the compiled agent path for id
name: metadata.name || finalAgentName, // Use YAML metadata.name (persona name) or fallback
title: metadata.title, // Use YAML title
icon: metadata.icon, // Use YAML icon
};
const manifestData = extractManifestData(xml, manifestMetadata, relativePath, 'custom');
manifestData.name = finalAgentName; // Use filename for the name field
manifestData.path = relativePath;
addToManifest(manifestFile, manifestData);
}
// Create IDE slash commands (async function)
await createIdeSlashCommands(projectDir, finalAgentName, relativePath, metadata);
// Update manifest.yaml
if (await fs.pathExists(manifestYamlFile)) {
updateManifestYaml(manifestYamlFile, finalAgentName, agentType);
}
results.count++;
results.agents.push(finalAgentName);
} catch (agentError) {
console.log(chalk.yellow(` ⚠️ Failed to reinstall ${agent.name}: ${agentError.message}`));
}
}
} catch (error) {
console.log(chalk.yellow(` ⚠️ Error reinstalling custom agents: ${error.message}`));
}
return results;
}
/**
* Copy IDE-specific documentation to BMAD docs
* @param {Array} ides - List of selected IDEs

View File

@@ -23,7 +23,7 @@ class ManifestGenerator {
/**
* Generate all manifests for the installation
* @param {string} bmadDir - .bmad
* @param {string} bmadDir - _bmad
* @param {Array} selectedModules - Selected modules for installation
* @param {Array} installedFiles - All installed files (optional, for hash tracking)
*/
@@ -47,7 +47,7 @@ class ManifestGenerator {
// But all modules should be included in the final manifest
this.preservedModules = [...new Set([...preservedModules, ...selectedModules, ...installedModules])]; // Include all installed modules
this.bmadDir = bmadDir;
this.bmadFolderName = path.basename(bmadDir); // Get the actual folder name (e.g., '.bmad' or 'bmad')
this.bmadFolderName = path.basename(bmadDir); // Get the actual folder name (e.g., '_bmad' or 'bmad')
this.allInstalledFiles = installedFiles;
if (!Object.prototype.hasOwnProperty.call(options, 'ides')) {

View File

@@ -1,79 +0,0 @@
/**
* Post-installation sidecar folder replacement utility
* Replaces {agent_sidecar_folder} placeholders in all installed agents
*/
const fs = require('fs-extra');
const path = require('node:path');
const yaml = require('yaml');
const glob = require('glob');
const chalk = require('chalk');
/**
* Replace {agent_sidecar_folder} placeholders in all agent files
* @param {string} bmadDir - Path to .bmad directory
* @returns {Object} Statistics about replacements made
*/
async function replaceAgentSidecarFolders(bmadDir) {
const results = {
filesScanned: 0,
filesReplaced: 0,
totalReplacements: 0,
errors: [],
};
try {
// Load core config to get agent_sidecar_folder value
const coreConfigPath = path.join(bmadDir, 'bmb', 'config.yaml');
if (!(await fs.pathExists(coreConfigPath))) {
throw new Error(`Core config not found at ${coreConfigPath}`);
}
const coreConfigContent = await fs.readFile(coreConfigPath, 'utf8');
const coreConfig = yaml.parse(coreConfigContent);
const agentSidecarFolder = coreConfig.agent_sidecar_folder;
// Use the literal value from config, don't resolve the placeholders
console.log(chalk.dim(`\n Replacing {agent_sidecar_folder} with: ${agentSidecarFolder}`));
// Find all agent .md files
const agentPattern = path.join(bmadDir, '**/*.md');
const agentFiles = glob.sync(agentPattern);
for (const agentFile of agentFiles) {
results.filesScanned++;
try {
let content = await fs.readFile(agentFile, 'utf8');
// Check if file contains {agent_sidecar_folder}
if (content.includes('{agent_sidecar_folder}')) {
// Replace all occurrences
const originalContent = content;
content = content.replaceAll('{agent_sidecar_folder}', agentSidecarFolder);
// Only write if content changed
if (content !== originalContent) {
await fs.writeFile(agentFile, content, 'utf8');
const replacementCount = (originalContent.match(/{agent_sidecar_folder}/g) || []).length;
results.filesReplaced++;
results.totalReplacements += replacementCount;
console.log(chalk.dim(` ✓ Replaced ${replacementCount} occurrence(s) in ${path.relative(bmadDir, agentFile)}`));
}
}
} catch (error) {
results.errors.push(`Error processing ${agentFile}: ${error.message}`);
}
}
return results;
} catch (error) {
results.errors.push(`Fatal error: ${error.message}`);
return results;
}
}
module.exports = { replaceAgentSidecarFolders };

View File

@@ -316,7 +316,7 @@ class CustomHandler {
// Create customize template if it doesn't exist
if (!(await fs.pathExists(customizePath))) {
const { getSourcePath } = require('../../../lib/project-root');
const genericTemplatePath = getSourcePath('utility', 'templates', 'agent.customize.template.yaml');
const genericTemplatePath = getSourcePath('utility', 'agent-components', 'agent.customize.template.yaml');
if (await fs.pathExists(genericTemplatePath)) {
// Copy with placeholder replacement
let templateContent = await fs.readFile(genericTemplatePath, 'utf8');
@@ -355,7 +355,7 @@ class CustomHandler {
const projectDir = path.dirname(bmadDir);
const resolvedSidecarFolder = config.agent_sidecar_folder
.replaceAll('{project-root}', projectDir)
.replaceAll('.bmad', path.basename(bmadDir));
.replaceAll('_bmad', path.basename(bmadDir));
// Create sidecar directory for this agent
const agentSidecarDir = path.join(resolvedSidecarFolder, agentName);

View File

@@ -31,18 +31,11 @@ class BaseIdeSetup {
/**
* Get the agent command activation header from the central template
* @returns {string} The activation header text (without XML tags)
* @returns {string} The activation header text
*/
async getAgentCommandHeader() {
const headerPath = path.join(getSourcePath(), 'src', 'utility', 'models', 'agent-command-header.md');
try {
const content = await fs.readFile(headerPath, 'utf8');
// Strip the <critical> tags to get plain text
return content.replaceAll(/<critical>|<\/critical>/g, '').trim();
} catch {
// Fallback if file doesn't exist
return "You must fully embody this agent's persona and follow all activation instructions, steps and rules exactly as specified. NEVER break character until given an exit command.";
}
const headerPath = path.join(getSourcePath(), 'src', 'utility', 'agent-components', 'agent-command-header.md');
return await fs.readFile(headerPath, 'utf8');
}
/**
@@ -527,26 +520,26 @@ class BaseIdeSetup {
}
/**
* Write file with content (replaces .bmad placeholder)
* Write file with content (replaces _bmad placeholder)
* @param {string} filePath - File path
* @param {string} content - File content
*/
async writeFile(filePath, content) {
// Replace .bmad placeholder if present
if (typeof content === 'string' && content.includes('.bmad')) {
content = content.replaceAll('.bmad', this.bmadFolderName);
// Replace _bmad placeholder if present
if (typeof content === 'string' && content.includes('_bmad')) {
content = content.replaceAll('_bmad', this.bmadFolderName);
}
// Replace escape sequence .bmad with literal .bmad
if (typeof content === 'string' && content.includes('.bmad')) {
content = content.replaceAll('.bmad', '.bmad');
// Replace escape sequence _bmad with literal _bmad
if (typeof content === 'string' && content.includes('_bmad')) {
content = content.replaceAll('_bmad', '_bmad');
}
await this.ensureDir(path.dirname(filePath));
await fs.writeFile(filePath, content, 'utf8');
}
/**
* Copy file from source to destination (replaces .bmad placeholder in text files)
* Copy file from source to destination (replaces _bmad placeholder in text files)
* @param {string} source - Source file path
* @param {string} dest - Destination file path
*/
@@ -563,14 +556,14 @@ class BaseIdeSetup {
// Read the file content
let content = await fs.readFile(source, 'utf8');
// Replace .bmad placeholder with actual folder name
if (content.includes('.bmad')) {
content = content.replaceAll('.bmad', this.bmadFolderName);
// Replace _bmad placeholder with actual folder name
if (content.includes('_bmad')) {
content = content.replaceAll('_bmad', this.bmadFolderName);
}
// Replace escape sequence .bmad with literal .bmad
if (content.includes('.bmad')) {
content = content.replaceAll('.bmad', '.bmad');
// Replace escape sequence _bmad with literal _bmad
if (content.includes('_bmad')) {
content = content.replaceAll('_bmad', '_bmad');
}
// Write to dest with replaced content

View File

@@ -119,7 +119,7 @@ class AntigravitySetup extends BaseIdeSetup {
await this.ensureDir(bmadWorkflowsDir);
// Generate agent launchers using AgentCommandGenerator
// This creates small launcher files that reference the actual agents in .bmad/
// This creates small launcher files that reference the actual agents in _bmad/
const agentGen = new AgentCommandGenerator(this.bmadFolderName);
const { artifacts: agentArtifacts, counts: agentCounts } = await agentGen.collectAgentArtifacts(bmadDir, options.selectedModules || []);

View File

@@ -118,7 +118,7 @@ class ClaudeCodeSetup extends BaseIdeSetup {
await this.ensureDir(bmadCommandsDir);
// Generate agent launchers using AgentCommandGenerator
// This creates small launcher files that reference the actual agents in .bmad/
// This creates small launcher files that reference the actual agents in _bmad/
const agentGen = new AgentCommandGenerator(this.bmadFolderName);
const { artifacts: agentArtifacts, counts: agentCounts } = await agentGen.collectAgentArtifacts(bmadDir, options.selectedModules || []);

View File

@@ -265,8 +265,8 @@ class CodexSetup extends BaseIdeSetup {
'',
chalk.white(' /prompts installed globally to your HOME DIRECTORY.'),
'',
chalk.yellow(' ⚠️ These prompts reference a specific .bmad path'),
chalk.dim(" To use with other projects, you'd need to copy the .bmad dir"),
chalk.yellow(' ⚠️ These prompts reference a specific _bmad path'),
chalk.dim(" To use with other projects, you'd need to copy the _bmad dir"),
'',
chalk.green(' ✓ You can now use /commands in Codex CLI'),
chalk.dim(' Example: /bmad-bmm-agents-pm'),

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}', '.bmad')
.replaceAll('{.bmad}', this.bmadFolderName)
.replaceAll('{_bmad}', '_bmad')
.replaceAll('{_bmad}', this.bmadFolderName)
.replaceAll('{{module}}', agent.module)
.replaceAll('{{name}}', agent.name);
@@ -196,8 +196,8 @@ ${contentWithoutFrontmatter}
// Replace template variables
const tomlContent = template
.replaceAll('{{taskName}}', taskName)
.replaceAll('{.bmad}', '.bmad')
.replaceAll('{.bmad}', this.bmadFolderName)
.replaceAll('{_bmad}', '_bmad')
.replaceAll('{_bmad}', this.bmadFolderName)
.replaceAll('{{module}}', task.module)
.replaceAll('{{filename}}', task.filename);

View File

@@ -45,11 +45,11 @@ class RooSetup extends BaseIdeSetup {
continue;
}
// Read the actual agent file from .bmad for metadata extraction (installed agents are .md files)
// Read the actual agent file from _bmad for metadata extraction (installed agents are .md files)
const agentPath = path.join(bmadDir, artifact.module, 'agents', `${artifact.name}.md`);
const content = await this.readFile(agentPath);
// Create command file that references the actual .bmad agent
// Create command file that references the actual _bmad agent
await this.createCommandFile({ module: artifact.module, name: artifact.name, path: agentPath }, content, commandPath, projectDir);
addedCount++;

View File

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

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/bmm/workflows/.../workflow.yaml
// To: {project-root}/_bmad/bmm/workflows/.../workflow.yaml
let workflowPath = workflow.path;
// Extract the relative path from source
@@ -131,8 +131,8 @@ class WorkflowCommandGenerator {
.replaceAll('{{module}}', workflow.module)
.replaceAll('{{description}}', workflow.description)
.replaceAll('{{workflow_path}}', workflowPath)
.replaceAll('.bmad', this.bmadFolderName)
.replaceAll('.bmad', '.bmad');
.replaceAll('_bmad', this.bmadFolderName)
.replaceAll('_bmad', '_bmad');
}
/**

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/{{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}/{{module}}/config.yaml - store ALL config values in memory for use throughout the session.
2. [ ] IMMEDIATE ACTION: Read and internalize the full agent definition at @{.bmad}/{{module}}/agents/{{name}}.md.
1. [ ] IMMEDIATE ACTION: Load and parse @{_bmad}/{{module}}/config.yaml - store ALL config values in memory for use throughout the session.
2. [ ] IMMEDIATE ACTION: Read and internalize the full agent definition at @{_bmad}/{{module}}/agents/{{name}}.md.
3. [ ] CONFIRM: The user's name from config is {user_name}.
Only after all checks are complete, greet the user by name and display the menu.
Acknowledge this checklist is complete in your first response.
AGENT DEFINITION: @{.bmad}/{{module}}/agents/{{name}}.md
AGENT DEFINITION: @{_bmad}/{{module}}/agents/{{name}}.md
"""

View File

@@ -3,10 +3,10 @@ prompt = """
Execute the following BMad Method task workflow:
PRE-FLIGHT CHECKLIST:
1. [ ] IMMEDIATE ACTION: Load and parse @{.bmad}/{{module}}/config.yaml.
2. [ ] IMMEDIATE ACTION: Read and load the task definition at @{.bmad}/{{module}}/tasks/{{filename}}.
1. [ ] IMMEDIATE ACTION: Load and parse @{_bmad}/{{module}}/config.yaml.
2. [ ] IMMEDIATE ACTION: Read and load the task definition at @{_bmad}/{{module}}/tasks/{{filename}}.
Follow all instructions and complete the task as defined.
TASK DEFINITION: @{.bmad}/{{module}}/tasks/{{filename}}
TASK DEFINITION: @{_bmad}/{{module}}/tasks/{{filename}}
"""

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/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 placeholder with actual folder name
* Copy a file and replace _bmad placeholder with actual folder name
* @param {string} sourcePath - Source file path
* @param {string} targetPath - Target file path
*/
@@ -62,14 +62,14 @@ class ModuleManager {
// Read the file content
let content = await fs.readFile(sourcePath, 'utf8');
// Replace escape sequence .bmad with literal .bmad
if (content.includes('.bmad')) {
content = content.replaceAll('.bmad', '.bmad');
// Replace escape sequence _bmad with literal _bmad
if (content.includes('_bmad')) {
content = content.replaceAll('_bmad', '_bmad');
}
// Replace .bmad placeholder with actual folder name
if (content.includes('.bmad')) {
content = content.replaceAll('.bmad', this.bmadFolderName);
// Replace _bmad placeholder with actual folder name
if (content.includes('_bmad')) {
content = content.replaceAll('_bmad', this.bmadFolderName);
}
// Write to target with replaced content
@@ -695,8 +695,8 @@ class ModuleManager {
// IMPORTANT: Replace escape sequence and placeholder BEFORE parsing YAML
// Otherwise parsing will fail on the placeholder
yamlContent = yamlContent.replaceAll('.bmad', '.bmad');
yamlContent = yamlContent.replaceAll('.bmad', this.bmadFolderName);
yamlContent = yamlContent.replaceAll('_bmad', '_bmad');
yamlContent = yamlContent.replaceAll('_bmad', this.bmadFolderName);
try {
// First check if web_bundle exists by parsing
@@ -815,7 +815,7 @@ class ModuleManager {
// Create customize template if it doesn't exist
if (!(await fs.pathExists(customizePath))) {
const { getSourcePath } = require('../../../lib/project-root');
const genericTemplatePath = getSourcePath('utility', 'templates', 'agent.customize.template.yaml');
const genericTemplatePath = getSourcePath('utility', 'agent-components', 'agent.customize.template.yaml');
if (await fs.pathExists(genericTemplatePath)) {
await this.copyFileWithPlaceholderReplacement(genericTemplatePath, customizePath);
console.log(chalk.dim(` Created customize: ${moduleName}-${agentName}.customize.yaml`));
@@ -853,9 +853,9 @@ class ModuleManager {
// Compile with customizations if any
const { xml } = compileAgent(yamlContent, {}, agentName, relativePath, { config: this.coreConfig });
// Replace .bmad placeholder if needed
if (xml.includes('.bmad') && this.bmadFolderName) {
const processedXml = xml.replaceAll('.bmad', this.bmadFolderName);
// Replace _bmad placeholder if needed
if (xml.includes('_bmad') && this.bmadFolderName) {
const processedXml = xml.replaceAll('_bmad', this.bmadFolderName);
await fs.writeFile(targetMdPath, processedXml, 'utf8');
} else {
await fs.writeFile(targetMdPath, xml, 'utf8');
@@ -872,7 +872,7 @@ class ModuleManager {
const projectDir = path.dirname(bmadDir);
const resolvedSidecarFolder = agentSidecarFolder
.replaceAll('{project-root}', projectDir)
.replaceAll('.bmad', path.basename(bmadDir));
.replaceAll('_bmad', path.basename(bmadDir));
// Create sidecar directory for this agent
const agentSidecarDir = path.join(resolvedSidecarFolder, agentName);
@@ -931,28 +931,23 @@ class ModuleManager {
* @param {string} moduleName - Module name
*/
async processAgentFiles(modulePath, moduleName) {
const agentsPath = path.join(modulePath, 'agents');
// Check if agents directory exists
if (!(await fs.pathExists(agentsPath))) {
return; // No agents to process
}
// Get all agent MD files recursively
const agentFiles = await this.findAgentMdFiles(agentsPath);
for (const agentFile of agentFiles) {
if (!agentFile.endsWith('.md')) continue;
let content = await fs.readFile(agentFile, 'utf8');
// Check if content has agent XML and no activation block
if (content.includes('<agent') && !content.includes('<activation')) {
// Inject the activation block using XML handler
content = this.xmlHandler.injectActivationSimple(content);
await fs.writeFile(agentFile, content, 'utf8');
}
}
// const agentsPath = path.join(modulePath, 'agents');
// // Check if agents directory exists
// if (!(await fs.pathExists(agentsPath))) {
// return; // No agents to process
// }
// // Get all agent MD files recursively
// const agentFiles = await this.findAgentMdFiles(agentsPath);
// for (const agentFile of agentFiles) {
// if (!agentFile.endsWith('.md')) continue;
// let content = await fs.readFile(agentFile, 'utf8');
// // Check if content has agent XML and no activation block
// if (content.includes('<agent') && !content.includes('<activation')) {
// // Inject the activation block using XML handler
// content = this.xmlHandler.injectActivationSimple(content);
// await fs.writeFile(agentFile, content, 'utf8');
// }
// }
}
/**
@@ -1030,10 +1025,10 @@ class ModuleManager {
const installWorkflowPath = item['workflow-install']; // Where to copy TO
// Parse SOURCE workflow path
// Handle both .bmad placeholder and hardcoded 'bmad'
// Example: {project-root}/.bmad/bmm/workflows/4-implementation/create-story/workflow.yaml
// Handle both _bmad placeholder and hardcoded 'bmad'
// Example: {project-root}/_bmad/bmm/workflows/4-implementation/create-story/workflow.yaml
// Or: {project-root}/bmad/bmm/workflows/4-implementation/create-story/workflow.yaml
const sourceMatch = sourceWorkflowPath.match(/\{project-root\}\/(?:\.bmad)\/([^/]+)\/workflows\/(.+)/);
const sourceMatch = sourceWorkflowPath.match(/\{project-root\}\/(?:_bmad)\/([^/]+)\/workflows\/(.+)/);
if (!sourceMatch) {
console.warn(chalk.yellow(` Could not parse workflow path: ${sourceWorkflowPath}`));
continue;
@@ -1042,9 +1037,9 @@ class ModuleManager {
const [, sourceModule, sourceWorkflowSubPath] = sourceMatch;
// Parse INSTALL workflow path
// Handle.bmad
// Example: {project-root}/.bmad/bmgd/workflows/4-production/create-story/workflow.yaml
const installMatch = installWorkflowPath.match(/\{project-root\}\/(\.bmad)\/([^/]+)\/workflows\/(.+)/);
// Handle_bmad
// Example: {project-root}/_bmad/bmgd/workflows/4-production/create-story/workflow.yaml
const installMatch = installWorkflowPath.match(/\{project-root\}\/(_bmad)\/([^/]+)\/workflows\/(.+)/);
if (!installMatch) {
console.warn(chalk.yellow(` Could not parse workflow-install path: ${installWorkflowPath}`));
continue;
@@ -1096,9 +1091,9 @@ class ModuleManager {
async updateWorkflowConfigSource(workflowYamlPath, newModuleName) {
let yamlContent = await fs.readFile(workflowYamlPath, 'utf8');
// Replace config_source: "{project-root}/.bmad/OLD_MODULE/config.yaml"
// with config_source: "{project-root}/.bmad/NEW_MODULE/config.yaml"
// Note: At this point .bmad has already been replaced with actual folder name
// Replace config_source: "{project-root}/_bmad/OLD_MODULE/config.yaml"
// with config_source: "{project-root}/_bmad/NEW_MODULE/config.yaml"
// Note: At this point _bmad has already been replaced with actual folder name
const configSourcePattern = /config_source:\s*["']?\{project-root\}\/[^/]+\/[^/]+\/config\.yaml["']?/g;
const newConfigSource = `config_source: "{project-root}/${this.bmadFolderName}/${newModuleName}/config.yaml"`;

View File

@@ -7,13 +7,13 @@ const { getSourcePath } = require('./project-root');
*/
class ActivationBuilder {
constructor() {
this.fragmentsDir = getSourcePath('utility', 'models', 'fragments');
this.agentComponents = getSourcePath('utility', 'agent-components');
this.fragmentCache = new Map();
}
/**
* Load a fragment file
* @param {string} fragmentName - Name of fragment file (e.g., 'activation-init.xml')
* @param {string} fragmentName - Name of fragment file (e.g., 'activation-init.txt')
* @returns {string} Fragment content
*/
async loadFragment(fragmentName) {
@@ -22,7 +22,7 @@ class ActivationBuilder {
return this.fragmentCache.get(fragmentName);
}
const fragmentPath = path.join(this.fragmentsDir, fragmentName);
const fragmentPath = path.join(this.agentComponents, fragmentName);
if (!(await fs.pathExists(fragmentPath))) {
throw new Error(`Fragment not found: ${fragmentName}`);
@@ -49,7 +49,7 @@ class ActivationBuilder {
activation += this.indent(steps, 2) + '\n';
// 2. Build menu handlers section with dynamic handlers
const menuHandlers = await this.loadFragment('menu-handlers.xml');
const menuHandlers = await this.loadFragment('menu-handlers.txt');
// Build handlers (load only needed handlers)
const handlers = await this.buildHandlers(profile);
@@ -63,11 +63,8 @@ class ActivationBuilder {
activation += '\n' + this.indent(processedHandlers, 2) + '\n';
// 3. Include rules (skip for web bundles as they're in web-bundle-activation-steps.xml)
if (!forWebBundle) {
const rules = await this.loadFragment('activation-rules.xml');
activation += this.indent(rules, 2) + '\n';
}
const rules = await this.loadFragment('activation-rules.txt');
activation += this.indent(rules, 2) + '\n';
activation += '</activation>';
@@ -83,7 +80,7 @@ class ActivationBuilder {
const handlerFragments = [];
for (const attrType of profile.usedAttributes) {
const fragmentName = `handler-${attrType}.xml`;
const fragmentName = `handler-${attrType}.txt`;
try {
const handler = await this.loadFragment(fragmentName);
handlerFragments.push(handler);
@@ -103,9 +100,7 @@ class ActivationBuilder {
* @returns {string} Steps XML
*/
async buildSteps(metadata = {}, agentSpecificActions = [], forWebBundle = false) {
// Use web-specific fragment for web bundles, standard fragment otherwise
const fragmentName = forWebBundle ? 'web-bundle-activation-steps.xml' : 'activation-steps.xml';
const stepsTemplate = await this.loadFragment(fragmentName);
const stepsTemplate = await this.loadFragment('activation-steps.txt');
// Extract basename from agent ID (e.g., "bmad/bmm/agents/pm.md" → "pm")
const agentBasename = metadata.id ? metadata.id.split('/').pop().replace('.md', '') : metadata.name || 'agent';

View File

@@ -1,5 +1,6 @@
const path = require('node:path');
const fs = require('fs-extra');
const { escapeXml } = require('../../lib/xml-utils');
const AgentPartyGenerator = {
/**
@@ -47,9 +48,9 @@ const AgentPartyGenerator = {
for (const agent of agents) {
xmlContent += ` <agent id="${agent.id}" name="${agent.name}" title="${agent.title || ''}" icon="${agent.icon || ''}">
<persona>
<role>${this.escapeXml(agent.role || '')}</role>
<identity>${this.escapeXml(agent.identity || '')}</identity>
<communication_style>${this.escapeXml(agent.communicationStyle || '')}</communication_style>
<role>${escapeXml(agent.role || '')}</role>
<identity>${escapeXml(agent.identity || '')}</identity>
<communication_style>${escapeXml(agent.communicationStyle || '')}</communication_style>
<principles>${agent.principles || ''}</principles>
</persona>
</agent>\n`;
@@ -124,19 +125,6 @@ const AgentPartyGenerator = {
return match ? match[1] : '';
},
/**
* Escape XML special characters
*/
escapeXml(text) {
if (!text) return '';
return text
.replaceAll('&', '&amp;')
.replaceAll('<', '&lt;')
.replaceAll('>', '&gt;')
.replaceAll('"', '&quot;')
.replaceAll("'", '&apos;');
},
/**
* Apply config overrides to agent details
* @param {Object} details - Original agent details

View File

@@ -8,22 +8,7 @@ const yaml = require('yaml');
const fs = require('node:fs');
const path = require('node:path');
const { processAgentYaml, extractInstallConfig, stripInstallConfig, getDefaultValues } = require('./template-engine');
// Use existing BMAD builder if available
let YamlXmlBuilder;
try {
YamlXmlBuilder = require('../../lib/yaml-xml-builder').YamlXmlBuilder;
} catch {
YamlXmlBuilder = null;
}
/**
* Escape XML special characters
*/
function escapeXml(text) {
if (!text) return '';
return text.replaceAll('&', '&amp;').replaceAll('<', '&lt;').replaceAll('>', '&gt;').replaceAll('"', '&quot;').replaceAll("'", '&apos;');
}
const { escapeXml } = require('../../../lib/xml-utils');
/**
* Build frontmatter for agent

View File

@@ -17,7 +17,7 @@ const { extractInstallConfig, getDefaultValues } = require('./template-engine');
*/
function findBmadConfig(startPath = process.cwd()) {
// Look for common BMAD folder names
const possibleNames = ['.bmad', 'bmad', '.bmad-method'];
const possibleNames = ['_bmad'];
for (const name of possibleNames) {
const configPath = path.join(startPath, name, 'bmb', 'config.yaml');
@@ -42,7 +42,7 @@ function findBmadConfig(startPath = process.cwd()) {
* @returns {string} Resolved path
*/
function resolvePath(pathStr, context) {
return pathStr.replaceAll('{project-root}', context.projectRoot).replaceAll('{bmad-folder}', context.bmadFolder);
return pathStr.replaceAll('{project-root}', context.projectRoot).replaceAll('{bmad-folder}', context_bmadFolder);
}
/**
@@ -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', 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'];
const possibleNames = ['_bmad'];
for (const name of possibleNames) {
const bmadFolder = path.join(checkPath, name);
const cfgFolder = path.join(bmadFolder, '_cfg');
@@ -689,7 +689,7 @@ function saveAgentSource(agentInfo, cfgFolder, agentName, answers = {}) {
*/
async function createIdeSlashCommands(projectRoot, agentName, agentPath, metadata) {
// Read manifest.yaml to get installed IDEs
const manifestPath = path.join(projectRoot, '.bmad', '_cfg', 'manifest.yaml');
const manifestPath = path.join(projectRoot, '_bmad', '_cfg', 'manifest.yaml');
let installedIdes = ['claude-code']; // Default to Claude Code if no manifest
if (fs.existsSync(manifestPath)) {

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 = '.bmad';
const bmadFolderName = '_bmad';
const bmadDir = path.join(confirmedDirectory, bmadFolderName);
await fs.ensureDir(bmadDir);
@@ -535,7 +535,7 @@ class UI {
// Show backup info and restore command
console.log('\n' + chalk.white.bold('Backups & Recovery:\n'));
console.log(chalk.dim(' Pre-injection backups are stored in:'));
console.log(chalk.cyan(' ~/.bmad-tts-backups/\n'));
console.log(chalk.cyan(' ~/_bmad-tts-backups/\n'));
console.log(chalk.dim(' To restore original files (removes TTS instructions):'));
console.log(chalk.cyan(` bmad-tts-injector.sh --restore ${result.path}\n`));

View File

@@ -44,18 +44,7 @@ class XmlHandler {
* @returns {Object} Parsed activation block
*/
async loadActivationTemplate() {
const templatePath = getSourcePath('utility', 'models', 'agent-activation-ide.xml');
try {
const xmlContent = await fs.readFile(templatePath, 'utf8');
// Parse the XML directly (file is now pure XML)
const parsed = await this.parser.parseStringPromise(xmlContent);
return parsed.activation;
} catch (error) {
console.error('Failed to load activation template:', error);
return null;
}
console.error('Failed to load activation template:', error);
}
/**
@@ -136,51 +125,10 @@ class XmlHandler {
}
/**
* Simple string-based injection (fallback method for legacy XML agents)
* This preserves formatting better than XML parsing
* TODO: DELETE THIS METHOD
*/
injectActivationSimple(agentContent, metadata = {}) {
// Check if already has activation
if (agentContent.includes('<activation')) {
return agentContent;
}
// Load template file
const templatePath = getSourcePath('utility', 'models', 'agent-activation-ide.xml');
try {
const templateContent = fs.readFileSync(templatePath, 'utf8');
// The file is now pure XML, use it directly with proper indentation
// Add 2 spaces of indentation for insertion into agent
let activationBlock = templateContent
.split('\n')
.map((line) => (line ? ' ' + line : ''))
.join('\n');
// Replace {agent-filename} with actual filename if metadata provided
if (metadata.module && metadata.name) {
const agentFilename = `${metadata.module}-${metadata.name}.md`;
activationBlock = activationBlock.replace('{agent-filename}', agentFilename);
}
// Find where to insert (after <agent> tag)
const agentMatch = agentContent.match(/(<agent[^>]*>)/);
if (!agentMatch) {
return agentContent;
}
const insertPos = agentMatch.index + agentMatch[0].length;
// Insert the activation block
const before = agentContent.slice(0, insertPos);
const after = agentContent.slice(insertPos);
return before + '\n' + activationBlock + after;
} catch (error) {
console.error('Error in simple injection:', error);
return agentContent;
}
console.error('Error in simple injection:', error);
}
/**

View File

@@ -4,6 +4,7 @@ const path = require('node:path');
const crypto = require('node:crypto');
const { AgentAnalyzer } = require('./agent-analyzer');
const { ActivationBuilder } = require('./activation-builder');
const { escapeXml } = require('../../lib/xml-utils');
/**
* Converts agent YAML files to XML format with smart activation injection
@@ -241,15 +242,15 @@ class YamlXmlBuilder {
let xml = ' <persona>\n';
if (persona.role) {
xml += ` <role>${this.escapeXml(persona.role)}</role>\n`;
xml += ` <role>${escapeXml(persona.role)}</role>\n`;
}
if (persona.identity) {
xml += ` <identity>${this.escapeXml(persona.identity)}</identity>\n`;
xml += ` <identity>${escapeXml(persona.identity)}</identity>\n`;
}
if (persona.communication_style) {
xml += ` <communication_style>${this.escapeXml(persona.communication_style)}</communication_style>\n`;
xml += ` <communication_style>${escapeXml(persona.communication_style)}</communication_style>\n`;
}
if (persona.principles) {
@@ -260,7 +261,7 @@ class YamlXmlBuilder {
} else {
principlesText = persona.principles;
}
xml += ` <principles>${this.escapeXml(principlesText)}</principles>\n`;
xml += ` <principles>${escapeXml(principlesText)}</principles>\n`;
}
xml += ' </persona>\n';
@@ -277,7 +278,7 @@ class YamlXmlBuilder {
let xml = ' <memories>\n';
for (const memory of memories) {
xml += ` <memory>${this.escapeXml(memory)}</memory>\n`;
xml += ` <memory>${escapeXml(memory)}</memory>\n`;
}
xml += ' </memories>\n';
@@ -314,7 +315,7 @@ class YamlXmlBuilder {
for (const prompt of promptsArray) {
xml += ` <prompt id="${prompt.id || ''}">\n`;
xml += ` <content>\n`;
xml += `${this.escapeXml(prompt.content || '')}\n`;
xml += `${escapeXml(prompt.content || '')}\n`;
xml += ` </content>\n`;
xml += ` </prompt>\n`;
}
@@ -351,7 +352,7 @@ class YamlXmlBuilder {
// Handle multi format menu items with nested handlers
if (item.multi && item.triggers && Array.isArray(item.triggers)) {
xml += ` <item type="multi">${this.escapeXml(item.multi)}\n`;
xml += ` <item type="multi">${escapeXml(item.multi)}\n`;
xml += this.buildNestedHandlers(item.triggers);
xml += ` </item>\n`;
}
@@ -381,7 +382,7 @@ class YamlXmlBuilder {
if (item.data) attrs.push(`data="${item.data}"`);
if (item.action) attrs.push(`action="${item.action}"`);
xml += ` <item ${attrs.join(' ')}>${this.escapeXml(item.description || '')}</item>\n`;
xml += ` <item ${attrs.join(' ')}>${escapeXml(item.description || '')}</item>\n`;
}
}
}
@@ -412,7 +413,7 @@ class YamlXmlBuilder {
// For nested handlers in multi items, we don't need cmd attribute
// The match attribute will handle fuzzy matching
const attrs = [`match="${this.escapeXml(execData.description || '')}"`];
const attrs = [`match="${escapeXml(execData.description || '')}"`];
// Add handler attributes based on exec data
if (execData.route) attrs.push(`exec="${execData.route}"`);
@@ -482,19 +483,6 @@ class YamlXmlBuilder {
return result;
}
/**
* Escape XML special characters
*/
escapeXml(text) {
if (!text) return '';
return text
.replaceAll('&', '&amp;')
.replaceAll('<', '&lt;')
.replaceAll('>', '&gt;')
.replaceAll('"', '&quot;')
.replaceAll("'", '&apos;');
}
/**
* Calculate file hash for build tracking
*/