From b695d1e1bd1c309910ed3e73ff225286450cb303 Mon Sep 17 00:00:00 2001 From: Brian Madison Date: Sun, 26 Apr 2026 13:32:32 -0500 Subject: [PATCH] fix(installer): resolve url-source custom modules from custom-modules cache resolveInstalledModuleYaml previously only searched ~/.bmad/cache/external-modules/, so modules installed via --custom-source (cached at ~/.bmad/cache/custom-modules////) could not be located on re-install runs. This caused warnings during npx bmad-method install: [warn] collectAgentsFromModuleYaml: could not locate module.yaml for '' [warn] writeCentralConfig: could not locate module.yaml for '' Adds a fallback that walks the custom-modules cache via _findCacheRepoRoots (identifying repo roots by .bmad-source.json or .claude-plugin/, not marketplace.json, so direct-mode modules are also covered), reuses the same searchRoot candidate-path logic, and matches by the discovered yaml's code or name field. Works without needing _resolutionCache to be populated, which fixes the re-install scenario where no --custom-source flag is passed. Closes #2312 --- tools/installer/project-root.js | 37 +++++++++++++++++++++++++++++++-- 1 file changed, 35 insertions(+), 2 deletions(-) diff --git a/tools/installer/project-root.js b/tools/installer/project-root.js index 123bd5978..539d03b21 100644 --- a/tools/installer/project-root.js +++ b/tools/installer/project-root.js @@ -1,5 +1,6 @@ const path = require('node:path'); const os = require('node:os'); +const yaml = require('yaml'); const fs = require('./fs-native'); /** @@ -86,8 +87,11 @@ function getExternalModuleCachePath(moduleName, ...segments) { * Built-in modules (core, bmm) live under . External official modules are * cloned into ~/.bmad/cache/external-modules// with varying internal * layouts (some at src/module.yaml, some at skills/module.yaml, some nested). - * Local custom-source modules are not cached; their path is read from the - * CustomModuleManager resolution cache set during the same install run. + * Url-source custom modules are cloned into ~/.bmad/cache/custom-modules//// + * and are resolved by walking the cache and matching `code` or `name` from the + * discovered module.yaml. Local custom-source modules are not cached; their + * path is read from the CustomModuleManager resolution cache set during the + * same install run. * This mirrors the candidate-path search in * ExternalModuleManager.findExternalModuleSource but performs no git/network * work, which keeps it safe to call during manifest writing. @@ -150,6 +154,35 @@ async function resolveInstalledModuleYaml(moduleName) { // Resolution cache unavailable — continue } + // Fallback: url-source custom modules cloned to ~/.bmad/cache/custom-modules/. + // Walk every cached repo, locate its module.yaml via searchRoot, and match by + // the yaml's `code` or `name` field. This works on re-install runs where + // _resolutionCache is empty and covers both discovery-mode (with marketplace.json) + // and direct-mode modules, since we identify repo roots by .bmad-source.json + // (written by cloneRepo) or .claude-plugin/ rather than by marketplace.json. + try { + const customCacheDir = path.join(os.homedir(), '.bmad', 'cache', 'custom-modules'); + if (await fs.pathExists(customCacheDir)) { + const { CustomModuleManager } = require('./modules/custom-module-manager'); + const customMgr = new CustomModuleManager(); + const repoRoots = await customMgr._findCacheRepoRoots(customCacheDir); + for (const { repoPath } of repoRoots) { + const candidate = await searchRoot(repoPath); + if (!candidate) continue; + try { + const parsed = yaml.parse(await fs.readFile(candidate, 'utf8')); + if (parsed && (parsed.code === moduleName || parsed.name === moduleName)) { + return candidate; + } + } catch { + // Malformed yaml — skip + } + } + } + } catch { + // Custom-modules cache walk failed — continue + } + return null; }