Close the remaining capability gaps where the skill's custom-module install was
narrower than the canonical installer (tools/installer/modules/custom-module-manager.js
+ channel-resolver.js). The new-spec install path was already shared code and the
legacy resolver a faithful port, so this targets only the independently-implemented
source/channel/cache plumbing.
- source.mjs parseSource: accept `owner/repo@ref`, and browser-style deep-path
git URLs (tree/blob, GitLab `-/tree`, Gitea `src/branch`, `?path=`), extracting
the embedded ref + repo subdirectory — so a module in a monorepo subfolder
installs directly. URL-based parsing handles Azure DevOps `_git`, nested
groups, and dotted repo names.
- lib/cache.mjs: shared clone cache at ~/.bmad/cache/custom-modules/<host>/<owner>/<repo>/
with .bmad-source.json/.bmad-channel.json metadata, matching the installer
(reuse on matching ref, fetch/refresh otherwise, keep stale copy on fetch
failure). materializeSource copies the module root out of the cache into a
throwaway temp tree so the cache is never mutated.
- lib/channel-resolver.mjs: node:-only port of resolveChannel (stable/next/pinned);
`stable` resolves the latest non-prerelease GitHub tag, falling back to next.
semver-lite gains prerelease/compare/rcompare so it stays registry-free.
- install.mjs/update.mjs: resolve channel+ref before clone; update re-resolves the
channel the module was installed with.
Tests + CI:
- test/test-bmad-module-source.mjs: unit coverage for parseSource, semver-lite, and
channel-resolver, at parity with the installer's test-parse-source-urls.js /
test-installer-channels.js.
- Wire the skill's unit test (test:skill-source) and its end-to-end integration
test (test:skill) into npm test, npm run quality, and the quality.yaml CI job —
the integration test was previously committed but only ever run by hand.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The skill is copied into _bmad/core/skills/bmad-module/ by the installer,
which strips node_modules, ships no package.json under the skill, and never
runs npm install there. Bare `import 'yaml'`/`import 'semver'` therefore
crashed at module load (ERR_MODULE_NOT_FOUND, exit 1) before any structured
exit code could fire. Every other installed script is zero-third-party-dep.
- Vendor the real yaml@2.8.4 as a deterministic esbuild single-file bundle
(scripts/lib/vendor/yaml.mjs), imported by relative path. Guarantees
byte-identical manifest.yaml round-trips with BMAD core's writer, which uses
the same library + options (tools/installer/core/manifest.js).
- Drop semver for a node:-only semver-lite.mjs (valid/validRange),
parity-tested against the real semver across 469 cases (400 fuzzed).
- Fix a third bare yaml import that the original report missed (frontmatter.mjs).
- Make bmad-module.mjs a zero-import launcher that maps any load failure to a
new documented EXIT.TOOLING (5) with reinstall guidance instead of leaking a
raw ESM stack trace; the verb dispatcher moves to cli.mjs.
- Enforce vendor freshness so a yaml/esbuild bump can't ship a stale bundle:
build-vendor.mjs --check is wired into npm test (pre-commit), npm run quality,
and the quality.yaml CI validate job. Adds vendor:build / vendor:check scripts.
- Ignore the generated vendor/ dir in eslint + prettier; document the rationale
in SKILL.md, README.md, and vendor/README.md.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>