feat: install for ide now sets up rules also for expansion agents!

This commit is contained in:
Brian Madison
2025-06-28 02:22:57 -05:00
parent 50d17ed65d
commit b82978fd38
3 changed files with 221 additions and 106 deletions

41
test-ide-paths.js Normal file
View File

@@ -0,0 +1,41 @@
// Test script to verify IDE setup paths for expansion pack agents
const path = require('path');
const fs = require('fs-extra');
// Simulate the findAgentPath logic
function simulateFindAgentPath(agentId, installDir) {
const possiblePaths = [
path.join(installDir, ".bmad-core", "agents", `${agentId}.md`),
path.join(installDir, "agents", `${agentId}.md`),
// Expansion pack paths
path.join(installDir, ".bmad-2d-phaser-game-dev", "agents", `${agentId}.md`),
path.join(installDir, ".bmad-infrastructure-devops", "agents", `${agentId}.md`),
path.join(installDir, ".bmad-creator-tools", "agents", `${agentId}.md`)
];
// Simulate finding the agent in an expansion pack
if (agentId === 'game-developer') {
return path.join(installDir, ".bmad-2d-phaser-game-dev", "agents", `${agentId}.md`);
}
// Default to core
return path.join(installDir, ".bmad-core", "agents", `${agentId}.md`);
}
// Test different scenarios
const testDir = '/project';
const agents = ['dev', 'game-developer', 'infra-devops-platform'];
console.log('Testing IDE path references:\n');
agents.forEach(agentId => {
const agentPath = simulateFindAgentPath(agentId, testDir);
const relativePath = path.relative(testDir, agentPath).replace(/\\/g, '/');
console.log(`Agent: ${agentId}`);
console.log(` Full path: ${agentPath}`);
console.log(` Relative path: ${relativePath}`);
console.log(` Roo customInstructions: CRITICAL Read the full YML from ${relativePath} ...`);
console.log(` Cursor MDC reference: [${relativePath}](mdc:${relativePath})`);
console.log('');
});

View File

@@ -0,0 +1,58 @@
# IDE-specific agent configurations
# This file defines agent-specific settings for different IDEs
# Roo Code file permissions
# Each agent can have restricted file access based on regex patterns
# If an agent is not listed here, it gets full edit access
roo-permissions:
# Core agents
analyst:
fileRegex: "\\.(md|txt)$"
description: "Documentation and text files"
pm:
fileRegex: "\\.(md|txt)$"
description: "Product documentation"
architect:
fileRegex: "\\.(md|txt|yml|yaml|json)$"
description: "Architecture docs and configs"
qa:
fileRegex: "\\.(test|spec)\\.(js|ts|jsx|tsx)$|\\.md$"
description: "Test files and documentation"
ux-expert:
fileRegex: "\\.(md|css|scss|html|jsx|tsx)$"
description: "Design-related files"
po:
fileRegex: "\\.(md|txt)$"
description: "Story and requirement docs"
sm:
fileRegex: "\\.(md|txt)$"
description: "Process and planning docs"
# Expansion pack agents
game-designer:
fileRegex: "\\.(md|txt|json|yaml|yml)$"
description: "Game design documents and configs"
game-sm:
fileRegex: "\\.(md|txt)$"
description: "Game project management docs"
# Cline agent ordering
# Lower numbers appear first in the list
# Agents not listed get order 99
cline-order:
# Core agents
bmad-master: 1
bmad-orchestrator: 2
pm: 3
analyst: 4
architect: 5
po: 6
sm: 7
dev: 8
qa: 9
ux-expert: 10
# Expansion pack agents
bmad-the-creator: 11
game-designer: 12
game-developer: 13
game-sm: 14
infra-devops-platform: 15

View File

