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.
This commit is contained in:
parent
1197122001
commit
08a3e88b86
|
|
@ -60,7 +60,7 @@ Dostupná ID nástrojů pro příznak `--tools`:
|
||||||
|
|
||||||
**Preferované:** `claude-code`, `cursor`
|
**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
|
## Režimy instalace
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -141,17 +141,8 @@ async function runTests() {
|
||||||
|
|
||||||
assert(windsurfInstaller?.target_dir === '.windsurf/skills', 'Windsurf target_dir uses native skills path');
|
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 tempProjectDir = await fs.mkdtemp(path.join(os.tmpdir(), 'bmad-windsurf-test-'));
|
||||||
const installedBmadDir = await createTestBmadFixture();
|
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();
|
const ideManager = new IdeManager();
|
||||||
await ideManager.ensureInitialized();
|
await ideManager.ensureInitialized();
|
||||||
|
|
@ -165,8 +156,6 @@ async function runTests() {
|
||||||
const skillFile = path.join(tempProjectDir, '.windsurf', 'skills', 'bmad-master', 'SKILL.md');
|
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(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(tempProjectDir);
|
||||||
await fs.remove(path.dirname(installedBmadDir));
|
await fs.remove(path.dirname(installedBmadDir));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
@ -187,17 +176,8 @@ async function runTests() {
|
||||||
|
|
||||||
assert(kiroInstaller?.target_dir === '.kiro/skills', 'Kiro target_dir uses native skills path');
|
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 tempProjectDir = await fs.mkdtemp(path.join(os.tmpdir(), 'bmad-kiro-test-'));
|
||||||
const installedBmadDir = await createTestBmadFixture();
|
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();
|
const ideManager = new IdeManager();
|
||||||
await ideManager.ensureInitialized();
|
await ideManager.ensureInitialized();
|
||||||
|
|
@ -211,8 +191,6 @@ async function runTests() {
|
||||||
const skillFile = path.join(tempProjectDir, '.kiro', 'skills', 'bmad-master', 'SKILL.md');
|
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(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(tempProjectDir);
|
||||||
await fs.remove(path.dirname(installedBmadDir));
|
await fs.remove(path.dirname(installedBmadDir));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
@ -233,17 +211,8 @@ async function runTests() {
|
||||||
|
|
||||||
assert(antigravityInstaller?.target_dir === '.agent/skills', 'Antigravity target_dir uses native skills path');
|
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 tempProjectDir = await fs.mkdtemp(path.join(os.tmpdir(), 'bmad-antigravity-test-'));
|
||||||
const installedBmadDir = await createTestBmadFixture();
|
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();
|
const ideManager = new IdeManager();
|
||||||
await ideManager.ensureInitialized();
|
await ideManager.ensureInitialized();
|
||||||
|
|
@ -257,8 +226,6 @@ async function runTests() {
|
||||||
const skillFile = path.join(tempProjectDir, '.agent', 'skills', 'bmad-master', 'SKILL.md');
|
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(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(tempProjectDir);
|
||||||
await fs.remove(path.dirname(installedBmadDir));
|
await fs.remove(path.dirname(installedBmadDir));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
@ -279,11 +246,6 @@ async function runTests() {
|
||||||
|
|
||||||
assert(auggieInstaller?.target_dir === '.augment/skills', 'Auggie target_dir uses native skills path');
|
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(
|
assert(
|
||||||
auggieInstaller?.ancestor_conflict_check !== true,
|
auggieInstaller?.ancestor_conflict_check !== true,
|
||||||
'Auggie installer does not enable ancestor conflict checks without verified inheritance',
|
'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 tempProjectDir = await fs.mkdtemp(path.join(os.tmpdir(), 'bmad-auggie-test-'));
|
||||||
const installedBmadDir = await createTestBmadFixture();
|
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();
|
const ideManager = new IdeManager();
|
||||||
await ideManager.ensureInitialized();
|
await ideManager.ensureInitialized();
|
||||||
|
|
@ -308,8 +266,6 @@ async function runTests() {
|
||||||
const skillFile = path.join(tempProjectDir, '.augment', 'skills', 'bmad-master', 'SKILL.md');
|
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(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(tempProjectDir);
|
||||||
await fs.remove(path.dirname(installedBmadDir));
|
await fs.remove(path.dirname(installedBmadDir));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
@ -330,28 +286,8 @@ async function runTests() {
|
||||||
|
|
||||||
assert(opencodeInstaller?.target_dir === '.opencode/skills', 'OpenCode target_dir uses native skills path');
|
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 tempProjectDir = await fs.mkdtemp(path.join(os.tmpdir(), 'bmad-opencode-test-'));
|
||||||
const installedBmadDir = await createTestBmadFixture();
|
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();
|
const ideManager = new IdeManager();
|
||||||
await ideManager.ensureInitialized();
|
await ideManager.ensureInitialized();
|
||||||
|
|
@ -365,13 +301,6 @@ async function runTests() {
|
||||||
const skillFile = path.join(tempProjectDir, '.opencode', 'skills', 'bmad-master', 'SKILL.md');
|
const skillFile = path.join(tempProjectDir, '.opencode', 'skills', 'bmad-master', 'SKILL.md');
|
||||||
assert(await fs.pathExists(skillFile), 'OpenCode install writes SKILL.md directory output');
|
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(tempProjectDir);
|
||||||
await fs.remove(path.dirname(installedBmadDir));
|
await fs.remove(path.dirname(installedBmadDir));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
@ -392,16 +321,8 @@ async function runTests() {
|
||||||
|
|
||||||
assert(claudeInstaller?.target_dir === '.claude/skills', 'Claude Code target_dir uses native skills path');
|
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 tempProjectDir9 = await fs.mkdtemp(path.join(os.tmpdir(), 'bmad-claude-code-test-'));
|
||||||
const installedBmadDir9 = await createTestBmadFixture();
|
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();
|
const ideManager9 = new IdeManager();
|
||||||
await ideManager9.ensureInitialized();
|
await ideManager9.ensureInitialized();
|
||||||
|
|
@ -420,8 +341,6 @@ async function runTests() {
|
||||||
const nameMatch9 = skillContent9.match(/^name:\s*(.+)$/m);
|
const nameMatch9 = skillContent9.match(/^name:\s*(.+)$/m);
|
||||||
assert(nameMatch9 && nameMatch9[1].trim() === 'bmad-master', 'Claude Code skill name frontmatter matches directory name exactly');
|
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(tempProjectDir9);
|
||||||
await fs.remove(path.dirname(installedBmadDir9));
|
await fs.remove(path.dirname(installedBmadDir9));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
@ -444,16 +363,8 @@ async function runTests() {
|
||||||
|
|
||||||
assert(codexInstaller?.target_dir === '.agents/skills', 'Codex target_dir uses native skills path');
|
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 tempProjectDir11 = await fs.mkdtemp(path.join(os.tmpdir(), 'bmad-codex-test-'));
|
||||||
const installedBmadDir11 = await createTestBmadFixture();
|
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();
|
const ideManager11 = new IdeManager();
|
||||||
await ideManager11.ensureInitialized();
|
await ideManager11.ensureInitialized();
|
||||||
|
|
@ -472,8 +383,6 @@ async function runTests() {
|
||||||
const nameMatch11 = skillContent11.match(/^name:\s*(.+)$/m);
|
const nameMatch11 = skillContent11.match(/^name:\s*(.+)$/m);
|
||||||
assert(nameMatch11 && nameMatch11[1].trim() === 'bmad-master', 'Codex skill name frontmatter matches directory name exactly');
|
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(tempProjectDir11);
|
||||||
await fs.remove(path.dirname(installedBmadDir11));
|
await fs.remove(path.dirname(installedBmadDir11));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
@ -496,18 +405,10 @@ async function runTests() {
|
||||||
|
|
||||||
assert(cursorInstaller?.target_dir === '.cursor/skills', 'Cursor target_dir uses native skills path');
|
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');
|
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 tempProjectDir13c = await fs.mkdtemp(path.join(os.tmpdir(), 'bmad-cursor-test-'));
|
||||||
const installedBmadDir13c = await createTestBmadFixture();
|
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();
|
const ideManager13c = new IdeManager();
|
||||||
await ideManager13c.ensureInitialized();
|
await ideManager13c.ensureInitialized();
|
||||||
|
|
@ -526,8 +427,6 @@ async function runTests() {
|
||||||
const nameMatch13c = skillContent13c.match(/^name:\s*(.+)$/m);
|
const nameMatch13c = skillContent13c.match(/^name:\s*(.+)$/m);
|
||||||
assert(nameMatch13c && nameMatch13c[1].trim() === 'bmad-master', 'Cursor skill name frontmatter matches directory name exactly');
|
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(tempProjectDir13c);
|
||||||
await fs.remove(path.dirname(installedBmadDir13c));
|
await fs.remove(path.dirname(installedBmadDir13c));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
@ -548,17 +447,8 @@ async function runTests() {
|
||||||
|
|
||||||
assert(rooInstaller?.target_dir === '.roo/skills', 'Roo target_dir uses native skills path');
|
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 tempProjectDir13 = await fs.mkdtemp(path.join(os.tmpdir(), 'bmad-roo-test-'));
|
||||||
const installedBmadDir13 = await createTestBmadFixture();
|
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();
|
const ideManager13 = new IdeManager();
|
||||||
await ideManager13.ensureInitialized();
|
await ideManager13.ensureInitialized();
|
||||||
|
|
@ -580,8 +470,6 @@ async function runTests() {
|
||||||
'Roo skill name frontmatter matches directory name exactly (lowercase alphanumeric + hyphens)',
|
'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
|
// Reinstall/upgrade: run setup again over existing skills output
|
||||||
const result13b = await ideManager13.setup('roo', tempProjectDir13, installedBmadDir13, {
|
const result13b = await ideManager13.setup('roo', tempProjectDir13, installedBmadDir13, {
|
||||||
silent: true,
|
silent: true,
|
||||||
|
|
@ -617,29 +505,11 @@ async function runTests() {
|
||||||
|
|
||||||
assert(copilotInstaller?.target_dir === '.github/skills', 'GitHub Copilot target_dir uses native skills path');
|
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 tempProjectDir17 = await fs.mkdtemp(path.join(os.tmpdir(), 'bmad-copilot-test-'));
|
||||||
const installedBmadDir17 = await createTestBmadFixture();
|
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');
|
const copilotInstructionsPath17 = path.join(tempProjectDir17, '.github', 'copilot-instructions.md');
|
||||||
|
await fs.ensureDir(path.dirname(copilotInstructionsPath17));
|
||||||
await fs.writeFile(
|
await fs.writeFile(
|
||||||
copilotInstructionsPath17,
|
copilotInstructionsPath17,
|
||||||
'User content before\n<!-- BMAD:START -->\nBMAD generated content\n<!-- BMAD:END -->\nUser content after\n',
|
'User content before\n<!-- BMAD:START -->\nBMAD generated content\n<!-- BMAD:END -->\nUser content after\n',
|
||||||
|
|
@ -662,10 +532,6 @@ async function runTests() {
|
||||||
const nameMatch17 = skillContent17.match(/^name:\s*(.+)$/m);
|
const nameMatch17 = skillContent17.match(/^name:\s*(.+)$/m);
|
||||||
assert(nameMatch17 && nameMatch17[1].trim() === 'bmad-master', 'GitHub Copilot skill name frontmatter matches directory name exactly');
|
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
|
// Verify copilot-instructions.md BMAD markers were stripped but user content preserved
|
||||||
const cleanedInstructions17 = await fs.readFile(copilotInstructionsPath17, 'utf8');
|
const cleanedInstructions17 = await fs.readFile(copilotInstructionsPath17, 'utf8');
|
||||||
assert(
|
assert(
|
||||||
|
|
@ -697,17 +563,8 @@ async function runTests() {
|
||||||
|
|
||||||
assert(clineInstaller?.target_dir === '.cline/skills', 'Cline target_dir uses native skills path');
|
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 tempProjectDir18 = await fs.mkdtemp(path.join(os.tmpdir(), 'bmad-cline-test-'));
|
||||||
const installedBmadDir18 = await createTestBmadFixture();
|
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();
|
const ideManager18 = new IdeManager();
|
||||||
await ideManager18.ensureInitialized();
|
await ideManager18.ensureInitialized();
|
||||||
|
|
@ -726,8 +583,6 @@ async function runTests() {
|
||||||
const nameMatch18 = skillContent18.match(/^name:\s*(.+)$/m);
|
const nameMatch18 = skillContent18.match(/^name:\s*(.+)$/m);
|
||||||
assert(nameMatch18 && nameMatch18[1].trim() === 'bmad-master', 'Cline skill name frontmatter matches directory name exactly');
|
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
|
// Reinstall/upgrade: run setup again over existing skills output
|
||||||
const result18b = await ideManager18.setup('cline', tempProjectDir18, installedBmadDir18, {
|
const result18b = await ideManager18.setup('cline', tempProjectDir18, installedBmadDir18, {
|
||||||
silent: true,
|
silent: true,
|
||||||
|
|
@ -757,17 +612,8 @@ async function runTests() {
|
||||||
|
|
||||||
assert(codebuddyInstaller?.target_dir === '.codebuddy/skills', 'CodeBuddy target_dir uses native skills path');
|
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 tempProjectDir19 = await fs.mkdtemp(path.join(os.tmpdir(), 'bmad-codebuddy-test-'));
|
||||||
const installedBmadDir19 = await createTestBmadFixture();
|
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();
|
const ideManager19 = new IdeManager();
|
||||||
await ideManager19.ensureInitialized();
|
await ideManager19.ensureInitialized();
|
||||||
|
|
@ -785,8 +631,6 @@ async function runTests() {
|
||||||
const nameMatch19 = skillContent19.match(/^name:\s*(.+)$/m);
|
const nameMatch19 = skillContent19.match(/^name:\s*(.+)$/m);
|
||||||
assert(nameMatch19 && nameMatch19[1].trim() === 'bmad-master', 'CodeBuddy skill name frontmatter matches directory name exactly');
|
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, {
|
const result19b = await ideManager19.setup('codebuddy', tempProjectDir19, installedBmadDir19, {
|
||||||
silent: true,
|
silent: true,
|
||||||
selectedModules: ['bmm'],
|
selectedModules: ['bmm'],
|
||||||
|
|
@ -815,17 +659,8 @@ async function runTests() {
|
||||||
|
|
||||||
assert(crushInstaller?.target_dir === '.crush/skills', 'Crush target_dir uses native skills path');
|
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 tempProjectDir20 = await fs.mkdtemp(path.join(os.tmpdir(), 'bmad-crush-test-'));
|
||||||
const installedBmadDir20 = await createTestBmadFixture();
|
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();
|
const ideManager20 = new IdeManager();
|
||||||
await ideManager20.ensureInitialized();
|
await ideManager20.ensureInitialized();
|
||||||
|
|
@ -843,8 +678,6 @@ async function runTests() {
|
||||||
const nameMatch20 = skillContent20.match(/^name:\s*(.+)$/m);
|
const nameMatch20 = skillContent20.match(/^name:\s*(.+)$/m);
|
||||||
assert(nameMatch20 && nameMatch20[1].trim() === 'bmad-master', 'Crush skill name frontmatter matches directory name exactly');
|
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, {
|
const result20b = await ideManager20.setup('crush', tempProjectDir20, installedBmadDir20, {
|
||||||
silent: true,
|
silent: true,
|
||||||
selectedModules: ['bmm'],
|
selectedModules: ['bmm'],
|
||||||
|
|
@ -873,16 +706,8 @@ async function runTests() {
|
||||||
|
|
||||||
assert(traeInstaller?.target_dir === '.trae/skills', 'Trae target_dir uses native skills path');
|
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 tempProjectDir21 = await fs.mkdtemp(path.join(os.tmpdir(), 'bmad-trae-test-'));
|
||||||
const installedBmadDir21 = await createTestBmadFixture();
|
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();
|
const ideManager21 = new IdeManager();
|
||||||
await ideManager21.ensureInitialized();
|
await ideManager21.ensureInitialized();
|
||||||
|
|
@ -900,8 +725,6 @@ async function runTests() {
|
||||||
const nameMatch21 = skillContent21.match(/^name:\s*(.+)$/m);
|
const nameMatch21 = skillContent21.match(/^name:\s*(.+)$/m);
|
||||||
assert(nameMatch21 && nameMatch21[1].trim() === 'bmad-master', 'Trae skill name frontmatter matches directory name exactly');
|
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, {
|
const result21b = await ideManager21.setup('trae', tempProjectDir21, installedBmadDir21, {
|
||||||
silent: true,
|
silent: true,
|
||||||
selectedModules: ['bmm'],
|
selectedModules: ['bmm'],
|
||||||
|
|
@ -932,11 +755,6 @@ async function runTests() {
|
||||||
|
|
||||||
assert(kiloConfig22?.installer?.target_dir === '.kilocode/skills', 'KiloCoder target_dir uses native skills path');
|
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();
|
const ideManager22 = new IdeManager();
|
||||||
await ideManager22.ensureInitialized();
|
await ideManager22.ensureInitialized();
|
||||||
|
|
||||||
|
|
@ -950,11 +768,6 @@ async function runTests() {
|
||||||
const tempProjectDir22 = await fs.mkdtemp(path.join(os.tmpdir(), 'bmad-kilo-test-'));
|
const tempProjectDir22 = await fs.mkdtemp(path.join(os.tmpdir(), 'bmad-kilo-test-'));
|
||||||
const installedBmadDir22 = await createTestBmadFixture();
|
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, {
|
const result22 = await ideManager22.setup('kilo', tempProjectDir22, installedBmadDir22, {
|
||||||
silent: true,
|
silent: true,
|
||||||
selectedModules: ['bmm'],
|
selectedModules: ['bmm'],
|
||||||
|
|
@ -969,8 +782,6 @@ async function runTests() {
|
||||||
const nameMatch22 = skillContent22.match(/^name:\s*(.+)$/m);
|
const nameMatch22 = skillContent22.match(/^name:\s*(.+)$/m);
|
||||||
assert(nameMatch22 && nameMatch22[1].trim() === 'bmad-master', 'KiloCoder skill name frontmatter matches directory name exactly');
|
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, {
|
const result22b = await ideManager22.setup('kilo', tempProjectDir22, installedBmadDir22, {
|
||||||
silent: true,
|
silent: true,
|
||||||
selectedModules: ['bmm'],
|
selectedModules: ['bmm'],
|
||||||
|
|
@ -999,16 +810,8 @@ async function runTests() {
|
||||||
|
|
||||||
assert(geminiInstaller?.target_dir === '.gemini/skills', 'Gemini target_dir uses native skills path');
|
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 tempProjectDir23 = await fs.mkdtemp(path.join(os.tmpdir(), 'bmad-gemini-test-'));
|
||||||
const installedBmadDir23 = await createTestBmadFixture();
|
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();
|
const ideManager23 = new IdeManager();
|
||||||
await ideManager23.ensureInitialized();
|
await ideManager23.ensureInitialized();
|
||||||
|
|
@ -1026,8 +829,6 @@ async function runTests() {
|
||||||
const nameMatch23 = skillContent23.match(/^name:\s*(.+)$/m);
|
const nameMatch23 = skillContent23.match(/^name:\s*(.+)$/m);
|
||||||
assert(nameMatch23 && nameMatch23[1].trim() === 'bmad-master', 'Gemini skill name frontmatter matches directory name exactly');
|
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, {
|
const result23b = await ideManager23.setup('gemini', tempProjectDir23, installedBmadDir23, {
|
||||||
silent: true,
|
silent: true,
|
||||||
selectedModules: ['bmm'],
|
selectedModules: ['bmm'],
|
||||||
|
|
@ -1055,16 +856,9 @@ async function runTests() {
|
||||||
const iflowInstaller = platformCodes24.platforms.iflow?.installer;
|
const iflowInstaller = platformCodes24.platforms.iflow?.installer;
|
||||||
|
|
||||||
assert(iflowInstaller?.target_dir === '.iflow/skills', 'iFlow target_dir uses native skills path');
|
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 tempProjectDir24 = await fs.mkdtemp(path.join(os.tmpdir(), 'bmad-iflow-test-'));
|
||||||
const installedBmadDir24 = await createTestBmadFixture();
|
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();
|
const ideManager24 = new IdeManager();
|
||||||
await ideManager24.ensureInitialized();
|
await ideManager24.ensureInitialized();
|
||||||
|
|
@ -1083,8 +877,6 @@ async function runTests() {
|
||||||
const nameMatch24 = skillContent24.match(/^name:\s*(.+)$/m);
|
const nameMatch24 = skillContent24.match(/^name:\s*(.+)$/m);
|
||||||
assert(nameMatch24 && nameMatch24[1].trim() === 'bmad-master', 'iFlow skill name frontmatter matches directory name exactly');
|
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(tempProjectDir24);
|
||||||
await fs.remove(path.dirname(installedBmadDir24));
|
await fs.remove(path.dirname(installedBmadDir24));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
@ -1104,16 +896,9 @@ async function runTests() {
|
||||||
const qwenInstaller = platformCodes25.platforms.qwen?.installer;
|
const qwenInstaller = platformCodes25.platforms.qwen?.installer;
|
||||||
|
|
||||||
assert(qwenInstaller?.target_dir === '.qwen/skills', 'QwenCoder target_dir uses native skills path');
|
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 tempProjectDir25 = await fs.mkdtemp(path.join(os.tmpdir(), 'bmad-qwen-test-'));
|
||||||
const installedBmadDir25 = await createTestBmadFixture();
|
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();
|
const ideManager25 = new IdeManager();
|
||||||
await ideManager25.ensureInitialized();
|
await ideManager25.ensureInitialized();
|
||||||
|
|
@ -1132,8 +917,6 @@ async function runTests() {
|
||||||
const nameMatch25 = skillContent25.match(/^name:\s*(.+)$/m);
|
const nameMatch25 = skillContent25.match(/^name:\s*(.+)$/m);
|
||||||
assert(nameMatch25 && nameMatch25[1].trim() === 'bmad-master', 'QwenCoder skill name frontmatter matches directory name exactly');
|
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(tempProjectDir25);
|
||||||
await fs.remove(path.dirname(installedBmadDir25));
|
await fs.remove(path.dirname(installedBmadDir25));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
@ -1153,16 +936,9 @@ async function runTests() {
|
||||||
const rovoInstaller = platformCodes26.platforms['rovo-dev']?.installer;
|
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 === '.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 tempProjectDir26 = await fs.mkdtemp(path.join(os.tmpdir(), 'bmad-rovodev-test-'));
|
||||||
const installedBmadDir26 = await createTestBmadFixture();
|
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
|
// Create a prompts.yml with BMAD entries and a user entry
|
||||||
const yaml26 = require('yaml');
|
const yaml26 = require('yaml');
|
||||||
|
|
@ -1173,6 +949,7 @@ async function runTests() {
|
||||||
{ name: 'my-custom-prompt', description: 'User prompt', content_file: 'custom.md' },
|
{ name: 'my-custom-prompt', description: 'User prompt', content_file: 'custom.md' },
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
await fs.ensureDir(path.dirname(promptsPath26));
|
||||||
await fs.writeFile(promptsPath26, promptsContent26);
|
await fs.writeFile(promptsPath26, promptsContent26);
|
||||||
|
|
||||||
const ideManager26 = new IdeManager();
|
const ideManager26 = new IdeManager();
|
||||||
|
|
@ -1192,8 +969,6 @@ async function runTests() {
|
||||||
const nameMatch26 = skillContent26.match(/^name:\s*(.+)$/m);
|
const nameMatch26 = skillContent26.match(/^name:\s*(.+)$/m);
|
||||||
assert(nameMatch26 && nameMatch26[1].trim() === 'bmad-master', 'Rovo Dev skill name frontmatter matches directory name exactly');
|
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
|
// Verify prompts.yml cleanup: BMAD entries removed, user entry preserved
|
||||||
const cleanedPrompts26 = yaml26.parse(await fs.readFile(promptsPath26, 'utf8'));
|
const cleanedPrompts26 = yaml26.parse(await fs.readFile(promptsPath26, 'utf8'));
|
||||||
assert(
|
assert(
|
||||||
|
|
|
||||||
|
|
@ -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] 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] 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 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] 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
|
- [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] 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] 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 fresh install — skills written to `.iflow/skills/bmad-master/SKILL.md`
|
||||||
- [x] Test legacy cleanup — legacy commands dir removed
|
- [x] Test legacy cleanup — legacy commands dir removed
|
||||||
- [x] Implement/extend automated tests — 6 assertions in test suite 24
|
- [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] 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] 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 fresh install — skills written to `.qwen/skills/bmad-master/SKILL.md`
|
||||||
- [x] Test legacy cleanup — legacy commands dir removed
|
- [x] Test legacy cleanup — legacy commands dir removed
|
||||||
- [x] Implement/extend automated tests — 6 assertions in test suite 25
|
- [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] 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] 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 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] 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
|
- [x] Implement/extend automated tests — 8 assertions in test suite 26
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ const { ExternalModuleManager } = require('../modules/external-manager');
|
||||||
const { resolveModuleVersion } = require('../modules/version-resolver');
|
const { resolveModuleVersion } = require('../modules/version-resolver');
|
||||||
|
|
||||||
const { ExistingInstall } = require('./existing-install');
|
const { ExistingInstall } = require('./existing-install');
|
||||||
|
const { warnPreNativeSkillsLegacy } = require('./legacy-warnings');
|
||||||
|
|
||||||
class Installer {
|
class Installer {
|
||||||
constructor() {
|
constructor() {
|
||||||
|
|
@ -41,6 +42,11 @@ class Installer {
|
||||||
const officialModules = await OfficialModules.build(config, paths);
|
const officialModules = await OfficialModules.build(config, paths);
|
||||||
const existingInstall = await ExistingInstall.detect(paths.bmadDir);
|
const existingInstall = await ExistingInstall.detect(paths.bmadDir);
|
||||||
|
|
||||||
|
await warnPreNativeSkillsLegacy({
|
||||||
|
projectRoot: paths.projectRoot,
|
||||||
|
existingVersion: existingInstall.installed ? existingInstall.version : null,
|
||||||
|
});
|
||||||
|
|
||||||
if (existingInstall.installed) {
|
if (existingInstall.installed) {
|
||||||
await this._removeDeselectedModules(existingInstall, config, paths);
|
await this._removeDeselectedModules(existingInstall, config, paths);
|
||||||
updateState = await this._prepareUpdateState(paths, config, existingInstall, officialModules);
|
updateState = await this._prepareUpdateState(paths, config, existingInstall, officialModules);
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
};
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
const os = require('node:os');
|
|
||||||
const path = require('node:path');
|
const path = require('node:path');
|
||||||
const fs = require('../fs-native');
|
const fs = require('../fs-native');
|
||||||
const yaml = require('yaml');
|
const yaml = require('yaml');
|
||||||
|
|
@ -16,7 +15,7 @@ const { BMAD_FOLDER_NAME } = require('./shared/path-utils');
|
||||||
* Features:
|
* Features:
|
||||||
* - Config-driven from platform-codes.yaml
|
* - Config-driven from platform-codes.yaml
|
||||||
* - Verbatim skill installation from skill-manifest.csv
|
* - 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 {
|
class ConfigDrivenIdeSetup {
|
||||||
constructor(platformCode, platformConfig) {
|
constructor(platformCode, platformConfig) {
|
||||||
|
|
@ -222,27 +221,6 @@ class ConfigDrivenIdeSetup {
|
||||||
removalSet = new Set();
|
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
|
// Strip BMAD markers from copilot-instructions.md if present
|
||||||
if (this.name === 'github-copilot') {
|
if (this.name === 'github-copilot') {
|
||||||
await this.cleanupCopilotInstructions(projectDir, options);
|
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
|
* Find the _bmad directory in a project
|
||||||
* @param {string} projectDir - Project directory
|
* @param {string} projectDir - Project directory
|
||||||
|
|
@ -605,43 +548,6 @@ class ConfigDrivenIdeSetup {
|
||||||
|
|
||||||
return null;
|
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 };
|
module.exports = { ConfigDrivenIdeSetup };
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,6 @@
|
||||||
# suspended: (optional) Message explaining why install is blocked
|
# suspended: (optional) Message explaining why install is blocked
|
||||||
# installer:
|
# installer:
|
||||||
# target_dir: Directory where skill directories are installed
|
# 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
|
# ancestor_conflict_check: (optional) Refuse install when ancestor dir has BMAD files
|
||||||
|
|
||||||
platforms:
|
platforms:
|
||||||
|
|
@ -14,90 +13,66 @@ platforms:
|
||||||
name: "Google Antigravity"
|
name: "Google Antigravity"
|
||||||
preferred: false
|
preferred: false
|
||||||
installer:
|
installer:
|
||||||
legacy_targets:
|
|
||||||
- .agent/workflows
|
|
||||||
target_dir: .agent/skills
|
target_dir: .agent/skills
|
||||||
|
|
||||||
auggie:
|
auggie:
|
||||||
name: "Auggie"
|
name: "Auggie"
|
||||||
preferred: false
|
preferred: false
|
||||||
installer:
|
installer:
|
||||||
legacy_targets:
|
|
||||||
- .augment/commands
|
|
||||||
target_dir: .augment/skills
|
target_dir: .augment/skills
|
||||||
|
|
||||||
claude-code:
|
claude-code:
|
||||||
name: "Claude Code"
|
name: "Claude Code"
|
||||||
preferred: true
|
preferred: true
|
||||||
installer:
|
installer:
|
||||||
legacy_targets:
|
|
||||||
- .claude/commands
|
|
||||||
target_dir: .claude/skills
|
target_dir: .claude/skills
|
||||||
|
|
||||||
cline:
|
cline:
|
||||||
name: "Cline"
|
name: "Cline"
|
||||||
preferred: false
|
preferred: false
|
||||||
installer:
|
installer:
|
||||||
legacy_targets:
|
|
||||||
- .clinerules/workflows
|
|
||||||
target_dir: .cline/skills
|
target_dir: .cline/skills
|
||||||
|
|
||||||
codex:
|
codex:
|
||||||
name: "Codex"
|
name: "Codex"
|
||||||
preferred: false
|
preferred: false
|
||||||
installer:
|
installer:
|
||||||
legacy_targets:
|
|
||||||
- .codex/prompts
|
|
||||||
- ~/.codex/prompts
|
|
||||||
target_dir: .agents/skills
|
target_dir: .agents/skills
|
||||||
|
|
||||||
codebuddy:
|
codebuddy:
|
||||||
name: "CodeBuddy"
|
name: "CodeBuddy"
|
||||||
preferred: false
|
preferred: false
|
||||||
installer:
|
installer:
|
||||||
legacy_targets:
|
|
||||||
- .codebuddy/commands
|
|
||||||
target_dir: .codebuddy/skills
|
target_dir: .codebuddy/skills
|
||||||
|
|
||||||
crush:
|
crush:
|
||||||
name: "Crush"
|
name: "Crush"
|
||||||
preferred: false
|
preferred: false
|
||||||
installer:
|
installer:
|
||||||
legacy_targets:
|
|
||||||
- .crush/commands
|
|
||||||
target_dir: .crush/skills
|
target_dir: .crush/skills
|
||||||
|
|
||||||
cursor:
|
cursor:
|
||||||
name: "Cursor"
|
name: "Cursor"
|
||||||
preferred: true
|
preferred: true
|
||||||
installer:
|
installer:
|
||||||
legacy_targets:
|
|
||||||
- .cursor/commands
|
|
||||||
target_dir: .cursor/skills
|
target_dir: .cursor/skills
|
||||||
|
|
||||||
gemini:
|
gemini:
|
||||||
name: "Gemini CLI"
|
name: "Gemini CLI"
|
||||||
preferred: false
|
preferred: false
|
||||||
installer:
|
installer:
|
||||||
legacy_targets:
|
|
||||||
- .gemini/commands
|
|
||||||
target_dir: .gemini/skills
|
target_dir: .gemini/skills
|
||||||
|
|
||||||
github-copilot:
|
github-copilot:
|
||||||
name: "GitHub Copilot"
|
name: "GitHub Copilot"
|
||||||
preferred: false
|
preferred: false
|
||||||
installer:
|
installer:
|
||||||
legacy_targets:
|
|
||||||
- .github/agents
|
|
||||||
- .github/prompts
|
|
||||||
target_dir: .github/skills
|
target_dir: .github/skills
|
||||||
|
|
||||||
iflow:
|
iflow:
|
||||||
name: "iFlow"
|
name: "iFlow"
|
||||||
preferred: false
|
preferred: false
|
||||||
installer:
|
installer:
|
||||||
legacy_targets:
|
|
||||||
- .iflow/commands
|
|
||||||
target_dir: .iflow/skills
|
target_dir: .iflow/skills
|
||||||
|
|
||||||
junie:
|
junie:
|
||||||
|
|
@ -110,8 +85,6 @@ platforms:
|
||||||
name: "KiloCoder"
|
name: "KiloCoder"
|
||||||
preferred: false
|
preferred: false
|
||||||
installer:
|
installer:
|
||||||
legacy_targets:
|
|
||||||
- .kilocode/workflows
|
|
||||||
target_dir: .kilocode/skills
|
target_dir: .kilocode/skills
|
||||||
|
|
||||||
kimi-code:
|
kimi-code:
|
||||||
|
|
@ -124,8 +97,6 @@ platforms:
|
||||||
name: "Kiro"
|
name: "Kiro"
|
||||||
preferred: false
|
preferred: false
|
||||||
installer:
|
installer:
|
||||||
legacy_targets:
|
|
||||||
- .kiro/steering
|
|
||||||
target_dir: .kiro/skills
|
target_dir: .kiro/skills
|
||||||
|
|
||||||
ona:
|
ona:
|
||||||
|
|
@ -138,11 +109,6 @@ platforms:
|
||||||
name: "OpenCode"
|
name: "OpenCode"
|
||||||
preferred: false
|
preferred: false
|
||||||
installer:
|
installer:
|
||||||
legacy_targets:
|
|
||||||
- .opencode/agents
|
|
||||||
- .opencode/commands
|
|
||||||
- .opencode/agent
|
|
||||||
- .opencode/command
|
|
||||||
target_dir: .opencode/skills
|
target_dir: .opencode/skills
|
||||||
|
|
||||||
pi:
|
pi:
|
||||||
|
|
@ -161,38 +127,28 @@ platforms:
|
||||||
name: "QwenCoder"
|
name: "QwenCoder"
|
||||||
preferred: false
|
preferred: false
|
||||||
installer:
|
installer:
|
||||||
legacy_targets:
|
|
||||||
- .qwen/commands
|
|
||||||
target_dir: .qwen/skills
|
target_dir: .qwen/skills
|
||||||
|
|
||||||
roo:
|
roo:
|
||||||
name: "Roo Code"
|
name: "Roo Code"
|
||||||
preferred: false
|
preferred: false
|
||||||
installer:
|
installer:
|
||||||
legacy_targets:
|
|
||||||
- .roo/commands
|
|
||||||
target_dir: .roo/skills
|
target_dir: .roo/skills
|
||||||
|
|
||||||
rovo-dev:
|
rovo-dev:
|
||||||
name: "Rovo Dev"
|
name: "Rovo Dev"
|
||||||
preferred: false
|
preferred: false
|
||||||
installer:
|
installer:
|
||||||
legacy_targets:
|
|
||||||
- .rovodev/workflows
|
|
||||||
target_dir: .rovodev/skills
|
target_dir: .rovodev/skills
|
||||||
|
|
||||||
trae:
|
trae:
|
||||||
name: "Trae"
|
name: "Trae"
|
||||||
preferred: false
|
preferred: false
|
||||||
installer:
|
installer:
|
||||||
legacy_targets:
|
|
||||||
- .trae/rules
|
|
||||||
target_dir: .trae/skills
|
target_dir: .trae/skills
|
||||||
|
|
||||||
windsurf:
|
windsurf:
|
||||||
name: "Windsurf"
|
name: "Windsurf"
|
||||||
preferred: false
|
preferred: false
|
||||||
installer:
|
installer:
|
||||||
legacy_targets:
|
|
||||||
- .windsurf/workflows
|
|
||||||
target_dir: .windsurf/skills
|
target_dir: .windsurf/skills
|
||||||
|
|
|
||||||
|
|
@ -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-"
|
|
||||||
Loading…
Reference in New Issue