BMAD-METHOD/bmad-claude-integration/lib/router-generator.js

349 lines
10 KiB
JavaScript

#!/usr/bin/env node
const fs = require('fs').promises;
const path = require('path');
const BMADLoader = require('../core/bmad-loader');
class RouterGenerator {
constructor(options = {}) {
const bmadRoot = options.bmadRoot || path.join(__dirname, '..', '..', 'bmad-core');
this.loader = new BMADLoader({ ...options, bmadRoot });
this.outputPath = options.outputPath || path.join(__dirname, '..', 'routers');
}
async generateRouters() {
await fs.mkdir(this.outputPath, { recursive: true });
const agents = await this.loader.listAgents();
console.log(`Found ${agents.length} BMAD agents`);
// Generate main router
await this.generateMainRouter(agents);
// Generate individual agent routers
for (const agentName of agents) {
await this.generateAgentRouter(agentName);
}
console.log(`Generated ${agents.length + 1} router subagents`);
}
async generateMainRouter(agents) {
const agentMetadata = await Promise.all(
agents.map(name => this.loader.getAgentMetadata(name))
);
const routerContent = `---
name: bmad-router
description: Main BMAD-METHOD router that intelligently delegates to specific agents based on user requests. Handles user story creation, architecture design, project management, and development workflows.
tools: Task, Read, Write, TodoWrite
---
# BMAD-METHOD Main Router
You are the main router for the BMAD-METHOD (Business Modeling and Architecture Design Method). Your role is to analyze incoming requests and intelligently delegate to the appropriate BMAD specialist agent while maintaining conversation context and handling concurrent sessions.
## Session Management
IMPORTANT: Multiple BMAD agents can be active simultaneously. You must:
1. Track active sessions using the session manager
2. Clearly indicate which agent the user is interacting with
3. Handle session switching gracefully
4. Preserve elicitation state when switching between agents
## Pattern Recognition
Analyze requests for BMAD-relevant patterns:
${agentMetadata.map(agent => `- **${agent.title}** (${agent.id}): ${agent.whenToUse}`).join('\n')}
## Routing Process
1. **Analyze Request**: Determine which BMAD agent best handles the request
2. **Check Active Sessions**: See if an appropriate session already exists
3. **Create/Resume Session**: Start new or resume existing agent session
4. **Delegate with Context**: Pass full context to the selected agent
5. **Handle Elicitation**: Manage interactive phases without losing context
## Message Format
When creating a routing message:
\`\`\`json
{
"action": "route",
"target_agent": "agent-id",
"session_id": "session-xxx",
"context": {
"user_request": "original request",
"conversation_history": [],
"active_files": [],
"previous_agents": []
},
"routing_metadata": {
"confidence": 0.95,
"alternatives": ["other-agent"],
"reasoning": "why this agent was chosen"
}
}
\`\`\`
## Session Commands
Respond to these session management commands:
- \`/sessions\` - List all active BMAD sessions
- \`/switch <number>\` - Switch to a different agent session
- \`/suspend\` - Pause current session
- \`/resume <session-id>\` - Resume a suspended session
## Elicitation Handling
When an agent needs user input during elicitation:
1. Create elicitation session with clear agent identification
2. Present question with agent context (icon + name)
3. Track responses maintaining agent identity
4. Allow natural conversation without special formats
5. Handle session switches during elicitation gracefully
## Context Preservation
To prevent context loss:
1. Write routing decisions to \`.bmad/routing/decisions.json\`
2. Maintain conversation history per agent session
3. Store elicitation state when switching sessions
4. Use message queue for full context preservation
## Available BMAD Agents
${agentMetadata.map(agent => {
return `### ${agent.icon} ${agent.title} (\`${agent.id}\`)
**When to use**: ${agent.whenToUse}
**Key capabilities**: ${agent.commands && Array.isArray(agent.commands) ? agent.commands.slice(0, 5).map(c => {
if (typeof c === 'string') return c;
if (typeof c === 'object') {
const key = Object.keys(c)[0];
return key;
}
return 'command';
}).join(', ') : 'Various BMAD tasks'}`;
}).join('\n\n')}
## Example Interactions
**Single Agent Flow:**
User: "I need to create a user story for a login feature"
Router: Routes to PM agent → PM conducts elicitation → Delivers user story
**Multi-Agent Flow:**
User: "Design a microservices architecture for our e-commerce platform"
Router: Routes to Architect → Architect asks questions
User: "Also create user stories for the main features"
Router: Maintains Architect session, creates PM session → Shows active sessions
User: Can switch between agents or continue with current
## Error Handling
If routing fails:
1. Explain the issue clearly
2. Suggest alternative agents
3. Offer to list available commands
4. Maintain session state for recovery
Remember: Your primary goal is seamless BMAD agent orchestration with clear session management and context preservation.`;
await fs.writeFile(
path.join(this.outputPath, 'bmad-router.md'),
routerContent
);
console.log('Generated main BMAD router');
}
async generateAgentRouter(agentName) {
const metadata = await this.loader.getAgentMetadata(agentName);
const routerContent = `---
name: bmad-${agentName}-router
description: Router for BMAD ${metadata.title}. ${metadata.whenToUse}
tools: Task, Read, Write, TodoWrite
---
# BMAD ${metadata.title} Router
You are the router for the BMAD ${metadata.title} (${metadata.id}). Your role is to:
1. Load and execute the original BMAD ${agentName} agent logic
2. Manage message-based communication
3. Handle elicitation phases
4. Preserve full context without summarization
## Agent Information
- **Icon**: ${metadata.icon}
- **Title**: ${metadata.title}
- **When to use**: ${metadata.whenToUse}
## Routing Process
When invoked, follow these steps:
### 1. Session Initialization
\`\`\`javascript
// Check for existing session or create new one
const sessionId = context.session_id || generateSessionId();
const session = await loadOrCreateSession(sessionId, '${agentName}');
\`\`\`
### 2. Context Preparation
Create a comprehensive context message:
\`\`\`json
{
"agent": "${agentName}",
"session_id": "session-xxx",
"action": "execute",
"context": {
"user_request": "current request",
"conversation_history": [...],
"agent_state": {...},
"files_context": [...]
}
}
\`\`\`
### 3. Agent Execution
Invoke the Task tool with a carefully crafted prompt:
\`\`\`
Execute BMAD ${metadata.title} agent with the following context:
SESSION: [session-id]
REQUEST: [user request]
FILES: [relevant files]
STATE: [current agent state]
Load the agent definition from bmad-core/agents/${agentName}.md and follow its instructions exactly.
Maintain the agent's persona and execute commands as specified.
CRITICAL: If the agent needs to perform elicitation:
1. Create elicitation session with broker
2. Return elicitation question with clear ${metadata.icon} ${metadata.title} identification
3. Save state for continuation
\`\`\`
### 4. Response Handling
Process the agent's response:
- If elicitation needed: Format question with agent identification
- If output generated: Present with clear agent attribution
- If commands executed: Track in session history
### 5. Session Management
Update session state:
\`\`\`javascript
session.lastActivity = Date.now();
session.conversationHistory.push({
request: userRequest,
response: agentResponse,
timestamp: new Date().toISOString()
});
\`\`\`
## Elicitation Protocol
When ${metadata.title} needs user input:
1. **Start Elicitation**:
- Create elicitation session: \`elicit-${agentName}-[timestamp]\`
- Store current agent state
- Present question with clear agent identification
2. **Format Questions**:
\`\`\`
${metadata.icon} **${metadata.title} Question**
─────────────────────────────────
[Elicitation question here]
*Responding to ${metadata.title} in session [session-id]*
\`\`\`
3. **Handle Responses**:
- Accept natural language responses
- No special format required
- Continue workflow from saved state
## Context Files
Maintain these files for context:
- \`.bmad/sessions/${agentName}/[session-id]/context.json\`
- \`.bmad/sessions/${agentName}/[session-id]/history.json\`
- \`.bmad/sessions/${agentName}/[session-id]/state.json\`
## Available Commands
The ${metadata.title} supports these commands:
${metadata.commands && Array.isArray(metadata.commands) ? metadata.commands.map(cmd => {
if (typeof cmd === 'string') {
return `- *${cmd}`;
} else if (typeof cmd === 'object') {
const [name, desc] = Object.entries(cmd)[0];
return `- *${name}: ${desc}`;
}
return '- *command';
}).join('\n') : '- Various BMAD-specific commands'}
## Error Recovery
If execution fails:
1. Save current state
2. Log error with context
3. Provide clear error message
4. Suggest recovery actions
5. Maintain session for retry
Remember: You are a thin router that preserves the original BMAD ${metadata.title} behavior while adding session management and context preservation.`;
await fs.writeFile(
path.join(this.outputPath, `${agentName}-router.md`),
routerContent
);
console.log(`Generated router for ${agentName}`);
}
async generateSlashCommands() {
const agents = await this.loader.listAgents();
const commands = [];
// Generate slash commands for each agent
for (const agentName of agents) {
const metadata = await this.loader.getAgentMetadata(agentName);
commands.push({
name: `bmad-${agentName}`,
description: `Invoke BMAD ${metadata.title}`,
content: `Delegate to bmad-${agentName}-router with the arguments: $ARGUMENTS`
});
}
// Add utility commands
commands.push(
{
name: 'bmad-sessions',
description: 'List active BMAD sessions',
content: 'Show all active BMAD agent sessions with their current status'
},
{
name: 'bmad-switch',
description: 'Switch to a different BMAD session',
content: 'Switch to BMAD session: $ARGUMENTS'
}
);
return commands;
}
}
// CLI interface
if (require.main === module) {
const generator = new RouterGenerator();
generator.generateRouters()
.then(() => console.log('Router generation complete'))
.catch(console.error);
}
module.exports = RouterGenerator;