feat: make agent capabilities data-driven via agent YAML metadata

Replace the hardcoded getAgentCapabilities() map with a data-driven
pipeline. Capabilities are now defined in each .agent.yaml source file,
compiled into the XML output, extracted into agent-manifest.csv by the
manifest generator, and read by the GitHub Copilot handler at install
time. New agents automatically get their capabilities without code
changes.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
jheyworth 2026-02-09 13:39:18 +00:00
parent eb0e1fb1a5
commit cb1a7cccc8
14 changed files with 21 additions and 25 deletions

View File

@ -5,6 +5,7 @@ agent:
title: Business Analyst title: Business Analyst
icon: 📊 icon: 📊
module: bmm module: bmm
capabilities: "market research, competitive analysis, requirements elicitation, domain expertise"
hasSidecar: false hasSidecar: false
persona: persona:

View File

@ -7,6 +7,7 @@ agent:
title: Architect title: Architect
icon: 🏗️ icon: 🏗️
module: bmm module: bmm
capabilities: "distributed systems, cloud infrastructure, API design, scalable patterns"
hasSidecar: false hasSidecar: false
persona: persona:

View File

@ -7,6 +7,7 @@ agent:
title: Developer Agent title: Developer Agent
icon: 💻 icon: 💻
module: bmm module: bmm
capabilities: "story execution, test-driven development, code implementation"
hasSidecar: false hasSidecar: false
persona: persona:

View File

@ -5,6 +5,7 @@ agent:
title: Product Manager title: Product Manager
icon: 📋 icon: 📋
module: bmm module: bmm
capabilities: "PRD creation, requirements discovery, stakeholder alignment, user interviews"
hasSidecar: false hasSidecar: false
persona: persona:

View File

@ -5,6 +5,7 @@ agent:
title: QA Engineer title: QA Engineer
icon: 🧪 icon: 🧪
module: bmm module: bmm
capabilities: "test automation, API testing, E2E testing, coverage analysis"
hasSidecar: false hasSidecar: false
persona: persona:

View File

@ -7,6 +7,7 @@ agent:
title: Quick Flow Solo Dev title: Quick Flow Solo Dev
icon: 🚀 icon: 🚀
module: bmm module: bmm
capabilities: "rapid spec creation, lean implementation, minimum ceremony"
hasSidecar: false hasSidecar: false
persona: persona:

View File

@ -7,6 +7,7 @@ agent:
title: Scrum Master title: Scrum Master
icon: 🏃 icon: 🏃
module: bmm module: bmm
capabilities: "sprint planning, story preparation, agile ceremonies, backlog management"
hasSidecar: false hasSidecar: false
persona: persona:

View File

@ -7,6 +7,7 @@ agent:
title: Technical Writer title: Technical Writer
icon: 📚 icon: 📚
module: bmm module: bmm
capabilities: "documentation, Mermaid diagrams, standards compliance, concept explanation"
hasSidecar: true hasSidecar: true
persona: persona:

View File

@ -7,6 +7,7 @@ agent:
title: UX Designer title: UX Designer
icon: 🎨 icon: 🎨
module: bmm module: bmm
capabilities: "user research, interaction design, UI patterns, experience strategy"
hasSidecar: false hasSidecar: false
persona: persona:

View File

@ -7,6 +7,7 @@ agent:
name: "BMad Master" name: "BMad Master"
title: "BMad Master Executor, Knowledge Custodian, and Workflow Orchestrator" title: "BMad Master Executor, Knowledge Custodian, and Workflow Orchestrator"
icon: "🧙" icon: "🧙"
capabilities: "runtime resource management, workflow orchestration, task execution, knowledge custodian"
hasSidecar: false hasSidecar: false
persona: persona:

View File

