From e0ea6a05008ecafdfb720bc74031344560f2408a Mon Sep 17 00:00:00 2001 From: Brian Date: Sat, 28 Mar 2026 00:33:10 -0500 Subject: [PATCH] fix: support skills/ folder as module source location (#2149) The installer now finds module.yaml in both skills/ and src/ directories, including one level deep in subfolders. Updates bmb module-definition to skills/module.yaml to match its actual structure. --- .../installer/external-official-modules.yaml | 2 +- tools/installer/modules/external-manager.js | 37 +++++++++++++++++-- 2 files changed, 35 insertions(+), 4 deletions(-) diff --git a/tools/installer/external-official-modules.yaml b/tools/installer/external-official-modules.yaml index 6a2fa259d..b62f3dc21 100644 --- a/tools/installer/external-official-modules.yaml +++ b/tools/installer/external-official-modules.yaml @@ -4,7 +4,7 @@ modules: bmad-builder: url: https://github.com/bmad-code-org/bmad-builder - module-definition: src/module.yaml + module-definition: skills/module.yaml code: bmb name: "BMad Builder" description: "Agent and Builder" diff --git a/tools/installer/modules/external-manager.js b/tools/installer/modules/external-manager.js index 467520163..fceb94e22 100644 --- a/tools/installer/modules/external-manager.js +++ b/tools/installer/modules/external-manager.js @@ -313,10 +313,41 @@ class ExternalModuleManager { // The module-definition specifies the path to module.yaml relative to repo root // We need to return the directory containing module.yaml - const moduleDefinitionPath = moduleInfo.moduleDefinition; // e.g., 'src/module.yaml' - const moduleDir = path.dirname(path.join(cloneDir, moduleDefinitionPath)); + const moduleDefinitionPath = moduleInfo.moduleDefinition; // e.g., 'skills/module.yaml' + const configuredPath = path.join(cloneDir, moduleDefinitionPath); - return moduleDir; + if (await fs.pathExists(configuredPath)) { + return path.dirname(configuredPath); + } + + // Fallback: search skills/ and src/ (root level and one level deep for subfolders) + for (const dir of ['skills', 'src']) { + const rootCandidate = path.join(cloneDir, dir, 'module.yaml'); + if (await fs.pathExists(rootCandidate)) { + return path.dirname(rootCandidate); + } + const dirPath = path.join(cloneDir, dir); + if (await fs.pathExists(dirPath)) { + const entries = await fs.readdir(dirPath, { withFileTypes: true }); + for (const entry of entries) { + if (entry.isDirectory()) { + const subCandidate = path.join(dirPath, entry.name, 'module.yaml'); + if (await fs.pathExists(subCandidate)) { + return path.dirname(subCandidate); + } + } + } + } + } + + // Check repo root as last fallback + const rootCandidate = path.join(cloneDir, 'module.yaml'); + if (await fs.pathExists(rootCandidate)) { + return path.dirname(rootCandidate); + } + + // Nothing found: return configured path (preserves old behavior for error messaging) + return path.dirname(configuredPath); } }