v4 detection cleanup

This commit is contained in:
Brian Madison 2025-12-27 20:31:12 +08:00
parent d4a94df29a
commit 2a8a4388a9
3 changed files with 52 additions and 173 deletions

View File

@ -1,6 +1,6 @@
# Document Project Workflow - Validation Checklist # Document Project Workflow - Validation Checklist
## Scan Level and Resumability (v1.2.0) ## Scan Level and Resumability
- [ ] Scan level selection offered (quick/deep/exhaustive) for initial_scan and full_rescan modes - [ ] Scan level selection offered (quick/deep/exhaustive) for initial_scan and full_rescan modes
- [ ] Deep-dive mode automatically uses exhaustive scan (no choice given) - [ ] Deep-dive mode automatically uses exhaustive scan (no choice given)
@ -223,7 +223,7 @@
All items in the following sections must be checked: All items in the following sections must be checked:
- ✓ Scan Level and Resumability (v1.2.0) - ✓ Scan Level and Resumability
- ✓ Write-as-you-go Architecture - ✓ Write-as-you-go Architecture
- ✓ Batching Strategy (if deep/exhaustive scan) - ✓ Batching Strategy (if deep/exhaustive scan)
- ✓ Project Detection and Classification - ✓ Project Detection and Classification

View File

@ -203,107 +203,17 @@ class Detector {
} }
/** /**
* Detect legacy BMAD v4 footprints (case-sensitive path checks) * Detect legacy BMAD v4 .bmad-method folder
* V4 used _bmad-method as default folder name
* V6+ uses configurable folder names and ALWAYS has _config/manifest.yaml with installation.version
* @param {string} projectDir - Project directory to check * @param {string} projectDir - Project directory to check
* @returns {{ hasLegacyV4: boolean, offenders: string[] }} * @returns {{ hasLegacyV4: boolean, offenders: string[] }}
*/ */
async detectLegacyV4(projectDir) { async detectLegacyV4(projectDir) {
// Helper: check if a directory is a V6+ installation
const isV6Installation = async (dirPath) => {
const manifestPath = path.join(dirPath, '_config', 'manifest.yaml');
if (!(await fs.pathExists(manifestPath))) {
return false;
}
try {
const yaml = require('yaml');
const manifestContent = await fs.readFile(manifestPath, 'utf8');
const manifest = yaml.parse(manifestContent);
// V6+ manifest has installation.version
return manifest && manifest.installation && manifest.installation.version;
} catch {
return false;
}
};
const offenders = []; const offenders = [];
// Strategy: // Check for .bmad-method folder
// 1. First scan for ANY V6+ installation (_config/manifest.yaml) const bmadMethodPath = path.join(projectDir, '.bmad-method');
// 2. If V6+ found → don't flag anything (user is already on V6+) if (await fs.pathExists(bmadMethodPath)) {
// 3. If NO V6+ found → flag folders with "bmad" in name as potential V4 legacy offenders.push(bmadMethodPath);
let hasV6Installation = false;
const potentialV4Folders = [];
try {
const entries = await fs.readdir(projectDir, { withFileTypes: true });
for (const entry of entries) {
if (entry.isDirectory()) {
const name = entry.name;
const fullPath = path.join(projectDir, entry.name);
// Check if directory is empty (skip empty leftover folders)
const dirContents = await fs.readdir(fullPath);
if (dirContents.length === 0) {
continue; // Skip empty folders
}
// Check if it's a V6+ installation by looking for _config/manifest.yaml
// This works for ANY folder name (not just bmad-prefixed)
const isV6 = await isV6Installation(fullPath);
if (isV6) {
// Found a V6+ installation - user is already on V6+
hasV6Installation = true;
// Don't break - continue scanning to be thorough
} else {
// Not V6+, check if this is the exact V4 folder name "bmad-method"
if (name === 'bmad-method') {
// This is the V4 default folder - flag it as legacy
potentialV4Folders.push(fullPath);
}
}
}
}
} catch {
// Ignore errors reading directory
}
// Only flag V4 folders if NO V6+ installation was found
if (!hasV6Installation && potentialV4Folders.length > 0) {
offenders.push(...potentialV4Folders);
}
// Check inside various IDE command folders for legacy bmad folders
// V4 used folders like 'bmad-method' or custom names in IDE commands
// V6+ uses 'bmad' in IDE commands (hardcoded in IDE handlers)
// Legacy V4 IDE command folders won't have a corresponding V6+ installation
const ideConfigFolders = ['.opencode', '.claude', '.crush', '.continue', '.cursor', '.windsurf', '.cline', '.roo-cline'];
for (const ideFolder of ideConfigFolders) {
const commandsDirName = ideFolder === '.opencode' ? 'command' : 'commands';
const commandsPath = path.join(projectDir, ideFolder, commandsDirName);
if (await fs.pathExists(commandsPath)) {
try {
const commandEntries = await fs.readdir(commandsPath, { withFileTypes: true });
for (const entry of commandEntries) {
if (entry.isDirectory()) {
const name = entry.name;
// V4 used 'bmad-method' or similar in IDE commands folders
// V6+ uses 'bmad' (hardcoded)
// So anything that's NOT 'bmad' but starts with bmad/Bmad is likely V4
if ((name.startsWith('bmad') || name.startsWith('Bmad') || name === 'BMad') && name !== 'bmad') {
offenders.push(path.join(commandsPath, entry.name));
}
}
}
} catch {
// Ignore errors reading commands directory
}
}
} }
return { hasLegacyV4: offenders.length > 0, offenders }; return { hasLegacyV4: offenders.length > 0, offenders };

View File

@ -2152,90 +2152,59 @@ class Installer {
} }
/** /**
* Handle legacy BMAD v4 migration with automatic backup * Handle legacy BMAD v4 detection with simple warning
* @param {string} projectDir - Project directory * @param {string} _projectDir - Project directory (unused in simplified version)
* @param {Object} legacyV4 - Legacy V4 detection result with offenders array * @param {Object} _legacyV4 - Legacy V4 detection result (unused in simplified version)
*/ */
async handleLegacyV4Migration(projectDir, legacyV4) { async handleLegacyV4Migration(_projectDir, _legacyV4) {
console.log(chalk.yellow.bold('\n⚠ Legacy BMAD v4 detected'));
console.log(chalk.dim('The installer found legacy artefacts in your project.\n'));
// Separate _bmad* folders (auto-backup) from other offending paths (manual cleanup)
const bmadFolders = legacyV4.offenders.filter((p) => {
const name = path.basename(p);
return name.startsWith('_bmad'); // Only dot-prefixed folders get auto-backed up
});
const otherOffenders = legacyV4.offenders.filter((p) => {
const name = path.basename(p);
return !name.startsWith('_bmad'); // Everything else is manual cleanup
});
const inquirer = require('inquirer').default || require('inquirer'); const inquirer = require('inquirer').default || require('inquirer');
// Show warning for other offending paths FIRST console.log('');
if (otherOffenders.length > 0) { console.log(chalk.yellow.bold('⚠️ Legacy BMAD v4 detected'));
console.log(chalk.yellow('⚠️ Recommended cleanup:')); console.log(chalk.yellow('─'.repeat(80)));
console.log(chalk.dim('It is recommended to remove the following items before proceeding:\n')); console.log(chalk.yellow('Found .bmad-method folder from BMAD v4 installation.'));
for (const p of otherOffenders) console.log(chalk.dim(` - ${p}`)); console.log('');
console.log(chalk.cyan('\nCleanup commands you can copy/paste:')); console.log(chalk.dim('Before continuing with installation, we recommend:'));
console.log(chalk.dim('macOS/Linux:')); console.log(chalk.dim(' 1. Remove the .bmad-method folder, OR'));
for (const p of otherOffenders) console.log(chalk.dim(` rm -rf '${p}'`)); console.log(chalk.dim(' 2. Back it up by renaming it to another name (e.g., bmad-method-backup)'));
console.log(chalk.dim('Windows:')); console.log('');
for (const p of otherOffenders) console.log(chalk.dim(` rmdir /S /Q "${p}"`));
const { cleanedUp } = await inquirer.prompt([ console.log(chalk.dim('If your v4 installation set up rules or commands, you should remove those as well.'));
{ console.log('');
type: 'confirm',
name: 'cleanedUp',
message: 'Have you completed the recommended cleanup? (You can proceed without it, but it is recommended)',
default: false,
},
]);
if (cleanedUp) {
console.log(chalk.green('✓ Cleanup acknowledged\n'));
} else {
console.log(chalk.yellow('⚠️ Proceeding without recommended cleanup\n'));
}
}
// Handle _bmad* folders with automatic backup
if (bmadFolders.length > 0) {
console.log(chalk.cyan('The following legacy folders will be moved to v4-backup:'));
for (const p of bmadFolders) console.log(chalk.dim(` - ${p}`));
const { proceed } = await inquirer.prompt([ const { proceed } = await inquirer.prompt([
{ {
type: 'confirm', type: 'list',
name: 'proceed', name: 'proceed',
message: 'Proceed with backing up legacy v4 folders?', message: 'What would you like to do?',
default: true, choices: [
{
name: 'Exit and clean up manually (recommended)',
value: 'exit',
short: 'Exit installation',
},
{
name: 'Continue with installation anyway',
value: 'continue',
short: 'Continue',
},
],
default: 'exit',
}, },
]); ]);
if (proceed) { if (proceed === 'exit') {
const backupDir = path.join(projectDir, 'v4-backup'); console.log('');
await fs.ensureDir(backupDir); console.log(chalk.cyan('Please remove the .bmad-method folder and any v4 rules/commands,'));
console.log(chalk.cyan('then run the installer again.'));
for (const folder of bmadFolders) { console.log('');
const folderName = path.basename(folder); process.exit(0);
const backupPath = path.join(backupDir, folderName);
// If backup already exists, add timestamp
let finalBackupPath = backupPath;
if (await fs.pathExists(backupPath)) {
const timestamp = new Date().toISOString().replaceAll(/[:.]/g, '-').split('T')[0];
finalBackupPath = path.join(backupDir, `${folderName}-${timestamp}`);
} }
await fs.move(folder, finalBackupPath, { overwrite: false }); console.log('');
console.log(chalk.green(`✓ Moved ${folderName} to ${path.relative(projectDir, finalBackupPath)}`)); console.log(chalk.yellow('⚠️ Proceeding with installation despite legacy v4 folder'));
} console.log('');
} else {
throw new Error('Installation cancelled by user');
}
}
} }
/** /**