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:
parent
08a3e88b86
commit
5ff534df94
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in New Issue