mirror of
https://github.com/bmadcode/BMAD-METHOD.git
synced 2025-12-29 16:14:59 +00:00
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:
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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`));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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`;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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`;
|
||||
|
||||
|
||||
@@ -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 {
|
||||
/**
|
||||
@@ -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]}`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
"""
|
||||
@@ -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}}
|
||||
"""
|
||||
@@ -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>
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user