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>
This commit is contained in:
Hayden Carson 2026-03-01 12:53:01 +08:00
parent 78ab55c7e3
commit 2a9d984a2c
1 changed files with 32 additions and 18 deletions

View File

@ -145,16 +145,15 @@ class GitHubCopilotSetup extends BaseIdeSetup {
createAgentContent(artifact, manifestEntry) { createAgentContent(artifact, manifestEntry) {
// Build enriched description from manifest metadata // Build enriched description from manifest metadata
let description; let description;
let name; // Use the raw agent name (e.g., "dev", "pm") for clean @mention selection
const name = artifact.name;
if (manifestEntry) { if (manifestEntry) {
const persona = manifestEntry.displayName || artifact.name; const persona = manifestEntry.displayName || artifact.name;
const title = manifestEntry.title || this.formatTitle(artifact.name); const title = manifestEntry.title || this.formatTitle(artifact.name);
const capabilities = manifestEntry.capabilities || 'agent capabilities'; const capabilities = manifestEntry.capabilities || 'agent capabilities';
description = `${persona}${title}: ${capabilities}`; description = `${persona}${title}: ${capabilities}`;
name = manifestEntry.displayName || this.formatTitle(artifact.name);
} else { } else {
description = `Activates the ${this.formatTitle(artifact.name)} agent persona.`; description = `Activates the ${this.formatTitle(artifact.name)} agent persona.`;
name = this.formatTitle(artifact.name);
} }
const safeName = this.escapeYamlSingleQuote(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); const helpEntries = await this.loadBmadHelp(bmadDir);
if (helpEntries) { 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) { for (const entry of helpEntries) {
const command = entry.command; const command = entry.command;
if (!command) continue; // Skip entries without a command (tech-writer commands have no command column) if (!command) continue; // Skip entries without a command (tech-writer commands have no command column)
const workflowFile = entry['workflow-file']; const workflowFile = entry['workflow-file'];
if (!workflowFile) continue; // Skip entries with no workflow file path 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); const promptPath = path.join(promptsDir, promptFileName);
await this.writeFile(promptPath, promptContent); await this.writeFile(promptPath, promptContent);
promptCount++; 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 * Determines the pattern (A, B, or A for .xml tasks) based on file extension
* @param {Object} entry - bmad-help.csv row * @param {Object} entry - bmad-help.csv row
* @param {string} workflowFile - Workflow file path * @param {string} workflowFile - Workflow file path
* @param {Map} agentManifest - Agent manifest data for display name lookup
* @returns {string} Prompt file content * @returns {string} Prompt file content
*/ */
createWorkflowPromptContent(entry, workflowFile, agentManifest) { createWorkflowPromptContent(entry, workflowFile) {
const description = this.escapeYamlSingleQuote(this.createPromptDescription(entry.name)); const description = this.escapeYamlSingleQuote(this.createPromptDescription(entry.name));
const promptName = this.escapeYamlSingleQuote(entry.name || description); const promptName = this.escapeYamlSingleQuote(entry.name || description);
// bmm/config.yaml is safe to hardcode here: these prompts are only generated when // 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}`; 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(); const agentName = (entry['agent-name'] || '').trim();
let agentLine = ''; let agentLine = '';
if (agentName) { if (agentName) {
const agentMeta = agentManifest.get(agentName); agentLine = `\nagent: '${this.escapeYamlSingleQuote(agentName)}'`;
const agentDisplayName = (agentMeta && agentMeta.displayName) || this.formatTitle(agentName);
agentLine = `\nagent: '${this.escapeYamlSingleQuote(agentDisplayName)}'`;
} }
// Include options (e.g., "Create Mode", "Validate Mode") when present
const options = (entry.options || '').trim();
const optionsInstruction = options ? `\n4. Use option: ${options}` : '';
return `--- return `---
name: '${promptName}' name: '${promptName}'
description: '${description}'${agentLine} description: '${description}'${agentLine}
--- ---
${body} ${body}${optionsInstruction}
`; `;
} }
@ -352,9 +368,8 @@ ${body}
const safeName = this.escapeYamlSingleQuote(entry.name); const safeName = this.escapeYamlSingleQuote(entry.name);
const safeDescription = this.escapeYamlSingleQuote(cmd.description); const safeDescription = this.escapeYamlSingleQuote(cmd.description);
// Use agent display name from merged CSV if available, otherwise format the raw name // Use raw agent name to match agent .agent.md name field
const agentDisplayName = (entry['agent-display-name'] || '').trim() || this.formatTitle(entry['agent-name']); const agentLine = `\nagent: '${this.escapeYamlSingleQuote(entry['agent-name'])}'`;
const agentLine = `\nagent: '${this.escapeYamlSingleQuote(agentDisplayName)}'`;
const content = `--- const content = `---
name: '${safeName}' name: '${safeName}'
@ -377,15 +392,14 @@ description: '${safeDescription}'${agentLine}
*/ */
createAgentActivatorPromptContent(artifact, manifestEntry) { createAgentActivatorPromptContent(artifact, manifestEntry) {
let description; let description;
let name;
if (manifestEntry) { if (manifestEntry) {
description = manifestEntry.title || this.formatTitle(artifact.name); description = manifestEntry.title || this.formatTitle(artifact.name);
name = manifestEntry.displayName || this.formatTitle(artifact.name);
} else { } else {
description = this.formatTitle(artifact.name); 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 safeName = this.escapeYamlSingleQuote(name);
const safeDescription = this.escapeYamlSingleQuote(description); const safeDescription = this.escapeYamlSingleQuote(description);
const agentPath = artifact.agentPath || artifact.relativePath; const agentPath = artifact.agentPath || artifact.relativePath;