@ -321,6 +321,7 @@ class ManifestGenerator {
const nameMatch = content.match(/name="([^"]+)"/); const nameMatch = content.match(/name="([^"]+)"/);
const titleMatch = content.match(/title="([^"]+)"/); const titleMatch = content.match(/title="([^"]+)"/);
const iconMatch = content.match(/icon="([^"]+)"/); const iconMatch = content.match(/icon="([^"]+)"/);
const capabilitiesMatch = content.match(/capabilities="([^"]+)"/);
// Extract persona fields // Extract persona fields
const roleMatch = content.match(/<role>([^<]+)<\/role>/); const roleMatch = content.match(/<role>([^<]+)<\/role>/);
@ -342,6 +343,7 @@ class ManifestGenerator {
displayName: nameMatch ? nameMatch[1] : agentName, displayName: nameMatch ? nameMatch[1] : agentName,
title: titleMatch ? titleMatch[1] : '', title: titleMatch ? titleMatch[1] : '',
icon: iconMatch ? iconMatch[1] : '', icon: iconMatch ? iconMatch[1] : '',
capabilities: capabilitiesMatch ? this.cleanForCSV(capabilitiesMatch[1]) : '',
role: roleMatch ? this.cleanForCSV(roleMatch[1]) : '', role: roleMatch ? this.cleanForCSV(roleMatch[1]) : '',
identity: identityMatch ? this.cleanForCSV(identityMatch[1]) : '', identity: identityMatch ? this.cleanForCSV(identityMatch[1]) : '',
communicationStyle: styleMatch ? this.cleanForCSV(styleMatch[1]) : '', communicationStyle: styleMatch ? this.cleanForCSV(styleMatch[1]) : '',
@ -784,7 +786,7 @@ class ManifestGenerator {
} }
// Create CSV header with persona fields // Create CSV header with persona fields
let csvContent = 'name,displayName,title,icon,role,identity,communicationStyle,principles,module,path\n'; let csvContent = 'name,displayName,title,icon,capabilities,role,identity,communicationStyle,principles,module,path\n';
// Combine existing and new agents, preferring new data for duplicates // Combine existing and new agents, preferring new data for duplicates
const allAgents = new Map(); const allAgents = new Map();
@ -802,6 +804,7 @@ class ManifestGenerator {
displayName: agent.displayName, displayName: agent.displayName,
title: agent.title, title: agent.title,
icon: agent.icon, icon: agent.icon,
capabilities: agent.capabilities,
role: agent.role, role: agent.role,
identity: agent.identity, identity: agent.identity,
communicationStyle: agent.communicationStyle, communicationStyle: agent.communicationStyle,
@ -818,6 +821,7 @@ class ManifestGenerator {
escapeCsv(record.displayName), escapeCsv(record.displayName),
escapeCsv(record.title), escapeCsv(record.title),
escapeCsv(record.icon), escapeCsv(record.icon),
escapeCsv(record.capabilities),
escapeCsv(record.role), escapeCsv(record.role),
escapeCsv(record.identity), escapeCsv(record.identity),
escapeCsv(record.communicationStyle), escapeCsv(record.communicationStyle),

View File

@ -159,7 +159,7 @@ class GitHubCopilotSetup extends BaseIdeSetup {
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 = this.getAgentCapabilities(artifact.name); const capabilities = manifestEntry.capabilities || 'agent capabilities';
description = `${persona}${title}: ${capabilities}`; description = `${persona}${title}: ${capabilities}`;
} else { } else {
description = `Activates the ${this.formatTitle(artifact.name)} agent persona.`; description = `Activates the ${this.formatTitle(artifact.name)} agent persona.`;
@ -188,28 +188,6 @@ You must fully embody this agent's persona and follow all activation instruction
`; `;
} }
/**
* Get capabilities string for an agent
* @param {string} agentName - Agent name
* @returns {string} Comma-separated capabilities
*/
getAgentCapabilities(agentName) {
const capabilitiesMap = {
'bmad-master': 'runtime resource management, workflow orchestration, task execution, knowledge custodian',
analyst: 'market research, competitive analysis, requirements elicitation, domain expertise',
architect: 'distributed systems, cloud infrastructure, API design, scalable patterns',
dev: 'story execution, test-driven development, code implementation',
pm: 'PRD creation, requirements discovery, stakeholder alignment, user interviews',
qa: 'test automation, API testing, E2E testing, coverage analysis',
'quick-flow-solo-dev': 'rapid spec creation, lean implementation, minimum ceremony',
sm: 'sprint planning, story preparation, agile ceremonies, backlog management',
'tech-writer': 'documentation, Mermaid diagrams, standards compliance, concept explanation',
'ux-designer': 'user research, interaction design, UI patterns, experience strategy',
};
return capabilitiesMap[agentName] || 'agent capabilities';
}
/** /**
* Generate .prompt.md files for workflows, tasks, tech-writer commands, and agent activators * Generate .prompt.md files for workflows, tasks, tech-writer commands, and agent activators
* @param {string} projectDir - Project directory * @param {string} projectDir - Project directory
@ -449,7 +427,7 @@ tools: ${toolsStr}
for (const agentName of agentOrder) { for (const agentName of agentOrder) {
const meta = agentManifest.get(agentName); const meta = agentManifest.get(agentName);
if (meta) { if (meta) {
const capabilities = this.getAgentCapabilities(agentName); const capabilities = meta.capabilities || 'agent capabilities';
const cleanTitle = (meta.title || '').replaceAll('""', '"'); const cleanTitle = (meta.title || '').replaceAll('""', '"');
agentsTable += `| ${agentName} | ${meta.displayName} | ${cleanTitle} | ${capabilities} |\n`; agentsTable += `| ${agentName} | ${meta.displayName} | ${cleanTitle} | ${capabilities} |\n`;
} }

View File

@ -279,6 +279,9 @@ async function compileToXml(agentYaml, agentName = '', targetPath = '') {
`title="${meta.title || ''}"`, `title="${meta.title || ''}"`,
`icon="${meta.icon || '🤖'}"`, `icon="${meta.icon || '🤖'}"`,
]; ];
if (meta.capabilities) {
agentAttrs.push(`capabilities="${escapeXml(meta.capabilities)}"`);
}
xml += `<agent ${agentAttrs.join(' ')}>\n`; xml += `<agent ${agentAttrs.join(' ')}>\n`;

View File

@ -228,6 +228,7 @@ function buildMetadataSchema(expectedModule) {
title: createNonEmptyString('agent.metadata.title'), title: createNonEmptyString('agent.metadata.title'),
icon: createNonEmptyString('agent.metadata.icon'), icon: createNonEmptyString('agent.metadata.icon'),
module: createNonEmptyString('agent.metadata.module').optional(), module: createNonEmptyString('agent.metadata.module').optional(),
capabilities: createNonEmptyString('agent.metadata.capabilities').optional(),
hasSidecar: z.boolean(), hasSidecar: z.boolean(),
}; };