diff --git a/test-ide-paths.js b/test-ide-paths.js new file mode 100644 index 00000000..cb29d56d --- /dev/null +++ b/test-ide-paths.js @@ -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(''); +}); \ No newline at end of file diff --git a/tools/installer/config/ide-agent-config.yml b/tools/installer/config/ide-agent-config.yml new file mode 100644 index 00000000..c4fa7d0f --- /dev/null +++ b/tools/installer/config/ide-agent-config.yml @@ -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 \ No newline at end of file diff --git a/tools/installer/lib/ide-setup.js b/tools/installer/lib/ide-setup.js index 49d541c1..aea2b891 100644 --- a/tools/installer/lib/ide-setup.js +++ b/tools/installer/lib/ide-setup.js @@ -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`);