@@ -1,4 +1,6 @@
const path = require("path");
const fs = require("fs-extra");
const yaml = require("js-yaml");
const fileManager = require("./file-manager");
const configLoader = require("./config-loader");
@@ -13,6 +15,27 @@ async function initializeModules() {
}
class IdeSetup {
constructor() {
this.ideAgentConfig = null;
}
async loadIdeAgentConfig() {
if (this.ideAgentConfig) return this.ideAgentConfig;
try {
const configPath = path.join(__dirname, '..', 'config', 'ide-agent-config.yml');
const configContent = await fs.readFile(configPath, 'utf8');
this.ideAgentConfig = yaml.load(configContent);
return this.ideAgentConfig;
} catch (error) {
console.warn('Failed to load IDE agent configuration, using defaults');
return {
'roo-permissions': {},
'cline-order': {}
};
}
}
async setup(ide, installDir, selectedAgent = null) {
await initializeModules();
const ideConfig = await configLoader.getIdeConfiguration(ide);
@@ -48,13 +71,10 @@ class IdeSetup {
await fileManager.ensureDirectory(cursorRulesDir);
for (const agentId of agents) {
// Check if .bmad-core is a subdirectory (full install) or if agents are in root (single agent install)
let agentPath = path.join(installDir, ".bmad-core", "agents", `${agentId}.md`);
if (!(await fileManager.pathExists(agentPath))) {
agentPath = path.join(installDir, "agents", `${agentId}.md`);
}
// Find the agent file
const agentPath = await this.findAgentPath(agentId, installDir);
if (await fileManager.pathExists(agentPath)) {
if (agentPath) {
const agentContent = await fileManager.readFile(agentPath);
const mdcPath = path.join(cursorRulesDir, `${agentId}.mdc`);
@@ -83,7 +103,8 @@ class IdeSetup {
}
mdcContent += "\n```\n\n";
mdcContent += "## File Reference\n\n";
mdcContent += `The complete agent definition is available in [.bmad-core/agents/${agentId}.md](mdc:.bmad-core/agents/${agentId}.md).\n\n`;
const relativePath = path.relative(installDir, agentPath).replace(/\\/g, '/');
mdcContent += `The complete agent definition is available in [${relativePath}](mdc:${relativePath}).\n\n`;
mdcContent += "## Usage\n\n";
mdcContent += `When the user types \`@${agentId}\`, activate this ${await this.getAgentTitle(
agentId,
@@ -107,14 +128,11 @@ class IdeSetup {
await fileManager.ensureDirectory(commandsDir);
for (const agentId of agents) {
// Check if .bmad-core is a subdirectory (full install) or if agents are in root (single agent install)
let agentPath = path.join(installDir, ".bmad-core", "agents", `${agentId}.md`);
if (!(await fileManager.pathExists(agentPath))) {
agentPath = path.join(installDir, "agents", `${agentId}.md`);
}
// Find the agent file
const agentPath = await this.findAgentPath(agentId, installDir);
const commandPath = path.join(commandsDir, `${agentId}.md`);
if (await fileManager.pathExists(agentPath)) {
if (agentPath) {
// Create command file with agent content
const agentContent = await fileManager.readFile(agentPath);
@@ -140,13 +158,10 @@ class IdeSetup {
await fileManager.ensureDirectory(windsurfRulesDir);
for (const agentId of agents) {
// Check if .bmad-core is a subdirectory (full install) or if agents are in root (single agent install)
let agentPath = path.join(installDir, ".bmad-core", "agents", `${agentId}.md`);
if (!(await fileManager.pathExists(agentPath))) {
agentPath = path.join(installDir, "agents", `${agentId}.md`);
}
// Find the agent file
const agentPath = await this.findAgentPath(agentId, installDir);
if (await fileManager.pathExists(agentPath)) {
if (agentPath) {
const agentContent = await fileManager.readFile(agentPath);
const mdPath = path.join(windsurfRulesDir, `${agentId}.md`);
@@ -170,7 +185,8 @@ class IdeSetup {
}
mdContent += "\n```\n\n";
mdContent += "## File Reference\n\n";
mdContent += `The complete agent definition is available in [.bmad-core/agents/${agentId}.md](.bmad-core/agents/${agentId}.md).\n\n`;
const relativePath = path.relative(installDir, agentPath).replace(/\\/g, '/');
mdContent += `The complete agent definition is available in [${relativePath}](${relativePath}).\n\n`;
mdContent += "## Usage\n\n";
mdContent += `When the user types \`@${agentId}\`, activate this ${await this.getAgentTitle(
agentId,
@@ -187,39 +203,86 @@ class IdeSetup {
return true;
}
async findAgentPath(agentId, installDir) {
// Try to find the agent file in various locations
const possiblePaths = [
path.join(installDir, ".bmad-core", "agents", `${agentId}.md`),
path.join(installDir, "agents", `${agentId}.md`)
];
// Also check expansion pack directories
const glob = require("glob");
const expansionDirs = glob.sync(".*/agents", { cwd: installDir });
for (const expDir of expansionDirs) {
possiblePaths.push(path.join(installDir, expDir, `${agentId}.md`));
}
for (const agentPath of possiblePaths) {
if (await fileManager.pathExists(agentPath)) {
return agentPath;
}
}
return null;
}
async getAllAgentIds(installDir) {
// Check if .bmad-core is a subdirectory (full install) or if agents are in root (single agent install)
const glob = require("glob");
const allAgentIds = [];
// Check core agents in .bmad-core or root
let agentsDir = path.join(installDir, ".bmad-core", "agents");
if (!(await fileManager.pathExists(agentsDir))) {
agentsDir = path.join(installDir, "agents");
}
const glob = require("glob");
const agentFiles = glob.sync("*.md", { cwd: agentsDir });
return agentFiles.map((file) => path.basename(file, ".md"));
if (await fileManager.pathExists(agentsDir)) {
const agentFiles = glob.sync("*.md", { cwd: agentsDir });
allAgentIds.push(...agentFiles.map((file) => path.basename(file, ".md")));
}
// Also check for expansion pack agents in dot folders
const expansionDirs = glob.sync(".*/agents", { cwd: installDir });
for (const expDir of expansionDirs) {
const fullExpDir = path.join(installDir, expDir);
const expAgentFiles = glob.sync("*.md", { cwd: fullExpDir });
allAgentIds.push(...expAgentFiles.map((file) => path.basename(file, ".md")));
}
// Remove duplicates
return [...new Set(allAgentIds)];
}
async getAgentTitle(agentId, installDir) {
// Try to read the actual agent file to get the title
let agentPath = path.join(installDir, ".bmad-core", "agents", `${agentId}.md`);
if (!(await fileManager.pathExists(agentPath))) {
agentPath = path.join(installDir, "agents", `${agentId}.md`);
// Try to find the agent file in various locations
const possiblePaths = [
path.join(installDir, ".bmad-core", "agents", `${agentId}.md`),
path.join(installDir, "agents", `${agentId}.md`)
];
// Also check expansion pack directories
const glob = require("glob");
const expansionDirs = glob.sync(".*/agents", { cwd: installDir });
for (const expDir of expansionDirs) {
possiblePaths.push(path.join(installDir, expDir, `${agentId}.md`));
}
if (await fileManager.pathExists(agentPath)) {
try {
const agentContent = await fileManager.readFile(agentPath);
const yamlMatch = agentContent.match(/```ya?ml\n([\s\S]*?)```/);
if (yamlMatch) {
const yaml = yamlMatch[1];
const titleMatch = yaml.match(/title:\s*(.+)/);
if (titleMatch) {
return titleMatch[1].trim();
for (const agentPath of possiblePaths) {
if (await fileManager.pathExists(agentPath)) {
try {
const agentContent = await fileManager.readFile(agentPath);
const yamlMatch = agentContent.match(/```ya?ml\n([\s\S]*?)```/);
if (yamlMatch) {
const yaml = yamlMatch[1];
const titleMatch = yaml.match(/title:\s*(.+)/);
if (titleMatch) {
return titleMatch[1].trim();
}
}
} catch (error) {
console.warn(`Failed to read agent title for ${agentId}: ${error.message}`);
}
} catch (error) {
console.warn(`Failed to read agent title for ${agentId}: ${error.message}`);
}
}
@@ -250,40 +313,9 @@ class IdeSetup {
// Create new modes content
let newModesContent = "";
// Define file permissions for each agent type
const agentPermissions = {
analyst: {
fileRegex: "\\.(md|txt)$",
description: "Documentation and text files",
},
pm: {
fileRegex: "\\.(md|txt)$",
description: "Product documentation",
},
architect: {
fileRegex: "\\.(md|txt|yml|yaml|json)$",
description: "Architecture docs and configs",
},
dev: null, // Full edit access
qa: {
fileRegex: "\\.(test|spec)\\.(js|ts|jsx|tsx)$|\\.md$",
description: "Test files and documentation",
},
"ux-expert": {
fileRegex: "\\.(md|css|scss|html|jsx|tsx)$",
description: "Design-related files",
},
po: {
fileRegex: "\\.(md|txt)$",
description: "Story and requirement docs",
},
sm: {
fileRegex: "\\.(md|txt)$",
description: "Process and planning docs",
},
"bmad-orchestrator": null, // Full edit access
"bmad-master": null, // Full edit access
};
// Load dynamic agent permissions from configuration
const config = await this.loadIdeAgentConfig();
const agentPermissions = config['roo-permissions'] || {};
for (const agentId of agents) {
// Skip if already exists
@@ -293,12 +325,9 @@ class IdeSetup {
}
// Read agent file to extract all information
let agentPath = path.join(installDir, ".bmad-core", "agents", `${agentId}.md`);
if (!(await fileManager.pathExists(agentPath))) {
agentPath = path.join(installDir, "agents", `${agentId}.md`);
}
const agentPath = await this.findAgentPath(agentId, installDir);
if (await fileManager.pathExists(agentPath)) {
if (agentPath) {
const agentContent = await fileManager.readFile(agentPath);
// Extract YAML content
@@ -324,7 +353,9 @@ class IdeSetup {
newModesContent += ` name: '${icon} ${title}'\n`;
newModesContent += ` roleDefinition: ${roleDefinition}\n`;
newModesContent += ` whenToUse: ${whenToUse}\n`;
newModesContent += ` customInstructions: CRITICAL Read the full YML from .bmad-core/agents/${agentId}.md start activation to alter your state of being follow startup section instructions stay in this being until told to exit this mode\n`;
// Get relative path from installDir to agent file
const relativePath = path.relative(installDir, agentPath).replace(/\\/g, '/');
newModesContent += ` customInstructions: CRITICAL Read the full YML from ${relativePath} start activation to alter your state of being follow startup section instructions stay in this being until told to exit this mode\n`;
newModesContent += ` groups:\n`;
newModesContent += ` - read\n`;
@@ -369,28 +400,15 @@ class IdeSetup {
await fileManager.ensureDirectory(clineRulesDir);
// Define agent order for numeric prefixes
const agentOrder = {
'bmad-master': 1,
'bmad-orchestrator': 2,
'pm': 3,
'analyst': 4,
'architect': 5,
'po': 6,
'sm': 7,
'dev': 8,
'qa': 9,
'ux-expert': 10
};
// Load dynamic agent ordering from configuration
const config = await this.loadIdeAgentConfig();
const agentOrder = config['cline-order'] || {};
for (const agentId of agents) {
// Check if .bmad-core is a subdirectory (full install) or if agents are in root (single agent install)
let agentPath = path.join(installDir, ".bmad-core", "agents", `${agentId}.md`);
if (!(await fileManager.pathExists(agentPath))) {
agentPath = path.join(installDir, "agents", `${agentId}.md`);
}
// Find the agent file
const agentPath = await this.findAgentPath(agentId, installDir);
if (await fileManager.pathExists(agentPath)) {
if (agentPath) {
const agentContent = await fileManager.readFile(agentPath);
// Get numeric prefix for ordering
@@ -418,7 +436,8 @@ class IdeSetup {
mdContent += `- Always maintain consistency with project documentation in .bmad-core/\n`;
mdContent += `- Follow the agent's specific guidelines and constraints\n`;
mdContent += `- Update relevant project files when making changes\n`;
mdContent += `- Reference the complete agent definition in [.bmad-core/agents/${agentId}.md](.bmad-core/agents/${agentId}.md)\n\n`;
const relativePath = path.relative(installDir, agentPath).replace(/\\/g, '/');
mdContent += `- Reference the complete agent definition in [${relativePath}](${relativePath})\n\n`;
mdContent += "## Usage\n\n";
mdContent += `Type \`@${agentId}\` to activate this ${await this.getAgentTitle(agentId, installDir)} persona.\n`;
@@ -444,12 +463,9 @@ class IdeSetup {
for (const agentId of agents) {
// Find the source agent file
let agentPath = path.join(installDir, ".bmad-core", "agents", `${agentId}.md`);
if (!(await fileManager.pathExists(agentPath))) {
agentPath = path.join(installDir, "agents", `${agentId}.md`);
}
const agentPath = await this.findAgentPath(agentId, installDir);
if (await fileManager.pathExists(agentPath)) {
if (agentPath) {
const agentContent = await fileManager.readFile(agentPath);
const contextFilePath = path.join(agentsContextDir, `${agentId}.md`);