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 <git-url> (cached at
~/.bmad/cache/custom-modules/<host>/<owner>/<repo>/) could not be located on
re-install runs. This caused warnings during npx bmad-method install:

  [warn] collectAgentsFromModuleYaml: could not locate module.yaml for '<name>'
  [warn] writeCentralConfig: could not locate module.yaml for '<name>'

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
This commit is contained in:
Brian Madison 2026-04-26 13:32:32 -05:00
parent be85e5b4a0
commit b695d1e1bd
1 changed files with 35 additions and 2 deletions

View File

@ -1,5 +1,6 @@
const path = require('node:path'); const path = require('node:path');
const os = require('node:os'); const os = require('node:os');
const yaml = require('yaml');
const fs = require('./fs-native'); const fs = require('./fs-native');
/** /**
@ -86,8 +87,11 @@ function getExternalModuleCachePath(moduleName, ...segments) {
* Built-in modules (core, bmm) live under <src>. External official modules are * Built-in modules (core, bmm) live under <src>. External official modules are
* cloned into ~/.bmad/cache/external-modules/<name>/ with varying internal * cloned into ~/.bmad/cache/external-modules/<name>/ with varying internal
* layouts (some at src/module.yaml, some at skills/module.yaml, some nested). * 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 * Url-source custom modules are cloned into ~/.bmad/cache/custom-modules/<host>/<owner>/<repo>/
* CustomModuleManager resolution cache set during the same install run. * 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 * This mirrors the candidate-path search in
* ExternalModuleManager.findExternalModuleSource but performs no git/network * ExternalModuleManager.findExternalModuleSource but performs no git/network
* work, which keeps it safe to call during manifest writing. * work, which keeps it safe to call during manifest writing.
@ -150,6 +154,35 @@ async function resolveInstalledModuleYaml(moduleName) {
// Resolution cache unavailable — continue // 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; return null;
} }