From 08a3e88b86d3ca2613d14a55cd101ed39137b302 Mon Sep 17 00:00:00 2001 From: Brian Madison Date: Sat, 25 Apr 2026 18:30:57 -0500 Subject: [PATCH] refactor(installer): replace legacy_targets auto-cleanup with upgrade warnings Removes the legacy_targets YAML field and its install-time auto-migration of pre-v6.1.0 directories (.claude/commands, .opencode/agents, etc.). On install, surface a warning instead: read manifest version and scan 24 known legacy paths, then print rm -rf commands the user can run themselves. Also deletes orphan tools/platform-codes.yaml (never loaded by any code) and fixes a stale URL in the cs translation. --- .../cs/how-to/non-interactive-installation.md | 2 +- test/test-installation-components.js | 229 +----------------- .../docs/native-skills-migration-checklist.md | 4 - tools/installer/core/installer.js | 6 + tools/installer/core/legacy-warnings.js | 108 +++++++++ tools/installer/ide/_config-driven.js | 96 +------- tools/installer/ide/platform-codes.yaml | 44 ---- tools/platform-codes.yaml | 175 ------------- 8 files changed, 118 insertions(+), 546 deletions(-) create mode 100644 tools/installer/core/legacy-warnings.js delete mode 100644 tools/platform-codes.yaml diff --git a/docs/cs/how-to/non-interactive-installation.md b/docs/cs/how-to/non-interactive-installation.md index 12ea31eb3..4d784f923 100644 --- a/docs/cs/how-to/non-interactive-installation.md +++ b/docs/cs/how-to/non-interactive-installation.md @@ -60,7 +60,7 @@ Dostupná ID nástrojů pro příznak `--tools`: **Preferované:** `claude-code`, `cursor` -Spusťte `npx bmad-method install` interaktivně jednou pro zobrazení aktuálního seznamu podporovaných nástrojů, nebo zkontrolujte [konfiguraci kódů platforem](https://github.com/bmad-code-org/BMAD-METHOD/blob/main/tools/cli/installers/lib/ide/platform-codes.yaml). +Spusťte `npx bmad-method install` interaktivně jednou pro zobrazení aktuálního seznamu podporovaných nástrojů, nebo zkontrolujte [konfiguraci kódů platforem](https://github.com/bmad-code-org/BMAD-METHOD/blob/main/tools/installer/ide/platform-codes.yaml). ## Režimy instalace diff --git a/test/test-installation-components.js b/test/test-installation-components.js index 58d6c7d8f..d42e71dda 100644 --- a/test/test-installation-components.js +++ b/test/test-installation-components.js @@ -141,17 +141,8 @@ async function runTests() { assert(windsurfInstaller?.target_dir === '.windsurf/skills', 'Windsurf target_dir uses native skills path'); - assert( - Array.isArray(windsurfInstaller?.legacy_targets) && windsurfInstaller.legacy_targets.includes('.windsurf/workflows'), - 'Windsurf installer cleans legacy workflow output', - ); - const tempProjectDir = await fs.mkdtemp(path.join(os.tmpdir(), 'bmad-windsurf-test-')); const installedBmadDir = await createTestBmadFixture(); - const legacyDir = path.join(tempProjectDir, '.windsurf', 'workflows', 'bmad-legacy-dir'); - await fs.ensureDir(legacyDir); - await fs.writeFile(path.join(tempProjectDir, '.windsurf', 'workflows', 'bmad-legacy.md'), 'legacy\n'); - await fs.writeFile(path.join(legacyDir, 'SKILL.md'), 'legacy\n'); const ideManager = new IdeManager(); await ideManager.ensureInitialized(); @@ -165,8 +156,6 @@ async function runTests() { const skillFile = path.join(tempProjectDir, '.windsurf', 'skills', 'bmad-master', 'SKILL.md'); assert(await fs.pathExists(skillFile), 'Windsurf install writes SKILL.md directory output'); - assert(!(await fs.pathExists(path.join(tempProjectDir, '.windsurf', 'workflows'))), 'Windsurf setup removes legacy workflows dir'); - await fs.remove(tempProjectDir); await fs.remove(path.dirname(installedBmadDir)); } catch (error) { @@ -187,17 +176,8 @@ async function runTests() { assert(kiroInstaller?.target_dir === '.kiro/skills', 'Kiro target_dir uses native skills path'); - assert( - Array.isArray(kiroInstaller?.legacy_targets) && kiroInstaller.legacy_targets.includes('.kiro/steering'), - 'Kiro installer cleans legacy steering output', - ); - const tempProjectDir = await fs.mkdtemp(path.join(os.tmpdir(), 'bmad-kiro-test-')); const installedBmadDir = await createTestBmadFixture(); - const legacyDir = path.join(tempProjectDir, '.kiro', 'steering', 'bmad-legacy-dir'); - await fs.ensureDir(legacyDir); - await fs.writeFile(path.join(tempProjectDir, '.kiro', 'steering', 'bmad-legacy.md'), 'legacy\n'); - await fs.writeFile(path.join(legacyDir, 'SKILL.md'), 'legacy\n'); const ideManager = new IdeManager(); await ideManager.ensureInitialized(); @@ -211,8 +191,6 @@ async function runTests() { const skillFile = path.join(tempProjectDir, '.kiro', 'skills', 'bmad-master', 'SKILL.md'); assert(await fs.pathExists(skillFile), 'Kiro install writes SKILL.md directory output'); - assert(!(await fs.pathExists(path.join(tempProjectDir, '.kiro', 'steering'))), 'Kiro setup removes legacy steering dir'); - await fs.remove(tempProjectDir); await fs.remove(path.dirname(installedBmadDir)); } catch (error) { @@ -233,17 +211,8 @@ async function runTests() { assert(antigravityInstaller?.target_dir === '.agent/skills', 'Antigravity target_dir uses native skills path'); - assert( - Array.isArray(antigravityInstaller?.legacy_targets) && antigravityInstaller.legacy_targets.includes('.agent/workflows'), - 'Antigravity installer cleans legacy workflow output', - ); - const tempProjectDir = await fs.mkdtemp(path.join(os.tmpdir(), 'bmad-antigravity-test-')); const installedBmadDir = await createTestBmadFixture(); - const legacyDir = path.join(tempProjectDir, '.agent', 'workflows', 'bmad-legacy-dir'); - await fs.ensureDir(legacyDir); - await fs.writeFile(path.join(tempProjectDir, '.agent', 'workflows', 'bmad-legacy.md'), 'legacy\n'); - await fs.writeFile(path.join(legacyDir, 'SKILL.md'), 'legacy\n'); const ideManager = new IdeManager(); await ideManager.ensureInitialized(); @@ -257,8 +226,6 @@ async function runTests() { const skillFile = path.join(tempProjectDir, '.agent', 'skills', 'bmad-master', 'SKILL.md'); assert(await fs.pathExists(skillFile), 'Antigravity install writes SKILL.md directory output'); - assert(!(await fs.pathExists(path.join(tempProjectDir, '.agent', 'workflows'))), 'Antigravity setup removes legacy workflows dir'); - await fs.remove(tempProjectDir); await fs.remove(path.dirname(installedBmadDir)); } catch (error) { @@ -279,11 +246,6 @@ async function runTests() { assert(auggieInstaller?.target_dir === '.augment/skills', 'Auggie target_dir uses native skills path'); - assert( - Array.isArray(auggieInstaller?.legacy_targets) && auggieInstaller.legacy_targets.includes('.augment/commands'), - 'Auggie installer cleans legacy command output', - ); - assert( auggieInstaller?.ancestor_conflict_check !== true, 'Auggie installer does not enable ancestor conflict checks without verified inheritance', @@ -291,10 +253,6 @@ async function runTests() { const tempProjectDir = await fs.mkdtemp(path.join(os.tmpdir(), 'bmad-auggie-test-')); const installedBmadDir = await createTestBmadFixture(); - const legacyDir = path.join(tempProjectDir, '.augment', 'commands', 'bmad-legacy-dir'); - await fs.ensureDir(legacyDir); - await fs.writeFile(path.join(tempProjectDir, '.augment', 'commands', 'bmad-legacy.md'), 'legacy\n'); - await fs.writeFile(path.join(legacyDir, 'SKILL.md'), 'legacy\n'); const ideManager = new IdeManager(); await ideManager.ensureInitialized(); @@ -308,8 +266,6 @@ async function runTests() { const skillFile = path.join(tempProjectDir, '.augment', 'skills', 'bmad-master', 'SKILL.md'); assert(await fs.pathExists(skillFile), 'Auggie install writes SKILL.md directory output'); - assert(!(await fs.pathExists(path.join(tempProjectDir, '.augment', 'commands'))), 'Auggie setup removes legacy commands dir'); - await fs.remove(tempProjectDir); await fs.remove(path.dirname(installedBmadDir)); } catch (error) { @@ -330,28 +286,8 @@ async function runTests() { assert(opencodeInstaller?.target_dir === '.opencode/skills', 'OpenCode target_dir uses native skills path'); - assert( - Array.isArray(opencodeInstaller?.legacy_targets) && - ['.opencode/agents', '.opencode/commands', '.opencode/agent', '.opencode/command'].every((legacyTarget) => - opencodeInstaller.legacy_targets.includes(legacyTarget), - ), - 'OpenCode installer cleans split legacy agent and command output', - ); - const tempProjectDir = await fs.mkdtemp(path.join(os.tmpdir(), 'bmad-opencode-test-')); const installedBmadDir = await createTestBmadFixture(); - const legacyDirs = [ - path.join(tempProjectDir, '.opencode', 'agents', 'bmad-legacy-agent'), - path.join(tempProjectDir, '.opencode', 'commands', 'bmad-legacy-command'), - path.join(tempProjectDir, '.opencode', 'agent', 'bmad-legacy-agent-singular'), - path.join(tempProjectDir, '.opencode', 'command', 'bmad-legacy-command-singular'), - ]; - - for (const legacyDir of legacyDirs) { - await fs.ensureDir(legacyDir); - await fs.writeFile(path.join(legacyDir, 'SKILL.md'), 'legacy\n'); - await fs.writeFile(path.join(path.dirname(legacyDir), `${path.basename(legacyDir)}.md`), 'legacy\n'); - } const ideManager = new IdeManager(); await ideManager.ensureInitialized(); @@ -365,13 +301,6 @@ async function runTests() { const skillFile = path.join(tempProjectDir, '.opencode', 'skills', 'bmad-master', 'SKILL.md'); assert(await fs.pathExists(skillFile), 'OpenCode install writes SKILL.md directory output'); - for (const legacyDir of ['agents', 'commands', 'agent', 'command']) { - assert( - !(await fs.pathExists(path.join(tempProjectDir, '.opencode', legacyDir))), - `OpenCode setup removes legacy .opencode/${legacyDir} dir`, - ); - } - await fs.remove(tempProjectDir); await fs.remove(path.dirname(installedBmadDir)); } catch (error) { @@ -392,16 +321,8 @@ async function runTests() { assert(claudeInstaller?.target_dir === '.claude/skills', 'Claude Code target_dir uses native skills path'); - assert( - Array.isArray(claudeInstaller?.legacy_targets) && claudeInstaller.legacy_targets.includes('.claude/commands'), - 'Claude Code installer cleans legacy command output', - ); - const tempProjectDir9 = await fs.mkdtemp(path.join(os.tmpdir(), 'bmad-claude-code-test-')); const installedBmadDir9 = await createTestBmadFixture(); - const legacyDir9 = path.join(tempProjectDir9, '.claude', 'commands'); - await fs.ensureDir(legacyDir9); - await fs.writeFile(path.join(legacyDir9, 'bmad-legacy.md'), 'legacy\n'); const ideManager9 = new IdeManager(); await ideManager9.ensureInitialized(); @@ -420,8 +341,6 @@ async function runTests() { const nameMatch9 = skillContent9.match(/^name:\s*(.+)$/m); assert(nameMatch9 && nameMatch9[1].trim() === 'bmad-master', 'Claude Code skill name frontmatter matches directory name exactly'); - assert(!(await fs.pathExists(legacyDir9)), 'Claude Code setup removes legacy commands dir'); - await fs.remove(tempProjectDir9); await fs.remove(path.dirname(installedBmadDir9)); } catch (error) { @@ -444,16 +363,8 @@ async function runTests() { assert(codexInstaller?.target_dir === '.agents/skills', 'Codex target_dir uses native skills path'); - assert( - Array.isArray(codexInstaller?.legacy_targets) && codexInstaller.legacy_targets.includes('.codex/prompts'), - 'Codex installer cleans legacy prompt output', - ); - const tempProjectDir11 = await fs.mkdtemp(path.join(os.tmpdir(), 'bmad-codex-test-')); const installedBmadDir11 = await createTestBmadFixture(); - const legacyDir11 = path.join(tempProjectDir11, '.codex', 'prompts'); - await fs.ensureDir(legacyDir11); - await fs.writeFile(path.join(legacyDir11, 'bmad-legacy.md'), 'legacy\n'); const ideManager11 = new IdeManager(); await ideManager11.ensureInitialized(); @@ -472,8 +383,6 @@ async function runTests() { const nameMatch11 = skillContent11.match(/^name:\s*(.+)$/m); assert(nameMatch11 && nameMatch11[1].trim() === 'bmad-master', 'Codex skill name frontmatter matches directory name exactly'); - assert(!(await fs.pathExists(legacyDir11)), 'Codex setup removes legacy prompts dir'); - await fs.remove(tempProjectDir11); await fs.remove(path.dirname(installedBmadDir11)); } catch (error) { @@ -496,18 +405,10 @@ async function runTests() { assert(cursorInstaller?.target_dir === '.cursor/skills', 'Cursor target_dir uses native skills path'); - assert( - Array.isArray(cursorInstaller?.legacy_targets) && cursorInstaller.legacy_targets.includes('.cursor/commands'), - 'Cursor installer cleans legacy command output', - ); - assert(!cursorInstaller?.ancestor_conflict_check, 'Cursor installer does not enable ancestor conflict checks'); const tempProjectDir13c = await fs.mkdtemp(path.join(os.tmpdir(), 'bmad-cursor-test-')); const installedBmadDir13c = await createTestBmadFixture(); - const legacyDir13c = path.join(tempProjectDir13c, '.cursor', 'commands'); - await fs.ensureDir(legacyDir13c); - await fs.writeFile(path.join(legacyDir13c, 'bmad-legacy.md'), 'legacy\n'); const ideManager13c = new IdeManager(); await ideManager13c.ensureInitialized(); @@ -526,8 +427,6 @@ async function runTests() { const nameMatch13c = skillContent13c.match(/^name:\s*(.+)$/m); assert(nameMatch13c && nameMatch13c[1].trim() === 'bmad-master', 'Cursor skill name frontmatter matches directory name exactly'); - assert(!(await fs.pathExists(legacyDir13c)), 'Cursor setup removes legacy commands dir'); - await fs.remove(tempProjectDir13c); await fs.remove(path.dirname(installedBmadDir13c)); } catch (error) { @@ -548,17 +447,8 @@ async function runTests() { assert(rooInstaller?.target_dir === '.roo/skills', 'Roo target_dir uses native skills path'); - assert( - Array.isArray(rooInstaller?.legacy_targets) && rooInstaller.legacy_targets.includes('.roo/commands'), - 'Roo installer cleans legacy command output', - ); - const tempProjectDir13 = await fs.mkdtemp(path.join(os.tmpdir(), 'bmad-roo-test-')); const installedBmadDir13 = await createTestBmadFixture(); - const legacyDir13 = path.join(tempProjectDir13, '.roo', 'commands', 'bmad-legacy-dir'); - await fs.ensureDir(legacyDir13); - await fs.writeFile(path.join(tempProjectDir13, '.roo', 'commands', 'bmad-legacy.md'), 'legacy\n'); - await fs.writeFile(path.join(legacyDir13, 'SKILL.md'), 'legacy\n'); const ideManager13 = new IdeManager(); await ideManager13.ensureInitialized(); @@ -580,8 +470,6 @@ async function runTests() { 'Roo skill name frontmatter matches directory name exactly (lowercase alphanumeric + hyphens)', ); - assert(!(await fs.pathExists(path.join(tempProjectDir13, '.roo', 'commands'))), 'Roo setup removes legacy commands dir'); - // Reinstall/upgrade: run setup again over existing skills output const result13b = await ideManager13.setup('roo', tempProjectDir13, installedBmadDir13, { silent: true, @@ -617,29 +505,11 @@ async function runTests() { assert(copilotInstaller?.target_dir === '.github/skills', 'GitHub Copilot target_dir uses native skills path'); - assert( - Array.isArray(copilotInstaller?.legacy_targets) && copilotInstaller.legacy_targets.includes('.github/agents'), - 'GitHub Copilot installer cleans legacy agents output', - ); - - assert( - Array.isArray(copilotInstaller?.legacy_targets) && copilotInstaller.legacy_targets.includes('.github/prompts'), - 'GitHub Copilot installer cleans legacy prompts output', - ); - const tempProjectDir17 = await fs.mkdtemp(path.join(os.tmpdir(), 'bmad-copilot-test-')); const installedBmadDir17 = await createTestBmadFixture(); - // Create legacy .github/agents/ and .github/prompts/ files - const legacyAgentsDir17 = path.join(tempProjectDir17, '.github', 'agents'); - const legacyPromptsDir17 = path.join(tempProjectDir17, '.github', 'prompts'); - await fs.ensureDir(legacyAgentsDir17); - await fs.ensureDir(legacyPromptsDir17); - await fs.writeFile(path.join(legacyAgentsDir17, 'bmad-legacy.agent.md'), 'legacy agent\n'); - await fs.writeFile(path.join(legacyPromptsDir17, 'bmad-legacy.prompt.md'), 'legacy prompt\n'); - - // Create legacy copilot-instructions.md with BMAD markers const copilotInstructionsPath17 = path.join(tempProjectDir17, '.github', 'copilot-instructions.md'); + await fs.ensureDir(path.dirname(copilotInstructionsPath17)); await fs.writeFile( copilotInstructionsPath17, 'User content before\n\nBMAD generated content\n\nUser content after\n', @@ -662,10 +532,6 @@ async function runTests() { const nameMatch17 = skillContent17.match(/^name:\s*(.+)$/m); assert(nameMatch17 && nameMatch17[1].trim() === 'bmad-master', 'GitHub Copilot skill name frontmatter matches directory name exactly'); - assert(!(await fs.pathExists(legacyAgentsDir17)), 'GitHub Copilot setup removes legacy agents dir'); - - assert(!(await fs.pathExists(legacyPromptsDir17)), 'GitHub Copilot setup removes legacy prompts dir'); - // Verify copilot-instructions.md BMAD markers were stripped but user content preserved const cleanedInstructions17 = await fs.readFile(copilotInstructionsPath17, 'utf8'); assert( @@ -697,17 +563,8 @@ async function runTests() { assert(clineInstaller?.target_dir === '.cline/skills', 'Cline target_dir uses native skills path'); - assert( - Array.isArray(clineInstaller?.legacy_targets) && clineInstaller.legacy_targets.includes('.clinerules/workflows'), - 'Cline installer cleans legacy workflow output', - ); - const tempProjectDir18 = await fs.mkdtemp(path.join(os.tmpdir(), 'bmad-cline-test-')); const installedBmadDir18 = await createTestBmadFixture(); - const legacyDir18 = path.join(tempProjectDir18, '.clinerules', 'workflows', 'bmad-legacy-dir'); - await fs.ensureDir(legacyDir18); - await fs.writeFile(path.join(tempProjectDir18, '.clinerules', 'workflows', 'bmad-legacy.md'), 'legacy\n'); - await fs.writeFile(path.join(legacyDir18, 'SKILL.md'), 'legacy\n'); const ideManager18 = new IdeManager(); await ideManager18.ensureInitialized(); @@ -726,8 +583,6 @@ async function runTests() { const nameMatch18 = skillContent18.match(/^name:\s*(.+)$/m); assert(nameMatch18 && nameMatch18[1].trim() === 'bmad-master', 'Cline skill name frontmatter matches directory name exactly'); - assert(!(await fs.pathExists(path.join(tempProjectDir18, '.clinerules', 'workflows'))), 'Cline setup removes legacy workflows dir'); - // Reinstall/upgrade: run setup again over existing skills output const result18b = await ideManager18.setup('cline', tempProjectDir18, installedBmadDir18, { silent: true, @@ -757,17 +612,8 @@ async function runTests() { assert(codebuddyInstaller?.target_dir === '.codebuddy/skills', 'CodeBuddy target_dir uses native skills path'); - assert( - Array.isArray(codebuddyInstaller?.legacy_targets) && codebuddyInstaller.legacy_targets.includes('.codebuddy/commands'), - 'CodeBuddy installer cleans legacy command output', - ); - const tempProjectDir19 = await fs.mkdtemp(path.join(os.tmpdir(), 'bmad-codebuddy-test-')); const installedBmadDir19 = await createTestBmadFixture(); - const legacyDir19 = path.join(tempProjectDir19, '.codebuddy', 'commands', 'bmad-legacy-dir'); - await fs.ensureDir(legacyDir19); - await fs.writeFile(path.join(tempProjectDir19, '.codebuddy', 'commands', 'bmad-legacy.md'), 'legacy\n'); - await fs.writeFile(path.join(legacyDir19, 'SKILL.md'), 'legacy\n'); const ideManager19 = new IdeManager(); await ideManager19.ensureInitialized(); @@ -785,8 +631,6 @@ async function runTests() { const nameMatch19 = skillContent19.match(/^name:\s*(.+)$/m); assert(nameMatch19 && nameMatch19[1].trim() === 'bmad-master', 'CodeBuddy skill name frontmatter matches directory name exactly'); - assert(!(await fs.pathExists(path.join(tempProjectDir19, '.codebuddy', 'commands'))), 'CodeBuddy setup removes legacy commands dir'); - const result19b = await ideManager19.setup('codebuddy', tempProjectDir19, installedBmadDir19, { silent: true, selectedModules: ['bmm'], @@ -815,17 +659,8 @@ async function runTests() { assert(crushInstaller?.target_dir === '.crush/skills', 'Crush target_dir uses native skills path'); - assert( - Array.isArray(crushInstaller?.legacy_targets) && crushInstaller.legacy_targets.includes('.crush/commands'), - 'Crush installer cleans legacy command output', - ); - const tempProjectDir20 = await fs.mkdtemp(path.join(os.tmpdir(), 'bmad-crush-test-')); const installedBmadDir20 = await createTestBmadFixture(); - const legacyDir20 = path.join(tempProjectDir20, '.crush', 'commands', 'bmad-legacy-dir'); - await fs.ensureDir(legacyDir20); - await fs.writeFile(path.join(tempProjectDir20, '.crush', 'commands', 'bmad-legacy.md'), 'legacy\n'); - await fs.writeFile(path.join(legacyDir20, 'SKILL.md'), 'legacy\n'); const ideManager20 = new IdeManager(); await ideManager20.ensureInitialized(); @@ -843,8 +678,6 @@ async function runTests() { const nameMatch20 = skillContent20.match(/^name:\s*(.+)$/m); assert(nameMatch20 && nameMatch20[1].trim() === 'bmad-master', 'Crush skill name frontmatter matches directory name exactly'); - assert(!(await fs.pathExists(path.join(tempProjectDir20, '.crush', 'commands'))), 'Crush setup removes legacy commands dir'); - const result20b = await ideManager20.setup('crush', tempProjectDir20, installedBmadDir20, { silent: true, selectedModules: ['bmm'], @@ -873,16 +706,8 @@ async function runTests() { assert(traeInstaller?.target_dir === '.trae/skills', 'Trae target_dir uses native skills path'); - assert( - Array.isArray(traeInstaller?.legacy_targets) && traeInstaller.legacy_targets.includes('.trae/rules'), - 'Trae installer cleans legacy rules output', - ); - const tempProjectDir21 = await fs.mkdtemp(path.join(os.tmpdir(), 'bmad-trae-test-')); const installedBmadDir21 = await createTestBmadFixture(); - const legacyDir21 = path.join(tempProjectDir21, '.trae', 'rules'); - await fs.ensureDir(legacyDir21); - await fs.writeFile(path.join(legacyDir21, 'bmad-legacy.md'), 'legacy\n'); const ideManager21 = new IdeManager(); await ideManager21.ensureInitialized(); @@ -900,8 +725,6 @@ async function runTests() { const nameMatch21 = skillContent21.match(/^name:\s*(.+)$/m); assert(nameMatch21 && nameMatch21[1].trim() === 'bmad-master', 'Trae skill name frontmatter matches directory name exactly'); - assert(!(await fs.pathExists(path.join(tempProjectDir21, '.trae', 'rules'))), 'Trae setup removes legacy rules dir'); - const result21b = await ideManager21.setup('trae', tempProjectDir21, installedBmadDir21, { silent: true, selectedModules: ['bmm'], @@ -932,11 +755,6 @@ async function runTests() { assert(kiloConfig22?.installer?.target_dir === '.kilocode/skills', 'KiloCoder target_dir uses native skills path'); - assert( - Array.isArray(kiloConfig22?.installer?.legacy_targets) && kiloConfig22.installer.legacy_targets.includes('.kilocode/workflows'), - 'KiloCoder installer cleans legacy workflows output', - ); - const ideManager22 = new IdeManager(); await ideManager22.ensureInitialized(); @@ -950,11 +768,6 @@ async function runTests() { const tempProjectDir22 = await fs.mkdtemp(path.join(os.tmpdir(), 'bmad-kilo-test-')); const installedBmadDir22 = await createTestBmadFixture(); - // Pre-populate legacy Kilo artifacts that should be cleaned up - const legacyDir22 = path.join(tempProjectDir22, '.kilocode', 'workflows'); - await fs.ensureDir(legacyDir22); - await fs.writeFile(path.join(legacyDir22, 'bmad-legacy.md'), 'legacy\n'); - const result22 = await ideManager22.setup('kilo', tempProjectDir22, installedBmadDir22, { silent: true, selectedModules: ['bmm'], @@ -969,8 +782,6 @@ async function runTests() { const nameMatch22 = skillContent22.match(/^name:\s*(.+)$/m); assert(nameMatch22 && nameMatch22[1].trim() === 'bmad-master', 'KiloCoder skill name frontmatter matches directory name exactly'); - assert(!(await fs.pathExists(path.join(tempProjectDir22, '.kilocode', 'workflows'))), 'KiloCoder setup removes legacy workflows dir'); - const result22b = await ideManager22.setup('kilo', tempProjectDir22, installedBmadDir22, { silent: true, selectedModules: ['bmm'], @@ -999,16 +810,8 @@ async function runTests() { assert(geminiInstaller?.target_dir === '.gemini/skills', 'Gemini target_dir uses native skills path'); - assert( - Array.isArray(geminiInstaller?.legacy_targets) && geminiInstaller.legacy_targets.includes('.gemini/commands'), - 'Gemini installer cleans legacy commands output', - ); - const tempProjectDir23 = await fs.mkdtemp(path.join(os.tmpdir(), 'bmad-gemini-test-')); const installedBmadDir23 = await createTestBmadFixture(); - const legacyDir23 = path.join(tempProjectDir23, '.gemini', 'commands'); - await fs.ensureDir(legacyDir23); - await fs.writeFile(path.join(legacyDir23, 'bmad-legacy.toml'), 'legacy\n'); const ideManager23 = new IdeManager(); await ideManager23.ensureInitialized(); @@ -1026,8 +829,6 @@ async function runTests() { const nameMatch23 = skillContent23.match(/^name:\s*(.+)$/m); assert(nameMatch23 && nameMatch23[1].trim() === 'bmad-master', 'Gemini skill name frontmatter matches directory name exactly'); - assert(!(await fs.pathExists(path.join(tempProjectDir23, '.gemini', 'commands'))), 'Gemini setup removes legacy commands dir'); - const result23b = await ideManager23.setup('gemini', tempProjectDir23, installedBmadDir23, { silent: true, selectedModules: ['bmm'], @@ -1055,16 +856,9 @@ async function runTests() { const iflowInstaller = platformCodes24.platforms.iflow?.installer; assert(iflowInstaller?.target_dir === '.iflow/skills', 'iFlow target_dir uses native skills path'); - assert( - Array.isArray(iflowInstaller?.legacy_targets) && iflowInstaller.legacy_targets.includes('.iflow/commands'), - 'iFlow installer cleans legacy commands output', - ); const tempProjectDir24 = await fs.mkdtemp(path.join(os.tmpdir(), 'bmad-iflow-test-')); const installedBmadDir24 = await createTestBmadFixture(); - const legacyDir24 = path.join(tempProjectDir24, '.iflow', 'commands'); - await fs.ensureDir(legacyDir24); - await fs.writeFile(path.join(legacyDir24, 'bmad-legacy.md'), 'legacy\n'); const ideManager24 = new IdeManager(); await ideManager24.ensureInitialized(); @@ -1083,8 +877,6 @@ async function runTests() { const nameMatch24 = skillContent24.match(/^name:\s*(.+)$/m); assert(nameMatch24 && nameMatch24[1].trim() === 'bmad-master', 'iFlow skill name frontmatter matches directory name exactly'); - assert(!(await fs.pathExists(path.join(tempProjectDir24, '.iflow', 'commands'))), 'iFlow setup removes legacy commands dir'); - await fs.remove(tempProjectDir24); await fs.remove(path.dirname(installedBmadDir24)); } catch (error) { @@ -1104,16 +896,9 @@ async function runTests() { const qwenInstaller = platformCodes25.platforms.qwen?.installer; assert(qwenInstaller?.target_dir === '.qwen/skills', 'QwenCoder target_dir uses native skills path'); - assert( - Array.isArray(qwenInstaller?.legacy_targets) && qwenInstaller.legacy_targets.includes('.qwen/commands'), - 'QwenCoder installer cleans legacy commands output', - ); const tempProjectDir25 = await fs.mkdtemp(path.join(os.tmpdir(), 'bmad-qwen-test-')); const installedBmadDir25 = await createTestBmadFixture(); - const legacyDir25 = path.join(tempProjectDir25, '.qwen', 'commands'); - await fs.ensureDir(legacyDir25); - await fs.writeFile(path.join(legacyDir25, 'bmad-legacy.md'), 'legacy\n'); const ideManager25 = new IdeManager(); await ideManager25.ensureInitialized(); @@ -1132,8 +917,6 @@ async function runTests() { const nameMatch25 = skillContent25.match(/^name:\s*(.+)$/m); assert(nameMatch25 && nameMatch25[1].trim() === 'bmad-master', 'QwenCoder skill name frontmatter matches directory name exactly'); - assert(!(await fs.pathExists(path.join(tempProjectDir25, '.qwen', 'commands'))), 'QwenCoder setup removes legacy commands dir'); - await fs.remove(tempProjectDir25); await fs.remove(path.dirname(installedBmadDir25)); } catch (error) { @@ -1153,16 +936,9 @@ async function runTests() { const rovoInstaller = platformCodes26.platforms['rovo-dev']?.installer; assert(rovoInstaller?.target_dir === '.rovodev/skills', 'Rovo Dev target_dir uses native skills path'); - assert( - Array.isArray(rovoInstaller?.legacy_targets) && rovoInstaller.legacy_targets.includes('.rovodev/workflows'), - 'Rovo Dev installer cleans legacy workflows output', - ); const tempProjectDir26 = await fs.mkdtemp(path.join(os.tmpdir(), 'bmad-rovodev-test-')); const installedBmadDir26 = await createTestBmadFixture(); - const legacyDir26 = path.join(tempProjectDir26, '.rovodev', 'workflows'); - await fs.ensureDir(legacyDir26); - await fs.writeFile(path.join(legacyDir26, 'bmad-legacy.md'), 'legacy\n'); // Create a prompts.yml with BMAD entries and a user entry const yaml26 = require('yaml'); @@ -1173,6 +949,7 @@ async function runTests() { { name: 'my-custom-prompt', description: 'User prompt', content_file: 'custom.md' }, ], }); + await fs.ensureDir(path.dirname(promptsPath26)); await fs.writeFile(promptsPath26, promptsContent26); const ideManager26 = new IdeManager(); @@ -1192,8 +969,6 @@ async function runTests() { const nameMatch26 = skillContent26.match(/^name:\s*(.+)$/m); assert(nameMatch26 && nameMatch26[1].trim() === 'bmad-master', 'Rovo Dev skill name frontmatter matches directory name exactly'); - assert(!(await fs.pathExists(path.join(tempProjectDir26, '.rovodev', 'workflows'))), 'Rovo Dev setup removes legacy workflows dir'); - // Verify prompts.yml cleanup: BMAD entries removed, user entry preserved const cleanedPrompts26 = yaml26.parse(await fs.readFile(promptsPath26, 'utf8')); assert( diff --git a/tools/docs/native-skills-migration-checklist.md b/tools/docs/native-skills-migration-checklist.md index 80c6a9296..e8fa4ad34 100644 --- a/tools/docs/native-skills-migration-checklist.md +++ b/tools/docs/native-skills-migration-checklist.md @@ -222,7 +222,6 @@ Support assumption: full Agent Skills support. Gemini CLI docs confirm workspace - [x] Confirm Gemini CLI native skills path is `.gemini/skills/{skill-name}/SKILL.md` (per [geminicli.com/docs/cli/skills](https://geminicli.com/docs/cli/skills/)) - [x] Implement native skills output — target_dir `.gemini/skills`, skill_format true, template_type default (replaces TOML templates) -- [x] Add legacy cleanup for `.gemini/commands` (via `legacy_targets`) - [x] Test fresh install — skills written to `.gemini/skills/bmad-master/SKILL.md` with correct frontmatter - [x] Test reinstall/upgrade from legacy TOML command output — legacy dir removed, skills installed - [x] Confirm no ancestor conflict protection is needed — Gemini CLI uses workspace > user > extension precedence, no ancestor directory inheritance @@ -236,7 +235,6 @@ Support assumption: full Agent Skills support. iFlow docs confirm workspace skil - [x] Confirm iFlow native skills path is `.iflow/skills/{skill-name}/SKILL.md` - [x] Implement native skills output — target_dir `.iflow/skills`, skill_format true, template_type default -- [x] Add legacy cleanup for `.iflow/commands` (via `legacy_targets`) - [x] Test fresh install — skills written to `.iflow/skills/bmad-master/SKILL.md` - [x] Test legacy cleanup — legacy commands dir removed - [x] Implement/extend automated tests — 6 assertions in test suite 24 @@ -249,7 +247,6 @@ Support assumption: full Agent Skills support. Qwen Code supports workspace skil - [x] Confirm QwenCoder native skills path is `.qwen/skills/{skill-name}/SKILL.md` - [x] Implement native skills output — target_dir `.qwen/skills`, skill_format true, template_type default -- [x] Add legacy cleanup for `.qwen/commands` (via `legacy_targets`) - [x] Test fresh install — skills written to `.qwen/skills/bmad-master/SKILL.md` - [x] Test legacy cleanup — legacy commands dir removed - [x] Implement/extend automated tests — 6 assertions in test suite 25 @@ -262,7 +259,6 @@ Support assumption: full Agent Skills support. Rovo Dev now supports workspace s - [x] Confirm Rovo Dev native skills path is `.rovodev/skills/{skill-name}/SKILL.md` (per Atlassian blog) - [x] Replace 257-line custom `rovodev.js` with config-driven entry in `platform-codes.yaml` -- [x] Add legacy cleanup for `.rovodev/workflows` (via `legacy_targets`) and BMAD entries in `prompts.yml` (via `cleanupRovoDevPrompts()` in `_config-driven.js`) - [x] Test fresh install — skills written to `.rovodev/skills/bmad-master/SKILL.md` - [x] Test legacy cleanup — legacy workflows dir removed, `prompts.yml` BMAD entries stripped while preserving user entries - [x] Implement/extend automated tests — 8 assertions in test suite 26 diff --git a/tools/installer/core/installer.js b/tools/installer/core/installer.js index ef6e8662f..4ed364c5f 100644 --- a/tools/installer/core/installer.js +++ b/tools/installer/core/installer.js @@ -14,6 +14,7 @@ const { ExternalModuleManager } = require('../modules/external-manager'); const { resolveModuleVersion } = require('../modules/version-resolver'); const { ExistingInstall } = require('./existing-install'); +const { warnPreNativeSkillsLegacy } = require('./legacy-warnings'); class Installer { constructor() { @@ -41,6 +42,11 @@ class Installer { const officialModules = await OfficialModules.build(config, paths); const existingInstall = await ExistingInstall.detect(paths.bmadDir); + await warnPreNativeSkillsLegacy({ + projectRoot: paths.projectRoot, + existingVersion: existingInstall.installed ? existingInstall.version : null, + }); + if (existingInstall.installed) { await this._removeDeselectedModules(existingInstall, config, paths); updateState = await this._prepareUpdateState(paths, config, existingInstall, officialModules); diff --git a/tools/installer/core/legacy-warnings.js b/tools/installer/core/legacy-warnings.js new file mode 100644 index 000000000..6c1bde03a --- /dev/null +++ b/tools/installer/core/legacy-warnings.js @@ -0,0 +1,108 @@ +const os = require('node:os'); +const path = require('node:path'); +const semver = require('semver'); +const fs = require('../fs-native'); +const prompts = require('../prompts'); + +const MIN_NATIVE_SKILLS_VERSION = '6.1.0'; + +const LEGACY_PATHS = [ + '.agent/workflows', + '.augment/commands', + '.claude/commands', + '.clinerules/workflows', + '.codex/prompts', + '~/.codex/prompts', + '.codebuddy/commands', + '.crush/commands', + '.cursor/commands', + '.gemini/commands', + '.github/agents', + '.github/prompts', + '.iflow/commands', + '.kilocode/workflows', + '.kiro/steering', + '.opencode/agents', + '.opencode/commands', + '.opencode/agent', + '.opencode/command', + '.qwen/commands', + '.roo/commands', + '.rovodev/workflows', + '.trae/rules', + '.windsurf/workflows', +]; + +function expandPath(p) { + if (p === '~') return os.homedir(); + if (p.startsWith('~/')) return path.join(os.homedir(), p.slice(2)); + return p; +} + +function resolveLegacyPath(projectRoot, p) { + if (path.isAbsolute(p) || p.startsWith('~')) return expandPath(p); + return path.join(projectRoot, p); +} + +async function findStaleLegacyDirs(projectRoot) { + const findings = []; + for (const legacyPath of LEGACY_PATHS) { + const resolved = resolveLegacyPath(projectRoot, legacyPath); + if (!(await fs.pathExists(resolved))) continue; + try { + const entries = await fs.readdir(resolved); + const bmadEntries = entries.filter( + (e) => typeof e === 'string' && e.toLowerCase().startsWith('bmad') && !e.toLowerCase().startsWith('bmad-os-'), + ); + if (bmadEntries.length > 0) { + findings.push({ path: resolved, displayPath: legacyPath, count: bmadEntries.length }); + } + } catch { + // Unreadable dir — skip + } + } + return findings; +} + +function isPreNativeSkillsVersion(version) { + if (!version) return false; + const coerced = semver.valid(version) || semver.valid(semver.coerce(version)); + if (!coerced) return false; + return semver.lt(coerced, MIN_NATIVE_SKILLS_VERSION); +} + +async function warnPreNativeSkillsLegacy({ projectRoot, existingVersion } = {}) { + const versionTriggered = isPreNativeSkillsVersion(existingVersion); + const staleDirs = await findStaleLegacyDirs(projectRoot); + + if (!versionTriggered && staleDirs.length === 0) return; + + if (versionTriggered) { + await prompts.log.warn( + `Detected previous BMAD install v${existingVersion} (pre-${MIN_NATIVE_SKILLS_VERSION}). ` + + `BMAD switched to native skills format in v${MIN_NATIVE_SKILLS_VERSION}; old command/workflow directories from your prior install may still be present.`, + ); + } + + if (staleDirs.length > 0) { + await prompts.log.warn( + `Found stale BMAD entries in ${staleDirs.length} legacy location(s) that the new installer no longer manages. ` + + `Your AI tool may load these alongside the new skills, causing duplicates. Remove them manually:`, + ); + for (const finding of staleDirs) { + await prompts.log.message(` rm -rf "${finding.path}"/bmad* # ${finding.count} stale entr${finding.count === 1 ? 'y' : 'ies'}`); + } + } else if (versionTriggered) { + await prompts.log.message( + ' No stale legacy directories detected, but if your AI tool shows duplicate BMAD commands after install, check for old `bmad-*` entries in tool-specific dirs (e.g. .claude/commands, .cursor/commands).', + ); + } +} + +module.exports = { + warnPreNativeSkillsLegacy, + findStaleLegacyDirs, + isPreNativeSkillsVersion, + LEGACY_PATHS, + MIN_NATIVE_SKILLS_VERSION, +}; diff --git a/tools/installer/ide/_config-driven.js b/tools/installer/ide/_config-driven.js index 563818f67..3047f8cfb 100644 --- a/tools/installer/ide/_config-driven.js +++ b/tools/installer/ide/_config-driven.js @@ -1,4 +1,3 @@ -const os = require('node:os'); const path = require('node:path'); const fs = require('../fs-native'); const yaml = require('yaml'); @@ -16,7 +15,7 @@ const { BMAD_FOLDER_NAME } = require('./shared/path-utils'); * Features: * - Config-driven from platform-codes.yaml * - Verbatim skill installation from skill-manifest.csv - * - Legacy directory cleanup and IDE-specific marker removal + * - IDE-specific marker removal (copilot-instructions, kilo modes, rovodev prompts) */ class ConfigDrivenIdeSetup { constructor(platformCode, platformConfig) { @@ -222,27 +221,6 @@ class ConfigDrivenIdeSetup { removalSet = new Set(); } - // Migrate legacy target directories (e.g. .opencode/agent → .opencode/agents) - // Legacy dirs are abandoned entirely, so use prefix matching (null removalSet) - if (this.installerConfig?.legacy_targets) { - const legacyDirsExist = await Promise.all( - this.installerConfig.legacy_targets.map((d) => - this.isGlobalPath(d) ? fs.pathExists(d.replace(/^~/, os.homedir())) : fs.pathExists(path.join(projectDir, d)), - ), - ); - if (legacyDirsExist.some(Boolean)) { - if (!options.silent) await prompts.log.message(' Migrating legacy directories...'); - for (const legacyDir of this.installerConfig.legacy_targets) { - if (this.isGlobalPath(legacyDir)) { - await this.warnGlobalLegacy(legacyDir, options); - } else { - await this.cleanupTarget(projectDir, legacyDir, options, null); - await this.removeEmptyParents(projectDir, legacyDir); - } - } - } - } - // Strip BMAD markers from copilot-instructions.md if present if (this.name === 'github-copilot') { await this.cleanupCopilotInstructions(projectDir, options); @@ -264,41 +242,6 @@ class ConfigDrivenIdeSetup { } } - /** - * Check if a path is global (starts with ~ or is absolute) - * @param {string} p - Path to check - * @returns {boolean} - */ - isGlobalPath(p) { - return p.startsWith('~') || path.isAbsolute(p); - } - - /** - * Warn about stale BMAD files in a global legacy directory (never auto-deletes) - * @param {string} legacyDir - Legacy directory path (may start with ~) - * @param {Object} options - Options (silent, etc.) - */ - async warnGlobalLegacy(legacyDir, options = {}) { - try { - const expanded = legacyDir.startsWith('~/') - ? path.join(os.homedir(), legacyDir.slice(2)) - : legacyDir === '~' - ? os.homedir() - : legacyDir; - - if (!(await fs.pathExists(expanded))) return; - - const entries = await fs.readdir(expanded); - const bmadFiles = entries.filter((e) => typeof e === 'string' && e.startsWith('bmad')); - - if (bmadFiles.length > 0 && !options.silent) { - await prompts.log.warn(`Found ${bmadFiles.length} stale BMAD file(s) in ${expanded}. Remove manually: rm ${expanded}/bmad-*`); - } - } catch { - // Errors reading global paths are silently ignored - } - } - /** * Find the _bmad directory in a project * @param {string} projectDir - Project directory @@ -605,43 +548,6 @@ class ConfigDrivenIdeSetup { return null; } - - /** - * Walk up ancestor directories from relativeDir toward projectDir, removing each if empty - * Stops at projectDir boundary — never removes projectDir itself - * @param {string} projectDir - Project root (boundary) - * @param {string} relativeDir - Relative directory to start from - */ - async removeEmptyParents(projectDir, relativeDir) { - const resolvedProject = path.resolve(projectDir); - let current = relativeDir; - let last = null; - while (current && current !== '.' && current !== last) { - last = current; - const fullPath = path.resolve(projectDir, current); - // Boundary guard: never traverse outside projectDir - if (!fullPath.startsWith(resolvedProject + path.sep) && fullPath !== resolvedProject) break; - try { - if (!(await fs.pathExists(fullPath))) { - // Dir already gone — advance current; last is reset at top of next iteration - current = path.dirname(current); - continue; - } - const remaining = await fs.readdir(fullPath); - if (remaining.length > 0) break; - await fs.rmdir(fullPath); - } catch (error) { - // ENOTEMPTY: TOCTOU race (file added between readdir and rmdir) — skip level, continue upward - // ENOENT: dir removed by another process between pathExists and rmdir — skip level, continue upward - if (error.code === 'ENOTEMPTY' || error.code === 'ENOENT') { - current = path.dirname(current); - continue; - } - break; // fatal error (e.g. EACCES) — stop upward walk - } - current = path.dirname(current); - } - } } module.exports = { ConfigDrivenIdeSetup }; diff --git a/tools/installer/ide/platform-codes.yaml b/tools/installer/ide/platform-codes.yaml index 1899473c0..85a5db03b 100644 --- a/tools/installer/ide/platform-codes.yaml +++ b/tools/installer/ide/platform-codes.yaml @@ -6,7 +6,6 @@ # suspended: (optional) Message explaining why install is blocked # installer: # target_dir: Directory where skill directories are installed -# legacy_targets: (optional) Old target dirs to clean up on reinstall # ancestor_conflict_check: (optional) Refuse install when ancestor dir has BMAD files platforms: @@ -14,90 +13,66 @@ platforms: name: "Google Antigravity" preferred: false installer: - legacy_targets: - - .agent/workflows target_dir: .agent/skills auggie: name: "Auggie" preferred: false installer: - legacy_targets: - - .augment/commands target_dir: .augment/skills claude-code: name: "Claude Code" preferred: true installer: - legacy_targets: - - .claude/commands target_dir: .claude/skills cline: name: "Cline" preferred: false installer: - legacy_targets: - - .clinerules/workflows target_dir: .cline/skills codex: name: "Codex" preferred: false installer: - legacy_targets: - - .codex/prompts - - ~/.codex/prompts target_dir: .agents/skills codebuddy: name: "CodeBuddy" preferred: false installer: - legacy_targets: - - .codebuddy/commands target_dir: .codebuddy/skills crush: name: "Crush" preferred: false installer: - legacy_targets: - - .crush/commands target_dir: .crush/skills cursor: name: "Cursor" preferred: true installer: - legacy_targets: - - .cursor/commands target_dir: .cursor/skills gemini: name: "Gemini CLI" preferred: false installer: - legacy_targets: - - .gemini/commands target_dir: .gemini/skills github-copilot: name: "GitHub Copilot" preferred: false installer: - legacy_targets: - - .github/agents - - .github/prompts target_dir: .github/skills iflow: name: "iFlow" preferred: false installer: - legacy_targets: - - .iflow/commands target_dir: .iflow/skills junie: @@ -110,8 +85,6 @@ platforms: name: "KiloCoder" preferred: false installer: - legacy_targets: - - .kilocode/workflows target_dir: .kilocode/skills kimi-code: @@ -124,8 +97,6 @@ platforms: name: "Kiro" preferred: false installer: - legacy_targets: - - .kiro/steering target_dir: .kiro/skills ona: @@ -138,11 +109,6 @@ platforms: name: "OpenCode" preferred: false installer: - legacy_targets: - - .opencode/agents - - .opencode/commands - - .opencode/agent - - .opencode/command target_dir: .opencode/skills pi: @@ -161,38 +127,28 @@ platforms: name: "QwenCoder" preferred: false installer: - legacy_targets: - - .qwen/commands target_dir: .qwen/skills roo: name: "Roo Code" preferred: false installer: - legacy_targets: - - .roo/commands target_dir: .roo/skills rovo-dev: name: "Rovo Dev" preferred: false installer: - legacy_targets: - - .rovodev/workflows target_dir: .rovodev/skills trae: name: "Trae" preferred: false installer: - legacy_targets: - - .trae/rules target_dir: .trae/skills windsurf: name: "Windsurf" preferred: false installer: - legacy_targets: - - .windsurf/workflows target_dir: .windsurf/skills diff --git a/tools/platform-codes.yaml b/tools/platform-codes.yaml deleted file mode 100644 index f57e9ef5c..000000000 --- a/tools/platform-codes.yaml +++ /dev/null @@ -1,175 +0,0 @@ -# BMAD Platform Codes Configuration -# Central configuration for all platform/IDE codes used in the BMAD system -# -# This file defines the standardized platform codes that are used throughout -# the installation system to identify different platforms (IDEs, tools, etc.) -# -# Format: -# code: Platform identifier used internally -# name: Display name shown to users -# preferred: Whether this platform is shown as a recommended option on install -# category: Type of platform (ide, tool, service, etc.) - -platforms: - # Recommended Platforms - claude-code: - name: "Claude Code" - preferred: true - category: cli - description: "Anthropic's official CLI for Claude" - - cursor: - name: "Cursor" - preferred: true - category: ide - description: "AI-first code editor" - - # Other IDEs and Tools - cline: - name: "Cline" - preferred: false - category: ide - description: "AI coding assistant" - - opencode: - name: "OpenCode" - preferred: false - category: ide - description: "OpenCode terminal coding assistant" - - codebuddy: - name: "CodeBuddy" - preferred: false - category: ide - description: "Tencent Cloud Code Assistant - AI-powered coding companion" - - auggie: - name: "Auggie" - preferred: false - category: cli - description: "AI development tool" - - roo: - name: "Roo Code" - preferred: false - category: ide - description: "Enhanced Cline fork" - - rovo-dev: - name: "Rovo Dev" - preferred: false - category: ide - description: "Atlassian's Rovo development environment" - - kiro: - name: "Kiro" - preferred: false - category: ide - description: "Amazon's AI-powered IDE" - - github-copilot: - name: "GitHub Copilot" - preferred: false - category: ide - description: "GitHub's AI pair programmer" - - codex: - name: "Codex" - preferred: false - category: cli - description: "OpenAI Codex integration" - - qwen: - name: "QwenCoder" - preferred: false - category: ide - description: "Qwen AI coding assistant" - - gemini: - name: "Gemini CLI" - preferred: false - category: cli - description: "Google's CLI for Gemini" - - iflow: - name: "iFlow" - preferred: false - category: ide - description: "AI workflow automation" - - kilo: - name: "KiloCoder" - preferred: false - category: ide - description: "AI coding platform" - - kimi-code: - name: "Kimi Code" - preferred: false - category: cli - description: "Moonshot AI's Kimi Code CLI" - - crush: - name: "Crush" - preferred: false - category: ide - description: "AI development assistant" - - antigravity: - name: "Google Antigravity" - preferred: false - category: ide - description: "Google's AI development environment" - - trae: - name: "Trae" - preferred: false - category: ide - description: "AI coding tool" - - windsurf: - name: "Windsurf" - preferred: false - category: ide - description: "AI-powered IDE with cascade flows" - - junie: - name: "Junie" - preferred: false - category: cli - description: "AI coding agent by JetBrains" - - ona: - name: "Ona" - preferred: false - category: ide - description: "Ona AI development environment" - -# Platform categories -categories: - ide: - name: "Integrated Development Environment" - description: "Full-featured code editors with AI assistance" - - cli: - name: "Command Line Interface" - description: "Terminal-based tools" - - tool: - name: "Development Tool" - description: "Standalone development utilities" - - service: - name: "Cloud Service" - description: "Cloud-based development platforms" - - extension: - name: "Editor Extension" - description: "Plugins for existing editors" - -# Naming conventions and rules -conventions: - code_format: "lowercase-kebab-case" - name_format: "Title Case" - max_code_length: 20 - allowed_characters: "a-z0-9-"