diff --git a/docs/reference/modules.md b/docs/reference/modules.md index 43a15d704..7c92ee5eb 100644 --- a/docs/reference/modules.md +++ b/docs/reference/modules.md @@ -73,7 +73,7 @@ Enterprise-grade test strategy, automation guidance, and release gate decisions ## BMad Automator (Experimental) -Automates the BMad story build loop with a pure skill bundle sourced from the separate Automator repository. +Automates the BMad story build loop with a self-contained skill bundle sourced from the separate Automator repository's root `skills/` folder. - **Code:** `baut` - **npm:** [`bmad-story-automator`](https://www.npmjs.com/package/bmad-story-automator) diff --git a/test/test-installation-components.js b/test/test-installation-components.js index 503fcfb3a..aaa9cef84 100644 --- a/test/test-installation-components.js +++ b/test/test-installation-components.js @@ -124,7 +124,7 @@ async function createAutomatorBmadFixture() { async function createAutomatorSourceRootFixture() { const repoRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'bmad-automator-source-')); - const sourceRoot = path.join(repoRoot, 'payload', '.claude', 'skills'); + const sourceRoot = path.join(repoRoot, 'skills'); for (const skillName of ['bmad-story-automator', 'bmad-story-automator-review']) { const skillDir = path.join(sourceRoot, skillName); @@ -135,10 +135,11 @@ async function createAutomatorSourceRootFixture() { ); } - await fs.ensureDir(path.join(repoRoot, 'source', 'scripts')); - await fs.writeFile(path.join(repoRoot, 'source', 'scripts', 'story-automator'), '#!/usr/bin/env bash\n'); - await fs.ensureDir(path.join(repoRoot, 'source', 'src', 'story_automator')); - await fs.writeFile(path.join(repoRoot, 'source', 'src', 'story_automator', 'cli.py'), 'def main():\n return 0\n'); + const storySkillDir = path.join(sourceRoot, 'bmad-story-automator'); + await fs.ensureDir(path.join(storySkillDir, 'scripts')); + await fs.writeFile(path.join(storySkillDir, 'scripts', 'story-automator'), '#!/usr/bin/env bash\n'); + await fs.ensureDir(path.join(storySkillDir, 'src', 'story_automator')); + await fs.writeFile(path.join(storySkillDir, 'src', 'story_automator', 'cli.py'), 'def main():\n return 0\n'); return { repoRoot, sourceRoot }; } @@ -3592,7 +3593,7 @@ async function runTests() { const automatorInfo42 = await externalManager42.getModuleByCode('baut'); assert(automatorInfo42 !== null, 'BMad Automator is registered as an external module'); assert(automatorInfo42.type === 'experimental', 'BMad Automator is marked experimental'); - assert(automatorInfo42.sourceRoot === 'payload/.claude/skills', 'BMad Automator uses source-root for pure skill payload'); + assert(automatorInfo42.sourceRoot === 'skills', 'BMad Automator uses root skills source-root for pure skill payload'); assert(automatorInfo42.defaultChannel === 'next', 'BMad Automator defaults to next for latest payload compatibility fixes'); assert( automatorInfo42.installTargets.length === 1 && automatorInfo42.installTargets.includes('claude-code'), @@ -3629,11 +3630,11 @@ async function runTests() { await officialModules42.install('baut', runtimeBmadDir42, null, { skipModuleInstaller: true, silent: true }); assert( await fs.pathExists(path.join(runtimeBmadDir42, 'baut', 'bmad-story-automator', 'scripts', 'story-automator')), - 'BMad Automator source-root install includes runtime helper', + 'BMad Automator self-contained skill install includes runtime helper', ); assert( await fs.pathExists(path.join(runtimeBmadDir42, 'baut', 'bmad-story-automator', 'src', 'story_automator', 'cli.py')), - 'BMad Automator source-root install includes Python runtime source', + 'BMad Automator self-contained skill install includes Python runtime source', ); await fs.remove(runtimeTargetRoot42).catch(() => {}); runtimeTargetRoot42 = null; diff --git a/tools/installer/modules/official-modules.js b/tools/installer/modules/official-modules.js index b93d3c774..615daba86 100644 --- a/tools/installer/modules/official-modules.js +++ b/tools/installer/modules/official-modules.js @@ -301,7 +301,6 @@ class OfficialModules { } await this.copyModuleWithFiltering(sourcePath, targetPath, fileTrackingCallback, options.moduleConfig); - await this.copyAutomatorRuntimeIfNeeded(moduleName, sourcePath, targetPath, fileTrackingCallback); if (!options.skipModuleInstaller) { await this.createModuleDirectories(moduleName, bmadDir, options); @@ -573,29 +572,6 @@ class OfficialModules { } } - async copyAutomatorRuntimeIfNeeded(moduleName, sourcePath, targetPath, fileTrackingCallback = null) { - if (moduleName !== 'baut') return; - - const storyTarget = path.join(targetPath, 'bmad-story-automator'); - if (!(await fs.pathExists(path.join(storyTarget, 'SKILL.md')))) return; - - const repoRoot = path.resolve(sourcePath, '..', '..', '..'); - const runtimeRoot = path.join(repoRoot, 'source'); - const runtimeParts = [ - ['scripts', 'scripts'], - ['src', 'src'], - ]; - - for (const [sourceRel, targetRel] of runtimeParts) { - const sourceDir = path.join(runtimeRoot, sourceRel); - const targetDir = path.join(storyTarget, targetRel); - if (!(await fs.pathExists(sourceDir))) { - throw new Error(`BMad Automator runtime source missing: source/${sourceRel}`); - } - await this.copyModuleWithFiltering(sourceDir, targetDir, fileTrackingCallback); - } - } - /** * Create directories declared in module.yaml's `directories` key * This replaces the security-risky module installer pattern with declarative config diff --git a/tools/installer/modules/registry-fallback.yaml b/tools/installer/modules/registry-fallback.yaml index 941560e2b..3c5208cf0 100644 --- a/tools/installer/modules/registry-fallback.yaml +++ b/tools/installer/modules/registry-fallback.yaml @@ -53,7 +53,7 @@ modules: bmad-automator: url: https://github.com/bmad-code-org/bmad-automator - source-root: payload/.claude/skills + source-root: skills code: baut name: "BMad Automator (Experimental)" description: "Experimental pure-skill story automation. Runs only from Claude Code; supports Claude Code and Codex worker sessions; requires tmux on macOS."