109 lines
3.4 KiB
JavaScript
109 lines
3.4 KiB
JavaScript
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,
|
|
};
|