feat(installer): consolidate to .agents/skills and add global_target_dir for all platforms

Updates platform-codes.yaml against verified primary docs for all 24 supported
platforms. 14 platforms (auggie, codex, crush, cursor, gemini, github-copilot,
kilo, kimi-code, opencode, pi, roo, rovo-dev, windsurf) move their project
target_dir to the cross-tool .agents/skills/ standard. Junie moves from the
broken .agents/skills/ to its own .junie/skills/ per JetBrains docs.

Adds global_target_dir to every platform: 11 share ~/.agents/skills/, Crush
uses XDG ~/.config/agents/skills/, Codex global stays ~/.codex/skills/, the
rest are tool-specific. Ona and Trae omit global (no documented home path).

Note: installer logic does not yet dedupe writes for platforms sharing a
target_dir — users installing multiple .agents/skills/ tools together will
overwrite the same files (harmless on install, but uninstalling one clears
the dir for the others). Coordination logic is the next step.
This commit is contained in:
Brian Madison 2026-04-25 19:41:25 -05:00
parent 08a3e88b86
commit 5ff534df94
2 changed files with 63 additions and 36 deletions

View File

@ -139,7 +139,7 @@ async function runTests() {
const platformCodes = await loadPlatformCodes();
const windsurfInstaller = platformCodes.platforms.windsurf?.installer;
assert(windsurfInstaller?.target_dir === '.windsurf/skills', 'Windsurf target_dir uses native skills path');
assert(windsurfInstaller?.target_dir === '.agents/skills', 'Windsurf target_dir uses native skills path');
const tempProjectDir = await fs.mkdtemp(path.join(os.tmpdir(), 'bmad-windsurf-test-'));
const installedBmadDir = await createTestBmadFixture();
@ -153,7 +153,7 @@ async function runTests() {
assert(result.success === true, 'Windsurf setup succeeds against temp project');
const skillFile = path.join(tempProjectDir, '.windsurf', 'skills', 'bmad-master', 'SKILL.md');
const skillFile = path.join(tempProjectDir, '.agents', 'skills', 'bmad-master', 'SKILL.md');
assert(await fs.pathExists(skillFile), 'Windsurf install writes SKILL.md directory output');
await fs.remove(tempProjectDir);
@ -244,7 +244,7 @@ async function runTests() {
const platformCodes = await loadPlatformCodes();
const auggieInstaller = platformCodes.platforms.auggie?.installer;
assert(auggieInstaller?.target_dir === '.augment/skills', 'Auggie target_dir uses native skills path');
assert(auggieInstaller?.target_dir === '.agents/skills', 'Auggie target_dir uses native skills path');
assert(
auggieInstaller?.ancestor_conflict_check !== true,
@ -263,7 +263,7 @@ async function runTests() {
assert(result.success === true, 'Auggie setup succeeds against temp project');
const skillFile = path.join(tempProjectDir, '.augment', 'skills', 'bmad-master', 'SKILL.md');
const skillFile = path.join(tempProjectDir, '.agents', 'skills', 'bmad-master', 'SKILL.md');
assert(await fs.pathExists(skillFile), 'Auggie install writes SKILL.md directory output');
await fs.remove(tempProjectDir);
@ -284,7 +284,7 @@ async function runTests() {
const platformCodes = await loadPlatformCodes();
const opencodeInstaller = platformCodes.platforms.opencode?.installer;
assert(opencodeInstaller?.target_dir === '.opencode/skills', 'OpenCode target_dir uses native skills path');
assert(opencodeInstaller?.target_dir === '.agents/skills', 'OpenCode target_dir uses native skills path');
const tempProjectDir = await fs.mkdtemp(path.join(os.tmpdir(), 'bmad-opencode-test-'));
const installedBmadDir = await createTestBmadFixture();
@ -298,7 +298,7 @@ async function runTests() {
assert(result.success === true, 'OpenCode setup succeeds against temp project');
const skillFile = path.join(tempProjectDir, '.opencode', 'skills', 'bmad-master', 'SKILL.md');
const skillFile = path.join(tempProjectDir, '.agents', 'skills', 'bmad-master', 'SKILL.md');
assert(await fs.pathExists(skillFile), 'OpenCode install writes SKILL.md directory output');
await fs.remove(tempProjectDir);
@ -403,7 +403,7 @@ async function runTests() {
const platformCodes13 = await loadPlatformCodes();
const cursorInstaller = platformCodes13.platforms.cursor?.installer;
assert(cursorInstaller?.target_dir === '.cursor/skills', 'Cursor target_dir uses native skills path');
assert(cursorInstaller?.target_dir === '.agents/skills', 'Cursor target_dir uses native skills path');
assert(!cursorInstaller?.ancestor_conflict_check, 'Cursor installer does not enable ancestor conflict checks');
@ -419,7 +419,7 @@ async function runTests() {
assert(result13c.success === true, 'Cursor setup succeeds against temp project');
const skillFile13c = path.join(tempProjectDir13c, '.cursor', 'skills', 'bmad-master', 'SKILL.md');
const skillFile13c = path.join(tempProjectDir13c, '.agents', 'skills', 'bmad-master', 'SKILL.md');
assert(await fs.pathExists(skillFile13c), 'Cursor install writes SKILL.md directory output');
// Verify name frontmatter matches directory name
@ -445,7 +445,7 @@ async function runTests() {
const platformCodes13 = await loadPlatformCodes();
const rooInstaller = platformCodes13.platforms.roo?.installer;
assert(rooInstaller?.target_dir === '.roo/skills', 'Roo target_dir uses native skills path');
assert(rooInstaller?.target_dir === '.agents/skills', 'Roo target_dir uses native skills path');
const tempProjectDir13 = await fs.mkdtemp(path.join(os.tmpdir(), 'bmad-roo-test-'));
const installedBmadDir13 = await createTestBmadFixture();
@ -459,7 +459,7 @@ async function runTests() {
assert(result13.success === true, 'Roo setup succeeds against temp project');
const skillFile13 = path.join(tempProjectDir13, '.roo', 'skills', 'bmad-master', 'SKILL.md');
const skillFile13 = path.join(tempProjectDir13, '.agents', 'skills', 'bmad-master', 'SKILL.md');
assert(await fs.pathExists(skillFile13), 'Roo install writes SKILL.md directory output');
// Verify name frontmatter matches directory name (Roo constraint: lowercase alphanumeric + hyphens)
@ -503,7 +503,7 @@ async function runTests() {
const platformCodes17 = await loadPlatformCodes();
const copilotInstaller = platformCodes17.platforms['github-copilot']?.installer;
assert(copilotInstaller?.target_dir === '.github/skills', 'GitHub Copilot target_dir uses native skills path');
assert(copilotInstaller?.target_dir === '.agents/skills', 'GitHub Copilot target_dir uses native skills path');
const tempProjectDir17 = await fs.mkdtemp(path.join(os.tmpdir(), 'bmad-copilot-test-'));
const installedBmadDir17 = await createTestBmadFixture();
@ -524,7 +524,7 @@ async function runTests() {
assert(result17.success === true, 'GitHub Copilot setup succeeds against temp project');
const skillFile17 = path.join(tempProjectDir17, '.github', 'skills', 'bmad-master', 'SKILL.md');
const skillFile17 = path.join(tempProjectDir17, '.agents', 'skills', 'bmad-master', 'SKILL.md');
assert(await fs.pathExists(skillFile17), 'GitHub Copilot install writes SKILL.md directory output');
// Verify name frontmatter matches directory name
@ -657,7 +657,7 @@ async function runTests() {
const platformCodes20 = await loadPlatformCodes();
const crushInstaller = platformCodes20.platforms.crush?.installer;
assert(crushInstaller?.target_dir === '.crush/skills', 'Crush target_dir uses native skills path');
assert(crushInstaller?.target_dir === '.agents/skills', 'Crush target_dir uses native skills path');
const tempProjectDir20 = await fs.mkdtemp(path.join(os.tmpdir(), 'bmad-crush-test-'));
const installedBmadDir20 = await createTestBmadFixture();
@ -671,7 +671,7 @@ async function runTests() {
assert(result20.success === true, 'Crush setup succeeds against temp project');
const skillFile20 = path.join(tempProjectDir20, '.crush', 'skills', 'bmad-master', 'SKILL.md');
const skillFile20 = path.join(tempProjectDir20, '.agents', 'skills', 'bmad-master', 'SKILL.md');
assert(await fs.pathExists(skillFile20), 'Crush install writes SKILL.md directory output');
const skillContent20 = await fs.readFile(skillFile20, 'utf8');
@ -753,7 +753,7 @@ async function runTests() {
assert(!kiloConfig22?.suspended, 'KiloCoder is not suspended');
assert(kiloConfig22?.installer?.target_dir === '.kilocode/skills', 'KiloCoder target_dir uses native skills path');
assert(kiloConfig22?.installer?.target_dir === '.agents/skills', 'KiloCoder target_dir uses native skills path');
const ideManager22 = new IdeManager();
await ideManager22.ensureInitialized();
@ -775,7 +775,7 @@ async function runTests() {
assert(result22.success === true, 'KiloCoder setup succeeds against temp project');
const skillFile22 = path.join(tempProjectDir22, '.kilocode', 'skills', 'bmad-master', 'SKILL.md');
const skillFile22 = path.join(tempProjectDir22, '.agents', 'skills', 'bmad-master', 'SKILL.md');
assert(await fs.pathExists(skillFile22), 'KiloCoder install writes SKILL.md directory output');
const skillContent22 = await fs.readFile(skillFile22, 'utf8');
@ -808,7 +808,7 @@ async function runTests() {
const platformCodes23 = await loadPlatformCodes();
const geminiInstaller = platformCodes23.platforms.gemini?.installer;
assert(geminiInstaller?.target_dir === '.gemini/skills', 'Gemini target_dir uses native skills path');
assert(geminiInstaller?.target_dir === '.agents/skills', 'Gemini target_dir uses native skills path');
const tempProjectDir23 = await fs.mkdtemp(path.join(os.tmpdir(), 'bmad-gemini-test-'));
const installedBmadDir23 = await createTestBmadFixture();
@ -822,7 +822,7 @@ async function runTests() {
assert(result23.success === true, 'Gemini setup succeeds against temp project');
const skillFile23 = path.join(tempProjectDir23, '.gemini', 'skills', 'bmad-master', 'SKILL.md');
const skillFile23 = path.join(tempProjectDir23, '.agents', 'skills', 'bmad-master', 'SKILL.md');
assert(await fs.pathExists(skillFile23), 'Gemini install writes SKILL.md directory output');
const skillContent23 = await fs.readFile(skillFile23, 'utf8');
@ -935,7 +935,7 @@ async function runTests() {
const platformCodes26 = await loadPlatformCodes();
const rovoInstaller = platformCodes26.platforms['rovo-dev']?.installer;
assert(rovoInstaller?.target_dir === '.rovodev/skills', 'Rovo Dev target_dir uses native skills path');
assert(rovoInstaller?.target_dir === '.agents/skills', 'Rovo Dev target_dir uses native skills path');
const tempProjectDir26 = await fs.mkdtemp(path.join(os.tmpdir(), 'bmad-rovodev-test-'));
const installedBmadDir26 = await createTestBmadFixture();
@ -961,7 +961,7 @@ async function runTests() {
assert(result26.success === true, 'Rovo Dev setup succeeds against temp project');
const skillFile26 = path.join(tempProjectDir26, '.rovodev', 'skills', 'bmad-master', 'SKILL.md');
const skillFile26 = path.join(tempProjectDir26, '.agents', 'skills', 'bmad-master', 'SKILL.md');
assert(await fs.pathExists(skillFile26), 'Rovo Dev install writes SKILL.md directory output');
// Verify name frontmatter matches directory name
@ -1070,7 +1070,7 @@ async function runTests() {
const platformCodes28 = await loadPlatformCodes();
const piInstaller = platformCodes28.platforms.pi?.installer;
assert(piInstaller?.target_dir === '.pi/skills', 'Pi target_dir uses native skills path');
assert(piInstaller?.target_dir === '.agents/skills', 'Pi target_dir uses native skills path');
tempProjectDir28 = await fs.mkdtemp(path.join(os.tmpdir(), 'bmad-pi-test-'));
installedBmadDir28 = await createTestBmadFixture();
@ -1100,7 +1100,7 @@ async function runTests() {
const detectedAfter28 = await ideManager28.detectInstalledIdes(tempProjectDir28);
assert(detectedAfter28.includes('pi'), 'Pi is detected after install');
const skillFile28 = path.join(tempProjectDir28, '.pi', 'skills', 'bmad-master', 'SKILL.md');
const skillFile28 = path.join(tempProjectDir28, '.agents', 'skills', 'bmad-master', 'SKILL.md');
assert(await fs.pathExists(skillFile28), 'Pi install writes SKILL.md directory output');
// Parse YAML frontmatter between --- markers

View File

@ -5,8 +5,13 @@
# preferred: Whether shown as a recommended option on install
# suspended: (optional) Message explaining why install is blocked
# installer:
# target_dir: Directory where skill directories are installed
# target_dir: Directory where skill directories are installed (project/workspace)
# global_target_dir: (optional) User-home directory for global install
# ancestor_conflict_check: (optional) Refuse install when ancestor dir has BMAD files
#
# Multiple platforms may share the same target_dir or global_target_dir — many tools
# read from the shared `.agents/skills/` and `~/.agents/skills/` cross-tool standard.
# Paths verified against each tool's primary docs as of 2026-04-25.
platforms:
antigravity:
@ -14,90 +19,105 @@ platforms:
preferred: false
installer:
target_dir: .agent/skills
global_target_dir: ~/.gemini/antigravity/skills
auggie:
name: "Auggie"
preferred: false
installer:
target_dir: .augment/skills
target_dir: .agents/skills
global_target_dir: ~/.agents/skills
claude-code:
name: "Claude Code"
preferred: true
installer:
target_dir: .claude/skills
global_target_dir: ~/.claude/skills
cline:
name: "Cline"
preferred: false
installer:
target_dir: .cline/skills
global_target_dir: ~/.cline/skills
codex:
name: "Codex"
preferred: false
installer:
target_dir: .agents/skills
global_target_dir: ~/.codex/skills
codebuddy:
name: "CodeBuddy"
preferred: false
installer:
target_dir: .codebuddy/skills
global_target_dir: ~/.codebuddy/skills
crush:
name: "Crush"
preferred: false
installer:
target_dir: .crush/skills
target_dir: .agents/skills
global_target_dir: ~/.config/agents/skills
cursor:
name: "Cursor"
preferred: true
installer:
target_dir: .cursor/skills
target_dir: .agents/skills
global_target_dir: ~/.agents/skills
gemini:
name: "Gemini CLI"
preferred: false
installer:
target_dir: .gemini/skills
target_dir: .agents/skills
global_target_dir: ~/.agents/skills
github-copilot:
name: "GitHub Copilot"
preferred: false
installer:
target_dir: .github/skills
target_dir: .agents/skills
global_target_dir: ~/.agents/skills
iflow:
name: "iFlow"
preferred: false
installer:
target_dir: .iflow/skills
global_target_dir: ~/.iflow/skills
junie:
name: "Junie"
preferred: false
installer:
target_dir: .agents/skills
target_dir: .junie/skills
global_target_dir: ~/.junie/skills
kilo:
name: "KiloCoder"
preferred: false
installer:
target_dir: .kilocode/skills
target_dir: .agents/skills
global_target_dir: ~/.kilocode/skills
kimi-code:
name: "Kimi Code"
preferred: false
installer:
target_dir: .kimi/skills
target_dir: .agents/skills
global_target_dir: ~/.agents/skills
kiro:
name: "Kiro"
preferred: false
installer:
target_dir: .kiro/skills
global_target_dir: ~/.kiro/skills
ona:
name: "Ona"
@ -109,37 +129,43 @@ platforms:
name: "OpenCode"
preferred: false
installer:
target_dir: .opencode/skills
target_dir: .agents/skills
global_target_dir: ~/.agents/skills
pi:
name: "Pi"
preferred: false
installer:
target_dir: .pi/skills
target_dir: .agents/skills
global_target_dir: ~/.agents/skills
qoder:
name: "Qoder"
preferred: false
installer:
target_dir: .qoder/skills
global_target_dir: ~/.qoder/skills
qwen:
name: "QwenCoder"
preferred: false
installer:
target_dir: .qwen/skills
global_target_dir: ~/.qwen/skills
roo:
name: "Roo Code"
preferred: false
installer:
target_dir: .roo/skills
target_dir: .agents/skills
global_target_dir: ~/.agents/skills
rovo-dev:
name: "Rovo Dev"
preferred: false
installer:
target_dir: .rovodev/skills
target_dir: .agents/skills
global_target_dir: ~/.agents/skills
trae:
name: "Trae"
@ -151,4 +177,5 @@ platforms:
name: "Windsurf"
preferred: false
installer:
target_dir: .windsurf/skills
target_dir: .agents/skills
global_target_dir: ~/.agents/skills