refactor: convert cursor, windsurf, crush, and github-copilot to flat structure
This commit is contained in:
parent
a07d2b7540
commit
4cb0669a9c
|
|
@ -4,10 +4,11 @@ const { BaseIdeSetup } = require('./_base-ide');
|
|||
const chalk = require('chalk');
|
||||
const { AgentCommandGenerator } = require('./shared/agent-command-generator');
|
||||
const { WorkflowCommandGenerator } = require('./shared/workflow-command-generator');
|
||||
const { getTasksFromBmad, getToolsFromBmad } = require('./shared/bmad-artifacts');
|
||||
|
||||
/**
|
||||
* Crush IDE setup handler
|
||||
* Creates commands in .crush/commands/ directory structure
|
||||
* Installs BMAD artifacts to .crush/commands with flattened naming
|
||||
*/
|
||||
class CrushSetup extends BaseIdeSetup {
|
||||
constructor() {
|
||||
|
|
@ -25,123 +26,131 @@ class CrushSetup extends BaseIdeSetup {
|
|||
async setup(projectDir, bmadDir, options = {}) {
|
||||
console.log(chalk.cyan(`Setting up ${this.name}...`));
|
||||
|
||||
// Create .crush/commands/bmad directory structure
|
||||
// Create .crush/commands directory
|
||||
const crushDir = path.join(projectDir, this.configDir);
|
||||
const commandsDir = path.join(crushDir, this.commandsDir, 'bmad');
|
||||
const commandsDir = path.join(crushDir, this.commandsDir);
|
||||
|
||||
await this.ensureDir(commandsDir);
|
||||
|
||||
// Generate agent launchers
|
||||
const agentGen = new AgentCommandGenerator(this.bmadFolderName);
|
||||
const { artifacts: agentArtifacts } = await agentGen.collectAgentArtifacts(bmadDir, options.selectedModules || []);
|
||||
// Clear old BMAD files
|
||||
await this.clearBmadPrefixedFiles(commandsDir);
|
||||
|
||||
// Get tasks, tools, and workflows (ALL workflows now generate commands)
|
||||
const tasks = await this.getTasks(bmadDir, true);
|
||||
const tools = await this.getTools(bmadDir, true);
|
||||
// Collect all artifacts
|
||||
const { artifacts, counts } = await this.collectCrushArtifacts(projectDir, bmadDir, options);
|
||||
|
||||
// Get ALL workflows using the new workflow command generator
|
||||
const workflowGenerator = new WorkflowCommandGenerator(this.bmadFolderName);
|
||||
const { artifacts: workflowArtifacts, counts: workflowCounts } = await workflowGenerator.collectWorkflowArtifacts(bmadDir);
|
||||
|
||||
// Convert workflow artifacts to expected format for organizeByModule
|
||||
const workflows = workflowArtifacts
|
||||
.filter((artifact) => artifact.type === 'workflow-command')
|
||||
.map((artifact) => ({
|
||||
module: artifact.module,
|
||||
name: path.basename(artifact.relativePath, '.md'),
|
||||
path: artifact.sourcePath,
|
||||
content: artifact.content,
|
||||
}));
|
||||
|
||||
// Organize by module
|
||||
const agentCount = await this.organizeByModule(commandsDir, agentArtifacts, tasks, tools, workflows, projectDir);
|
||||
// Write flattened files
|
||||
const written = await this.writeFlattenedArtifacts(artifacts, commandsDir);
|
||||
|
||||
console.log(chalk.green(`✓ ${this.name} configured:`));
|
||||
console.log(chalk.dim(` - ${agentCount.agents} agent commands created`));
|
||||
console.log(chalk.dim(` - ${agentCount.tasks} task commands created`));
|
||||
console.log(chalk.dim(` - ${agentCount.tools} tool commands created`));
|
||||
console.log(chalk.dim(` - ${agentCount.workflows} workflow commands created`));
|
||||
console.log(chalk.dim(` - Commands directory: ${path.relative(projectDir, commandsDir)}`));
|
||||
console.log(chalk.dim('\n Commands can be accessed via Crush command palette'));
|
||||
console.log(chalk.dim(` - ${counts.agents} agent commands created`));
|
||||
console.log(chalk.dim(` - ${counts.tasks} task commands created`));
|
||||
console.log(chalk.dim(` - ${counts.tools} tool commands created`));
|
||||
console.log(chalk.dim(` - ${counts.workflows} workflow commands created`));
|
||||
if (counts.workflowLaunchers > 0) {
|
||||
console.log(chalk.dim(` - ${counts.workflowLaunchers} workflow launchers created`));
|
||||
}
|
||||
console.log(chalk.dim(` - ${written} files written to ${path.relative(projectDir, commandsDir)}`));
|
||||
|
||||
// Usage instructions
|
||||
console.log(chalk.yellow('\n ⚠️ How to Use Crush Commands'));
|
||||
console.log(chalk.cyan(' BMAD commands are available via the Crush command palette'));
|
||||
console.log(chalk.dim(' Usage:'));
|
||||
console.log(chalk.dim(' - All BMAD items start with "bmad-"'));
|
||||
console.log(chalk.dim(' - Example: /bmad-bmm-agents-pm'));
|
||||
|
||||
return {
|
||||
success: true,
|
||||
...agentCount,
|
||||
agents: counts.agents,
|
||||
tasks: counts.tasks,
|
||||
tools: counts.tools,
|
||||
workflows: counts.workflows,
|
||||
workflowLaunchers: counts.workflowLaunchers,
|
||||
written,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Organize commands by module
|
||||
* Detect Crush installation by checking for .crush/commands directory
|
||||
*/
|
||||
async organizeByModule(commandsDir, agentArtifacts, tasks, tools, workflows, projectDir) {
|
||||
// Get unique modules
|
||||
const modules = new Set();
|
||||
for (const artifact of agentArtifacts) modules.add(artifact.module);
|
||||
for (const task of tasks) modules.add(task.module);
|
||||
for (const tool of tools) modules.add(tool.module);
|
||||
for (const workflow of workflows) modules.add(workflow.module);
|
||||
async detect(projectDir) {
|
||||
const commandsDir = path.join(projectDir, this.configDir, this.commandsDir);
|
||||
|
||||
let agentCount = 0;
|
||||
let taskCount = 0;
|
||||
let toolCount = 0;
|
||||
let workflowCount = 0;
|
||||
|
||||
// Create module directories
|
||||
for (const module of modules) {
|
||||
const moduleDir = path.join(commandsDir, module);
|
||||
const moduleAgentsDir = path.join(moduleDir, 'agents');
|
||||
const moduleTasksDir = path.join(moduleDir, 'tasks');
|
||||
const moduleToolsDir = path.join(moduleDir, 'tools');
|
||||
const moduleWorkflowsDir = path.join(moduleDir, 'workflows');
|
||||
|
||||
await this.ensureDir(moduleAgentsDir);
|
||||
await this.ensureDir(moduleTasksDir);
|
||||
await this.ensureDir(moduleToolsDir);
|
||||
await this.ensureDir(moduleWorkflowsDir);
|
||||
|
||||
// Write module-specific agent launchers
|
||||
const moduleAgents = agentArtifacts.filter((a) => a.module === module);
|
||||
for (const artifact of moduleAgents) {
|
||||
const targetPath = path.join(moduleAgentsDir, `${artifact.name}.md`);
|
||||
await this.writeFile(targetPath, artifact.content);
|
||||
agentCount++;
|
||||
}
|
||||
|
||||
// Copy module-specific tasks
|
||||
const moduleTasks = tasks.filter((t) => t.module === module);
|
||||
for (const task of moduleTasks) {
|
||||
const content = await this.readFile(task.path);
|
||||
const commandContent = this.createTaskCommand(task, content);
|
||||
const targetPath = path.join(moduleTasksDir, `${task.name}.md`);
|
||||
await this.writeFile(targetPath, commandContent);
|
||||
taskCount++;
|
||||
}
|
||||
|
||||
// Copy module-specific tools
|
||||
const moduleTools = tools.filter((t) => t.module === module);
|
||||
for (const tool of moduleTools) {
|
||||
const content = await this.readFile(tool.path);
|
||||
const commandContent = this.createToolCommand(tool, content);
|
||||
const targetPath = path.join(moduleToolsDir, `${tool.name}.md`);
|
||||
await this.writeFile(targetPath, commandContent);
|
||||
toolCount++;
|
||||
}
|
||||
|
||||
// Copy module-specific workflow commands (already generated)
|
||||
const moduleWorkflows = workflows.filter((w) => w.module === module);
|
||||
for (const workflow of moduleWorkflows) {
|
||||
// Use the pre-generated workflow command content
|
||||
const targetPath = path.join(moduleWorkflowsDir, `${workflow.name}.md`);
|
||||
await this.writeFile(targetPath, workflow.content);
|
||||
workflowCount++;
|
||||
}
|
||||
if (!(await fs.pathExists(commandsDir))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const entries = await fs.readdir(commandsDir);
|
||||
return entries.some((entry) => entry.startsWith('bmad-'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Collect all artifacts for Crush export
|
||||
*/
|
||||
async collectCrushArtifacts(projectDir, bmadDir, options = {}) {
|
||||
const selectedModules = options.selectedModules || [];
|
||||
const artifacts = [];
|
||||
|
||||
// Generate agent launchers
|
||||
const agentGen = new AgentCommandGenerator(this.bmadFolderName);
|
||||
const { artifacts: agentArtifacts } = await agentGen.collectAgentArtifacts(bmadDir, selectedModules);
|
||||
|
||||
// Process agent launchers
|
||||
for (const agentArtifact of agentArtifacts) {
|
||||
artifacts.push({
|
||||
type: 'agent',
|
||||
module: agentArtifact.module,
|
||||
sourcePath: agentArtifact.sourcePath,
|
||||
relativePath: agentArtifact.relativePath,
|
||||
content: agentArtifact.content,
|
||||
});
|
||||
}
|
||||
|
||||
// Get tasks
|
||||
const tasks = await getTasksFromBmad(bmadDir, selectedModules);
|
||||
for (const task of tasks) {
|
||||
const rawContent = await this.readFile(task.path);
|
||||
const content = this.createTaskCommand(task, rawContent);
|
||||
|
||||
artifacts.push({
|
||||
type: 'task',
|
||||
module: task.module,
|
||||
sourcePath: task.path,
|
||||
relativePath: path.join(task.module, 'tasks', `${task.name}.md`),
|
||||
content,
|
||||
});
|
||||
}
|
||||
|
||||
// Get tools
|
||||
const tools = await getToolsFromBmad(bmadDir, selectedModules);
|
||||
for (const tool of tools) {
|
||||
const rawContent = await this.readFile(tool.path);
|
||||
const content = this.createToolCommand(tool, rawContent);
|
||||
|
||||
artifacts.push({
|
||||
type: 'tool',
|
||||
module: tool.module,
|
||||
sourcePath: tool.path,
|
||||
relativePath: path.join(tool.module, 'tools', `${tool.name}.md`),
|
||||
content,
|
||||
});
|
||||
}
|
||||
|
||||
// Get workflows
|
||||
const workflowGenerator = new WorkflowCommandGenerator(this.bmadFolderName);
|
||||
const { artifacts: workflowArtifacts, counts: workflowCounts } = await workflowGenerator.collectWorkflowArtifacts(bmadDir);
|
||||
|
||||
// Add workflow artifacts
|
||||
artifacts.push(...workflowArtifacts);
|
||||
|
||||
return {
|
||||
agents: agentCount,
|
||||
tasks: taskCount,
|
||||
tools: toolCount,
|
||||
workflows: workflowCount,
|
||||
artifacts,
|
||||
counts: {
|
||||
agents: agentArtifacts.length,
|
||||
tasks: tasks.length,
|
||||
tools: tools.length,
|
||||
workflows: workflowCounts.commands,
|
||||
workflowLaunchers: workflowCounts.launchers,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -153,7 +162,7 @@ class CrushSetup extends BaseIdeSetup {
|
|||
const nameMatch = content.match(/name="([^"]+)"/);
|
||||
const taskName = nameMatch ? nameMatch[1] : this.formatTitle(task.name);
|
||||
|
||||
let commandContent = `# /task-${task.name} Command
|
||||
return `# /task-${task.name} Command
|
||||
|
||||
When this command is used, execute the following task:
|
||||
|
||||
|
|
@ -169,8 +178,6 @@ This command executes the ${taskName} task from the BMAD ${task.module.toUpperCa
|
|||
|
||||
Part of the BMAD ${task.module.toUpperCase()} module.
|
||||
`;
|
||||
|
||||
return commandContent;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -181,7 +188,7 @@ Part of the BMAD ${task.module.toUpperCase()} module.
|
|||
const nameMatch = content.match(/name="([^"]+)"/);
|
||||
const toolName = nameMatch ? nameMatch[1] : this.formatTitle(tool.name);
|
||||
|
||||
let commandContent = `# /tool-${tool.name} Command
|
||||
return `# /tool-${tool.name} Command
|
||||
|
||||
When this command is used, execute the following tool:
|
||||
|
||||
|
|
@ -197,56 +204,18 @@ This command executes the ${toolName} tool from the BMAD ${tool.module.toUpperCa
|
|||
|
||||
Part of the BMAD ${tool.module.toUpperCase()} module.
|
||||
`;
|
||||
|
||||
return commandContent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create workflow command content
|
||||
*/
|
||||
createWorkflowCommand(workflow, content) {
|
||||
const workflowName = workflow.name ? this.formatTitle(workflow.name) : 'Workflow';
|
||||
|
||||
let commandContent = `# /${workflow.name} Command
|
||||
|
||||
When this command is used, execute the following workflow:
|
||||
|
||||
## ${workflowName} Workflow
|
||||
|
||||
${content}
|
||||
|
||||
## Command Usage
|
||||
|
||||
This command executes the ${workflowName} workflow from the BMAD ${workflow.module.toUpperCase()} module.
|
||||
|
||||
## Module
|
||||
|
||||
Part of the BMAD ${workflow.module.toUpperCase()} module.
|
||||
`;
|
||||
|
||||
return commandContent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format name as title
|
||||
*/
|
||||
formatTitle(name) {
|
||||
return name
|
||||
.split('-')
|
||||
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
|
||||
.join(' ');
|
||||
}
|
||||
// Uses inherited flattenFilename(), writeFlattenedArtifacts(), and clearBmadPrefixedFiles() from BaseIdeSetup
|
||||
|
||||
/**
|
||||
* Cleanup Crush configuration
|
||||
*/
|
||||
async cleanup(projectDir) {
|
||||
const fs = require('fs-extra');
|
||||
const bmadCommandsDir = path.join(projectDir, this.configDir, this.commandsDir, 'bmad');
|
||||
|
||||
if (await fs.pathExists(bmadCommandsDir)) {
|
||||
await fs.remove(bmadCommandsDir);
|
||||
console.log(chalk.dim(`Removed BMAD commands from Crush`));
|
||||
const commandsDir = path.join(projectDir, this.configDir, this.commandsDir);
|
||||
const removedCount = await this.clearBmadPrefixedFiles(commandsDir);
|
||||
if (removedCount > 0) {
|
||||
console.log(chalk.dim(` Removed ${removedCount} old BMAD items from ${this.name}`));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -259,11 +228,13 @@ Part of the BMAD ${workflow.module.toUpperCase()} module.
|
|||
* @returns {Object} Installation result
|
||||
*/
|
||||
async installCustomAgentLauncher(projectDir, agentName, agentPath, metadata) {
|
||||
const crushDir = path.join(projectDir, this.configDir);
|
||||
const bmadCommandsDir = path.join(crushDir, this.commandsDir, 'bmad');
|
||||
const commandsDir = path.join(projectDir, this.configDir, this.commandsDir);
|
||||
|
||||
// Create .crush/commands/bmad directory if it doesn't exist
|
||||
await fs.ensureDir(bmadCommandsDir);
|
||||
if (!(await this.exists(path.join(projectDir, this.configDir)))) {
|
||||
return null; // IDE not configured for this project
|
||||
}
|
||||
|
||||
await this.ensureDir(commandsDir);
|
||||
|
||||
// Create custom agent launcher
|
||||
const launcherContent = `# ${agentName} Custom Agent
|
||||
|
|
@ -282,8 +253,8 @@ The agent will follow the persona and instructions from the main agent file.
|
|||
|
||||
*Generated by BMAD Method*`;
|
||||
|
||||
const fileName = `custom-${agentName.toLowerCase()}.md`;
|
||||
const launcherPath = path.join(bmadCommandsDir, fileName);
|
||||
const fileName = `bmad-custom-agents-${agentName.toLowerCase()}.md`;
|
||||
const launcherPath = path.join(commandsDir, fileName);
|
||||
|
||||
// Write the launcher file
|
||||
await fs.writeFile(launcherPath, launcherContent, 'utf8');
|
||||
|
|
@ -291,7 +262,7 @@ The agent will follow the persona and instructions from the main agent file.
|
|||
return {
|
||||
ide: 'crush',
|
||||
path: path.relative(projectDir, launcherPath),
|
||||
command: agentName,
|
||||
command: `bmad-custom-agents-${agentName}`,
|
||||
type: 'custom-agent-launcher',
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,14 @@
|
|||
const path = require('node:path');
|
||||
const fs = require('fs-extra');
|
||||
const { BaseIdeSetup } = require('./_base-ide');
|
||||
const chalk = require('chalk');
|
||||
const { AgentCommandGenerator } = require('./shared/agent-command-generator');
|
||||
const { WorkflowCommandGenerator } = require('./shared/workflow-command-generator');
|
||||
const { getTasksFromBmad, getToolsFromBmad } = require('./shared/bmad-artifacts');
|
||||
|
||||
/**
|
||||
* Cursor IDE setup handler
|
||||
* Installs BMAD artifacts to .cursor/rules with flattened naming and MDC format
|
||||
*/
|
||||
class CursorSetup extends BaseIdeSetup {
|
||||
constructor() {
|
||||
|
|
@ -14,20 +17,6 @@ class CursorSetup extends BaseIdeSetup {
|
|||
this.rulesDir = 'rules';
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleanup old BMAD installation before reinstalling
|
||||
* @param {string} projectDir - Project directory
|
||||
*/
|
||||
async cleanup(projectDir) {
|
||||
const fs = require('fs-extra');
|
||||
const bmadRulesDir = path.join(projectDir, this.configDir, this.rulesDir, 'bmad');
|
||||
|
||||
if (await fs.pathExists(bmadRulesDir)) {
|
||||
await fs.remove(bmadRulesDir);
|
||||
console.log(chalk.dim(` Removed old BMAD rules from ${this.name}`));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup Cursor IDE configuration
|
||||
* @param {string} projectDir - Project directory
|
||||
|
|
@ -37,138 +26,216 @@ class CursorSetup extends BaseIdeSetup {
|
|||
async setup(projectDir, bmadDir, options = {}) {
|
||||
console.log(chalk.cyan(`Setting up ${this.name}...`));
|
||||
|
||||
// Clean up old BMAD installation first
|
||||
await this.cleanup(projectDir);
|
||||
|
||||
// Create .cursor/rules directory structure
|
||||
// Create .cursor/rules directory
|
||||
const cursorDir = path.join(projectDir, this.configDir);
|
||||
const rulesDir = path.join(cursorDir, this.rulesDir);
|
||||
const bmadRulesDir = path.join(rulesDir, 'bmad');
|
||||
|
||||
await this.ensureDir(bmadRulesDir);
|
||||
await this.ensureDir(rulesDir);
|
||||
|
||||
// Generate agent launchers first
|
||||
const agentGen = new AgentCommandGenerator(this.bmadFolderName);
|
||||
const { artifacts: agentArtifacts } = await agentGen.collectAgentArtifacts(bmadDir, options.selectedModules || []);
|
||||
// Clear old BMAD files
|
||||
await this.clearBmadPrefixedFiles(rulesDir);
|
||||
|
||||
// Convert artifacts to agent format for index creation
|
||||
const agents = agentArtifacts.map((a) => ({ module: a.module, name: a.name }));
|
||||
// Collect all artifacts
|
||||
const { artifacts, counts } = await this.collectCursorArtifacts(projectDir, bmadDir, options);
|
||||
|
||||
// Get tasks, tools, and workflows (ALL workflows now generate commands)
|
||||
const tasks = await this.getTasks(bmadDir, true);
|
||||
const tools = await this.getTools(bmadDir, true);
|
||||
// Write flattened files
|
||||
const written = await this.writeFlattenedArtifacts(artifacts, rulesDir);
|
||||
|
||||
// Get ALL workflows using the new workflow command generator
|
||||
const workflowGenerator = new WorkflowCommandGenerator(this.bmadFolderName);
|
||||
const { artifacts: workflowArtifacts, counts: workflowCounts } = await workflowGenerator.collectWorkflowArtifacts(bmadDir);
|
||||
|
||||
// Convert artifacts to workflow objects for directory creation
|
||||
const workflows = workflowArtifacts
|
||||
.filter((artifact) => artifact.type === 'workflow-command')
|
||||
.map((artifact) => ({
|
||||
module: artifact.module,
|
||||
name: path.basename(artifact.relativePath, '.md'),
|
||||
path: artifact.sourcePath,
|
||||
}));
|
||||
|
||||
// Create directories for each module
|
||||
const modules = new Set();
|
||||
for (const item of [...agents, ...tasks, ...tools, ...workflows]) modules.add(item.module);
|
||||
|
||||
for (const module of modules) {
|
||||
await this.ensureDir(path.join(bmadRulesDir, module));
|
||||
await this.ensureDir(path.join(bmadRulesDir, module, 'agents'));
|
||||
await this.ensureDir(path.join(bmadRulesDir, module, 'tasks'));
|
||||
await this.ensureDir(path.join(bmadRulesDir, module, 'tools'));
|
||||
await this.ensureDir(path.join(bmadRulesDir, module, 'workflows'));
|
||||
}
|
||||
|
||||
// Process and write agent launchers with MDC format
|
||||
let agentCount = 0;
|
||||
for (const artifact of agentArtifacts) {
|
||||
// Add MDC metadata header to launcher (but don't call processContent which adds activation headers)
|
||||
const content = this.wrapLauncherWithMDC(artifact.content, {
|
||||
module: artifact.module,
|
||||
name: artifact.name,
|
||||
});
|
||||
|
||||
const targetPath = path.join(bmadRulesDir, artifact.module, 'agents', `${artifact.name}.mdc`);
|
||||
|
||||
await this.writeFile(targetPath, content);
|
||||
agentCount++;
|
||||
}
|
||||
|
||||
// Process and copy tasks
|
||||
let taskCount = 0;
|
||||
for (const task of tasks) {
|
||||
const content = await this.readAndProcess(task.path, {
|
||||
module: task.module,
|
||||
name: task.name,
|
||||
});
|
||||
|
||||
const targetPath = path.join(bmadRulesDir, task.module, 'tasks', `${task.name}.mdc`);
|
||||
|
||||
await this.writeFile(targetPath, content);
|
||||
taskCount++;
|
||||
}
|
||||
|
||||
// Process and copy tools
|
||||
let toolCount = 0;
|
||||
for (const tool of tools) {
|
||||
const content = await this.readAndProcess(tool.path, {
|
||||
module: tool.module,
|
||||
name: tool.name,
|
||||
});
|
||||
|
||||
const targetPath = path.join(bmadRulesDir, tool.module, 'tools', `${tool.name}.mdc`);
|
||||
|
||||
await this.writeFile(targetPath, content);
|
||||
toolCount++;
|
||||
}
|
||||
|
||||
// Process and copy workflow commands (generated, not raw workflows)
|
||||
let workflowCount = 0;
|
||||
for (const artifact of workflowArtifacts) {
|
||||
if (artifact.type === 'workflow-command') {
|
||||
// Add MDC metadata header to workflow command
|
||||
const content = this.wrapLauncherWithMDC(artifact.content, {
|
||||
module: artifact.module,
|
||||
name: path.basename(artifact.relativePath, '.md'),
|
||||
});
|
||||
|
||||
const targetPath = path.join(bmadRulesDir, artifact.module, 'workflows', `${path.basename(artifact.relativePath, '.md')}.mdc`);
|
||||
|
||||
await this.writeFile(targetPath, content);
|
||||
workflowCount++;
|
||||
}
|
||||
}
|
||||
|
||||
// Create BMAD index file (but NOT .cursorrules - user manages that)
|
||||
await this.createBMADIndex(bmadRulesDir, agents, tasks, tools, workflows, modules);
|
||||
// Create BMAD index file
|
||||
await this.createBMADIndex(rulesDir, counts);
|
||||
|
||||
console.log(chalk.green(`✓ ${this.name} configured:`));
|
||||
console.log(chalk.dim(` - ${agentCount} agents installed`));
|
||||
console.log(chalk.dim(` - ${taskCount} tasks installed`));
|
||||
console.log(chalk.dim(` - ${toolCount} tools installed`));
|
||||
console.log(chalk.dim(` - ${workflowCount} workflows installed`));
|
||||
console.log(chalk.dim(` - Rules directory: ${path.relative(projectDir, bmadRulesDir)}`));
|
||||
console.log(chalk.dim(` - ${counts.agents} agents installed`));
|
||||
console.log(chalk.dim(` - ${counts.tasks} tasks installed`));
|
||||
console.log(chalk.dim(` - ${counts.tools} tools installed`));
|
||||
console.log(chalk.dim(` - ${counts.workflows} workflow commands installed`));
|
||||
if (counts.workflowLaunchers > 0) {
|
||||
console.log(chalk.dim(` - ${counts.workflowLaunchers} workflow launchers installed`));
|
||||
}
|
||||
console.log(chalk.dim(` - ${written} files written to ${path.relative(projectDir, rulesDir)}`));
|
||||
|
||||
// Usage instructions
|
||||
console.log(chalk.yellow('\n ⚠️ How to Use Cursor Rules'));
|
||||
console.log(chalk.cyan(' BMAD rules are available as @ references in Cursor'));
|
||||
console.log(chalk.dim(' Usage:'));
|
||||
console.log(chalk.dim(' - Reference rules with @bmad-{module}-{type}-{name}'));
|
||||
console.log(chalk.dim(' - All BMAD items start with "bmad-"'));
|
||||
console.log(chalk.dim(' - Example: @bmad-bmm-agents-pm'));
|
||||
|
||||
return {
|
||||
success: true,
|
||||
agents: agentCount,
|
||||
tasks: taskCount,
|
||||
tools: toolCount,
|
||||
workflows: workflowCount,
|
||||
agents: counts.agents,
|
||||
tasks: counts.tasks,
|
||||
tools: counts.tools,
|
||||
workflows: counts.workflows,
|
||||
workflowLaunchers: counts.workflowLaunchers,
|
||||
written,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect Cursor installation by checking for .cursor/rules directory
|
||||
*/
|
||||
async detect(projectDir) {
|
||||
const rulesDir = path.join(projectDir, this.configDir, this.rulesDir);
|
||||
|
||||
if (!(await fs.pathExists(rulesDir))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const entries = await fs.readdir(rulesDir);
|
||||
return entries.some((entry) => entry.startsWith('bmad-'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Collect all artifacts for Cursor export
|
||||
*/
|
||||
async collectCursorArtifacts(projectDir, bmadDir, options = {}) {
|
||||
const selectedModules = options.selectedModules || [];
|
||||
const artifacts = [];
|
||||
|
||||
// Generate agent launchers
|
||||
const agentGen = new AgentCommandGenerator(this.bmadFolderName);
|
||||
const { artifacts: agentArtifacts } = await agentGen.collectAgentArtifacts(bmadDir, selectedModules);
|
||||
|
||||
// Process agent launchers with Cursor MDC frontmatter
|
||||
for (const agentArtifact of agentArtifacts) {
|
||||
const content = this.addCursorMDCFrontmatter(agentArtifact.content, {
|
||||
module: agentArtifact.module,
|
||||
name: agentArtifact.name,
|
||||
type: 'agent',
|
||||
});
|
||||
|
||||
artifacts.push({
|
||||
type: 'agent',
|
||||
module: agentArtifact.module,
|
||||
sourcePath: agentArtifact.sourcePath,
|
||||
relativePath: agentArtifact.relativePath.replace(/\.md$/, '.mdc'),
|
||||
content,
|
||||
});
|
||||
}
|
||||
|
||||
// Get tasks
|
||||
const tasks = await getTasksFromBmad(bmadDir, selectedModules);
|
||||
for (const task of tasks) {
|
||||
const rawContent = await this.readFile(task.path);
|
||||
const content = this.addCursorMDCFrontmatter(rawContent, {
|
||||
module: task.module,
|
||||
name: task.name,
|
||||
type: 'task',
|
||||
});
|
||||
|
||||
artifacts.push({
|
||||
type: 'task',
|
||||
module: task.module,
|
||||
sourcePath: task.path,
|
||||
relativePath: path.join(task.module, 'tasks', `${task.name}.mdc`),
|
||||
content,
|
||||
});
|
||||
}
|
||||
|
||||
// Get tools
|
||||
const tools = await getToolsFromBmad(bmadDir, selectedModules);
|
||||
for (const tool of tools) {
|
||||
const rawContent = await this.readFile(tool.path);
|
||||
const content = this.addCursorMDCFrontmatter(rawContent, {
|
||||
module: tool.module,
|
||||
name: tool.name,
|
||||
type: 'tool',
|
||||
});
|
||||
|
||||
artifacts.push({
|
||||
type: 'tool',
|
||||
module: tool.module,
|
||||
sourcePath: tool.path,
|
||||
relativePath: path.join(tool.module, 'tools', `${tool.name}.mdc`),
|
||||
content,
|
||||
});
|
||||
}
|
||||
|
||||
// Get workflows
|
||||
const workflowGenerator = new WorkflowCommandGenerator(this.bmadFolderName);
|
||||
const { artifacts: workflowArtifacts, counts: workflowCounts } = await workflowGenerator.collectWorkflowArtifacts(bmadDir);
|
||||
|
||||
// Process workflow artifacts with Cursor MDC frontmatter
|
||||
for (const artifact of workflowArtifacts) {
|
||||
const content = this.addCursorMDCFrontmatter(artifact.content, {
|
||||
module: artifact.module,
|
||||
name: artifact.name || path.basename(artifact.relativePath, '.md'),
|
||||
type: 'workflow',
|
||||
});
|
||||
|
||||
artifacts.push({
|
||||
...artifact,
|
||||
relativePath: artifact.relativePath.replace(/\.md$/, '.mdc'),
|
||||
content,
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
artifacts,
|
||||
counts: {
|
||||
agents: agentArtifacts.length,
|
||||
tasks: tasks.length,
|
||||
tools: tools.length,
|
||||
workflows: workflowCounts.commands,
|
||||
workflowLaunchers: workflowCounts.launchers,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Add Cursor-specific MDC frontmatter
|
||||
* @param {string} content - Original content
|
||||
* @param {Object} metadata - Artifact metadata (module, name, type)
|
||||
* @returns {string} Content with MDC frontmatter
|
||||
*/
|
||||
addCursorMDCFrontmatter(content, metadata) {
|
||||
// Strip existing frontmatter
|
||||
const frontmatterRegex = /^---\s*\n[\s\S]*?\n---\s*\n/;
|
||||
const contentWithoutFrontmatter = content.replace(frontmatterRegex, '');
|
||||
|
||||
// Build description based on type
|
||||
let description;
|
||||
switch (metadata.type) {
|
||||
case 'agent': {
|
||||
description = `BMAD ${metadata.module.toUpperCase()} Agent: ${this.formatTitle(metadata.name)}`;
|
||||
break;
|
||||
}
|
||||
case 'task': {
|
||||
description = `BMAD ${metadata.module.toUpperCase()} Task: ${this.formatTitle(metadata.name)}`;
|
||||
break;
|
||||
}
|
||||
case 'tool': {
|
||||
description = `BMAD ${metadata.module.toUpperCase()} Tool: ${this.formatTitle(metadata.name)}`;
|
||||
break;
|
||||
}
|
||||
case 'workflow': {
|
||||
description = `BMAD ${metadata.module.toUpperCase()} Workflow: ${this.formatTitle(metadata.name)}`;
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
description = `BMAD ${metadata.module.toUpperCase()}: ${this.formatTitle(metadata.name)}`;
|
||||
}
|
||||
}
|
||||
|
||||
// Create MDC frontmatter
|
||||
return `---
|
||||
description: ${description}
|
||||
globs:
|
||||
alwaysApply: false
|
||||
---
|
||||
|
||||
${contentWithoutFrontmatter}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create BMAD index file for easy navigation
|
||||
*/
|
||||
async createBMADIndex(bmadRulesDir, agents, tasks, tools, workflows, modules) {
|
||||
const indexPath = path.join(bmadRulesDir, 'index.mdc');
|
||||
async createBMADIndex(rulesDir, counts) {
|
||||
const indexPath = path.join(rulesDir, 'bmad-index.mdc');
|
||||
|
||||
let content = `---
|
||||
const content = `---
|
||||
description: BMAD Method - Master Index
|
||||
globs:
|
||||
alwaysApply: true
|
||||
|
|
@ -180,68 +247,25 @@ This is the master index for all BMAD agents, tasks, tools, and workflows availa
|
|||
|
||||
## Installation Complete!
|
||||
|
||||
BMAD rules have been installed to: \`.cursor/rules/bmad/\`
|
||||
BMAD rules have been installed to: \`.cursor/rules/\` with flattened naming.
|
||||
|
||||
**Note:** BMAD does not modify your \`.cursorrules\` file. You manage that separately.
|
||||
|
||||
## How to Use
|
||||
|
||||
- Reference specific agents: @bmad/{module}/agents/{agent-name}
|
||||
- Reference specific tasks: @bmad/{module}/tasks/{task-name}
|
||||
- Reference specific tools: @bmad/{module}/tools/{tool-name}
|
||||
- Reference specific workflows: @bmad/{module}/workflows/{workflow-name}
|
||||
- Reference entire modules: @bmad/{module}
|
||||
- Reference this index: @bmad/index
|
||||
- Reference specific rules: @bmad-{module}-{type}-{name}
|
||||
- Examples:
|
||||
- @bmad-bmm-agents-pm
|
||||
- @bmad-core-tasks-workflow
|
||||
- @bmad-bmm-workflows-create-prd
|
||||
|
||||
## Available Modules
|
||||
## Statistics
|
||||
|
||||
`;
|
||||
- **${counts.agents}** agents installed
|
||||
- **${counts.tasks}** tasks installed
|
||||
- **${counts.tools}** tools installed
|
||||
- **${counts.workflows}** workflow commands installed
|
||||
|
||||
for (const module of modules) {
|
||||
content += `### ${module.toUpperCase()}\n\n`;
|
||||
|
||||
// List agents for this module
|
||||
const moduleAgents = agents.filter((a) => a.module === module);
|
||||
if (moduleAgents.length > 0) {
|
||||
content += `**Agents:**\n`;
|
||||
for (const agent of moduleAgents) {
|
||||
content += `- @bmad/${module}/agents/${agent.name} - ${agent.name}\n`;
|
||||
}
|
||||
content += '\n';
|
||||
}
|
||||
|
||||
// List tasks for this module
|
||||
const moduleTasks = tasks.filter((t) => t.module === module);
|
||||
if (moduleTasks.length > 0) {
|
||||
content += `**Tasks:**\n`;
|
||||
for (const task of moduleTasks) {
|
||||
content += `- @bmad/${module}/tasks/${task.name} - ${task.name}\n`;
|
||||
}
|
||||
content += '\n';
|
||||
}
|
||||
|
||||
// List tools for this module
|
||||
const moduleTools = tools.filter((t) => t.module === module);
|
||||
if (moduleTools.length > 0) {
|
||||
content += `**Tools:**\n`;
|
||||
for (const tool of moduleTools) {
|
||||
content += `- @bmad/${module}/tools/${tool.name} - ${tool.name}\n`;
|
||||
}
|
||||
content += '\n';
|
||||
}
|
||||
|
||||
// List workflows for this module
|
||||
const moduleWorkflows = workflows.filter((w) => w.module === module);
|
||||
if (moduleWorkflows.length > 0) {
|
||||
content += `**Workflows:**\n`;
|
||||
for (const workflow of moduleWorkflows) {
|
||||
content += `- @bmad/${module}/workflows/${workflow.name} - ${workflow.name}\n`;
|
||||
}
|
||||
content += '\n';
|
||||
}
|
||||
}
|
||||
|
||||
content += `
|
||||
## Quick Reference
|
||||
|
||||
- All BMAD rules are Manual type - reference them explicitly when needed
|
||||
|
|
@ -261,107 +285,17 @@ specific agent expertise, task workflows, tools, or guided workflows.
|
|||
await this.writeFile(indexPath, content);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read and process file content
|
||||
*/
|
||||
async readAndProcess(filePath, metadata) {
|
||||
const fs = require('fs-extra');
|
||||
const content = await fs.readFile(filePath, 'utf8');
|
||||
return this.processContent(content, metadata);
|
||||
}
|
||||
// Uses inherited flattenFilename(), writeFlattenedArtifacts(), and clearBmadPrefixedFiles() from BaseIdeSetup
|
||||
|
||||
/**
|
||||
* Override processContent to add MDC metadata header for Cursor
|
||||
* @param {string} content - File content
|
||||
* @param {Object} metadata - File metadata
|
||||
* @returns {string} Processed content with MDC header
|
||||
* Cleanup Cursor configuration
|
||||
*/
|
||||
processContent(content, metadata = {}) {
|
||||
// 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, '');
|
||||
async cleanup(projectDir) {
|
||||
const rulesDir = path.join(projectDir, this.configDir, this.rulesDir);
|
||||
const removedCount = await this.clearBmadPrefixedFiles(rulesDir);
|
||||
if (removedCount > 0) {
|
||||
console.log(chalk.dim(` Removed ${removedCount} old BMAD items from ${this.name}`));
|
||||
}
|
||||
|
||||
// Determine the type and description based on content
|
||||
const isAgent = content.includes('<agent');
|
||||
const isTask = content.includes('<task');
|
||||
const isTool = content.includes('<tool');
|
||||
const isWorkflow = content.includes('workflow:') || content.includes('name:');
|
||||
|
||||
let description = '';
|
||||
let globs = '';
|
||||
|
||||
if (isAgent) {
|
||||
// Extract agent title if available
|
||||
const titleMatch = content.match(/title="([^"]+)"/);
|
||||
const title = titleMatch ? titleMatch[1] : metadata.name;
|
||||
description = `BMAD ${metadata.module.toUpperCase()} Agent: ${title}`;
|
||||
globs = '';
|
||||
} else if (isTask) {
|
||||
// Extract task name if available
|
||||
const nameMatch = content.match(/name="([^"]+)"/);
|
||||
const taskName = nameMatch ? nameMatch[1] : metadata.name;
|
||||
description = `BMAD ${metadata.module.toUpperCase()} Task: ${taskName}`;
|
||||
globs = '';
|
||||
} else if (isTool) {
|
||||
// Extract tool name if available
|
||||
const nameMatch = content.match(/name="([^"]+)"/);
|
||||
const toolName = nameMatch ? nameMatch[1] : metadata.name;
|
||||
description = `BMAD ${metadata.module.toUpperCase()} Tool: ${toolName}`;
|
||||
globs = '';
|
||||
} else if (isWorkflow) {
|
||||
// Workflow
|
||||
description = `BMAD ${metadata.module.toUpperCase()} Workflow: ${metadata.name}`;
|
||||
globs = '';
|
||||
} else {
|
||||
description = `BMAD ${metadata.module.toUpperCase()}: ${metadata.name}`;
|
||||
globs = '';
|
||||
}
|
||||
|
||||
// Create MDC metadata header
|
||||
const mdcHeader = `---
|
||||
description: ${description}
|
||||
globs: ${globs}
|
||||
alwaysApply: false
|
||||
---
|
||||
|
||||
`;
|
||||
|
||||
// Add the MDC header to the processed content
|
||||
return mdcHeader + processed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrap launcher content with MDC metadata (without base processing)
|
||||
* Launchers are already complete and should not have activation headers injected
|
||||
*/
|
||||
wrapLauncherWithMDC(launcherContent, metadata = {}) {
|
||||
// Strip the launcher's frontmatter - we'll replace it with MDC frontmatter
|
||||
const frontmatterRegex = /^---\s*\n[\s\S]*?\n---\s*\n/;
|
||||
const contentWithoutFrontmatter = launcherContent.replace(frontmatterRegex, '');
|
||||
|
||||
// Extract metadata from launcher frontmatter for MDC description
|
||||
const nameMatch = launcherContent.match(/name:\s*"([^"]+)"/);
|
||||
const name = nameMatch ? nameMatch[1] : metadata.name;
|
||||
|
||||
const description = `BMAD ${metadata.module.toUpperCase()} Agent: ${name}`;
|
||||
|
||||
// Create MDC metadata header
|
||||
const mdcHeader = `---
|
||||
description: ${description}
|
||||
globs:
|
||||
alwaysApply: false
|
||||
---
|
||||
|
||||
`;
|
||||
|
||||
// Return MDC header + launcher content (without its original frontmatter)
|
||||
return mdcHeader + contentWithoutFrontmatter;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -373,13 +307,13 @@ alwaysApply: false
|
|||
* @returns {Object|null} Info about created command
|
||||
*/
|
||||
async installCustomAgentLauncher(projectDir, agentName, agentPath, metadata) {
|
||||
const customAgentsDir = path.join(projectDir, this.configDir, this.rulesDir, 'bmad', 'custom', 'agents');
|
||||
const rulesDir = path.join(projectDir, this.configDir, this.rulesDir);
|
||||
|
||||
if (!(await this.exists(path.join(projectDir, this.configDir)))) {
|
||||
return null; // IDE not configured for this project
|
||||
}
|
||||
|
||||
await this.ensureDir(customAgentsDir);
|
||||
await this.ensureDir(rulesDir);
|
||||
|
||||
const launcherContent = `You must fully embody this agent's persona and follow all activation instructions exactly as specified. NEVER break character until given an exit command.
|
||||
|
||||
|
|
@ -403,12 +337,15 @@ alwaysApply: false
|
|||
${launcherContent}
|
||||
`;
|
||||
|
||||
const launcherPath = path.join(customAgentsDir, `${agentName}.mdc`);
|
||||
await this.writeFile(launcherPath, mdcContent);
|
||||
const fileName = `bmad-custom-agents-${agentName}.mdc`;
|
||||
const launcherPath = path.join(rulesDir, fileName);
|
||||
await fs.writeFile(launcherPath, mdcContent);
|
||||
|
||||
return {
|
||||
path: launcherPath,
|
||||
command: `@${agentName}`,
|
||||
ide: 'cursor',
|
||||
path: path.relative(projectDir, launcherPath),
|
||||
command: `@bmad-custom-agents-${agentName}`,
|
||||
type: 'custom-agent-launcher',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
const path = require('node:path');
|
||||
const fs = require('fs-extra');
|
||||
const { BaseIdeSetup } = require('./_base-ide');
|
||||
const chalk = require('chalk');
|
||||
const inquirer = require('inquirer').default || require('inquirer');
|
||||
|
|
@ -6,7 +7,7 @@ const { AgentCommandGenerator } = require('./shared/agent-command-generator');
|
|||
|
||||
/**
|
||||
* GitHub Copilot setup handler
|
||||
* Creates agents in .github/agents/ and configures VS Code settings
|
||||
* Installs BMAD artifacts to .github/agents with flattened naming
|
||||
*/
|
||||
class GitHubCopilotSetup extends BaseIdeSetup {
|
||||
constructor() {
|
||||
|
|
@ -103,45 +104,132 @@ class GitHubCopilotSetup extends BaseIdeSetup {
|
|||
const agentsDir = path.join(githubDir, this.agentsDir);
|
||||
await this.ensureDir(agentsDir);
|
||||
|
||||
// Clean up any existing BMAD files before reinstalling
|
||||
await this.cleanup(projectDir);
|
||||
// Clear old BMAD files
|
||||
await this.clearBmadPrefixedFiles(agentsDir);
|
||||
|
||||
// Generate agent launchers
|
||||
const agentGen = new AgentCommandGenerator(this.bmadFolderName);
|
||||
const { artifacts: agentArtifacts } = await agentGen.collectAgentArtifacts(bmadDir, options.selectedModules || []);
|
||||
// Collect all artifacts
|
||||
const { artifacts, counts } = await this.collectCopilotArtifacts(projectDir, bmadDir, options);
|
||||
|
||||
// Create agent files with bmd- prefix
|
||||
let agentCount = 0;
|
||||
for (const artifact of agentArtifacts) {
|
||||
const content = artifact.content;
|
||||
const agentContent = await this.createAgentContent({ module: artifact.module, name: artifact.name }, content);
|
||||
|
||||
// Use bmd- prefix: bmd-custom-{module}-{name}.agent.md
|
||||
const targetPath = path.join(agentsDir, `bmd-custom-${artifact.module}-${artifact.name}.agent.md`);
|
||||
await this.writeFile(targetPath, agentContent);
|
||||
agentCount++;
|
||||
|
||||
console.log(chalk.green(` ✓ Created agent: bmd-custom-${artifact.module}-${artifact.name}`));
|
||||
}
|
||||
// Write flattened files
|
||||
const written = await this.writeFlattenedArtifacts(artifacts, agentsDir);
|
||||
|
||||
console.log(chalk.green(`✓ ${this.name} configured:`));
|
||||
console.log(chalk.dim(` - ${agentCount} agents created`));
|
||||
console.log(chalk.dim(` - Agents directory: ${path.relative(projectDir, agentsDir)}`));
|
||||
console.log(chalk.dim(` - ${counts.agents} agents created`));
|
||||
console.log(chalk.dim(` - ${written} files written to ${path.relative(projectDir, agentsDir)}`));
|
||||
console.log(chalk.dim(` - VS Code settings configured`));
|
||||
console.log(chalk.dim('\n Agents available in VS Code Chat view'));
|
||||
|
||||
// Usage instructions
|
||||
console.log(chalk.yellow('\n ⚠️ How to Use GitHub Copilot Agents'));
|
||||
console.log(chalk.cyan(' BMAD agents are available in VS Code Chat view'));
|
||||
console.log(chalk.dim(' Usage:'));
|
||||
console.log(chalk.dim(' - All BMAD items start with "bmad-"'));
|
||||
console.log(chalk.dim(' - Example: @bmad-bmm-agents-pm'));
|
||||
|
||||
return {
|
||||
success: true,
|
||||
agents: agentCount,
|
||||
agents: counts.agents,
|
||||
settings: true,
|
||||
written,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect GitHub Copilot installation by checking for .github/agents directory
|
||||
*/
|
||||
async detect(projectDir) {
|
||||
const agentsDir = path.join(projectDir, this.configDir, this.agentsDir);
|
||||
|
||||
if (!(await fs.pathExists(agentsDir))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const entries = await fs.readdir(agentsDir);
|
||||
return entries.some((entry) => entry.startsWith('bmad-'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Collect all artifacts for GitHub Copilot export
|
||||
*/
|
||||
async collectCopilotArtifacts(projectDir, bmadDir, options = {}) {
|
||||
const selectedModules = options.selectedModules || [];
|
||||
const artifacts = [];
|
||||
|
||||
// Generate agent launchers
|
||||
const agentGen = new AgentCommandGenerator(this.bmadFolderName);
|
||||
const { artifacts: agentArtifacts } = await agentGen.collectAgentArtifacts(bmadDir, selectedModules);
|
||||
|
||||
// Process agent launchers with GitHub Copilot frontmatter (tools array)
|
||||
for (const agentArtifact of agentArtifacts) {
|
||||
const content = this.addCopilotFrontmatter(agentArtifact.content, {
|
||||
module: agentArtifact.module,
|
||||
name: agentArtifact.name,
|
||||
});
|
||||
|
||||
artifacts.push({
|
||||
type: 'agent',
|
||||
module: agentArtifact.module,
|
||||
sourcePath: agentArtifact.sourcePath,
|
||||
relativePath: agentArtifact.relativePath.replace(/\.md$/, '.agent.md'),
|
||||
content,
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
artifacts,
|
||||
counts: {
|
||||
agents: agentArtifacts.length,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Add GitHub Copilot-specific frontmatter with tools array
|
||||
* @param {string} content - Original content
|
||||
* @param {Object} metadata - Artifact metadata (module, name)
|
||||
* @returns {string} Content with Copilot frontmatter
|
||||
*/
|
||||
addCopilotFrontmatter(content, metadata) {
|
||||
// Strip existing frontmatter
|
||||
const frontmatterRegex = /^---\s*\n[\s\S]*?\n---\s*\n/;
|
||||
const contentWithoutFrontmatter = content.replace(frontmatterRegex, '').trim();
|
||||
|
||||
const title = this.formatTitle(metadata.name);
|
||||
const description = `Activates the ${title} agent persona.`;
|
||||
|
||||
// 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 = [
|
||||
'changes', // List of source control changes
|
||||
'edit', // Edit files in your workspace including: createFile, createDirectory, editNotebook, newJupyterNotebook and editFiles
|
||||
'fetch', // Fetch content from web page
|
||||
'githubRepo', // Perform code search in GitHub repo
|
||||
'problems', // Add workspace issues from Problems panel
|
||||
'runCommands', // Runs commands in the terminal including: getTerminalOutput, terminalSelection, terminalLastCommand and runInTerminal
|
||||
'runTasks', // Runs tasks and gets their output for your workspace
|
||||
'runTests', // Run unit tests in workspace
|
||||
'search', // Search and read files in your workspace, including:fileSearch, textSearch, listDirectory, readFile, codebase and searchResults
|
||||
'runSubagent', // Runs a task within an isolated subagent context. Enables efficient organization of tasks and context window management.
|
||||
'testFailure', // Get unit test failure information
|
||||
'todos', // Tool for managing and tracking todo items for task planning
|
||||
'usages', // Find references and navigate definitions
|
||||
];
|
||||
|
||||
return `---
|
||||
description: "${description.replaceAll('"', String.raw`\"`)}"
|
||||
tools: ${JSON.stringify(tools)}
|
||||
---
|
||||
|
||||
# ${title} Agent
|
||||
|
||||
${contentWithoutFrontmatter}
|
||||
|
||||
`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure VS Code settings for GitHub Copilot
|
||||
*/
|
||||
async configureVsCodeSettings(projectDir, options) {
|
||||
const fs = require('fs-extra');
|
||||
const vscodeDir = path.join(projectDir, this.vscodeDir);
|
||||
const settingsPath = path.join(vscodeDir, 'settings.json');
|
||||
|
||||
|
|
@ -207,72 +295,15 @@ class GitHubCopilotSetup extends BaseIdeSetup {
|
|||
console.log(chalk.green(' ✓ VS Code settings configured'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create agent content
|
||||
*/
|
||||
async createAgentContent(agent, content) {
|
||||
// Extract metadata from launcher frontmatter if present
|
||||
const descMatch = content.match(/description:\s*"([^"]+)"/);
|
||||
const title = descMatch ? descMatch[1] : this.formatTitle(agent.name);
|
||||
|
||||
const description = `Activates the ${title} agent persona.`;
|
||||
|
||||
// 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, '').trim();
|
||||
}
|
||||
|
||||
// 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 = [
|
||||
'changes', // List of source control changes
|
||||
'edit', // Edit files in your workspace including: createFile, createDirectory, editNotebook, newJupyterNotebook and editFiles
|
||||
'fetch', // Fetch content from web page
|
||||
'githubRepo', // Perform code search in GitHub repo
|
||||
'problems', // Add workspace issues from Problems panel
|
||||
'runCommands', // Runs commands in the terminal including: getTerminalOutput, terminalSelection, terminalLastCommand and runInTerminal
|
||||
'runTasks', // Runs tasks and gets their output for your workspace
|
||||
'runTests', // Run unit tests in workspace
|
||||
'search', // Search and read files in your workspace, including:fileSearch, textSearch, listDirectory, readFile, codebase and searchResults
|
||||
'runSubagent', // Runs a task within an isolated subagent context. Enables efficient organization of tasks and context window management.
|
||||
'testFailure', // Get unit test failure information
|
||||
'todos', // Tool for managing and tracking todo items for task planning
|
||||
'usages', // Find references and navigate definitions
|
||||
];
|
||||
|
||||
let agentContent = `---
|
||||
description: "${description.replaceAll('"', String.raw`\"`)}"
|
||||
tools: ${JSON.stringify(tools)}
|
||||
---
|
||||
|
||||
# ${title} Agent
|
||||
|
||||
${cleanContent}
|
||||
|
||||
`;
|
||||
|
||||
return agentContent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format name as title
|
||||
*/
|
||||
formatTitle(name) {
|
||||
return name
|
||||
.split('-')
|
||||
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
|
||||
.join(' ');
|
||||
}
|
||||
// Uses inherited flattenFilename(), writeFlattenedArtifacts(), and clearBmadPrefixedFiles() from BaseIdeSetup
|
||||
|
||||
/**
|
||||
* Cleanup GitHub Copilot configuration - surgically remove only BMAD files
|
||||
*/
|
||||
async cleanup(projectDir) {
|
||||
const fs = require('fs-extra');
|
||||
const agentsDir = path.join(projectDir, this.configDir, this.agentsDir);
|
||||
|
||||
// Clean up old chatmodes directory
|
||||
// Clean up old chatmodes directory (legacy)
|
||||
const chatmodesDir = path.join(projectDir, this.configDir, 'chatmodes');
|
||||
if (await fs.pathExists(chatmodesDir)) {
|
||||
const files = await fs.readdir(chatmodesDir);
|
||||
|
|
@ -291,21 +322,9 @@ ${cleanContent}
|
|||
}
|
||||
|
||||
// Clean up new agents directory
|
||||
const agentsDir = path.join(projectDir, this.configDir, this.agentsDir);
|
||||
if (await fs.pathExists(agentsDir)) {
|
||||
const files = await fs.readdir(agentsDir);
|
||||
let removed = 0;
|
||||
|
||||
for (const file of files) {
|
||||
if (file.startsWith('bmd-') && file.endsWith('.agent.md')) {
|
||||
await fs.remove(path.join(agentsDir, file));
|
||||
removed++;
|
||||
}
|
||||
}
|
||||
|
||||
if (removed > 0) {
|
||||
console.log(chalk.dim(` Cleaned up ${removed} existing BMAD agents`));
|
||||
}
|
||||
const removedCount = await this.clearBmadPrefixedFiles(agentsDir);
|
||||
if (removedCount > 0) {
|
||||
console.log(chalk.dim(` Cleaned up ${removedCount} existing BMAD agents`));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -374,12 +393,15 @@ tools: ${JSON.stringify(copilotTools)}
|
|||
${launcherContent}
|
||||
`;
|
||||
|
||||
const agentFilePath = path.join(agentsDir, `bmd-custom-${agentName}.agent.md`);
|
||||
await this.writeFile(agentFilePath, agentContent);
|
||||
const fileName = `bmad-custom-agents-${agentName}.agent.md`;
|
||||
const agentFilePath = path.join(agentsDir, fileName);
|
||||
await fs.writeFile(agentFilePath, agentContent);
|
||||
|
||||
return {
|
||||
path: agentFilePath,
|
||||
command: `bmd-custom-${agentName}`,
|
||||
ide: 'github-copilot',
|
||||
path: path.relative(projectDir, agentFilePath),
|
||||
command: `@bmad-custom-agents-${agentName}`,
|
||||
type: 'custom-agent-launcher',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,14 @@
|
|||
const path = require('node:path');
|
||||
const fs = require('fs-extra');
|
||||
const { BaseIdeSetup } = require('./_base-ide');
|
||||
const chalk = require('chalk');
|
||||
const { AgentCommandGenerator } = require('./shared/agent-command-generator');
|
||||
const { WorkflowCommandGenerator } = require('./shared/workflow-command-generator');
|
||||
const { getTasksFromBmad } = require('./shared/bmad-artifacts');
|
||||
|
||||
/**
|
||||
* Windsurf IDE setup handler
|
||||
* Installs BMAD artifacts to .windsurf/workflows with flattened naming
|
||||
*/
|
||||
class WindsurfSetup extends BaseIdeSetup {
|
||||
constructor() {
|
||||
|
|
@ -22,188 +26,186 @@ class WindsurfSetup extends BaseIdeSetup {
|
|||
async setup(projectDir, bmadDir, options = {}) {
|
||||
console.log(chalk.cyan(`Setting up ${this.name}...`));
|
||||
|
||||
// Create .windsurf/workflows/bmad directory structure
|
||||
// Create .windsurf/workflows directory
|
||||
const windsurfDir = path.join(projectDir, this.configDir);
|
||||
const workflowsDir = path.join(windsurfDir, this.workflowsDir);
|
||||
const bmadWorkflowsDir = path.join(workflowsDir, 'bmad');
|
||||
|
||||
await this.ensureDir(bmadWorkflowsDir);
|
||||
await this.ensureDir(workflowsDir);
|
||||
|
||||
// Clean up any existing BMAD workflows before reinstalling
|
||||
await this.cleanup(projectDir);
|
||||
// Clear old BMAD files
|
||||
await this.clearBmadPrefixedFiles(workflowsDir);
|
||||
|
||||
// Generate agent launchers
|
||||
const agentGen = new AgentCommandGenerator(this.bmadFolderName);
|
||||
const { artifacts: agentArtifacts } = await agentGen.collectAgentArtifacts(bmadDir, options.selectedModules || []);
|
||||
// Collect all artifacts
|
||||
const { artifacts, counts } = await this.collectWindsurfArtifacts(projectDir, bmadDir, options);
|
||||
|
||||
// Convert artifacts to agent format for module organization
|
||||
const agents = agentArtifacts.map((a) => ({ module: a.module, name: a.name }));
|
||||
|
||||
// Get tasks, tools, and workflows (standalone only)
|
||||
const tasks = await this.getTasks(bmadDir, true);
|
||||
const tools = await this.getTools(bmadDir, true);
|
||||
const workflows = await this.getWorkflows(bmadDir, true);
|
||||
|
||||
// Create directories for each module under bmad/
|
||||
const modules = new Set();
|
||||
for (const item of [...agents, ...tasks, ...tools, ...workflows]) modules.add(item.module);
|
||||
|
||||
for (const module of modules) {
|
||||
await this.ensureDir(path.join(bmadWorkflowsDir, module));
|
||||
await this.ensureDir(path.join(bmadWorkflowsDir, module, 'agents'));
|
||||
await this.ensureDir(path.join(bmadWorkflowsDir, module, 'tasks'));
|
||||
await this.ensureDir(path.join(bmadWorkflowsDir, module, 'tools'));
|
||||
await this.ensureDir(path.join(bmadWorkflowsDir, module, 'workflows'));
|
||||
}
|
||||
|
||||
// Process agent launchers as workflows with organized structure
|
||||
let agentCount = 0;
|
||||
for (const artifact of agentArtifacts) {
|
||||
const processedContent = this.createWorkflowContent({ module: artifact.module, name: artifact.name }, artifact.content);
|
||||
|
||||
// Organized path: bmad/module/agents/agent-name.md
|
||||
const targetPath = path.join(bmadWorkflowsDir, artifact.module, 'agents', `${artifact.name}.md`);
|
||||
await this.writeFile(targetPath, processedContent);
|
||||
agentCount++;
|
||||
}
|
||||
|
||||
// Process tasks as workflows with organized structure
|
||||
let taskCount = 0;
|
||||
for (const task of tasks) {
|
||||
const content = await this.readFile(task.path);
|
||||
const processedContent = this.createTaskWorkflowContent(task, content);
|
||||
|
||||
// Organized path: bmad/module/tasks/task-name.md
|
||||
const targetPath = path.join(bmadWorkflowsDir, task.module, 'tasks', `${task.name}.md`);
|
||||
await this.writeFile(targetPath, processedContent);
|
||||
taskCount++;
|
||||
}
|
||||
|
||||
// Process tools as workflows with organized structure
|
||||
let toolCount = 0;
|
||||
for (const tool of tools) {
|
||||
const content = await this.readFile(tool.path);
|
||||
const processedContent = this.createToolWorkflowContent(tool, content);
|
||||
|
||||
// Organized path: bmad/module/tools/tool-name.md
|
||||
const targetPath = path.join(bmadWorkflowsDir, tool.module, 'tools', `${tool.name}.md`);
|
||||
await this.writeFile(targetPath, processedContent);
|
||||
toolCount++;
|
||||
}
|
||||
|
||||
// Process workflows with organized structure
|
||||
let workflowCount = 0;
|
||||
for (const workflow of workflows) {
|
||||
const content = await this.readFile(workflow.path);
|
||||
const processedContent = this.createWorkflowWorkflowContent(workflow, content);
|
||||
|
||||
// Organized path: bmad/module/workflows/workflow-name.md
|
||||
const targetPath = path.join(bmadWorkflowsDir, workflow.module, 'workflows', `${workflow.name}.md`);
|
||||
await this.writeFile(targetPath, processedContent);
|
||||
workflowCount++;
|
||||
}
|
||||
// Write flattened files
|
||||
const written = await this.writeFlattenedArtifacts(artifacts, workflowsDir);
|
||||
|
||||
console.log(chalk.green(`✓ ${this.name} configured:`));
|
||||
console.log(chalk.dim(` - ${agentCount} agents installed`));
|
||||
console.log(chalk.dim(` - ${taskCount} tasks installed`));
|
||||
console.log(chalk.dim(` - ${toolCount} tools installed`));
|
||||
console.log(chalk.dim(` - ${workflowCount} workflows installed`));
|
||||
console.log(chalk.dim(` - Organized in modules: ${[...modules].join(', ')}`));
|
||||
console.log(chalk.dim(` - Workflows directory: ${path.relative(projectDir, workflowsDir)}`));
|
||||
console.log(chalk.dim(` - ${counts.agents} agents installed`));
|
||||
console.log(chalk.dim(` - ${counts.tasks} tasks installed`));
|
||||
console.log(chalk.dim(` - ${counts.workflows} workflow commands installed`));
|
||||
if (counts.workflowLaunchers > 0) {
|
||||
console.log(chalk.dim(` - ${counts.workflowLaunchers} workflow launchers installed`));
|
||||
}
|
||||
console.log(chalk.dim(` - ${written} files written to ${path.relative(projectDir, workflowsDir)}`));
|
||||
|
||||
// Usage instructions
|
||||
console.log(chalk.yellow('\n ⚠️ How to Use Windsurf Workflows'));
|
||||
console.log(chalk.cyan(' BMAD workflows are available as slash commands in Windsurf'));
|
||||
console.log(chalk.dim(' Usage:'));
|
||||
console.log(chalk.dim(' - Type / to see available commands'));
|
||||
console.log(chalk.dim(' - All BMAD items start with "bmad-"'));
|
||||
console.log(chalk.dim(' - Example: /bmad-bmm-agents-pm'));
|
||||
|
||||
// Provide additional configuration hints
|
||||
if (options.showHints !== false) {
|
||||
console.log(chalk.dim('\n Windsurf workflow settings:'));
|
||||
console.log(chalk.dim(' - auto_execution_mode: 3 (recommended for agents)'));
|
||||
console.log(chalk.dim(' - auto_execution_mode: 2 (recommended for tasks/tools)'));
|
||||
console.log(chalk.dim(' - auto_execution_mode: 1 (recommended for workflows)'));
|
||||
console.log(chalk.dim(' - Workflows can be triggered via the Windsurf menu'));
|
||||
console.log(chalk.dim(' - auto_execution_mode: 3 (for agents)'));
|
||||
console.log(chalk.dim(' - auto_execution_mode: 2 (for tasks)'));
|
||||
console.log(chalk.dim(' - auto_execution_mode: 1 (for workflows)'));
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
agents: agentCount,
|
||||
tasks: taskCount,
|
||||
tools: toolCount,
|
||||
workflows: workflowCount,
|
||||
agents: counts.agents,
|
||||
tasks: counts.tasks,
|
||||
workflows: counts.workflows,
|
||||
workflowLaunchers: counts.workflowLaunchers,
|
||||
written,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create workflow content for an agent
|
||||
* Detect Windsurf installation by checking for .windsurf/workflows directory
|
||||
*/
|
||||
createWorkflowContent(agent, content) {
|
||||
// Strip existing frontmatter from launcher
|
||||
async detect(projectDir) {
|
||||
const workflowsDir = path.join(projectDir, this.configDir, this.workflowsDir);
|
||||
|
||||
if (!(await fs.pathExists(workflowsDir))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const entries = await fs.readdir(workflowsDir);
|
||||
return entries.some((entry) => entry.startsWith('bmad-'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Collect all artifacts for Windsurf export
|
||||
*/
|
||||
async collectWindsurfArtifacts(projectDir, bmadDir, options = {}) {
|
||||
const selectedModules = options.selectedModules || [];
|
||||
const artifacts = [];
|
||||
|
||||
// Generate agent launchers
|
||||
const agentGen = new AgentCommandGenerator(this.bmadFolderName);
|
||||
const { artifacts: agentArtifacts } = await agentGen.collectAgentArtifacts(bmadDir, selectedModules);
|
||||
|
||||
// Process agent launchers with Windsurf frontmatter
|
||||
for (const agentArtifact of agentArtifacts) {
|
||||
const content = this.addWindsurfFrontmatter(agentArtifact.content, agentArtifact.name, 'agent');
|
||||
|
||||
artifacts.push({
|
||||
type: 'agent',
|
||||
module: agentArtifact.module,
|
||||
sourcePath: agentArtifact.sourcePath,
|
||||
relativePath: agentArtifact.relativePath,
|
||||
content,
|
||||
});
|
||||
}
|
||||
|
||||
// Get tasks
|
||||
const tasks = await getTasksFromBmad(bmadDir, selectedModules);
|
||||
for (const task of tasks) {
|
||||
const rawContent = await this.readFile(task.path);
|
||||
const content = this.addWindsurfFrontmatter(rawContent, `task-${task.name}`, 'task');
|
||||
|
||||
artifacts.push({
|
||||
type: 'task',
|
||||
module: task.module,
|
||||
sourcePath: task.path,
|
||||
relativePath: path.join(task.module, 'tasks', `${task.name}.md`),
|
||||
content,
|
||||
});
|
||||
}
|
||||
|
||||
// Get workflows
|
||||
const workflowGenerator = new WorkflowCommandGenerator(this.bmadFolderName);
|
||||
const { artifacts: workflowArtifacts, counts: workflowCounts } = await workflowGenerator.collectWorkflowArtifacts(bmadDir);
|
||||
|
||||
// Process workflow artifacts with Windsurf frontmatter
|
||||
for (const artifact of workflowArtifacts) {
|
||||
const content = this.addWindsurfFrontmatter(
|
||||
artifact.content,
|
||||
artifact.name || path.basename(artifact.relativePath, '.md'),
|
||||
'workflow',
|
||||
);
|
||||
|
||||
artifacts.push({
|
||||
...artifact,
|
||||
content,
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
artifacts,
|
||||
counts: {
|
||||
agents: agentArtifacts.length,
|
||||
tasks: tasks.length,
|
||||
workflows: workflowCounts.commands,
|
||||
workflowLaunchers: workflowCounts.launchers,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Add Windsurf-specific frontmatter with auto_execution_mode
|
||||
* @param {string} content - Original content
|
||||
* @param {string} name - Artifact name for description
|
||||
* @param {string} type - Artifact type (agent, task, workflow)
|
||||
* @returns {string} Content with Windsurf frontmatter
|
||||
*/
|
||||
addWindsurfFrontmatter(content, name, type) {
|
||||
// Strip existing frontmatter
|
||||
const frontmatterRegex = /^---\s*\n[\s\S]*?\n---\s*\n/;
|
||||
const contentWithoutFrontmatter = content.replace(frontmatterRegex, '');
|
||||
|
||||
// Create simple Windsurf frontmatter matching original format
|
||||
let workflowContent = `---
|
||||
description: ${agent.name}
|
||||
auto_execution_mode: 3
|
||||
// Determine auto_execution_mode based on type
|
||||
let mode;
|
||||
switch (type) {
|
||||
case 'agent': {
|
||||
mode = 3;
|
||||
break;
|
||||
}
|
||||
case 'task': {
|
||||
mode = 2;
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
mode = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Create Windsurf frontmatter
|
||||
return `---
|
||||
description: ${name}
|
||||
auto_execution_mode: ${mode}
|
||||
---
|
||||
|
||||
${contentWithoutFrontmatter}`;
|
||||
|
||||
return workflowContent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create workflow content for a task
|
||||
*/
|
||||
createTaskWorkflowContent(task, content) {
|
||||
// Create simple Windsurf frontmatter matching original format
|
||||
let workflowContent = `---
|
||||
description: task-${task.name}
|
||||
auto_execution_mode: 2
|
||||
---
|
||||
|
||||
${content}`;
|
||||
|
||||
return workflowContent;
|
||||
}
|
||||
// Uses inherited flattenFilename(), writeFlattenedArtifacts(), and clearBmadPrefixedFiles() from BaseIdeSetup
|
||||
|
||||
/**
|
||||
* Create workflow content for a tool
|
||||
*/
|
||||
createToolWorkflowContent(tool, content) {
|
||||
// Create simple Windsurf frontmatter matching original format
|
||||
let workflowContent = `---
|
||||
description: tool-${tool.name}
|
||||
auto_execution_mode: 2
|
||||
---
|
||||
|
||||
${content}`;
|
||||
|
||||
return workflowContent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create workflow content for a workflow
|
||||
*/
|
||||
createWorkflowWorkflowContent(workflow, content) {
|
||||
// Create simple Windsurf frontmatter matching original format
|
||||
let workflowContent = `---
|
||||
description: ${workflow.name}
|
||||
auto_execution_mode: 1
|
||||
---
|
||||
|
||||
${content}`;
|
||||
|
||||
return workflowContent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleanup Windsurf configuration - surgically remove only BMAD files
|
||||
* Cleanup Windsurf configuration
|
||||
*/
|
||||
async cleanup(projectDir) {
|
||||
const fs = require('fs-extra');
|
||||
const bmadPath = path.join(projectDir, this.configDir, this.workflowsDir, 'bmad');
|
||||
|
||||
if (await fs.pathExists(bmadPath)) {
|
||||
// Remove the entire bmad folder - this is our territory
|
||||
await fs.remove(bmadPath);
|
||||
console.log(chalk.dim(` Cleaned up existing BMAD workflows`));
|
||||
const workflowsDir = path.join(projectDir, this.configDir, this.workflowsDir);
|
||||
const removedCount = await this.clearBmadPrefixedFiles(workflowsDir);
|
||||
if (removedCount > 0) {
|
||||
console.log(chalk.dim(` Removed ${removedCount} old BMAD items from ${this.name}`));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -216,14 +218,13 @@ ${content}`;
|
|||
* @returns {Object|null} Info about created command
|
||||
*/
|
||||
async installCustomAgentLauncher(projectDir, agentName, agentPath, metadata) {
|
||||
const fs = require('fs-extra');
|
||||
const customAgentsDir = path.join(projectDir, this.configDir, this.workflowsDir, 'bmad', 'custom', 'agents');
|
||||
const workflowsDir = path.join(projectDir, this.configDir, this.workflowsDir);
|
||||
|
||||
if (!(await this.exists(path.join(projectDir, this.configDir)))) {
|
||||
return null; // IDE not configured for this project
|
||||
}
|
||||
|
||||
await this.ensureDir(customAgentsDir);
|
||||
await this.ensureDir(workflowsDir);
|
||||
|
||||
const launcherContent = `You must fully embody this agent's persona and follow all activation instructions exactly as specified. NEVER break character until given an exit command.
|
||||
|
||||
|
|
@ -245,12 +246,15 @@ auto_execution_mode: 3
|
|||
|
||||
${launcherContent}`;
|
||||
|
||||
const launcherPath = path.join(customAgentsDir, `${agentName}.md`);
|
||||
const fileName = `bmad-custom-agents-${agentName}.md`;
|
||||
const launcherPath = path.join(workflowsDir, fileName);
|
||||
await fs.writeFile(launcherPath, workflowContent);
|
||||
|
||||
return {
|
||||
path: launcherPath,
|
||||
command: `bmad/custom/agents/${agentName}`,
|
||||
ide: 'windsurf',
|
||||
path: path.relative(projectDir, launcherPath),
|
||||
command: `bmad-custom-agents-${agentName}`,
|
||||
type: 'custom-agent-launcher',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue