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