mirror of
https://github.com/bmad-code-org/BMAD-METHOD.git
synced 2026-01-30 04:32:02 +00:00
- Add .github/prompts directory alongside .github/agents - Use UnifiedInstaller with TemplateType.COPILOT for prompts/workflows/tasks/tools - Fix typo: bmd-custom- -> bmad- prefix for agents - Update cleanup to handle both directories - Format fixes for auggie.js and windsurf.js
245 lines
8.4 KiB
JavaScript
245 lines
8.4 KiB
JavaScript
const path = require('node:path');
|
|
const { BaseIdeSetup } = require('./_base-ide');
|
|
const chalk = require('chalk');
|
|
const { UnifiedInstaller, NamingStyle, TemplateType } = require('./shared/unified-installer');
|
|
const fs = require('fs-extra');
|
|
|
|
/**
|
|
* Windsurf IDE setup handler
|
|
*
|
|
* Uses UnifiedInstaller for consistent artifact collection and writing.
|
|
* Windsurf-specific configuration:
|
|
* - Flat file naming (FLAT_DASH): bmad-bmm-agent-pm.md
|
|
* - Windsurf frontmatter with auto_execution_mode
|
|
*/
|
|
class WindsurfSetup extends BaseIdeSetup {
|
|
constructor() {
|
|
super('windsurf', 'Windsurf', true); // preferred IDE
|
|
this.configDir = '.windsurf';
|
|
this.workflowsDir = 'workflows';
|
|
this.unifiedInstaller = new UnifiedInstaller(this.bmadFolderName);
|
|
}
|
|
|
|
/**
|
|
* Setup Windsurf IDE configuration
|
|
* @param {string} projectDir - Project directory
|
|
* @param {string} bmadDir - BMAD installation directory
|
|
* @param {Object} options - Setup options
|
|
*/
|
|
async setup(projectDir, bmadDir, options = {}) {
|
|
console.log(chalk.cyan(`Setting up ${this.name}...`));
|
|
|
|
// Create .windsurf/workflows directory
|
|
const windsurfDir = path.join(projectDir, this.configDir);
|
|
const workflowsDir = path.join(windsurfDir, this.workflowsDir);
|
|
|
|
await this.ensureDir(workflowsDir);
|
|
|
|
// Clean up any existing BMAD workflows before reinstalling
|
|
await this.cleanup(projectDir);
|
|
|
|
// Use UnifiedInstaller with Windsurf-specific configuration
|
|
const counts = await this.unifiedInstaller.install(
|
|
projectDir,
|
|
bmadDir,
|
|
{
|
|
targetDir: workflowsDir,
|
|
namingStyle: NamingStyle.FLAT_DASH,
|
|
templateType: TemplateType.WINDSURF,
|
|
customTemplateFn: this.windsurfTemplate.bind(this),
|
|
},
|
|
options.selectedModules || [],
|
|
);
|
|
|
|
// Post-process tasks and tools to add Windsurf auto_execution_mode
|
|
// UnifiedInstaller handles agents/workflows correctly, but tasks/tools
|
|
// need special handling for proper Windsurf frontmatter
|
|
await this.addWindsurfTaskToolFrontmatter(workflowsDir);
|
|
|
|
console.log(chalk.green(`✓ ${this.name} configured:`));
|
|
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} workflows installed`));
|
|
console.log(chalk.dim(` - Total: ${counts.total} items`));
|
|
console.log(chalk.dim(` - Workflows directory: ${path.relative(projectDir, workflowsDir)}`));
|
|
|
|
// 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'));
|
|
}
|
|
|
|
return {
|
|
success: true,
|
|
...counts,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Windsurf-specific template function
|
|
* Adds proper Windsurf frontmatter with auto_execution_mode
|
|
*/
|
|
windsurfTemplate(artifact, content, templateType) {
|
|
// Strip existing frontmatter
|
|
const frontmatterRegex = /^---\s*\n[\s\S]*?\n---\s*\n/;
|
|
const contentWithoutFrontmatter = content.replace(frontmatterRegex, '');
|
|
|
|
// Determine auto_execution_mode based on type
|
|
let autoExecMode = '1'; // default for workflows
|
|
let description = artifact.name || artifact.displayName || 'workflow';
|
|
|
|
if (artifact.type === 'agent') {
|
|
autoExecMode = '3';
|
|
description = artifact.name || 'agent';
|
|
} else if (artifact.type === 'workflow') {
|
|
autoExecMode = '1';
|
|
description = artifact.name || 'workflow';
|
|
}
|
|
|
|
return `---
|
|
description: ${description}
|
|
auto_execution_mode: ${autoExecMode}
|
|
---
|
|
|
|
${contentWithoutFrontmatter}`;
|
|
}
|
|
|
|
/**
|
|
* Add Windsurf auto_execution_mode to task and tool files
|
|
* These are generated by TaskToolCommandGenerator with basic YAML
|
|
* but need the Windsurf-specific auto_execution_mode field
|
|
*/
|
|
async addWindsurfTaskToolFrontmatter(workflowsDir) {
|
|
if (!(await fs.pathExists(workflowsDir))) {
|
|
return;
|
|
}
|
|
|
|
const entries = await fs.readdir(workflowsDir, { withFileTypes: true });
|
|
let updatedCount = 0;
|
|
|
|
for (const entry of entries) {
|
|
if (!entry.name.startsWith('bmad-') || !entry.name.endsWith('.md')) {
|
|
continue;
|
|
}
|
|
|
|
const filePath = path.join(workflowsDir, entry.name);
|
|
let content = await fs.readFile(filePath, 'utf8');
|
|
|
|
// Check if this is a task or tool file
|
|
// They have pattern: bmad-module-task-name.md or bmad-module-tool-name.md
|
|
const parts = entry.name.replace('bmad-', '').replace('.md', '').split('-');
|
|
if (parts.length < 2) continue;
|
|
|
|
const type = parts.at(-2); // second to last part should be 'task' or 'tool'
|
|
|
|
if (type === 'task' || type === 'tool') {
|
|
// Check if auto_execution_mode is already present
|
|
if (content.includes('auto_execution_mode')) {
|
|
continue;
|
|
}
|
|
|
|
// Extract existing description if present
|
|
const descMatch = content.match(/description: '(.+?)'/);
|
|
const description = descMatch ? descMatch[1] : entry.name.replace('.md', '');
|
|
|
|
// Strip existing frontmatter and add Windsurf-specific frontmatter
|
|
const frontmatterRegex = /^---\s*\n[\s\S]*?\n---\s*\n/;
|
|
const contentWithoutFrontmatter = content.replace(frontmatterRegex, '');
|
|
|
|
content = `---
|
|
description: '${description}'
|
|
auto_execution_mode: 2
|
|
---
|
|
|
|
${contentWithoutFrontmatter}`;
|
|
|
|
await fs.writeFile(filePath, content, 'utf8');
|
|
updatedCount++;
|
|
}
|
|
}
|
|
|
|
if (updatedCount > 0) {
|
|
console.log(chalk.dim(` Updated ${updatedCount} task/tool files with Windsurf frontmatter`));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Cleanup Windsurf configuration - remove only BMAD files
|
|
*/
|
|
async cleanup(projectDir) {
|
|
const workflowsDir = path.join(projectDir, this.configDir, this.workflowsDir);
|
|
|
|
if (await fs.pathExists(workflowsDir)) {
|
|
// Remove all bmad* files from workflows directory
|
|
const entries = await fs.readdir(workflowsDir, { withFileTypes: true });
|
|
let removedCount = 0;
|
|
|
|
for (const entry of entries) {
|
|
if (entry.name.startsWith('bmad')) {
|
|
const entryPath = path.join(workflowsDir, entry.name);
|
|
await fs.remove(entryPath);
|
|
removedCount++;
|
|
}
|
|
}
|
|
|
|
if (removedCount > 0) {
|
|
console.log(chalk.dim(` Cleaned up ${removedCount} existing BMAD workflow files`));
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Install a custom agent launcher for Windsurf
|
|
* @param {string} projectDir - Project directory
|
|
* @param {string} agentName - Agent name (e.g., "fred-commit-poet")
|
|
* @param {string} agentPath - Path to compiled agent (relative to project root)
|
|
* @param {Object} metadata - Agent metadata
|
|
* @returns {Object|null} Info about created command
|
|
*/
|
|
async installCustomAgentLauncher(projectDir, agentName, agentPath, metadata) {
|
|
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(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.
|
|
|
|
<agent-activation CRITICAL="TRUE">
|
|
1. LOAD the FULL agent file from @${agentPath}
|
|
2. READ its entire contents - this contains the complete agent persona, menu, and instructions
|
|
3. FOLLOW every step in the <activation> section precisely
|
|
4. DISPLAY the welcome/greeting as instructed
|
|
5. PRESENT the numbered menu
|
|
6. WAIT for user input before proceeding
|
|
</agent-activation>
|
|
`;
|
|
|
|
// Windsurf uses workflow format with frontmatter - flat naming
|
|
const workflowContent = `---
|
|
description: ${metadata.title || agentName}
|
|
auto_execution_mode: 3
|
|
---
|
|
|
|
${launcherContent}`;
|
|
|
|
// Use flat naming: bmad-custom-agent-agentname.md
|
|
const flatName = `bmad-custom-agent-${agentName}.md`;
|
|
const launcherPath = path.join(workflowsDir, flatName);
|
|
await fs.writeFile(launcherPath, workflowContent);
|
|
|
|
return {
|
|
path: launcherPath,
|
|
command: flatName.replace('.md', ''),
|
|
};
|
|
}
|
|
}
|
|
|
|
module.exports = { WindsurfSetup };
|