Major Enhancements:

- Installation path is now fully configurable, allowing users to specify custom installation directories during setup
  - Default installation location changed to .bmad (hidden directory) for cleaner project root organization

    Web Bundle Improvements:

    - All web bundles (single agent and team) now include party mode support for multi-agent collaboration!
    - Advanced elicitation capabilities integrated into standalone agents
    - All bundles enhanced with party mode agent manifests
    - Added default-party.csv files to bmm, bmgd, and cis module teams
    - The default party file is what will be used with single agent bundles. teams can customize for different party configurations before web bundling through a setting in the team yaml file
    - New web bundle outputs for all agents (analyst, architect, dev, pm, sm, tea, tech-writer, ux-designer, game-*, creative-squad)

    Phase 4 Workflow Updates (In Progress):

    - Initiated shift to separate phase 4 implementation artifacts from documentation
        - Phase 4 implementation artifacts (stories, code review, sprint plan, context files) will move to dedicated location outside docs folder
        - Installer questions and configuration added for artifact path selection
        - Updated workflow.yaml files for code-review, sprint-planning, story-context, epic-tech-context, and retrospective workflows to support this, but still might require some udpates

    Additional Changes:

    - New agent and action command header models for standardization
    - Enhanced web-bundle-activation-steps fragment
    - Updated web-bundler.js to support new structure
    - VS Code settings updated for new .bmad directory
    - Party mode instructions and workflow enhanced for better orchestration

   IDE Installer Updates:

    - Show version number of installer in cli
    - improved Installer UX
    - Gemini TOML Improved to have clear loading instructions with @ commands
    - All tools agent launcher mds improved to use a central file template critical indication isntead of hardcoding in 2 different locations.
This commit is contained in:
Brian Madison
2025-11-09 17:39:05 -06:00
parent fd2521ec69
commit 7eb52520fa
433 changed files with 125975 additions and 689 deletions

View File

@@ -205,6 +205,18 @@ class WebBundler {
this.stats.warnings.push({ agent: agentName, warnings: dependencyWarnings });
}
// Check for module's default-party.csv and include it as agent manifest
const defaultPartyPath = path.join(this.modulesPath, moduleName, 'teams', 'default-party.csv');
if (await fs.pathExists(defaultPartyPath)) {
const partyContent = await fs.readFile(defaultPartyPath, 'utf8');
// Process any placeholders in the CSV content
const processedPartyContent = this.processProjectRootReferences(partyContent);
// Wrap as text to preserve raw CSV format in CDATA
const wrappedParty = this.wrapContentInXml(processedPartyContent, 'bmad/_cfg/agent-manifest.csv', 'text');
dependencies.set('bmad/_cfg/agent-manifest.csv', wrappedParty);
console.log(chalk.gray(` + Added party manifest from module default-party.csv`));
}
// Remove commands for skipped workflows from agent XML
if (skippedWorkflows.length > 0) {
agentXml = this.removeSkippedWorkflowCommands(agentXml, skippedWorkflows);
@@ -260,6 +272,24 @@ class WebBundler {
const allAgentXmls = [];
const warnings = [];
// Check if team has a party CSV file (agent manifest)
const hasPartyFile = teamConfig.party && teamConfig.party.endsWith('.csv');
if (hasPartyFile) {
// Load the party CSV and add it as bmad/_cfg/agent-manifest.csv
const partyPath = path.join(path.dirname(teamPath), teamConfig.party.replace(/^\.\//, ''));
if (await fs.pathExists(partyPath)) {
const partyContent = await fs.readFile(partyPath, 'utf8');
// Process any placeholders in the CSV content
const processedPartyContent = this.processProjectRootReferences(partyContent);
// Wrap as text/csv to preserve raw CSV format in CDATA
const wrappedParty = this.wrapContentInXml(processedPartyContent, 'bmad/_cfg/agent-manifest.csv', 'text');
dependencies.set('bmad/_cfg/agent-manifest.csv', wrappedParty);
console.log(chalk.gray(` + Added agent manifest from: ${teamConfig.party}`));
} else {
console.log(chalk.yellow(` ⚠ Party file not found: ${partyPath}`));
}
}
// 1. First, always add the bmad-web-orchestrator (XML file only, no transformation needed)
const orchestratorXmlPath = path.join(this.sourceDir, 'core', 'agents', 'bmad-web-orchestrator.agent.xml');
@@ -476,8 +506,9 @@ class WebBundler {
}
// Parse paths to extract module and workflow location
const sourceMatch = sourceWorkflowPath.match(/\{project-root\}\/bmad\/([^/]+)\/workflows\/(.+)/);
const installMatch = installWorkflowPath.match(/\{project-root\}\/bmad\/([^/]+)\/workflows\/(.+)/);
// Support both {project-root}/bmad/... and {project-root}/{bmad_folder}/... patterns
const sourceMatch = sourceWorkflowPath.match(/\{project-root\}\/(?:\{bmad_folder\}|bmad)\/([^/]+)\/workflows\/(.+)/);
const installMatch = installWorkflowPath.match(/\{project-root\}\/(?:\{bmad_folder\}|bmad)\/([^/]+)\/workflows\/(.+)/);
if (!sourceMatch || !installMatch) {
continue;
@@ -528,8 +559,9 @@ class WebBundler {
let yamlContent = await fs.readFile(workflowYamlPath, 'utf8');
// Replace config_source with new module reference
const configSourcePattern = /config_source:\s*["']?\{project-root\}\/bmad\/[^/]+\/config\.yaml["']?/g;
const newConfigSource = `config_source: "{project-root}/bmad/${newModuleName}/config.yaml"`;
// Support both old format (bmad) and new format ({bmad_folder})
const configSourcePattern = /config_source:\s*["']?\{project-root\}\/(?:\{bmad_folder\}|bmad)\/[^/]+\/config\.yaml["']?/g;
const newConfigSource = `config_source: "{project-root}/{bmad_folder}/${newModuleName}/config.yaml"`;
const updatedYaml = yamlContent.replaceAll(configSourcePattern, newConfigSource);
await fs.writeFile(workflowYamlPath, updatedYaml, 'utf8');
@@ -651,7 +683,8 @@ class WebBundler {
/system-prompts="([^"]+)"/g,
/tools="([^"]+)"/g,
/knowledge="([^"]+)"/g,
/{project-root}\/([^"'\s<>]+)/g,
/{project-root}\/([^"'\s<>]+)/g, // Legacy {project-root} paths
/\bbmad\/([^"'\s<>]+)/g, // Direct bmad/ paths (after {bmad_folder} replacement)
];
for (const pattern of patterns) {
@@ -660,9 +693,16 @@ class WebBundler {
let filePath = match[1];
// Remove {project-root} prefix if present
filePath = filePath.replace(/^{project-root}\//, '');
// Remove {bmad_folder} prefix if present (should be rare, mostly replaced already)
filePath = filePath.replace(/^{bmad_folder}\//, 'bmad/');
// For bmad/ pattern, prepend 'bmad/' since it was captured without it
if (pattern.source.includes(String.raw`\bbmad\/`)) {
filePath = 'bmad/' + filePath;
}
// Skip obvious placeholder/example paths
if (filePath && !filePath.includes('path/to/') && !filePath.includes('example')) {
if (filePath && !filePath.includes('path/to/') && !filePath.includes('example') && !filePath.includes('...')) {
refs.add(filePath);
}
}
@@ -680,6 +720,8 @@ class WebBundler {
while ((match = pattern.exec(xml)) !== null) {
let workflowPath = match[1];
workflowPath = workflowPath.replace(/^{project-root}\//, '');
// Remove {bmad_folder} prefix if present and replace with bmad
workflowPath = workflowPath.replace(/^{bmad_folder}\//, 'bmad/');
// Skip obvious placeholder/example paths
if (workflowPath && workflowPath.endsWith('.yaml') && !workflowPath.includes('path/to/') && !workflowPath.includes('example')) {
@@ -725,6 +767,11 @@ class WebBundler {
* Process a file dependency recursively
*/
async processFileDependency(filePath, dependencies, processed, moduleName, warnings = []) {
// Skip workflow YAML files - they're handled by processWorkflowDependency
if (filePath.includes('/workflow') && filePath.endsWith('workflow.yaml')) {
return;
}
// Skip if already processed
if (processed.has(filePath)) {
return;
@@ -766,7 +813,8 @@ class WebBundler {
const deps = depMatch[1].match(/['"]([^'"]+)['"]/g);
if (deps) {
for (const dep of deps) {
const depPath = dep.replaceAll(/['"]/g, '').replace(/^{project-root}\//, '');
let depPath = dep.replaceAll(/['"]/g, '').replace(/^{project-root}\//, '');
depPath = depPath.replace(/^{bmad_folder}\//, 'bmad/');
if (depPath && !processed.has(depPath)) {
await this.processFileDependency(depPath, dependencies, processed, moduleName, warnings);
}
@@ -779,7 +827,8 @@ class WebBundler {
const templates = templateMatch[1].match(/['"]([^'"]+)['"]/g);
if (templates) {
for (const template of templates) {
const templatePath = template.replaceAll(/['"]/g, '').replace(/^{project-root}\//, '');
let templatePath = template.replaceAll(/['"]/g, '').replace(/^{project-root}\//, '');
templatePath = templatePath.replace(/^{bmad_folder}\//, 'bmad/');
if (templatePath && !processed.has(templatePath)) {
await this.processFileDependency(templatePath, dependencies, processed, moduleName, warnings);
}
@@ -967,8 +1016,13 @@ class WebBundler {
bundleYamlContent = yamlContent;
}
// Process {project-root} and {bmad_folder} references in the YAML content
bundleYamlContent = this.processProjectRootReferences(bundleYamlContent);
// Include the YAML file with only web_bundle content, wrapped in XML
const yamlId = workflowPath.replace(/^{project-root}\//, '');
// Process the workflow path to create a clean ID
let yamlId = workflowPath.replace(/^{project-root}\//, '');
yamlId = yamlId.replace(/^{bmad_folder}\//, 'bmad/');
const wrappedYaml = this.wrapContentInXml(bundleYamlContent, yamlId, 'yaml');
dependencies.set(yamlId, wrappedYaml);
@@ -985,7 +1039,11 @@ class WebBundler {
const bundleFiles = workflowConfig.web_bundle.web_bundle_files;
for (const bundleFilePath of bundleFiles) {
if (processed.has(bundleFilePath)) {
// Process the file path to create a clean ID for checking if already processed
let cleanFilePath = bundleFilePath.replace(/^{project-root}\//, '');
cleanFilePath = cleanFilePath.replace(/^{bmad_folder}\//, 'bmad/');
if (processed.has(cleanFilePath)) {
continue;
}
@@ -1002,7 +1060,7 @@ class WebBundler {
await this.processWorkflowDependency(bundleFilePath, dependencies, processed, moduleName, warnings);
} else {
// Regular file - process normally
processed.add(bundleFilePath);
processed.add(cleanFilePath);
// Read the file content
let fileContent = await fs.readFile(bundleActualPath, 'utf8');
@@ -1012,8 +1070,8 @@ class WebBundler {
fileContent = this.processProjectRootReferences(fileContent);
// Wrap in XML with proper escaping
const wrappedContent = this.wrapContentInXml(fileContent, bundleFilePath, fileExt);
dependencies.set(bundleFilePath, wrappedContent);
const wrappedContent = this.wrapContentInXml(fileContent, cleanFilePath, fileExt);
dependencies.set(cleanFilePath, wrappedContent);
}
}
}
@@ -1039,7 +1097,9 @@ class WebBundler {
return;
}
const fileContent = await fs.readFile(actualPath, 'utf8');
let fileContent = await fs.readFile(actualPath, 'utf8');
// Process {project-root} and {bmad_folder} references
fileContent = this.processProjectRootReferences(fileContent);
const wrappedContent = this.wrapContentInXml(fileContent, coreWorkflowPath, 'xml');
dependencies.set(coreWorkflowPath, wrappedContent);
}
@@ -1063,7 +1123,9 @@ class WebBundler {
continue;
}
const fileContent = await fs.readFile(actualPath, 'utf8');
let fileContent = await fs.readFile(actualPath, 'utf8');
// Process {project-root} and {bmad_folder} references
fileContent = this.processProjectRootReferences(fileContent);
const fileExt = path.extname(actualPath).toLowerCase().replace('.', '');
const wrappedContent = this.wrapContentInXml(fileContent, filePath, fileExt);
dependencies.set(filePath, wrappedContent);
@@ -1096,6 +1158,8 @@ class WebBundler {
async processWildcardDependency(pattern, dependencies, processed, moduleName, warnings = []) {
// Remove {project-root} prefix
pattern = pattern.replace(/^{project-root}\//, '');
// Replace {bmad_folder} with bmad
pattern = pattern.replace(/^{bmad_folder}\//, 'bmad/');
// Get directory and file pattern
const lastSlash = pattern.lastIndexOf('/');
@@ -1163,6 +1227,9 @@ class WebBundler {
resolveFilePath(filePath, moduleName) {
// Remove {project-root} prefix
filePath = filePath.replace(/^{project-root}\//, '');
// Replace {bmad_folder} with bmad
filePath = filePath.replace(/^{bmad_folder}\//, 'bmad/');
filePath = filePath.replace(/^{bmad_folder}$/, 'bmad');
// Check temp directory first for _cfg files
if (filePath.startsWith('bmad/_cfg/')) {
@@ -1229,13 +1296,15 @@ class WebBundler {
}
/**
* Process and remove {project-root} references
* Process and remove {project-root} references and replace {bmad_folder} with bmad
*/
processProjectRootReferences(content) {
// Remove {project-root}/ prefix (with slash)
content = content.replaceAll('{project-root}/', '');
// Also remove {project-root} without slash
content = content.replaceAll('{project-root}', '');
// Replace {bmad_folder} with bmad
content = content.replaceAll('{bmad_folder}', 'bmad');
return content;
}

View File

@@ -13,6 +13,41 @@ class ConfigCollector {
this.currentProjectDir = null;
}
/**
* Find the bmad installation directory in a project
* V6+ installations can use ANY folder name but ALWAYS have _cfg/manifest.yaml
* @param {string} projectDir - Project directory
* @returns {Promise<string>} Path to bmad directory
*/
async findBmadDir(projectDir) {
// Check if project directory exists
if (!(await fs.pathExists(projectDir))) {
// Project doesn't exist yet, return default
return path.join(projectDir, 'bmad');
}
// V6+ strategy: Look for ANY directory with _cfg/manifest.yaml
// This is the definitive marker of a V6+ installation
try {
const entries = await fs.readdir(projectDir, { withFileTypes: true });
for (const entry of entries) {
if (entry.isDirectory()) {
const manifestPath = path.join(projectDir, entry.name, '_cfg', 'manifest.yaml');
if (await fs.pathExists(manifestPath)) {
// Found a V6+ installation
return path.join(projectDir, entry.name);
}
}
}
} catch {
// Ignore errors, fall through to default
}
// No V6+ installation found, return default
// This will be used for new installations
return path.join(projectDir, 'bmad');
}
/**
* Load existing config if it exists from module config files
* @param {string} projectDir - Target project directory
@@ -25,7 +60,8 @@ class ConfigCollector {
return false;
}
const bmadDir = path.join(projectDir, 'bmad');
// Find the actual bmad directory (handles custom folder names)
const bmadDir = await this.findBmadDir(projectDir);
// Check if bmad directory exists
if (!(await fs.pathExists(bmadDir))) {
@@ -165,17 +201,13 @@ class ConfigCollector {
this.allAnswers[`${moduleName}_${key}`] = value;
}
}
// Show "no config" message for modules with no new questions
CLIUtils.displayModuleNoConfig(moduleName, moduleConfig.header, moduleConfig.subheader);
return false; // No new fields
}
// If we have new fields, show prompt section and collect only new fields
// If we have new fields, build questions first
if (newKeys.length > 0) {
console.log(chalk.yellow(`\n📋 New configuration options available for ${moduleName}`));
if (moduleConfig.prompt) {
const prompts = Array.isArray(moduleConfig.prompt) ? moduleConfig.prompt : [moduleConfig.prompt];
CLIUtils.displayPromptSection(prompts);
}
const questions = [];
for (const key of newKeys) {
const item = moduleConfig[key];
@@ -186,6 +218,8 @@ class ConfigCollector {
}
if (questions.length > 0) {
// Only show header if we actually have questions
CLIUtils.displayModuleConfigHeader(moduleName, moduleConfig.header, moduleConfig.subheader);
console.log(); // Line break before questions
const answers = await inquirer.prompt(questions);
@@ -212,6 +246,9 @@ class ConfigCollector {
}
this.collectedConfig[moduleName][originalKey] = result;
}
} else {
// New keys exist but no questions generated - show no config message
CLIUtils.displayModuleNoConfig(moduleName, moduleConfig.header, moduleConfig.subheader);
}
}
@@ -331,12 +368,6 @@ class ConfigCollector {
return;
}
// Display module prompts using better formatting
if (moduleConfig.prompt) {
const prompts = Array.isArray(moduleConfig.prompt) ? moduleConfig.prompt : [moduleConfig.prompt];
CLIUtils.displayPromptSection(prompts);
}
// Process each config item
const questions = [];
const configKeys = Object.keys(moduleConfig).filter((key) => key !== 'prompt');
@@ -355,7 +386,9 @@ class ConfigCollector {
}
}
// Display appropriate header based on whether there are questions
if (questions.length > 0) {
CLIUtils.displayModuleConfigHeader(moduleName, moduleConfig.header, moduleConfig.subheader);
console.log(); // Line break before questions
const answers = await inquirer.prompt(questions);
@@ -456,10 +489,10 @@ class ConfigCollector {
this.collectedConfig[moduleName][originalKey] = result;
}
// Display module completion message after collecting all answers (unless skipped)
if (!skipCompletion) {
CLIUtils.displayModuleComplete(moduleName);
}
// No longer display completion boxes - keep output clean
} else {
// No questions for this module - show completion message
CLIUtils.displayModuleNoConfig(moduleName, moduleConfig.header, moduleConfig.subheader);
}
}

View File

@@ -39,6 +39,7 @@ class ManifestGenerator {
this.updatedModules = [...new Set(['core', ...selectedModules])]; // Only these get rescanned
this.preservedModules = preservedModules; // These stay as-is in CSVs
this.bmadDir = bmadDir;
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')) {
@@ -140,8 +141,8 @@ class ManifestGenerator {
// Build relative path for installation
const installPath =
moduleName === 'core'
? `bmad/core/workflows/${relativePath}/workflow.yaml`
: `bmad/${moduleName}/workflows/${relativePath}/workflow.yaml`;
? `${this.bmadFolderName}/core/workflows/${relativePath}/workflow.yaml`
: `${this.bmadFolderName}/${moduleName}/workflows/${relativePath}/workflow.yaml`;
// Check for standalone property (default: false)
const standalone = workflow.standalone === true;
@@ -241,7 +242,8 @@ class ManifestGenerator {
const principlesMatch = content.match(/<principles>([\s\S]*?)<\/principles>/);
// Build relative path for installation
const installPath = moduleName === 'core' ? `bmad/core/agents/${file}` : `bmad/${moduleName}/agents/${file}`;
const installPath =
moduleName === 'core' ? `${this.bmadFolderName}/core/agents/${file}` : `${this.bmadFolderName}/${moduleName}/agents/${file}`;
const agentName = file.replace('.md', '');
@@ -324,7 +326,8 @@ class ManifestGenerator {
const standalone = !!standaloneMatch;
// Build relative path for installation
const installPath = moduleName === 'core' ? `bmad/core/tasks/${file}` : `bmad/${moduleName}/tasks/${file}`;
const installPath =
moduleName === 'core' ? `${this.bmadFolderName}/core/tasks/${file}` : `${this.bmadFolderName}/${moduleName}/tasks/${file}`;
const taskName = file.replace(/\.(xml|md)$/, '');
tasks.push({
@@ -393,7 +396,8 @@ class ManifestGenerator {
const standalone = !!standaloneMatch;
// Build relative path for installation
const installPath = moduleName === 'core' ? `bmad/core/tools/${file}` : `bmad/${moduleName}/tools/${file}`;
const installPath =
moduleName === 'core' ? `${this.bmadFolderName}/core/tools/${file}` : `${this.bmadFolderName}/${moduleName}/tools/${file}`;
const toolName = file.replace(/\.(xml|md)$/, '');
tools.push({
@@ -659,7 +663,7 @@ class ManifestGenerator {
} else {
// Fallback: use the collected workflows/agents/tasks
for (const file of this.files) {
const filePath = path.join(this.bmadDir, file.path.replace('bmad/', ''));
const filePath = path.join(this.bmadDir, file.path.replace(this.bmadFolderName + '/', ''));
const hash = await this.calculateFileHash(filePath);
allFiles.push({
...file,

View File

@@ -29,6 +29,22 @@ class BaseIdeSetup {
this.bmadFolderName = bmadFolderName;
}
/**
* Get the agent command activation header from the central template
* @returns {string} The activation header text (without XML tags)
*/
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.";
}
}
/**
* Main setup method - must be implemented by subclasses
* @param {string} projectDir - Project directory

View File

@@ -1,70 +1,17 @@
const path = require('node:path');
const os = require('node:os');
const { BaseIdeSetup } = require('./_base-ide');
const chalk = require('chalk');
const inquirer = require('inquirer');
/**
* Auggie CLI setup handler
* Allows flexible installation of agents to multiple locations
* Installs to project directory (.augment/commands)
*/
class AuggieSetup extends BaseIdeSetup {
constructor() {
super('auggie', 'Auggie CLI');
this.defaultLocations = [
{ name: 'Project Directory (.augment/commands)', value: '.augment/commands', checked: true },
{ name: 'User Home (~/.augment/commands)', value: path.join(os.homedir(), '.augment', 'commands') },
{ name: 'Custom Location', value: 'custom' },
];
this.detectionPaths = ['.augment'];
}
/**
* Collect configuration choices before installation
* @param {Object} options - Configuration options
* @returns {Object} Collected configuration
*/
async collectConfiguration(options = {}) {
const response = await inquirer.prompt([
{
type: 'checkbox',
name: 'locations',
message: 'Select Auggie CLI installation locations:',
choices: this.defaultLocations,
validate: (answers) => {
if (answers.length === 0) {
return 'Please select at least one location';
}
return true;
},
},
]);
const locations = [];
for (const loc of response.locations) {
if (loc === 'custom') {
const custom = await inquirer.prompt([
{
type: 'input',
name: 'path',
message: 'Enter custom path for Auggie commands:',
validate: (input) => {
if (!input.trim()) {
return 'Path cannot be empty';
}
return true;
},
},
]);
locations.push(custom.path);
} else {
locations.push(loc);
}
}
return { auggieLocations: locations };
}
/**
* Setup Auggie CLI configuration
* @param {string} projectDir - Project directory
@@ -74,14 +21,8 @@ class AuggieSetup extends BaseIdeSetup {
async setup(projectDir, bmadDir, options = {}) {
console.log(chalk.cyan(`Setting up ${this.name}...`));
// Use pre-collected configuration if available
const config = options.preCollectedConfig || {};
const locations = await this.getInstallLocations(projectDir, { ...options, auggieLocations: config.auggieLocations });
if (locations.length === 0) {
console.log(chalk.yellow('No locations selected. Skipping Auggie CLI setup.'));
return { success: false, reason: 'no-locations' };
}
// Always use project directory
const location = path.join(projectDir, '.augment', 'commands');
// Clean up old BMAD installation first
await this.cleanup(projectDir);
@@ -92,151 +33,93 @@ class AuggieSetup extends BaseIdeSetup {
const tools = await this.getTools(bmadDir, true);
const workflows = await this.getWorkflows(bmadDir, true);
let totalInstalled = 0;
const bmadCommandsDir = path.join(location, 'bmad');
const agentsDir = path.join(bmadCommandsDir, 'agents');
const tasksDir = path.join(bmadCommandsDir, 'tasks');
const toolsDir = path.join(bmadCommandsDir, 'tools');
const workflowsDir = path.join(bmadCommandsDir, 'workflows');
// Install to each selected location
for (const location of locations) {
console.log(chalk.dim(`\n Installing to: ${location}`));
await this.ensureDir(agentsDir);
await this.ensureDir(tasksDir);
await this.ensureDir(toolsDir);
await this.ensureDir(workflowsDir);
const bmadCommandsDir = path.join(location, 'bmad');
const agentsDir = path.join(bmadCommandsDir, 'agents');
const tasksDir = path.join(bmadCommandsDir, 'tasks');
const toolsDir = path.join(bmadCommandsDir, 'tools');
const workflowsDir = path.join(bmadCommandsDir, 'workflows');
// Install agents
for (const agent of agents) {
const content = await this.readFile(agent.path);
const commandContent = await this.createAgentCommand(agent, content);
await this.ensureDir(agentsDir);
await this.ensureDir(tasksDir);
await this.ensureDir(toolsDir);
await this.ensureDir(workflowsDir);
// Install agents
for (const agent of agents) {
const content = await this.readFile(agent.path);
const commandContent = this.createAgentCommand(agent, content);
const targetPath = path.join(agentsDir, `${agent.module}-${agent.name}.md`);
await this.writeFile(targetPath, commandContent);
totalInstalled++;
}
// Install tasks
for (const task of tasks) {
const content = await this.readFile(task.path);
const commandContent = this.createTaskCommand(task, content);
const targetPath = path.join(tasksDir, `${task.module}-${task.name}.md`);
await this.writeFile(targetPath, commandContent);
totalInstalled++;
}
// Install tools
for (const tool of tools) {
const content = await this.readFile(tool.path);
const commandContent = this.createToolCommand(tool, content);
const targetPath = path.join(toolsDir, `${tool.module}-${tool.name}.md`);
await this.writeFile(targetPath, commandContent);
totalInstalled++;
}
// Install workflows
for (const workflow of workflows) {
const content = await this.readFile(workflow.path);
const commandContent = this.createWorkflowCommand(workflow, content);
const targetPath = path.join(workflowsDir, `${workflow.module}-${workflow.name}.md`);
await this.writeFile(targetPath, commandContent);
totalInstalled++;
}
console.log(
chalk.green(` ✓ Installed ${agents.length} agents, ${tasks.length} tasks, ${tools.length} tools, ${workflows.length} workflows`),
);
const targetPath = path.join(agentsDir, `${agent.module}-${agent.name}.md`);
await this.writeFile(targetPath, commandContent);
}
console.log(chalk.green(`\n${this.name} configured:`));
console.log(chalk.dim(` - ${totalInstalled} total commands installed`));
console.log(chalk.dim(` - ${locations.length} location(s) configured`));
// Install tasks
for (const task of tasks) {
const content = await this.readFile(task.path);
const commandContent = this.createTaskCommand(task, content);
const targetPath = path.join(tasksDir, `${task.module}-${task.name}.md`);
await this.writeFile(targetPath, commandContent);
}
// Install tools
for (const tool of tools) {
const content = await this.readFile(tool.path);
const commandContent = this.createToolCommand(tool, content);
const targetPath = path.join(toolsDir, `${tool.module}-${tool.name}.md`);
await this.writeFile(targetPath, commandContent);
}
// Install workflows
for (const workflow of workflows) {
const content = await this.readFile(workflow.path);
const commandContent = this.createWorkflowCommand(workflow, content);
const targetPath = path.join(workflowsDir, `${workflow.module}-${workflow.name}.md`);
await this.writeFile(targetPath, commandContent);
}
const totalInstalled = agents.length + tasks.length + tools.length + workflows.length;
console.log(chalk.green(`${this.name} configured:`));
console.log(chalk.dim(` - ${agents.length} agents installed`));
console.log(chalk.dim(` - ${tasks.length} tasks installed`));
console.log(chalk.dim(` - ${tools.length} tools installed`));
console.log(chalk.dim(` - ${workflows.length} workflows installed`));
console.log(chalk.dim(` - Location: ${path.relative(projectDir, location)}`));
console.log(chalk.yellow(`\n 💡 Tip: Add 'model: gpt-4o' to command frontmatter to specify AI model`));
return {
success: true,
commands: totalInstalled,
locations: locations.length,
agents: agents.length,
tasks: tasks.length,
tools: tools.length,
workflows: workflows.length,
};
}
/**
* Get installation locations from user
*/
async getInstallLocations(projectDir, options) {
if (options.auggieLocations) {
// Process the pre-collected locations to resolve relative paths
const processedLocations = [];
for (const loc of options.auggieLocations) {
if (loc === '.augment/commands') {
// Relative to project directory
processedLocations.push(path.join(projectDir, loc));
} else {
processedLocations.push(loc);
}
}
return processedLocations;
}
const response = await inquirer.prompt([
{
type: 'checkbox',
name: 'locations',
message: 'Select Auggie CLI installation locations:',
choices: this.defaultLocations,
validate: (answers) => {
if (answers.length === 0) {
return 'Please select at least one location';
}
return true;
},
},
]);
const locations = [];
for (const loc of response.locations) {
if (loc === 'custom') {
const custom = await inquirer.prompt([
{
type: 'input',
name: 'path',
message: 'Enter custom path for Auggie commands:',
validate: (input) => {
if (!input.trim()) {
return 'Path cannot be empty';
}
return true;
},
},
]);
locations.push(custom.path);
} else if (loc.startsWith('.augment')) {
// Relative to project directory
locations.push(path.join(projectDir, loc));
} else {
locations.push(loc);
}
}
return locations;
}
/**
* Create agent command content
*/
createAgentCommand(agent, content) {
async createAgentCommand(agent, content) {
const titleMatch = content.match(/title="([^"]+)"/);
const title = titleMatch ? titleMatch[1] : this.formatTitle(agent.name);
return `# ${title} Agent
// Extract description from agent if available
const whenToUseMatch = content.match(/whenToUse="([^"]+)"/);
const description = whenToUseMatch ? whenToUseMatch[1] : `Activate the ${title} agent`;
## Activation
Type \`@${agent.name}\` to activate this agent.
// Get the activation header from central template
const activationHeader = await this.getAgentCommandHeader();
return `---
description: "${description}"
---
# ${title} Agent
${activationHeader}
${content}
@@ -252,10 +135,11 @@ BMAD ${agent.module.toUpperCase()} module
const nameMatch = content.match(/name="([^"]+)"/);
const taskName = nameMatch ? nameMatch[1] : this.formatTitle(task.name);
return `# ${taskName} Task
return `---
description: "Execute the ${taskName} task"
---
## Activation
Type \`@task-${task.name}\` to execute this task.
# ${taskName} Task
${content}
@@ -271,10 +155,11 @@ BMAD ${task.module.toUpperCase()} module
const nameMatch = content.match(/name="([^"]+)"/);
const toolName = nameMatch ? nameMatch[1] : this.formatTitle(tool.name);
return `# ${toolName} Tool
return `---
description: "Use the ${toolName} tool"
---
## Activation
Type \`@tool-${tool.name}\` to execute this tool.
# ${toolName} Tool
${content}
@@ -287,13 +172,13 @@ BMAD ${tool.module.toUpperCase()} module
* Create workflow command content
*/
createWorkflowCommand(workflow, content) {
return `# ${workflow.name} Workflow
const description = workflow.description || `Execute the ${workflow.name} workflow`;
## Description
${workflow.description || 'No description provided'}
return `---
description: "${description}"
---
## Activation
Type \`@workflow-${workflow.name}\` to execute this workflow.
# ${workflow.name} Workflow
${content}
@@ -308,16 +193,13 @@ BMAD ${workflow.module.toUpperCase()} module
async cleanup(projectDir) {
const fs = require('fs-extra');
// Check common locations - bmad folder structure
const locations = [path.join(os.homedir(), '.augment', 'commands'), path.join(projectDir, '.augment', 'commands')];
// Only clean up project directory
const location = path.join(projectDir, '.augment', 'commands');
const bmadDir = path.join(location, 'bmad');
for (const location of locations) {
const bmadDir = path.join(location, 'bmad');
if (await fs.pathExists(bmadDir)) {
await fs.remove(bmadDir);
console.log(chalk.dim(` Removed old BMAD commands from ${location}`));
}
if (await fs.pathExists(bmadDir)) {
await fs.remove(bmadDir);
console.log(chalk.dim(` Removed old BMAD commands`));
}
}
}

View File

@@ -2,8 +2,8 @@ const path = require('node:path');
const { BaseIdeSetup } = require('./_base-ide');
const chalk = require('chalk');
const { getProjectRoot, getSourcePath, getModulePath } = require('../../../lib/project-root');
const { WorkflowCommandGenerator } = require('./workflow-command-generator');
const { TaskToolCommandGenerator } = require('./task-tool-command-generator');
const { WorkflowCommandGenerator } = require('./shared/workflow-command-generator');
const { TaskToolCommandGenerator } = require('./shared/task-tool-command-generator');
const {
loadModuleInjectionConfig,
shouldApplyInjection,
@@ -161,7 +161,7 @@ class ClaudeCodeSetup extends BaseIdeSetup {
// await this.createClaudeConfig(projectDir, modules);
// Generate workflow commands from manifest (if it exists)
const workflowGen = new WorkflowCommandGenerator();
const workflowGen = new WorkflowCommandGenerator(this.bmadFolderName);
const { artifacts: workflowArtifacts } = await workflowGen.collectWorkflowArtifacts(bmadDir);
// Write only workflow-command artifacts, skip workflow-launcher READMEs

View File

@@ -2,7 +2,7 @@ const path = require('node:path');
const fs = require('fs-extra');
const chalk = require('chalk');
const { BaseIdeSetup } = require('./_base-ide');
const { WorkflowCommandGenerator } = require('./workflow-command-generator');
const { WorkflowCommandGenerator } = require('./shared/workflow-command-generator');
const { getAgentsFromBmad, getTasksFromBmad } = require('./shared/bmad-artifacts');
/**
@@ -11,7 +11,7 @@ const { getAgentsFromBmad, getTasksFromBmad } = require('./shared/bmad-artifacts
*/
class ClineSetup extends BaseIdeSetup {
constructor() {
super('cline', 'Cline', true); // preferred IDE
super('cline', 'Cline', false);
this.configDir = '.clinerules';
this.workflowsDir = 'workflows';
}
@@ -131,7 +131,7 @@ class ClineSetup extends BaseIdeSetup {
}
// Get workflows
const workflowGenerator = new WorkflowCommandGenerator();
const workflowGenerator = new WorkflowCommandGenerator(this.bmadFolderName);
const { artifacts: workflowArtifacts, counts: workflowCounts } = await workflowGenerator.collectWorkflowArtifacts(bmadDir);
artifacts.push(...workflowArtifacts);

View File

@@ -2,41 +2,18 @@ const path = require('node:path');
const fs = require('fs-extra');
const os = require('node:os');
const chalk = require('chalk');
const inquirer = require('inquirer');
const { BaseIdeSetup } = require('./_base-ide');
const { WorkflowCommandGenerator } = require('./workflow-command-generator');
const { WorkflowCommandGenerator } = require('./shared/workflow-command-generator');
const { getAgentsFromBmad, getTasksFromBmad } = require('./shared/bmad-artifacts');
/**
* Codex setup handler (supports both CLI and Web)
* Codex setup handler (CLI mode)
*/
class CodexSetup extends BaseIdeSetup {
constructor() {
super('codex', 'Codex', true); // preferred IDE
}
/**
* Collect configuration choices before installation
* @param {Object} options - Configuration options
* @returns {Object} Collected configuration
*/
async collectConfiguration(options = {}) {
const response = await inquirer.prompt([
{
type: 'list',
name: 'mode',
message: 'Select Codex deployment mode:',
choices: [
{ name: 'CLI (Command-line interface)', value: 'cli' },
{ name: 'Web (Browser-based interface)', value: 'web' },
],
default: 'cli',
},
]);
return { codexMode: response.mode };
}
/**
* Setup Codex configuration
* @param {string} projectDir - Project directory
@@ -46,8 +23,8 @@ class CodexSetup extends BaseIdeSetup {
async setup(projectDir, bmadDir, options = {}) {
console.log(chalk.cyan(`Setting up ${this.name}...`));
const config = options.preCollectedConfig || {};
const mode = config.codexMode || options.codexMode || 'cli';
// Always use CLI mode
const mode = 'cli';
const { artifacts, counts } = await this.collectClaudeArtifacts(projectDir, bmadDir, options);
@@ -57,16 +34,13 @@ class CodexSetup extends BaseIdeSetup {
const written = await this.flattenAndWriteArtifacts(artifacts, destDir);
console.log(chalk.green(`${this.name} configured:`));
console.log(chalk.dim(` - Mode: ${mode === 'web' ? 'Web' : 'CLI'}`));
console.log(chalk.dim(` - Mode: CLI`));
console.log(chalk.dim(` - ${counts.agents} agents exported`));
console.log(chalk.dim(` - ${counts.tasks} tasks exported`));
console.log(chalk.dim(` - ${counts.workflows} workflow commands exported`));
if (counts.workflowLaunchers > 0) {
console.log(chalk.dim(` - ${counts.workflowLaunchers} workflow launchers exported`));
}
if (counts.subagents > 0) {
console.log(chalk.dim(` - ${counts.subagents} subagents exported`));
}
console.log(chalk.dim(` - ${written} Codex prompt files written`));
console.log(chalk.dim(` - Destination: ${destDir}`));
@@ -158,7 +132,7 @@ class CodexSetup extends BaseIdeSetup {
});
}
const workflowGenerator = new WorkflowCommandGenerator();
const workflowGenerator = new WorkflowCommandGenerator(this.bmadFolderName);
const { artifacts: workflowArtifacts, counts: workflowCounts } = await workflowGenerator.collectWorkflowArtifacts(bmadDir);
artifacts.push(...workflowArtifacts);

View File

@@ -84,7 +84,7 @@ class CrushSetup extends BaseIdeSetup {
const moduleAgents = agents.filter((a) => a.module === module);
for (const agent of moduleAgents) {
const content = await this.readFile(agent.path);
const commandContent = this.createAgentCommand(agent, content, projectDir);
const commandContent = await this.createAgentCommand(agent, content, projectDir);
const targetPath = path.join(moduleAgentsDir, `${agent.name}.md`);
await this.writeFile(targetPath, commandContent);
agentCount++;
@@ -132,7 +132,7 @@ class CrushSetup extends BaseIdeSetup {
/**
* Create agent command content
*/
createAgentCommand(agent, content, projectDir) {
async createAgentCommand(agent, content, projectDir) {
// Extract metadata
const titleMatch = content.match(/title="([^"]+)"/);
const title = titleMatch ? titleMatch[1] : this.formatTitle(agent.name);
@@ -140,12 +140,15 @@ class CrushSetup extends BaseIdeSetup {
const iconMatch = content.match(/icon="([^"]+)"/);
const icon = iconMatch ? iconMatch[1] : '🤖';
// Get the activation header from central template
const activationHeader = await this.getAgentCommandHeader();
// Get relative path
const relativePath = path.relative(projectDir, agent.path).replaceAll('\\', '/');
let commandContent = `# /${agent.name} Command
When this command is used, adopt the following agent persona:
${activationHeader}
## ${icon} ${title} Agent
@@ -159,9 +162,6 @@ This command activates the ${title} agent from the BMAD ${agent.module.toUpperCa
Complete agent definition: [${relativePath}](${relativePath})
## Module
Part of the BMAD ${agent.module.toUpperCase()} module.
`;
return commandContent;

View File

@@ -256,6 +256,13 @@ specific agent expertise, task workflows, tools, or guided workflows.
// First apply base processing (includes activation injection for agents)
let processed = super.processContent(content, metadata);
// Strip any existing frontmatter from the processed content
// This prevents duplicate frontmatter blocks
const frontmatterRegex = /^---\s*\n[\s\S]*?\n---\s*\n/;
if (frontmatterRegex.test(processed)) {
processed = processed.replace(frontmatterRegex, '');
}
// Determine the type and description based on content
const isAgent = content.includes('<agent');
const isTask = content.includes('<task');

View File

@@ -1,4 +1,6 @@
const path = require('node:path');
const fs = require('fs-extra');
const yaml = require('js-yaml');
const { BaseIdeSetup } = require('./_base-ide');
const chalk = require('chalk');
@@ -8,9 +10,39 @@ const chalk = require('chalk');
*/
class GeminiSetup extends BaseIdeSetup {
constructor() {
super('gemini', 'Gemini CLI', true); // preferred IDE
super('gemini', 'Gemini CLI', false);
this.configDir = '.gemini';
this.commandsDir = 'commands';
this.agentTemplatePath = path.join(__dirname, 'templates', 'gemini-agent-command.toml');
this.taskTemplatePath = path.join(__dirname, 'templates', 'gemini-task-command.toml');
}
/**
* Load config values from bmad installation
* @param {string} bmadDir - BMAD installation directory
* @returns {Object} Config values
*/
async loadConfigValues(bmadDir) {
const configValues = {
user_name: 'User', // Default fallback
};
// Try to load core config.yaml
const coreConfigPath = path.join(bmadDir, 'core', 'config.yaml');
if (await fs.pathExists(coreConfigPath)) {
try {
const configContent = await fs.readFile(coreConfigPath, 'utf8');
const config = yaml.load(configContent);
if (config.user_name) {
configValues.user_name = config.user_name;
}
} catch (error) {
console.warn(chalk.yellow(` Warning: Could not load config values: ${error.message}`));
}
}
return configValues;
}
/**
@@ -39,7 +71,7 @@ class GeminiSetup extends BaseIdeSetup {
let agentCount = 0;
for (const agent of agents) {
const content = await this.readFile(agent.path);
const tomlContent = this.createAgentToml(agent, content, bmadDir);
const tomlContent = await this.createAgentToml(agent, content);
// Flat structure: bmad-agent-{module}-{name}.toml
const tomlPath = path.join(commandsDir, `bmad-agent-${agent.module}-${agent.name}.toml`);
@@ -53,7 +85,7 @@ class GeminiSetup extends BaseIdeSetup {
let taskCount = 0;
for (const task of tasks) {
const content = await this.readFile(task.path);
const tomlContent = this.createTaskToml(task, content, bmadDir);
const tomlContent = await this.createTaskToml(task, content);
// Flat structure: bmad-task-{module}-{name}.toml
const tomlPath = path.join(commandsDir, `bmad-task-${task.module}-${task.name}.toml`);
@@ -78,51 +110,44 @@ class GeminiSetup extends BaseIdeSetup {
}
/**
* Create agent TOML content
* Create agent TOML content using template
*/
createAgentToml(agent, content, bmadDir) {
async createAgentToml(agent, content) {
// Extract metadata
const titleMatch = content.match(/title="([^"]+)"/);
const title = titleMatch ? titleMatch[1] : this.formatTitle(agent.name);
// Get relative path from project root to agent file
const relativePath = path.relative(process.cwd(), agent.path).replaceAll('\\', '/');
// Load template
const template = await fs.readFile(this.agentTemplatePath, 'utf8');
// Create TOML content
const tomlContent = `description = "Activates the ${title} agent from the BMad Method."
prompt = """
CRITICAL: You are now the BMad '${title}' agent. Adopt its persona and capabilities as defined in the following configuration.
Read and internalize the full agent definition, following all instructions and maintaining this persona until explicitly told to switch or exit.
@${relativePath}
"""
`;
// Replace template variables
// Note: {user_name} and other {config_values} are left as-is for runtime substitution by Gemini
const tomlContent = template
.replaceAll('{{title}}', title)
.replaceAll('{{bmad_folder}}', this.bmadFolderName)
.replaceAll('{{module}}', agent.module)
.replaceAll('{{name}}', agent.name);
return tomlContent;
}
/**
* Create task TOML content
* Create task TOML content using template
*/
createTaskToml(task, content, bmadDir) {
async createTaskToml(task, content) {
// Extract task name from XML if available
const nameMatch = content.match(/<name>([^<]+)<\/name>/);
const taskName = nameMatch ? nameMatch[1] : this.formatTitle(task.name);
// Get relative path from project root to task file
const relativePath = path.relative(process.cwd(), task.path).replaceAll('\\', '/');
// Load template
const template = await fs.readFile(this.taskTemplatePath, 'utf8');
// Create TOML content
const tomlContent = `description = "Executes the ${taskName} task from the BMad Method."
prompt = """
Execute the following BMad Method task workflow:
@${relativePath}
Follow all instructions and complete the task as defined.
"""
`;
// Replace template variables
const tomlContent = template
.replaceAll('{{taskName}}', taskName)
.replaceAll('{{bmad_folder}}', this.bmadFolderName)
.replaceAll('{{module}}', task.module)
.replaceAll('{{filename}}', task.filename);
return tomlContent;
}

View File

@@ -111,7 +111,7 @@ class GitHubCopilotSetup extends BaseIdeSetup {
let modeCount = 0;
for (const agent of agents) {
const content = await this.readFile(agent.path);
const chatmodeContent = this.createChatmodeContent(agent, content);
const chatmodeContent = await this.createChatmodeContent(agent, content);
// Use bmad- prefix: bmad-agent-{module}-{name}.chatmode.md
const targetPath = path.join(chatmodesDir, `bmad-agent-${agent.module}-${agent.name}.chatmode.md`);
@@ -206,7 +206,7 @@ class GitHubCopilotSetup extends BaseIdeSetup {
/**
* Create chat mode content
*/
createChatmodeContent(agent, content) {
async createChatmodeContent(agent, content) {
// Extract metadata
const titleMatch = content.match(/title="([^"]+)"/);
const title = titleMatch ? titleMatch[1] : this.formatTitle(agent.name);
@@ -214,6 +214,16 @@ class GitHubCopilotSetup extends BaseIdeSetup {
const whenToUseMatch = content.match(/whenToUse="([^"]+)"/);
const description = whenToUseMatch ? whenToUseMatch[1] : `Activates the ${title} agent persona.`;
// Get the activation header from central template
const activationHeader = await this.getAgentCommandHeader();
// Strip any existing frontmatter from the content
const frontmatterRegex = /^---\s*\n[\s\S]*?\n---\s*\n/;
let cleanContent = content;
if (frontmatterRegex.test(content)) {
cleanContent = content.replace(frontmatterRegex, '');
}
// Available GitHub Copilot tools (November 2025 - Official VS Code Documentation)
// Reference: https://code.visualstudio.com/docs/copilot/reference/copilot-vscode-features#_chat-tools
const tools = [
@@ -248,11 +258,10 @@ tools: ${JSON.stringify(tools)}
# ${title} Agent
${content}
${activationHeader}
## Module
${cleanContent}
Part of the BMAD ${agent.module.toUpperCase()} module.
`;
return chatmodeContent;

View File

@@ -39,7 +39,7 @@ class IFlowSetup extends BaseIdeSetup {
let agentCount = 0;
for (const agent of agents) {
const content = await this.readFile(agent.path);
const commandContent = this.createAgentCommand(agent, content);
const commandContent = await this.createAgentCommand(agent, content);
const targetPath = path.join(agentsDir, `${agent.module}-${agent.name}.md`);
await this.writeFile(targetPath, commandContent);
@@ -72,14 +72,17 @@ class IFlowSetup extends BaseIdeSetup {
/**
* Create agent command content
*/
createAgentCommand(agent, content) {
async createAgentCommand(agent, content) {
// Extract metadata
const titleMatch = content.match(/title="([^"]+)"/);
const title = titleMatch ? titleMatch[1] : this.formatTitle(agent.name);
// Get the activation header from central template
const activationHeader = await this.getAgentCommandHeader();
let commandContent = `# /${agent.name} Command
When this command is used, adopt the following agent persona:
${activationHeader}
## ${title} Agent
@@ -89,9 +92,6 @@ ${content}
This command activates the ${title} agent from the BMAD ${agent.module.toUpperCase()} module.
## Module
Part of the BMAD ${agent.module.toUpperCase()} module.
`;
return commandContent;

View File

@@ -55,7 +55,7 @@ class KiloSetup extends BaseIdeSetup {
}
const content = await this.readFile(agent.path);
const modeEntry = this.createModeEntry(agent, content, projectDir);
const modeEntry = await this.createModeEntry(agent, content, projectDir);
newModesContent += modeEntry;
addedCount++;
@@ -90,7 +90,7 @@ class KiloSetup extends BaseIdeSetup {
/**
* Create a mode entry for an agent
*/
createModeEntry(agent, content, projectDir) {
async createModeEntry(agent, content, projectDir) {
// Extract metadata
const titleMatch = content.match(/title="([^"]+)"/);
const title = titleMatch ? titleMatch[1] : this.formatTitle(agent.name);
@@ -101,6 +101,9 @@ class KiloSetup extends BaseIdeSetup {
const whenToUseMatch = content.match(/whenToUse="([^"]+)"/);
const whenToUse = whenToUseMatch ? whenToUseMatch[1] : `Use for ${title} tasks`;
// Get the activation header from central template
const activationHeader = await this.getAgentCommandHeader();
const roleDefinitionMatch = content.match(/roleDefinition="([^"]+)"/);
const roleDefinition = roleDefinitionMatch
? roleDefinitionMatch[1]
@@ -115,7 +118,7 @@ class KiloSetup extends BaseIdeSetup {
modeEntry += ` name: '${icon} ${title}'\n`;
modeEntry += ` roleDefinition: ${roleDefinition}\n`;
modeEntry += ` whenToUse: ${whenToUse}\n`;
modeEntry += ` customInstructions: CRITICAL Read the full YAML from ${relativePath} start activation to alter your state of being follow startup section instructions stay in this being until told to exit this mode\n`;
modeEntry += ` customInstructions: ${activationHeader} Read the full YAML from ${relativePath} start activation to alter your state of being follow startup section instructions stay in this being until told to exit this mode\n`;
modeEntry += ` groups:\n`;
modeEntry += ` - read\n`;
modeEntry += ` - edit\n`;

View File

@@ -4,8 +4,8 @@ const os = require('node:os');
const chalk = require('chalk');
const yaml = require('js-yaml');
const { BaseIdeSetup } = require('./_base-ide');
const { WorkflowCommandGenerator } = require('./workflow-command-generator');
const { TaskToolCommandGenerator } = require('./task-tool-command-generator');
const { WorkflowCommandGenerator } = require('./shared/workflow-command-generator');
const { TaskToolCommandGenerator } = require('./shared/task-tool-command-generator');
const { getAgentsFromBmad } = require('./shared/bmad-artifacts');
@@ -44,7 +44,7 @@ class OpenCodeSetup extends BaseIdeSetup {
name: agent.name,
});
const agentContent = this.createAgentContent(processed, agent);
const agentContent = await this.createAgentContent(processed, agent);
// Flat structure in agent folder: bmad-agent-{module}-{name}.md
const targetPath = path.join(agentsBaseDir, `bmad-agent-${agent.module}-${agent.name}.md`);
await this.writeFile(targetPath, agentContent);
@@ -52,7 +52,7 @@ class OpenCodeSetup extends BaseIdeSetup {
}
// Install workflow commands with flat naming: bmad-workflow-{module}-{name}.md
const workflowGenerator = new WorkflowCommandGenerator();
const workflowGenerator = new WorkflowCommandGenerator(this.bmadFolderName);
const { artifacts: workflowArtifacts, counts: workflowCounts } = await workflowGenerator.collectWorkflowArtifacts(bmadDir);
let workflowCommandCount = 0;
@@ -127,7 +127,7 @@ class OpenCodeSetup extends BaseIdeSetup {
return this.processContent(content, metadata);
}
createAgentContent(content, metadata) {
async createAgentContent(content, metadata) {
const { frontmatter = {}, body } = this.parseFrontmatter(content);
frontmatter.description =
@@ -140,7 +140,10 @@ class OpenCodeSetup extends BaseIdeSetup {
const frontmatterString = this.stringifyFrontmatter(frontmatter);
return `${frontmatterString}\n${body}`;
// Get the activation header from central template
const activationHeader = await this.getAgentCommandHeader();
return `${frontmatterString}\n\n${activationHeader}\n\n${body}`;
}
parseFrontmatter(content) {

View File

@@ -1,7 +1,6 @@
const path = require('node:path');
const { BaseIdeSetup } = require('./_base-ide');
const chalk = require('chalk');
const inquirer = require('inquirer');
/**
* Roo IDE setup handler
@@ -35,31 +34,6 @@ class RooSetup extends BaseIdeSetup {
};
}
/**
* Collect configuration choices before installation
* @param {Object} options - Configuration options
* @returns {Object} Collected configuration
*/
async collectConfiguration(options = {}) {
const response = await inquirer.prompt([
{
type: 'list',
name: 'permissions',
message: 'Select default file edit permissions for BMAD agents:',
choices: [
{ name: 'Development files only (js, ts, py, etc.)', value: 'dev' },
{ name: 'Configuration files only (json, yaml, xml, etc.)', value: 'config' },
{ name: 'Documentation files only (md, txt, doc, etc.)', value: 'docs' },
{ name: 'All files (unrestricted access)', value: 'all' },
{ name: 'Custom per agent (will be configured individually)', value: 'custom' },
],
default: 'dev',
},
]);
return { permissions: response.permissions };
}
/**
* Setup Roo IDE configuration
* @param {string} projectDir - Project directory
@@ -87,9 +61,8 @@ class RooSetup extends BaseIdeSetup {
// Get agents
const agents = await this.getAgents(bmadDir);
// Use pre-collected configuration if available
const config = options.preCollectedConfig || {};
let permissionChoice = config.permissions || options.permissions || 'dev';
// Always use 'all' permissions - users can customize in .roomodes file
const permissionChoice = 'all';
// Create modes content
let newModesContent = '';
@@ -107,7 +80,7 @@ class RooSetup extends BaseIdeSetup {
}
const content = await this.readFile(agent.path);
const modeEntry = this.createModeEntry(agent, content, permissionChoice, projectDir);
const modeEntry = await this.createModeEntry(agent, content, permissionChoice, projectDir);
newModesContent += modeEntry;
addedCount++;
@@ -133,8 +106,9 @@ class RooSetup extends BaseIdeSetup {
console.log(chalk.dim(` - ${skippedCount} modes skipped (already exist)`));
}
console.log(chalk.dim(` - Configuration file: ${this.configFile}`));
console.log(chalk.dim(` - Permission level: ${permissionChoice}`));
console.log(chalk.dim('\n Modes will be available when you open this project in Roo Code'));
console.log(chalk.dim(` - Permission level: all (unrestricted)`));
console.log(chalk.yellow(`\n 💡 Tip: Edit ${this.configFile} to customize file permissions per agent`));
console.log(chalk.dim(` Modes will be available when you open this project in Roo Code`));
return {
success: true,
@@ -143,33 +117,10 @@ class RooSetup extends BaseIdeSetup {
};
}
/**
* Ask user about permission configuration
*/
async askPermissions() {
const response = await inquirer.prompt([
{
type: 'list',
name: 'permissions',
message: 'Select default file edit permissions for BMAD agents:',
choices: [
{ name: 'Development files only (js, ts, py, etc.)', value: 'dev' },
{ name: 'Configuration files only (json, yaml, xml, etc.)', value: 'config' },
{ name: 'Documentation files only (md, txt, doc, etc.)', value: 'docs' },
{ name: 'All files (unrestricted access)', value: 'all' },
{ name: 'Custom per agent (will be configured individually)', value: 'custom' },
],
default: 'dev',
},
]);
return response.permissions;
}
/**
* Create a mode entry for an agent
*/
createModeEntry(agent, content, permissionChoice, projectDir) {
async createModeEntry(agent, content, permissionChoice, projectDir) {
// Extract metadata from agent content
const titleMatch = content.match(/title="([^"]+)"/);
const title = titleMatch ? titleMatch[1] : this.formatTitle(agent.name);
@@ -180,6 +131,9 @@ class RooSetup extends BaseIdeSetup {
const whenToUseMatch = content.match(/whenToUse="([^"]+)"/);
const whenToUse = whenToUseMatch ? whenToUseMatch[1] : `Use for ${title} tasks`;
// Get the activation header from central template
const activationHeader = await this.getAgentCommandHeader();
const roleDefinitionMatch = content.match(/roleDefinition="([^"]+)"/);
const roleDefinition = roleDefinitionMatch
? roleDefinitionMatch[1]
@@ -202,7 +156,7 @@ class RooSetup extends BaseIdeSetup {
modeEntry += ` roleDefinition: ${roleDefinition}\n`;
modeEntry += ` whenToUse: ${whenToUse}\n`;
modeEntry += ` customInstructions: CRITICAL Read the full YAML from ${relativePath} start activation to alter your state of being follow startup section instructions stay in this being until told to exit this mode\n`;
modeEntry += ` customInstructions: ${activationHeader} Read the full YAML from ${relativePath} start activation to alter your state of being follow startup section instructions stay in this being until told to exit this mode\n`;
modeEntry += ` groups:\n`;
modeEntry += ` - read\n`;

View File

@@ -4,7 +4,7 @@ const csv = require('csv-parse/sync');
const chalk = require('chalk');
/**
* Generates Claude Code command files for standalone tasks and tools
* Generates command files for standalone tasks and tools
*/
class TaskToolCommandGenerator {
/**

View File

@@ -4,11 +4,12 @@ const csv = require('csv-parse/sync');
const chalk = require('chalk');
/**
* Generates Claude Code command files for each workflow in the manifest
* Generates command files for each workflow in the manifest
*/
class WorkflowCommandGenerator {
constructor() {
this.templatePath = path.join(__dirname, 'workflow-command-template.md');
constructor(bmadFolderName = 'bmad') {
this.templatePath = path.join(__dirname, '../templates/workflow-command-template.md');
this.bmadFolderName = bmadFolderName;
}
/**
@@ -103,19 +104,19 @@ 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_folder}/bmm/workflows/.../workflow.yaml
let workflowPath = workflow.path;
// Extract the relative path from source
if (workflowPath.includes('/src/modules/')) {
const match = workflowPath.match(/\/src\/modules\/(.+)/);
if (match) {
workflowPath = `{project-root}/bmad/${match[1]}`;
workflowPath = `${this.bmadFolderName}/${match[1]}`;
}
} else if (workflowPath.includes('/src/core/')) {
const match = workflowPath.match(/\/src\/core\/(.+)/);
if (match) {
workflowPath = `{project-root}/bmad/core/${match[1]}`;
workflowPath = `${this.bmadFolderName}/core/${match[1]}`;
}
}
@@ -125,6 +126,7 @@ class WorkflowCommandGenerator {
.replaceAll('{{module}}', workflow.module)
.replaceAll('{{description}}', workflow.description)
.replaceAll('{{workflow_path}}', workflowPath)
.replaceAll('{bmad_folder}', this.bmadFolderName)
.replaceAll('{{interactive}}', workflow.interactive)
.replaceAll('{{author}}', workflow.author || 'BMAD');
}
@@ -186,7 +188,7 @@ class WorkflowCommandGenerator {
## Execution
When running any workflow:
1. LOAD {project-root}/bmad/core/tasks/workflow.xml
1. LOAD {project-root}/${this.bmadFolderName}/core/tasks/workflow.xml
2. Pass the workflow path as 'workflow-config' parameter
3. Follow workflow.xml instructions EXACTLY
4. Save outputs after EACH section
@@ -205,12 +207,12 @@ When running any workflow:
if (workflowPath.includes('/src/modules/')) {
const match = workflowPath.match(/\/src\/modules\/(.+)/);
if (match) {
transformed = `{project-root}/bmad/${match[1]}`;
transformed = `{project-root}/${this.bmadFolderName}/${match[1]}`;
}
} else if (workflowPath.includes('/src/core/')) {
const match = workflowPath.match(/\/src\/core\/(.+)/);
if (match) {
transformed = `{project-root}/bmad/core/${match[1]}`;
transformed = `{project-root}/${this.bmadFolderName}/core/${match[1]}`;
}
}

View File

@@ -0,0 +1,14 @@
description = "Activates the {{title}} agent from the BMad Method."
prompt = """
CRITICAL: You are now the BMad '{{title}}' agent.
PRE-FLIGHT CHECKLIST:
1. [ ] IMMEDIATE ACTION: Load and parse @{{bmad_folder}}/{{module}}/config.yaml - store ALL config values in memory for use throughout the session.
2. [ ] IMMEDIATE ACTION: Read and internalize the full agent definition at @{{bmad_folder}}/{{module}}/agents/{{name}}.md.
3. [ ] CONFIRM: The user's name from config is {user_name}.
Only after all checks are complete, greet the user by name and display the menu.
Acknowledge this checklist is complete in your first response.
AGENT DEFINITION: @{{bmad_folder}}/{{module}}/agents/{{name}}.md
"""

View File

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

View File

@@ -2,14 +2,12 @@
description: '{{description}}'
---
# {{name}}
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 {project-root}/bmad/core/tasks/workflow.xml
1. Always LOAD the FULL {project-root}/{bmad_folder}/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
4. Follow workflow.xml instructions EXACTLY as written to process and follow the specific workflow config and its instructions
5. Save outputs after EACH section when generating any documents from templates
</steps>

View File

@@ -40,7 +40,7 @@ class TraeSetup extends BaseIdeSetup {
let agentCount = 0;
for (const agent of agents) {
const content = await this.readFile(agent.path);
const processedContent = this.createAgentRule(agent, content, bmadDir, projectDir);
const processedContent = await this.createAgentRule(agent, content, bmadDir, projectDir);
// Use bmad- prefix: bmad-agent-{module}-{name}.md
const targetPath = path.join(rulesDir, `bmad-agent-${agent.module}-${agent.name}.md`);
@@ -108,7 +108,7 @@ class TraeSetup extends BaseIdeSetup {
/**
* Create rule content for an agent
*/
createAgentRule(agent, content, bmadDir, projectDir) {
async createAgentRule(agent, content, bmadDir, projectDir) {
// Extract metadata from agent content
const titleMatch = content.match(/title="([^"]+)"/);
const title = titleMatch ? titleMatch[1] : this.formatTitle(agent.name);
@@ -116,6 +116,9 @@ class TraeSetup extends BaseIdeSetup {
const iconMatch = content.match(/icon="([^"]+)"/);
const icon = iconMatch ? iconMatch[1] : '🤖';
// Get the activation header from central template
const activationHeader = await this.getAgentCommandHeader();
// Extract YAML content if available
const yamlMatch = content.match(/```ya?ml\r?\n([\s\S]*?)```/);
const yamlContent = yamlMatch ? yamlMatch[1] : content;
@@ -129,7 +132,9 @@ This rule is triggered when the user types \`@${agent.name}\` and activates the
## Agent Activation
CRITICAL: Read the full YAML, start activation to alter your state of being, follow startup section instructions, stay in this being until told to exit this mode:
${activationHeader}
Read the full YAML, start activation to alter your state of being, follow startup section instructions, stay in this being until told to exit this mode:
\`\`\`yaml
${yamlContent}
@@ -143,9 +148,6 @@ The complete agent definition is available in [${relativePath}](${relativePath})
When the user types \`@${agent.name}\`, activate this ${title} persona and follow all instructions defined in the YAML configuration above.
## Module
Part of the BMAD ${agent.module.toUpperCase()} module.
`;
return ruleContent;

View File

@@ -2,13 +2,31 @@ const chalk = require('chalk');
const boxen = require('boxen');
const wrapAnsi = require('wrap-ansi');
const figlet = require('figlet');
const path = require('node:path');
const CLIUtils = {
/**
* Display BMAD logo
* Get version from package.json
*/
displayLogo() {
console.clear();
getVersion() {
try {
const packageJson = require(path.join(__dirname, '..', '..', '..', 'package.json'));
return packageJson.version || 'Unknown';
} catch {
return 'Unknown';
}
},
/**
* Display BMAD logo
* @param {boolean} clearScreen - Whether to clear the screen first (default: true for initial display only)
*/
displayLogo(clearScreen = true) {
if (clearScreen) {
console.clear();
}
const version = this.getVersion();
// ASCII art logo
const logo = `
@@ -20,7 +38,7 @@ const CLIUtils = {
╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═════╝`;
console.log(chalk.cyan(logo));
console.log(chalk.dim(' Build More, Architect Dreams\n'));
console.log(chalk.dim(` Build More, Architect Dreams`) + chalk.cyan.bold(` v${version}`) + '\n');
},
/**
@@ -64,18 +82,35 @@ const CLIUtils = {
},
/**
* Display prompt section
* @param {string|Array} prompts - Prompts to display
* Display module configuration header
* @param {string} moduleName - Module name (fallback if no custom header)
* @param {string} header - Custom header from install-config.yaml
* @param {string} subheader - Custom subheader from install-config.yaml
*/
displayPromptSection(prompts) {
const promptArray = Array.isArray(prompts) ? prompts : [prompts];
displayModuleConfigHeader(moduleName, header = null, subheader = null) {
// Simple blue banner with custom header/subheader if provided
console.log('\n' + chalk.cyan('─'.repeat(80)));
console.log(chalk.cyan(header || `Configuring ${moduleName.toUpperCase()} Module`));
if (subheader) {
console.log(chalk.dim(`${subheader}`));
}
console.log(chalk.cyan('─'.repeat(80)) + '\n');
},
const formattedPrompts = promptArray.map((p) => wrapAnsi(p, 76, { hard: true, wordWrap: true }));
this.displayBox(formattedPrompts, {
borderColor: 'yellow',
borderStyle: 'double',
});
/**
* Display module with no custom configuration
* @param {string} moduleName - Module name (fallback if no custom header)
* @param {string} header - Custom header from install-config.yaml
* @param {string} subheader - Custom subheader from install-config.yaml
*/
displayModuleNoConfig(moduleName, header = null, subheader = null) {
// Show full banner with header/subheader, just like modules with config
console.log('\n' + chalk.cyan('─'.repeat(80)));
console.log(chalk.cyan(header || `${moduleName.toUpperCase()} Module - No Custom Configuration`));
if (subheader) {
console.log(chalk.dim(`${subheader}`));
}
console.log(chalk.cyan('─'.repeat(80)) + '\n');
},
/**
@@ -164,44 +199,11 @@ const CLIUtils = {
/**
* Display module completion message
* @param {string} moduleName - Name of the completed module
* @param {boolean} clearScreen - Whether to clear the screen first
* @param {boolean} clearScreen - Whether to clear the screen first (deprecated, always false now)
*/
displayModuleComplete(moduleName, clearScreen = true) {
if (clearScreen) {
console.clear();
this.displayLogo();
}
let message;
// Special messages for specific modules
if (moduleName.toLowerCase() === 'bmm') {
message = `Thank you for configuring the BMAD™ Method Module (BMM)!
Your responses have been saved and will be used to configure your installation.`;
} else if (moduleName.toLowerCase() === 'cis') {
message = `Thank you for choosing the BMAD™ Creative Innovation Suite, an early beta
release with much more planned!
With this BMAD™ Creative Innovation Suite Configuration, remember that all
paths are relative to project root, with no leading slash.`;
} else if (moduleName.toLowerCase() === 'core') {
message = `Thank you for choosing the BMAD™ Method, your gateway to dreaming, planning
and building with real world proven techniques.
All paths are relative to project root, with no leading slash.`;
} else {
message = `Thank you for configuring the BMAD™ ${moduleName.toUpperCase()} module!
Your responses have been saved and will be used to configure your installation.`;
}
this.displayBox(message, {
borderColor: 'yellow',
borderStyle: 'double',
padding: 1,
margin: 1,
});
displayModuleComplete(moduleName, clearScreen = false) {
// No longer clear screen or show boxes - just a simple completion message
// This is deprecated but kept for backwards compatibility
},
};

View File

@@ -17,7 +17,8 @@ class UI {
*/
async promptInstall() {
CLIUtils.displayLogo();
CLIUtils.displaySection('BMAD™ Setup', 'Build More, Architect Dreams');
const version = CLIUtils.getVersion();
CLIUtils.displaySection('BMAD™ Setup', `Build More, Architect Dreams v${version}`);
const confirmedDirectory = await this.getConfirmedDirectory();
@@ -102,9 +103,7 @@ class UI {
// This allows text-based prompts to complete before the checkbox prompt
const toolSelection = await this.promptToolSelection(confirmedDirectory, selectedModules);
console.clear();
CLIUtils.displayLogo();
CLIUtils.displayModuleComplete('core', false); // false = don't clear the screen again
// No more screen clearing - keep output flowing
return {
actionType: actionType || 'update', // Preserve reinstall or update action