From 2a9d984a2c9f63356d6fb891db3952592179398c Mon Sep 17 00:00:00 2001 From: Hayden Carson Date: Sun, 1 Mar 2026 12:53:01 +0800 Subject: [PATCH] fix: use raw agent names and resolve duplicate command filenames Use raw agent names (e.g., 'dev', 'pm') for the name field in .agent.md frontmatter and all agent references in prompt frontmatter, instead of persona display names. This provides clean @mention and /command names. Resolve Create Story / Validate Story filename collision where both entries shared command 'bmad-bmm-create-story'. When multiple entries share a command, derive unique filenames from the entry name slug. Also pass workflow options (Create Mode, Validate Mode) to the prompt body so each prompt invokes the correct workflow mode. Fixes #1794 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../cli/installers/lib/ide/github-copilot.js | 50 ++++++++++++------- 1 file changed, 32 insertions(+), 18 deletions(-) diff --git a/tools/cli/installers/lib/ide/github-copilot.js b/tools/cli/installers/lib/ide/github-copilot.js index d3028f5ad..b20ce2fd0 100644 --- a/tools/cli/installers/lib/ide/github-copilot.js +++ b/tools/cli/installers/lib/ide/github-copilot.js @@ -145,16 +145,15 @@ class GitHubCopilotSetup extends BaseIdeSetup { createAgentContent(artifact, manifestEntry) { // Build enriched description from manifest metadata let description; - let name; + // Use the raw agent name (e.g., "dev", "pm") for clean @mention selection + const name = artifact.name; if (manifestEntry) { const persona = manifestEntry.displayName || artifact.name; const title = manifestEntry.title || this.formatTitle(artifact.name); const capabilities = manifestEntry.capabilities || 'agent capabilities'; description = `${persona} — ${title}: ${capabilities}`; - name = manifestEntry.displayName || this.formatTitle(artifact.name); } else { description = `Activates the ${this.formatTitle(artifact.name)} agent persona.`; - name = this.formatTitle(artifact.name); } const safeName = this.escapeYamlSingleQuote(name); @@ -197,14 +196,30 @@ You must fully embody this agent's persona and follow all activation instruction const helpEntries = await this.loadBmadHelp(bmadDir); if (helpEntries) { + // Detect duplicate commands to derive unique filenames when multiple entries share one + const commandCounts = new Map(); + for (const entry of helpEntries) { + if (!entry.command || !entry['workflow-file']) continue; + commandCounts.set(entry.command, (commandCounts.get(entry.command) || 0) + 1); + } + for (const entry of helpEntries) { const command = entry.command; if (!command) continue; // Skip entries without a command (tech-writer commands have no command column) const workflowFile = entry['workflow-file']; if (!workflowFile) continue; // Skip entries with no workflow file path - const promptFileName = `${command}.prompt.md`; - const promptContent = this.createWorkflowPromptContent(entry, workflowFile, agentManifest); + + // When multiple entries share the same command, derive a unique filename from the entry name + let promptFileName; + if (commandCounts.get(command) > 1) { + const slug = entry.name.toLowerCase().replaceAll(/[^a-z0-9]+/g, '-'); + promptFileName = `bmad-bmm-${slug}.prompt.md`; + } else { + promptFileName = `${command}.prompt.md`; + } + + const promptContent = this.createWorkflowPromptContent(entry, workflowFile); const promptPath = path.join(promptsDir, promptFileName); await this.writeFile(promptPath, promptContent); promptCount++; @@ -241,10 +256,9 @@ You must fully embody this agent's persona and follow all activation instruction * Determines the pattern (A, B, or A for .xml tasks) based on file extension * @param {Object} entry - bmad-help.csv row * @param {string} workflowFile - Workflow file path - * @param {Map} agentManifest - Agent manifest data for display name lookup * @returns {string} Prompt file content */ - createWorkflowPromptContent(entry, workflowFile, agentManifest) { + createWorkflowPromptContent(entry, workflowFile) { const description = this.escapeYamlSingleQuote(this.createPromptDescription(entry.name)); const promptName = this.escapeYamlSingleQuote(entry.name || description); // bmm/config.yaml is safe to hardcode here: these prompts are only generated when @@ -267,21 +281,23 @@ You must fully embody this agent's persona and follow all activation instruction 2. Load and follow the workflow at {project-root}/${workflowFile}`; } - // Build the agent line: use agent displayName from manifest if available + // Build the agent line: use raw agent name to match agent .agent.md name field const agentName = (entry['agent-name'] || '').trim(); let agentLine = ''; if (agentName) { - const agentMeta = agentManifest.get(agentName); - const agentDisplayName = (agentMeta && agentMeta.displayName) || this.formatTitle(agentName); - agentLine = `\nagent: '${this.escapeYamlSingleQuote(agentDisplayName)}'`; + agentLine = `\nagent: '${this.escapeYamlSingleQuote(agentName)}'`; } + // Include options (e.g., "Create Mode", "Validate Mode") when present + const options = (entry.options || '').trim(); + const optionsInstruction = options ? `\n4. Use option: ${options}` : ''; + return `--- name: '${promptName}' description: '${description}'${agentLine} --- -${body} +${body}${optionsInstruction} `; } @@ -352,9 +368,8 @@ ${body} const safeName = this.escapeYamlSingleQuote(entry.name); const safeDescription = this.escapeYamlSingleQuote(cmd.description); - // Use agent display name from merged CSV if available, otherwise format the raw name - const agentDisplayName = (entry['agent-display-name'] || '').trim() || this.formatTitle(entry['agent-name']); - const agentLine = `\nagent: '${this.escapeYamlSingleQuote(agentDisplayName)}'`; + // Use raw agent name to match agent .agent.md name field + const agentLine = `\nagent: '${this.escapeYamlSingleQuote(entry['agent-name'])}'`; const content = `--- name: '${safeName}' @@ -377,15 +392,14 @@ description: '${safeDescription}'${agentLine} */ createAgentActivatorPromptContent(artifact, manifestEntry) { let description; - let name; if (manifestEntry) { description = manifestEntry.title || this.formatTitle(artifact.name); - name = manifestEntry.displayName || this.formatTitle(artifact.name); } else { description = this.formatTitle(artifact.name); - name = this.formatTitle(artifact.name); } + // Use the raw agent name (e.g., "dev") to match agent .agent.md name field + const name = artifact.name; const safeName = this.escapeYamlSingleQuote(name); const safeDescription = this.escapeYamlSingleQuote(description); const agentPath = artifact.agentPath || artifact.relativePath;