Compare commits
3 Commits
a342d941fb
...
f11abfe256
| Author | SHA1 | Date |
|---|---|---|
|
|
f11abfe256 | |
|
|
0a7329331b | |
|
|
3b297fc964 |
|
|
@ -73,7 +73,7 @@ Enterprise-grade test strategy, automation guidance, and release gate decisions
|
||||||
|
|
||||||
## BMad Automator (Experimental)
|
## 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`
|
- **Code:** `baut`
|
||||||
- **npm:** [`bmad-story-automator`](https://www.npmjs.com/package/bmad-story-automator)
|
- **npm:** [`bmad-story-automator`](https://www.npmjs.com/package/bmad-story-automator)
|
||||||
|
|
|
||||||
|
|
@ -124,7 +124,7 @@ async function createAutomatorBmadFixture() {
|
||||||
|
|
||||||
async function createAutomatorSourceRootFixture() {
|
async function createAutomatorSourceRootFixture() {
|
||||||
const repoRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'bmad-automator-source-'));
|
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']) {
|
for (const skillName of ['bmad-story-automator', 'bmad-story-automator-review']) {
|
||||||
const skillDir = path.join(sourceRoot, skillName);
|
const skillDir = path.join(sourceRoot, skillName);
|
||||||
|
|
@ -135,10 +135,11 @@ async function createAutomatorSourceRootFixture() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
await fs.ensureDir(path.join(repoRoot, 'source', 'scripts'));
|
const storySkillDir = path.join(sourceRoot, 'bmad-story-automator');
|
||||||
await fs.writeFile(path.join(repoRoot, 'source', 'scripts', 'story-automator'), '#!/usr/bin/env bash\n');
|
await fs.ensureDir(path.join(storySkillDir, 'scripts'));
|
||||||
await fs.ensureDir(path.join(repoRoot, 'source', 'src', 'story_automator'));
|
await fs.writeFile(path.join(storySkillDir, 'scripts', 'story-automator'), '#!/usr/bin/env bash\n');
|
||||||
await fs.writeFile(path.join(repoRoot, 'source', 'src', 'story_automator', 'cli.py'), 'def main():\n return 0\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 };
|
return { repoRoot, sourceRoot };
|
||||||
}
|
}
|
||||||
|
|
@ -3592,7 +3593,7 @@ async function runTests() {
|
||||||
const automatorInfo42 = await externalManager42.getModuleByCode('baut');
|
const automatorInfo42 = await externalManager42.getModuleByCode('baut');
|
||||||
assert(automatorInfo42 !== null, 'BMad Automator is registered as an external module');
|
assert(automatorInfo42 !== null, 'BMad Automator is registered as an external module');
|
||||||
assert(automatorInfo42.type === 'experimental', 'BMad Automator is marked experimental');
|
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.defaultChannel === 'next', 'BMad Automator defaults to next for latest payload compatibility fixes');
|
||||||
assert(
|
assert(
|
||||||
automatorInfo42.installTargets.length === 1 && automatorInfo42.installTargets.includes('claude-code'),
|
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 });
|
await officialModules42.install('baut', runtimeBmadDir42, null, { skipModuleInstaller: true, silent: true });
|
||||||
assert(
|
assert(
|
||||||
await fs.pathExists(path.join(runtimeBmadDir42, 'baut', 'bmad-story-automator', 'scripts', 'story-automator')),
|
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(
|
assert(
|
||||||
await fs.pathExists(path.join(runtimeBmadDir42, 'baut', 'bmad-story-automator', 'src', 'story_automator', 'cli.py')),
|
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(() => {});
|
await fs.remove(runtimeTargetRoot42).catch(() => {});
|
||||||
runtimeTargetRoot42 = null;
|
runtimeTargetRoot42 = null;
|
||||||
|
|
|
||||||
|
|
@ -247,6 +247,8 @@ class ManifestGenerator {
|
||||||
const moduleYamlPath = await resolveInstalledModuleYaml(moduleName);
|
const moduleYamlPath = await resolveInstalledModuleYaml(moduleName);
|
||||||
if (!moduleYamlPath) {
|
if (!moduleYamlPath) {
|
||||||
if (await this._isSkillOnlyModule(moduleName)) {
|
if (await this._isSkillOnlyModule(moduleName)) {
|
||||||
|
// Pure source-root skill bundles intentionally ship no module.yaml,
|
||||||
|
// so there is no agent roster to emit and no warning to surface.
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
// External modules live in ~/.bmad/cache/external-modules, not src/modules.
|
// External modules live in ~/.bmad/cache/external-modules, not src/modules.
|
||||||
|
|
@ -445,6 +447,8 @@ class ManifestGenerator {
|
||||||
const moduleYamlPath = await resolveInstalledModuleYaml(moduleName);
|
const moduleYamlPath = await resolveInstalledModuleYaml(moduleName);
|
||||||
if (!moduleYamlPath) {
|
if (!moduleYamlPath) {
|
||||||
if (await this._isSkillOnlyModule(moduleName)) {
|
if (await this._isSkillOnlyModule(moduleName)) {
|
||||||
|
// Pure source-root skill bundles intentionally ship no module.yaml,
|
||||||
|
// so there is no installer config schema to read or warning to surface.
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
console.warn(
|
console.warn(
|
||||||
|
|
|
||||||
|
|
@ -301,7 +301,6 @@ class OfficialModules {
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.copyModuleWithFiltering(sourcePath, targetPath, fileTrackingCallback, options.moduleConfig);
|
await this.copyModuleWithFiltering(sourcePath, targetPath, fileTrackingCallback, options.moduleConfig);
|
||||||
await this.copyAutomatorRuntimeIfNeeded(moduleName, sourcePath, targetPath, fileTrackingCallback);
|
|
||||||
|
|
||||||
if (!options.skipModuleInstaller) {
|
if (!options.skipModuleInstaller) {
|
||||||
await this.createModuleDirectories(moduleName, bmadDir, options);
|
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
|
* Create directories declared in module.yaml's `directories` key
|
||||||
* This replaces the security-risky module installer pattern with declarative config
|
* This replaces the security-risky module installer pattern with declarative config
|
||||||
|
|
|
||||||
|
|
@ -53,7 +53,7 @@ modules:
|
||||||
|
|
||||||
bmad-automator:
|
bmad-automator:
|
||||||
url: https://github.com/bmad-code-org/bmad-automator
|
url: https://github.com/bmad-code-org/bmad-automator
|
||||||
source-root: payload/.claude/skills
|
source-root: skills
|
||||||
code: baut
|
code: baut
|
||||||
name: "BMad Automator (Experimental)"
|
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."
|
description: "Experimental pure-skill story automation. Runs only from Claude Code; supports Claude Code and Codex worker sessions; requires tmux on macOS."
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue