From 5aef6379b9eda5317506ebfe547a17249b94698a Mon Sep 17 00:00:00 2001 From: Brian Madison Date: Sat, 24 Jan 2026 12:12:06 -0600 Subject: [PATCH] Fix github-copilot installer to use UnifiedInstaller for prompts - 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 --- tools/cli/installers/lib/ide/auggie.js | 17 +++-- .../cli/installers/lib/ide/github-copilot.js | 65 +++++++++++++++---- .../lib/ide/shared/unified-installer.js | 60 ++++++++++++++++- tools/cli/installers/lib/ide/windsurf.js | 19 ++++-- 4 files changed, 134 insertions(+), 27 deletions(-) diff --git a/tools/cli/installers/lib/ide/auggie.js b/tools/cli/installers/lib/ide/auggie.js index 2a65e57d..5a4170b5 100644 --- a/tools/cli/installers/lib/ide/auggie.js +++ b/tools/cli/installers/lib/ide/auggie.js @@ -28,12 +28,17 @@ class AuggieSetup extends BaseIdeSetup { const targetDir = path.join(projectDir, '.augment', 'commands'); // Install using UnifiedInstaller - const counts = await this.installer.install(projectDir, bmadDir, { - targetDir, - namingStyle: NamingStyle.FLAT_COLON, - templateType: TemplateType.AUGMENT, - includeNestedStructure: false, - }, options.selectedModules || []); + const counts = await this.installer.install( + projectDir, + bmadDir, + { + targetDir, + namingStyle: NamingStyle.FLAT_COLON, + templateType: TemplateType.AUGMENT, + includeNestedStructure: false, + }, + options.selectedModules || [], + ); console.log(chalk.green(`✓ ${this.name} configured:`)); console.log(chalk.dim(` - ${counts.agents} agents installed`)); diff --git a/tools/cli/installers/lib/ide/github-copilot.js b/tools/cli/installers/lib/ide/github-copilot.js index a7c6c925..3c504701 100644 --- a/tools/cli/installers/lib/ide/github-copilot.js +++ b/tools/cli/installers/lib/ide/github-copilot.js @@ -2,6 +2,7 @@ const path = require('node:path'); const { BaseIdeSetup } = require('./_base-ide'); const chalk = require('chalk'); const { AgentCommandGenerator } = require('./shared/agent-command-generator'); +const { UnifiedInstaller, NamingStyle, TemplateType } = require('./shared/unified-installer'); const prompts = require('../../../lib/prompts'); /** @@ -13,6 +14,7 @@ class GitHubCopilotSetup extends BaseIdeSetup { super('github-copilot', 'GitHub Copilot', true); // preferred IDE this.configDir = '.github'; this.agentsDir = 'agents'; + this.promptsDir = 'prompts'; this.vscodeDir = '.vscode'; } @@ -94,41 +96,63 @@ class GitHubCopilotSetup extends BaseIdeSetup { const config = options.preCollectedConfig || {}; await this.configureVsCodeSettings(projectDir, { ...options, ...config }); - // Create .github/agents directory + // Create .github/agents and .github/prompts directories const githubDir = path.join(projectDir, this.configDir); const agentsDir = path.join(githubDir, this.agentsDir); + const promptsDir = path.join(githubDir, this.promptsDir); await this.ensureDir(agentsDir); + await this.ensureDir(promptsDir); // Clean up any existing BMAD files before reinstalling await this.cleanup(projectDir); - // Generate agent launchers + // 1. Generate agent launchers (custom .agent.md format - not using UnifiedInstaller) const agentGen = new AgentCommandGenerator(this.bmadFolderName); const { artifacts: agentArtifacts } = await agentGen.collectAgentArtifacts(bmadDir, options.selectedModules || []); - // Create agent files with bmd- prefix + // Create agent files with bmad- 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`); + // Use bmad- prefix: bmad-{module}-{name}.agent.md + const targetPath = path.join(agentsDir, `bmad-${artifact.module}-${artifact.name}.agent.md`); await this.writeFile(targetPath, agentContent); agentCount++; - console.log(chalk.green(` ✓ Created agent: bmd-custom-${artifact.module}-${artifact.name}`)); + console.log(chalk.green(` ✓ Created agent: bmad-${artifact.module}-${artifact.name}`)); } + // 2. Install prompts using UnifiedInstaller + const installer = new UnifiedInstaller(this.bmadFolderName); + const promptCounts = await installer.install( + projectDir, + bmadDir, + { + targetDir: promptsDir, + namingStyle: NamingStyle.FLAT_DASH, + templateType: TemplateType.COPILOT, + }, + options.selectedModules || [], + ); + console.log(chalk.green(`✓ ${this.name} configured:`)); console.log(chalk.dim(` - ${agentCount} agents created`)); + console.log( + chalk.dim( + ` - ${promptCounts.agents} prompts, ${promptCounts.workflows} workflows, ${promptCounts.tasks + promptCounts.tools} tasks/tools`, + ), + ); console.log(chalk.dim(` - Agents directory: ${path.relative(projectDir, agentsDir)}`)); + console.log(chalk.dim(` - Prompts directory: ${path.relative(projectDir, promptsDir)}`)); console.log(chalk.dim(` - VS Code settings configured`)); - console.log(chalk.dim('\n Agents available in VS Code Chat view')); + console.log(chalk.dim('\n Agents and prompts available in VS Code Chat view')); return { success: true, agents: agentCount, + prompts: promptCounts.total, settings: true, }; } @@ -286,14 +310,15 @@ ${cleanContent} } } - // Clean up new agents directory + // Clean up 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')) { + // Remove old bmd-* files (typo fix) and current bmad-* files + if ((file.startsWith('bmd-') || file.startsWith('bmad-')) && file.endsWith('.agent.md')) { await fs.remove(path.join(agentsDir, file)); removed++; } @@ -303,6 +328,24 @@ ${cleanContent} console.log(chalk.dim(` Cleaned up ${removed} existing BMAD agents`)); } } + + // Clean up prompts directory + const promptsDir = path.join(projectDir, this.configDir, this.promptsDir); + if (await fs.pathExists(promptsDir)) { + const files = await fs.readdir(promptsDir); + let removed = 0; + + for (const file of files) { + if (file.startsWith('bmad-') && file.endsWith('.md')) { + await fs.remove(path.join(promptsDir, file)); + removed++; + } + } + + if (removed > 0) { + console.log(chalk.dim(` Cleaned up ${removed} existing BMAD prompts`)); + } + } } /** @@ -370,12 +413,12 @@ tools: ${JSON.stringify(copilotTools)} ${launcherContent} `; - const agentFilePath = path.join(agentsDir, `bmd-custom-${agentName}.agent.md`); + const agentFilePath = path.join(agentsDir, `bmad-${agentName}.agent.md`); await this.writeFile(agentFilePath, agentContent); return { path: agentFilePath, - command: `bmd-custom-${agentName}`, + command: `bmad-${agentName}`, }; } } diff --git a/tools/cli/installers/lib/ide/shared/unified-installer.js b/tools/cli/installers/lib/ide/shared/unified-installer.js index 75633460..ef93dcd3 100644 --- a/tools/cli/installers/lib/ide/shared/unified-installer.js +++ b/tools/cli/installers/lib/ide/shared/unified-installer.js @@ -40,6 +40,7 @@ const TemplateType = { WINDSURF: 'windsurf', // YAML with auto_execution_mode AUGMENT: 'augment', // YAML frontmatter GEMINI: 'gemini', // TOML frontmatter with description/prompt + COPILOT: 'copilot', // YAML with tools array for GitHub Copilot }; /** @@ -98,7 +99,15 @@ class UnifiedInstaller { // 1. Install Agents const agentGen = new AgentCommandGenerator(this.bmadFolderName); const { artifacts: agentArtifacts } = await agentGen.collectAgentArtifacts(bmadDir, selectedModules); - counts.agents = await this.writeArtifacts(agentArtifacts, targetDir, namingStyle, templateType, fileExtension, customTemplateFn, 'agent'); + counts.agents = await this.writeArtifacts( + agentArtifacts, + targetDir, + namingStyle, + templateType, + fileExtension, + customTemplateFn, + 'agent', + ); // 2. Install Workflows (filter out README artifacts) const workflowGen = new WorkflowCommandGenerator(this.bmadFolderName); @@ -170,7 +179,9 @@ class UnifiedInstaller { * @returns {Promise} Number of artifacts written */ async writeArtifacts(artifacts, targetDir, namingStyle, templateType, fileExtension, customTemplateFn, artifactType) { - console.log(`[DEBUG] writeArtifacts: artifactType=${artifactType}, count=${artifacts.length}, targetDir=${targetDir}, fileExtension=${fileExtension}`); + console.log( + `[DEBUG] writeArtifacts: artifactType=${artifactType}, count=${artifacts.length}, targetDir=${targetDir}, fileExtension=${fileExtension}`, + ); let written = 0; for (const artifact of artifacts) { @@ -238,6 +249,11 @@ class UnifiedInstaller { return this.addGeminiFrontmatter(artifact, content); } + case TemplateType.COPILOT: { + // Add Copilot frontmatter with tools array + return this.addCopilotFrontmatter(artifact, content); + } + default: { return content; } @@ -305,7 +321,7 @@ description: ${name} } // Escape any triple quotes in content - const escapedContent = contentWithoutFrontmatter.replace(/"""/g, '\\"\\"\\"'); + const escapedContent = contentWithoutFrontmatter.replaceAll('"""', String.raw`\"\"\"`); return `description = "${description}" prompt = """ @@ -314,6 +330,44 @@ ${escapedContent} `; } + /** + * Add GitHub Copilot frontmatter with tools array + */ + addCopilotFrontmatter(artifact, content) { + // Remove existing frontmatter if present + const frontmatterRegex = /^---\s*\n[\s\S]*?\n---\s*\n/; + const contentWithoutFrontmatter = content.replace(frontmatterRegex, ''); + + // GitHub Copilot tools array (as specified) + const tools = [ + 'changes', + 'edit', + 'fetch', + 'githubRepo', + 'problems', + 'runCommands', + 'runTasks', + 'runTests', + 'search', + 'runSubagent', + 'testFailure', + 'todos', + 'usages', + ]; + + const name = artifact.name || artifact.displayName || 'prompt'; + const description = `Activates the ${name} ${artifact.type || 'workflow'}.`; + + const frontmatter = `--- +description: "${description}" +tools: ${JSON.stringify(tools)} +--- + +`; + + return frontmatter + contentWithoutFrontmatter; + } + /** * Get tasks from manifest CSV */ diff --git a/tools/cli/installers/lib/ide/windsurf.js b/tools/cli/installers/lib/ide/windsurf.js index 6463311e..2be6e189 100644 --- a/tools/cli/installers/lib/ide/windsurf.js +++ b/tools/cli/installers/lib/ide/windsurf.js @@ -39,12 +39,17 @@ class WindsurfSetup extends BaseIdeSetup { 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 || []); + 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 @@ -129,7 +134,7 @@ ${contentWithoutFrontmatter}`; const parts = entry.name.replace('bmad-', '').replace('.md', '').split('-'); if (parts.length < 2) continue; - const type = parts[parts.length - 2]; // second to last part should be 'task' or 'tool' + 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