From 7d6180b6a2cba0340e19d7dc44f2e6c6cad36f0d Mon Sep 17 00:00:00 2001 From: Fredrik Sandberg Date: Sat, 21 Feb 2026 16:28:49 +0100 Subject: [PATCH] fix installer to discover and install local src/modules - listAvailable() now scans src/modules/ so local modules appear in the UI - findModuleSource() checks src/modules/{code} before falling back to GitHub - Handle menu object format (menu.items) alongside legacy flat array in vendorCrossModuleWorkflows, agent compiler, and agent analyzer Co-Authored-By: Claude Sonnet 4.6 --- tools/cli/installers/lib/modules/manager.js | 25 ++++++++++++++++++++- tools/cli/lib/agent-analyzer.js | 4 +++- tools/cli/lib/agent/compiler.js | 9 +++++--- 3 files changed, 33 insertions(+), 5 deletions(-) diff --git a/tools/cli/installers/lib/modules/manager.js b/tools/cli/installers/lib/modules/manager.js index f162593b7..15be58014 100644 --- a/tools/cli/installers/lib/modules/manager.js +++ b/tools/cli/installers/lib/modules/manager.js @@ -204,6 +204,21 @@ class ModuleManager { } } + // Scan src/modules/ for locally-defined modules + const localModulesPath = getSourcePath('modules'); + if (await fs.pathExists(localModulesPath)) { + const moduleEntries = await fs.readdir(localModulesPath, { withFileTypes: true }); + for (const entry of moduleEntries) { + if (entry.isDirectory()) { + const modulePath = path.join(localModulesPath, entry.name); + const moduleInfo = await this.getModuleInfo(modulePath, entry.name, 'src/modules'); + if (moduleInfo && !modules.some((m) => m.id === moduleInfo.id)) { + modules.push(moduleInfo); + } + } + } + } + // Check for cached custom modules in _config/custom/ if (this.bmadDir) { const customCacheDir = path.join(this.bmadDir, '_config', 'custom'); @@ -308,6 +323,12 @@ class ModuleManager { } } + // Check src/modules/{moduleCode} for locally-defined modules (takes priority over external) + const localModulePath = getModulePath(moduleCode); + if (await fs.pathExists(localModulePath)) { + return localModulePath; + } + // Check external official modules const externalSource = await this.findExternalModuleSource(moduleCode, options); if (externalSource) { @@ -1150,7 +1171,9 @@ class ModuleManager { const agentYaml = yaml.parse(await fs.readFile(agentPath, 'utf8')); // Check if agent has menu items with workflow-install - const menuItems = agentYaml?.agent?.menu || []; + // menu can be an array OR an object with an `items` array + const menu = agentYaml?.agent?.menu; + const menuItems = Array.isArray(menu) ? menu : menu?.items || []; const workflowInstallItems = menuItems.filter((item) => item['workflow-install']); if (workflowInstallItems.length === 0) { diff --git a/tools/cli/lib/agent-analyzer.js b/tools/cli/lib/agent-analyzer.js index ae834a098..23d1cf847 100644 --- a/tools/cli/lib/agent-analyzer.js +++ b/tools/cli/lib/agent-analyzer.js @@ -23,7 +23,9 @@ class AgentAnalyzer { } // Analyze menu items (support both 'menu' and legacy 'commands') - const menuItems = agentYaml.agent?.menu || agentYaml.agent?.commands || []; + // menu can be an array or an object with an `items` array + const rawMenu = agentYaml.agent?.menu || agentYaml.agent?.commands; + const menuItems = Array.isArray(rawMenu) ? rawMenu : rawMenu?.items || []; for (const item of menuItems) { // Track the menu item diff --git a/tools/cli/lib/agent/compiler.js b/tools/cli/lib/agent/compiler.js index f9f71baab..acd8422d7 100644 --- a/tools/cli/lib/agent/compiler.js +++ b/tools/cli/lib/agent/compiler.js @@ -309,8 +309,10 @@ async function compileToXml(agentYaml, agentName = '', targetPath = '') { xml += buildMemoriesXml(agent.memories); } - // Menu section - xml += buildMenuXml(agent.menu || []); + // Menu section — menu can be an array or an object with an `items` array + const menuData = agent.menu; + const menuItems = Array.isArray(menuData) ? menuData : menuData?.items || []; + xml += buildMenuXml(menuItems); // Closing agent tag xml += '\n'; @@ -397,7 +399,8 @@ async function compileAgent(yamlContent, answers = {}, agentName = '', targetPat // For menu: append to existing or create new if (customizations.menu) { - const existing = agentYaml.agent.menu || []; + const existingMenu = agentYaml.agent.menu; + const existing = Array.isArray(existingMenu) ? existingMenu : existingMenu?.items || []; agentYaml.agent.menu = [...existing, ...customizations.menu]; }