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) {
// 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;