From dfae0291ca0fa9c1b730a8c5168d487709d8f8f3 Mon Sep 17 00:00:00 2001 From: pbean Date: Thu, 28 May 2026 19:29:40 -0700 Subject: [PATCH] fix(bmad-module): copy file-typed agents/commands entries on install MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The copy planner treated every skills/agents/commands entry as a directory and ran it through addDirRecursive, which lists files *under* the path. For a subagent declared as a file (e.g. `"agents": ["./agents/foo.md"]` — a standard Claude-Code shape) that listed nothing, so the agent was silently dropped from the install even though rewriteManifestPaths already remapped it to `./agents/foo.md`. Stat each entry and branch: directories recurse as before, files are queued directly (honoring the ignore matcher). Verified by the comprehensive fixture's changelog-archivist.md agent. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../bmad-module/scripts/lib/install-plan.mjs | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/core-skills/bmad-module/scripts/lib/install-plan.mjs b/src/core-skills/bmad-module/scripts/lib/install-plan.mjs index 66f9a18f4..485dad460 100644 --- a/src/core-skills/bmad-module/scripts/lib/install-plan.mjs +++ b/src/core-skills/bmad-module/scripts/lib/install-plan.mjs @@ -248,8 +248,18 @@ export async function buildCopyPlan(sourceDir, manifest, ignoreMatch) { const srcRel = stripDotSlash(declared); if (!srcRel) continue; const destRel = `${destPrefix}/${path.posix.basename(srcRel)}`; - await addDirRecursive(srcRel, destRel); - if (destPrefix === 'skills') skillDestDirs.push(destRel); + // Entries may be directories (skills, agent packs) or single files + // (e.g. a subagent declared as `./agents/foo.md`). Stat to branch; + // rewriteManifestPaths() remaps both to `/`. + try { + const stat = await fs.stat(path.join(sourceDir, srcRel)); + if (stat.isDirectory()) { + await addDirRecursive(srcRel, destRel); + if (destPrefix === 'skills') skillDestDirs.push(destRel); + } else if (stat.isFile() && (!ignoreMatch || !ignoreMatch(srcRel))) addFile(srcRel, destRel); + } catch { + /* missing — validateDeclaredPaths surfaces declared misses */ + } } }