chore: add code formatting config and pre-commit hooks (#450)
This commit is contained in:
@@ -1,15 +1,15 @@
|
||||
const fs = require("fs").promises;
|
||||
const path = require("path");
|
||||
const { glob } = require("glob");
|
||||
const fs = require('node:fs').promises;
|
||||
const path = require('node:path');
|
||||
const { glob } = require('glob');
|
||||
|
||||
// Dynamic imports for ES modules
|
||||
let chalk, ora, inquirer;
|
||||
|
||||
// Initialize ES modules
|
||||
async function initializeModules() {
|
||||
chalk = (await import("chalk")).default;
|
||||
ora = (await import("ora")).default;
|
||||
inquirer = (await import("inquirer")).default;
|
||||
chalk = (await import('chalk')).default;
|
||||
ora = (await import('ora')).default;
|
||||
inquirer = (await import('inquirer')).default;
|
||||
}
|
||||
|
||||
class V3ToV4Upgrader {
|
||||
@@ -25,23 +25,15 @@ class V3ToV4Upgrader {
|
||||
process.stdin.resume();
|
||||
|
||||
// 1. Welcome message
|
||||
console.log(
|
||||
chalk.bold("\nWelcome to BMad-Method V3 to V4 Upgrade Tool\n")
|
||||
);
|
||||
console.log(
|
||||
"This tool will help you upgrade your BMad-Method V3 project to V4.\n"
|
||||
);
|
||||
console.log(chalk.cyan("What this tool does:"));
|
||||
console.log("- Creates a backup of your V3 files (.bmad-v3-backup/)");
|
||||
console.log("- Installs the new V4 .bmad-core structure");
|
||||
console.log(
|
||||
"- Preserves your PRD, Architecture, and Stories in the new format\n"
|
||||
);
|
||||
console.log(chalk.yellow("What this tool does NOT do:"));
|
||||
console.log(
|
||||
"- Modify your document content (use doc-migration-task after upgrade)"
|
||||
);
|
||||
console.log("- Touch any files outside bmad-agent/ and docs/\n");
|
||||
console.log(chalk.bold('\nWelcome to BMad-Method V3 to V4 Upgrade Tool\n'));
|
||||
console.log('This tool will help you upgrade your BMad-Method V3 project to V4.\n');
|
||||
console.log(chalk.cyan('What this tool does:'));
|
||||
console.log('- Creates a backup of your V3 files (.bmad-v3-backup/)');
|
||||
console.log('- Installs the new V4 .bmad-core structure');
|
||||
console.log('- Preserves your PRD, Architecture, and Stories in the new format\n');
|
||||
console.log(chalk.yellow('What this tool does NOT do:'));
|
||||
console.log('- Modify your document content (use doc-migration-task after upgrade)');
|
||||
console.log('- Touch any files outside bmad-agent/ and docs/\n');
|
||||
|
||||
// 2. Get project path
|
||||
const projectPath = await this.getProjectPath(options.projectPath);
|
||||
@@ -49,15 +41,11 @@ class V3ToV4Upgrader {
|
||||
// 3. Validate V3 structure
|
||||
const validation = await this.validateV3Project(projectPath);
|
||||
if (!validation.isValid) {
|
||||
console.error(
|
||||
chalk.red("\nError: This doesn't appear to be a V3 project.")
|
||||
);
|
||||
console.error("Expected to find:");
|
||||
console.error("- bmad-agent/ directory");
|
||||
console.error("- docs/ directory\n");
|
||||
console.error(
|
||||
"Please check you're in the correct directory and try again."
|
||||
);
|
||||
console.error(chalk.red("\nError: This doesn't appear to be a V3 project."));
|
||||
console.error('Expected to find:');
|
||||
console.error('- bmad-agent/ directory');
|
||||
console.error('- docs/ directory\n');
|
||||
console.error("Please check you're in the correct directory and try again.");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -68,15 +56,15 @@ class V3ToV4Upgrader {
|
||||
if (!options.dryRun) {
|
||||
const { confirm } = await inquirer.prompt([
|
||||
{
|
||||
type: "confirm",
|
||||
name: "confirm",
|
||||
message: "Continue with upgrade?",
|
||||
type: 'confirm',
|
||||
name: 'confirm',
|
||||
message: 'Continue with upgrade?',
|
||||
default: true,
|
||||
},
|
||||
]);
|
||||
|
||||
if (!confirm) {
|
||||
console.log("Upgrade cancelled.");
|
||||
console.log('Upgrade cancelled.');
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -106,7 +94,7 @@ class V3ToV4Upgrader {
|
||||
|
||||
process.exit(0);
|
||||
} catch (error) {
|
||||
console.error(chalk.red("\nUpgrade error:"), error.message);
|
||||
console.error(chalk.red('\nUpgrade error:'), error.message);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
@@ -118,9 +106,9 @@ class V3ToV4Upgrader {
|
||||
|
||||
const { projectPath } = await inquirer.prompt([
|
||||
{
|
||||
type: "input",
|
||||
name: "projectPath",
|
||||
message: "Please enter the path to your V3 project:",
|
||||
type: 'input',
|
||||
name: 'projectPath',
|
||||
message: 'Please enter the path to your V3 project:',
|
||||
default: process.cwd(),
|
||||
},
|
||||
]);
|
||||
@@ -129,45 +117,45 @@ class V3ToV4Upgrader {
|
||||
}
|
||||
|
||||
async validateV3Project(projectPath) {
|
||||
const spinner = ora("Validating project structure...").start();
|
||||
const spinner = ora('Validating project structure...').start();
|
||||
|
||||
try {
|
||||
const bmadAgentPath = path.join(projectPath, "bmad-agent");
|
||||
const docsPath = path.join(projectPath, "docs");
|
||||
const bmadAgentPath = path.join(projectPath, 'bmad-agent');
|
||||
const docsPath = path.join(projectPath, 'docs');
|
||||
|
||||
const hasBmadAgent = await this.pathExists(bmadAgentPath);
|
||||
const hasDocs = await this.pathExists(docsPath);
|
||||
|
||||
if (hasBmadAgent) {
|
||||
spinner.text = "✓ Found bmad-agent/ directory";
|
||||
console.log(chalk.green("\n✓ Found bmad-agent/ directory"));
|
||||
spinner.text = '✓ Found bmad-agent/ directory';
|
||||
console.log(chalk.green('\n✓ Found bmad-agent/ directory'));
|
||||
}
|
||||
|
||||
if (hasDocs) {
|
||||
console.log(chalk.green("✓ Found docs/ directory"));
|
||||
console.log(chalk.green('✓ Found docs/ directory'));
|
||||
}
|
||||
|
||||
const isValid = hasBmadAgent && hasDocs;
|
||||
|
||||
if (isValid) {
|
||||
spinner.succeed("This appears to be a valid V3 project");
|
||||
spinner.succeed('This appears to be a valid V3 project');
|
||||
} else {
|
||||
spinner.fail("Invalid V3 project structure");
|
||||
spinner.fail('Invalid V3 project structure');
|
||||
}
|
||||
|
||||
return { isValid, hasBmadAgent, hasDocs };
|
||||
} catch (error) {
|
||||
spinner.fail("Validation failed");
|
||||
spinner.fail('Validation failed');
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async analyzeProject(projectPath) {
|
||||
const docsPath = path.join(projectPath, "docs");
|
||||
const bmadAgentPath = path.join(projectPath, "bmad-agent");
|
||||
const docsPath = path.join(projectPath, 'docs');
|
||||
const bmadAgentPath = path.join(projectPath, 'bmad-agent');
|
||||
|
||||
// Find PRD
|
||||
const prdCandidates = ["prd.md", "PRD.md", "product-requirements.md"];
|
||||
const prdCandidates = ['prd.md', 'PRD.md', 'product-requirements.md'];
|
||||
let prdFile = null;
|
||||
for (const candidate of prdCandidates) {
|
||||
const candidatePath = path.join(docsPath, candidate);
|
||||
@@ -178,11 +166,7 @@ class V3ToV4Upgrader {
|
||||
}
|
||||
|
||||
// Find Architecture
|
||||
const archCandidates = [
|
||||
"architecture.md",
|
||||
"Architecture.md",
|
||||
"technical-architecture.md",
|
||||
];
|
||||
const archCandidates = ['architecture.md', 'Architecture.md', 'technical-architecture.md'];
|
||||
let archFile = null;
|
||||
for (const candidate of archCandidates) {
|
||||
const candidatePath = path.join(docsPath, candidate);
|
||||
@@ -194,9 +178,9 @@ class V3ToV4Upgrader {
|
||||
|
||||
// Find Front-end Architecture (V3 specific)
|
||||
const frontEndCandidates = [
|
||||
"front-end-architecture.md",
|
||||
"frontend-architecture.md",
|
||||
"ui-architecture.md",
|
||||
'front-end-architecture.md',
|
||||
'frontend-architecture.md',
|
||||
'ui-architecture.md',
|
||||
];
|
||||
let frontEndArchFile = null;
|
||||
for (const candidate of frontEndCandidates) {
|
||||
@@ -209,10 +193,10 @@ class V3ToV4Upgrader {
|
||||
|
||||
// Find UX/UI spec
|
||||
const uxSpecCandidates = [
|
||||
"ux-ui-spec.md",
|
||||
"ux-ui-specification.md",
|
||||
"ui-spec.md",
|
||||
"ux-spec.md",
|
||||
'ux-ui-spec.md',
|
||||
'ux-ui-specification.md',
|
||||
'ui-spec.md',
|
||||
'ux-spec.md',
|
||||
];
|
||||
let uxSpecFile = null;
|
||||
for (const candidate of uxSpecCandidates) {
|
||||
@@ -224,12 +208,7 @@ class V3ToV4Upgrader {
|
||||
}
|
||||
|
||||
// Find v0 prompt or UX prompt
|
||||
const uxPromptCandidates = [
|
||||
"v0-prompt.md",
|
||||
"ux-prompt.md",
|
||||
"ui-prompt.md",
|
||||
"design-prompt.md",
|
||||
];
|
||||
const uxPromptCandidates = ['v0-prompt.md', 'ux-prompt.md', 'ui-prompt.md', 'design-prompt.md'];
|
||||
let uxPromptFile = null;
|
||||
for (const candidate of uxPromptCandidates) {
|
||||
const candidatePath = path.join(docsPath, candidate);
|
||||
@@ -240,19 +219,19 @@ class V3ToV4Upgrader {
|
||||
}
|
||||
|
||||
// Find epic files
|
||||
const epicFiles = await glob("epic*.md", { cwd: docsPath });
|
||||
const epicFiles = await glob('epic*.md', { cwd: docsPath });
|
||||
|
||||
// Find story files
|
||||
const storiesPath = path.join(docsPath, "stories");
|
||||
const storiesPath = path.join(docsPath, 'stories');
|
||||
let storyFiles = [];
|
||||
if (await this.pathExists(storiesPath)) {
|
||||
storyFiles = await glob("*.md", { cwd: storiesPath });
|
||||
storyFiles = await glob('*.md', { cwd: storiesPath });
|
||||
}
|
||||
|
||||
// Count custom files in bmad-agent
|
||||
const bmadAgentFiles = await glob("**/*.md", {
|
||||
const bmadAgentFiles = await glob('**/*.md', {
|
||||
cwd: bmadAgentPath,
|
||||
ignore: ["node_modules/**"],
|
||||
ignore: ['node_modules/**'],
|
||||
});
|
||||
|
||||
return {
|
||||
@@ -268,279 +247,233 @@ class V3ToV4Upgrader {
|
||||
}
|
||||
|
||||
async showPreflightCheck(analysis, options) {
|
||||
console.log(chalk.bold("\nProject Analysis:"));
|
||||
console.log(chalk.bold('\nProject Analysis:'));
|
||||
console.log(
|
||||
`- PRD found: ${
|
||||
analysis.prdFile
|
||||
? `docs/${analysis.prdFile}`
|
||||
: chalk.yellow("Not found")
|
||||
}`
|
||||
`- PRD found: ${analysis.prdFile ? `docs/${analysis.prdFile}` : chalk.yellow('Not found')}`,
|
||||
);
|
||||
console.log(
|
||||
`- Architecture found: ${
|
||||
analysis.archFile
|
||||
? `docs/${analysis.archFile}`
|
||||
: chalk.yellow("Not found")
|
||||
}`
|
||||
analysis.archFile ? `docs/${analysis.archFile}` : chalk.yellow('Not found')
|
||||
}`,
|
||||
);
|
||||
if (analysis.frontEndArchFile) {
|
||||
console.log(
|
||||
`- Front-end Architecture found: docs/${analysis.frontEndArchFile}`
|
||||
);
|
||||
console.log(`- Front-end Architecture found: docs/${analysis.frontEndArchFile}`);
|
||||
}
|
||||
console.log(
|
||||
`- UX/UI Spec found: ${
|
||||
analysis.uxSpecFile
|
||||
? `docs/${analysis.uxSpecFile}`
|
||||
: chalk.yellow("Not found")
|
||||
}`
|
||||
analysis.uxSpecFile ? `docs/${analysis.uxSpecFile}` : chalk.yellow('Not found')
|
||||
}`,
|
||||
);
|
||||
console.log(
|
||||
`- UX/Design Prompt found: ${
|
||||
analysis.uxPromptFile
|
||||
? `docs/${analysis.uxPromptFile}`
|
||||
: chalk.yellow("Not found")
|
||||
}`
|
||||
);
|
||||
console.log(
|
||||
`- Epic files found: ${analysis.epicFiles.length} files (epic*.md)`
|
||||
);
|
||||
console.log(
|
||||
`- Stories found: ${analysis.storyFiles.length} files in docs/stories/`
|
||||
analysis.uxPromptFile ? `docs/${analysis.uxPromptFile}` : chalk.yellow('Not found')
|
||||
}`,
|
||||
);
|
||||
console.log(`- Epic files found: ${analysis.epicFiles.length} files (epic*.md)`);
|
||||
console.log(`- Stories found: ${analysis.storyFiles.length} files in docs/stories/`);
|
||||
console.log(`- Custom files in bmad-agent/: ${analysis.customFileCount}`);
|
||||
|
||||
if (!options.dryRun) {
|
||||
console.log("\nThe following will be backed up to .bmad-v3-backup/:");
|
||||
console.log("- bmad-agent/ (entire directory)");
|
||||
console.log("- docs/ (entire directory)");
|
||||
console.log('\nThe following will be backed up to .bmad-v3-backup/:');
|
||||
console.log('- bmad-agent/ (entire directory)');
|
||||
console.log('- docs/ (entire directory)');
|
||||
|
||||
if (analysis.epicFiles.length > 0) {
|
||||
console.log(
|
||||
chalk.green(
|
||||
"\nNote: Epic files found! They will be placed in docs/prd/ with an index.md file."
|
||||
)
|
||||
'\nNote: Epic files found! They will be placed in docs/prd/ with an index.md file.',
|
||||
),
|
||||
);
|
||||
console.log(
|
||||
chalk.green(
|
||||
"Since epic files exist, you won't need to shard the PRD after upgrade."
|
||||
)
|
||||
chalk.green("Since epic files exist, you won't need to shard the PRD after upgrade."),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async createBackup(projectPath) {
|
||||
const spinner = ora("Creating backup...").start();
|
||||
const spinner = ora('Creating backup...').start();
|
||||
|
||||
try {
|
||||
const backupPath = path.join(projectPath, ".bmad-v3-backup");
|
||||
const backupPath = path.join(projectPath, '.bmad-v3-backup');
|
||||
|
||||
// Check if backup already exists
|
||||
if (await this.pathExists(backupPath)) {
|
||||
spinner.fail("Backup directory already exists");
|
||||
console.error(
|
||||
chalk.red(
|
||||
"\nError: Backup directory .bmad-v3-backup/ already exists."
|
||||
)
|
||||
);
|
||||
console.error("\nThis might mean an upgrade was already attempted.");
|
||||
console.error(
|
||||
"Please remove or rename the existing backup and try again."
|
||||
);
|
||||
throw new Error("Backup already exists");
|
||||
spinner.fail('Backup directory already exists');
|
||||
console.error(chalk.red('\nError: Backup directory .bmad-v3-backup/ already exists.'));
|
||||
console.error('\nThis might mean an upgrade was already attempted.');
|
||||
console.error('Please remove or rename the existing backup and try again.');
|
||||
throw new Error('Backup already exists');
|
||||
}
|
||||
|
||||
// Create backup directory
|
||||
await fs.mkdir(backupPath, { recursive: true });
|
||||
spinner.text = "✓ Created .bmad-v3-backup/";
|
||||
console.log(chalk.green("\n✓ Created .bmad-v3-backup/"));
|
||||
spinner.text = '✓ Created .bmad-v3-backup/';
|
||||
console.log(chalk.green('\n✓ Created .bmad-v3-backup/'));
|
||||
|
||||
// Move bmad-agent
|
||||
const bmadAgentSrc = path.join(projectPath, "bmad-agent");
|
||||
const bmadAgentDest = path.join(backupPath, "bmad-agent");
|
||||
await fs.rename(bmadAgentSrc, bmadAgentDest);
|
||||
console.log(chalk.green("✓ Moved bmad-agent/ to backup"));
|
||||
const bmadAgentSource = path.join(projectPath, 'bmad-agent');
|
||||
const bmadAgentDestination = path.join(backupPath, 'bmad-agent');
|
||||
await fs.rename(bmadAgentSource, bmadAgentDestination);
|
||||
console.log(chalk.green('✓ Moved bmad-agent/ to backup'));
|
||||
|
||||
// Move docs
|
||||
const docsSrc = path.join(projectPath, "docs");
|
||||
const docsDest = path.join(backupPath, "docs");
|
||||
const docsSrc = path.join(projectPath, 'docs');
|
||||
const docsDest = path.join(backupPath, 'docs');
|
||||
await fs.rename(docsSrc, docsDest);
|
||||
console.log(chalk.green("✓ Moved docs/ to backup"));
|
||||
console.log(chalk.green('✓ Moved docs/ to backup'));
|
||||
|
||||
spinner.succeed("Backup created successfully");
|
||||
spinner.succeed('Backup created successfully');
|
||||
} catch (error) {
|
||||
spinner.fail("Backup failed");
|
||||
spinner.fail('Backup failed');
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async installV4Structure(projectPath) {
|
||||
const spinner = ora("Installing V4 structure...").start();
|
||||
const spinner = ora('Installing V4 structure...').start();
|
||||
|
||||
try {
|
||||
// Get the source bmad-core directory (without dot prefix)
|
||||
const sourcePath = path.join(__dirname, "..", "..", "bmad-core");
|
||||
const destPath = path.join(projectPath, ".bmad-core");
|
||||
const sourcePath = path.join(__dirname, '..', '..', 'bmad-core');
|
||||
const destinationPath = path.join(projectPath, '.bmad-core');
|
||||
|
||||
// Copy .bmad-core
|
||||
await this.copyDirectory(sourcePath, destPath);
|
||||
spinner.text = "✓ Copied fresh .bmad-core/ directory from V4";
|
||||
console.log(
|
||||
chalk.green("\n✓ Copied fresh .bmad-core/ directory from V4")
|
||||
);
|
||||
await this.copyDirectory(sourcePath, destinationPath);
|
||||
spinner.text = '✓ Copied fresh .bmad-core/ directory from V4';
|
||||
console.log(chalk.green('\n✓ Copied fresh .bmad-core/ directory from V4'));
|
||||
|
||||
// Create docs directory
|
||||
const docsPath = path.join(projectPath, "docs");
|
||||
const docsPath = path.join(projectPath, 'docs');
|
||||
await fs.mkdir(docsPath, { recursive: true });
|
||||
console.log(chalk.green("✓ Created new docs/ directory"));
|
||||
console.log(chalk.green('✓ Created new docs/ directory'));
|
||||
|
||||
// Create install manifest for future updates
|
||||
await this.createInstallManifest(projectPath);
|
||||
console.log(chalk.green("✓ Created install manifest"));
|
||||
console.log(chalk.green('✓ Created install manifest'));
|
||||
|
||||
console.log(
|
||||
chalk.yellow(
|
||||
"\nNote: Your V3 bmad-agent content has been backed up and NOT migrated."
|
||||
)
|
||||
chalk.yellow('\nNote: Your V3 bmad-agent content has been backed up and NOT migrated.'),
|
||||
);
|
||||
console.log(
|
||||
chalk.yellow(
|
||||
"The new V4 agents are completely different and look for different file structures."
|
||||
)
|
||||
'The new V4 agents are completely different and look for different file structures.',
|
||||
),
|
||||
);
|
||||
|
||||
spinner.succeed("V4 structure installed successfully");
|
||||
spinner.succeed('V4 structure installed successfully');
|
||||
} catch (error) {
|
||||
spinner.fail("V4 installation failed");
|
||||
spinner.fail('V4 installation failed');
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async migrateDocuments(projectPath, analysis) {
|
||||
const spinner = ora("Migrating your project documents...").start();
|
||||
const spinner = ora('Migrating your project documents...').start();
|
||||
|
||||
try {
|
||||
const backupDocsPath = path.join(projectPath, ".bmad-v3-backup", "docs");
|
||||
const newDocsPath = path.join(projectPath, "docs");
|
||||
const backupDocsPath = path.join(projectPath, '.bmad-v3-backup', 'docs');
|
||||
const newDocsPath = path.join(projectPath, 'docs');
|
||||
let copiedCount = 0;
|
||||
|
||||
// Copy PRD
|
||||
if (analysis.prdFile) {
|
||||
const src = path.join(backupDocsPath, analysis.prdFile);
|
||||
const dest = path.join(newDocsPath, analysis.prdFile);
|
||||
await fs.copyFile(src, dest);
|
||||
const source = path.join(backupDocsPath, analysis.prdFile);
|
||||
const destination = path.join(newDocsPath, analysis.prdFile);
|
||||
await fs.copyFile(source, destination);
|
||||
console.log(chalk.green(`\n✓ Copied PRD to docs/${analysis.prdFile}`));
|
||||
copiedCount++;
|
||||
}
|
||||
|
||||
// Copy Architecture
|
||||
if (analysis.archFile) {
|
||||
const src = path.join(backupDocsPath, analysis.archFile);
|
||||
const dest = path.join(newDocsPath, analysis.archFile);
|
||||
await fs.copyFile(src, dest);
|
||||
console.log(
|
||||
chalk.green(`✓ Copied Architecture to docs/${analysis.archFile}`)
|
||||
);
|
||||
const source = path.join(backupDocsPath, analysis.archFile);
|
||||
const destination = path.join(newDocsPath, analysis.archFile);
|
||||
await fs.copyFile(source, destination);
|
||||
console.log(chalk.green(`✓ Copied Architecture to docs/${analysis.archFile}`));
|
||||
copiedCount++;
|
||||
}
|
||||
|
||||
// Copy Front-end Architecture if exists
|
||||
if (analysis.frontEndArchFile) {
|
||||
const src = path.join(backupDocsPath, analysis.frontEndArchFile);
|
||||
const dest = path.join(newDocsPath, analysis.frontEndArchFile);
|
||||
await fs.copyFile(src, dest);
|
||||
const source = path.join(backupDocsPath, analysis.frontEndArchFile);
|
||||
const destination = path.join(newDocsPath, analysis.frontEndArchFile);
|
||||
await fs.copyFile(source, destination);
|
||||
console.log(
|
||||
chalk.green(
|
||||
`✓ Copied Front-end Architecture to docs/${analysis.frontEndArchFile}`
|
||||
)
|
||||
chalk.green(`✓ Copied Front-end Architecture to docs/${analysis.frontEndArchFile}`),
|
||||
);
|
||||
console.log(
|
||||
chalk.yellow(
|
||||
"Note: V4 uses a single full-stack-architecture.md - use doc-migration-task to merge"
|
||||
)
|
||||
'Note: V4 uses a single full-stack-architecture.md - use doc-migration-task to merge',
|
||||
),
|
||||
);
|
||||
copiedCount++;
|
||||
}
|
||||
|
||||
// Copy UX/UI Spec if exists
|
||||
if (analysis.uxSpecFile) {
|
||||
const src = path.join(backupDocsPath, analysis.uxSpecFile);
|
||||
const dest = path.join(newDocsPath, analysis.uxSpecFile);
|
||||
await fs.copyFile(src, dest);
|
||||
console.log(
|
||||
chalk.green(`✓ Copied UX/UI Spec to docs/${analysis.uxSpecFile}`)
|
||||
);
|
||||
const source = path.join(backupDocsPath, analysis.uxSpecFile);
|
||||
const destination = path.join(newDocsPath, analysis.uxSpecFile);
|
||||
await fs.copyFile(source, destination);
|
||||
console.log(chalk.green(`✓ Copied UX/UI Spec to docs/${analysis.uxSpecFile}`));
|
||||
copiedCount++;
|
||||
}
|
||||
|
||||
// Copy UX/Design Prompt if exists
|
||||
if (analysis.uxPromptFile) {
|
||||
const src = path.join(backupDocsPath, analysis.uxPromptFile);
|
||||
const dest = path.join(newDocsPath, analysis.uxPromptFile);
|
||||
await fs.copyFile(src, dest);
|
||||
console.log(
|
||||
chalk.green(
|
||||
`✓ Copied UX/Design Prompt to docs/${analysis.uxPromptFile}`
|
||||
)
|
||||
);
|
||||
const source = path.join(backupDocsPath, analysis.uxPromptFile);
|
||||
const destination = path.join(newDocsPath, analysis.uxPromptFile);
|
||||
await fs.copyFile(source, destination);
|
||||
console.log(chalk.green(`✓ Copied UX/Design Prompt to docs/${analysis.uxPromptFile}`));
|
||||
copiedCount++;
|
||||
}
|
||||
|
||||
// Copy stories
|
||||
if (analysis.storyFiles.length > 0) {
|
||||
const storiesDir = path.join(newDocsPath, "stories");
|
||||
const storiesDir = path.join(newDocsPath, 'stories');
|
||||
await fs.mkdir(storiesDir, { recursive: true });
|
||||
|
||||
for (const storyFile of analysis.storyFiles) {
|
||||
const src = path.join(backupDocsPath, "stories", storyFile);
|
||||
const dest = path.join(storiesDir, storyFile);
|
||||
await fs.copyFile(src, dest);
|
||||
const source = path.join(backupDocsPath, 'stories', storyFile);
|
||||
const destination = path.join(storiesDir, storyFile);
|
||||
await fs.copyFile(source, destination);
|
||||
}
|
||||
console.log(
|
||||
chalk.green(
|
||||
`✓ Copied ${analysis.storyFiles.length} story files to docs/stories/`
|
||||
)
|
||||
chalk.green(`✓ Copied ${analysis.storyFiles.length} story files to docs/stories/`),
|
||||
);
|
||||
copiedCount += analysis.storyFiles.length;
|
||||
}
|
||||
|
||||
// Copy epic files to prd subfolder
|
||||
if (analysis.epicFiles.length > 0) {
|
||||
const prdDir = path.join(newDocsPath, "prd");
|
||||
const prdDir = path.join(newDocsPath, 'prd');
|
||||
await fs.mkdir(prdDir, { recursive: true });
|
||||
|
||||
for (const epicFile of analysis.epicFiles) {
|
||||
const src = path.join(backupDocsPath, epicFile);
|
||||
const dest = path.join(prdDir, epicFile);
|
||||
await fs.copyFile(src, dest);
|
||||
const source = path.join(backupDocsPath, epicFile);
|
||||
const destination = path.join(prdDir, epicFile);
|
||||
await fs.copyFile(source, destination);
|
||||
}
|
||||
console.log(
|
||||
chalk.green(
|
||||
`✓ Found and copied ${analysis.epicFiles.length} epic files to docs/prd/`
|
||||
)
|
||||
chalk.green(`✓ Found and copied ${analysis.epicFiles.length} epic files to docs/prd/`),
|
||||
);
|
||||
|
||||
// Create index.md for the prd folder
|
||||
await this.createPrdIndex(projectPath, analysis);
|
||||
console.log(chalk.green("✓ Created index.md in docs/prd/"));
|
||||
console.log(chalk.green('✓ Created index.md in docs/prd/'));
|
||||
|
||||
console.log(
|
||||
chalk.green(
|
||||
"\nNote: Epic files detected! These are compatible with V4 and have been copied."
|
||||
)
|
||||
);
|
||||
console.log(
|
||||
chalk.green(
|
||||
"You won't need to shard the PRD since epics already exist."
|
||||
)
|
||||
'\nNote: Epic files detected! These are compatible with V4 and have been copied.',
|
||||
),
|
||||
);
|
||||
console.log(chalk.green("You won't need to shard the PRD since epics already exist."));
|
||||
copiedCount += analysis.epicFiles.length;
|
||||
}
|
||||
|
||||
spinner.succeed(`Migrated ${copiedCount} documents successfully`);
|
||||
} catch (error) {
|
||||
spinner.fail("Document migration failed");
|
||||
spinner.fail('Document migration failed');
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
@@ -548,21 +481,21 @@ class V3ToV4Upgrader {
|
||||
async setupIDE(projectPath, selectedIdes) {
|
||||
// Use the IDE selections passed from the installer
|
||||
if (!selectedIdes || selectedIdes.length === 0) {
|
||||
console.log(chalk.dim("No IDE setup requested - skipping"));
|
||||
console.log(chalk.dim('No IDE setup requested - skipping'));
|
||||
return;
|
||||
}
|
||||
|
||||
const ideSetup = require("../installer/lib/ide-setup");
|
||||
const spinner = ora("Setting up IDE rules for all agents...").start();
|
||||
const ideSetup = require('../installer/lib/ide-setup');
|
||||
const spinner = ora('Setting up IDE rules for all agents...').start();
|
||||
|
||||
try {
|
||||
const ideMessages = {
|
||||
cursor: "Rules created in .cursor/rules/bmad/",
|
||||
"claude-code": "Commands created in .claude/commands/BMad/",
|
||||
windsurf: "Rules created in .windsurf/rules/",
|
||||
trae: "Rules created in.trae/rules/",
|
||||
roo: "Custom modes created in .roomodes",
|
||||
cline: "Rules created in .clinerules/",
|
||||
cursor: 'Rules created in .cursor/rules/bmad/',
|
||||
'claude-code': 'Commands created in .claude/commands/BMad/',
|
||||
windsurf: 'Rules created in .windsurf/workflows/',
|
||||
trae: 'Rules created in.trae/rules/',
|
||||
roo: 'Custom modes created in .roomodes',
|
||||
cline: 'Rules created in .clinerules/',
|
||||
};
|
||||
|
||||
// Setup each selected IDE
|
||||
@@ -573,17 +506,15 @@ class V3ToV4Upgrader {
|
||||
}
|
||||
|
||||
spinner.succeed(`IDE setup complete for ${selectedIdes.length} IDE(s)!`);
|
||||
} catch (error) {
|
||||
spinner.fail("IDE setup failed");
|
||||
console.error(
|
||||
chalk.yellow("IDE setup failed, but upgrade is complete.")
|
||||
);
|
||||
} catch {
|
||||
spinner.fail('IDE setup failed');
|
||||
console.error(chalk.yellow('IDE setup failed, but upgrade is complete.'));
|
||||
}
|
||||
}
|
||||
|
||||
showCompletionReport(projectPath, analysis) {
|
||||
console.log(chalk.bold.green("\n✓ Upgrade Complete!\n"));
|
||||
console.log(chalk.bold("Summary:"));
|
||||
console.log(chalk.bold.green('\n✓ Upgrade Complete!\n'));
|
||||
console.log(chalk.bold('Summary:'));
|
||||
console.log(`- V3 files backed up to: .bmad-v3-backup/`);
|
||||
console.log(`- V4 structure installed: .bmad-core/ (fresh from V4)`);
|
||||
|
||||
@@ -596,50 +527,36 @@ class V3ToV4Upgrader {
|
||||
analysis.storyFiles.length;
|
||||
console.log(
|
||||
`- Documents migrated: ${totalDocs} files${
|
||||
analysis.epicFiles.length > 0
|
||||
? ` + ${analysis.epicFiles.length} epics`
|
||||
: ""
|
||||
}`
|
||||
analysis.epicFiles.length > 0 ? ` + ${analysis.epicFiles.length} epics` : ''
|
||||
}`,
|
||||
);
|
||||
|
||||
console.log(chalk.bold("\nImportant Changes:"));
|
||||
console.log(
|
||||
"- The V4 agents (sm, dev, etc.) expect different file structures than V3"
|
||||
);
|
||||
console.log(
|
||||
"- Your V3 bmad-agent content was NOT migrated (it's incompatible)"
|
||||
);
|
||||
console.log(chalk.bold('\nImportant Changes:'));
|
||||
console.log('- The V4 agents (sm, dev, etc.) expect different file structures than V3');
|
||||
console.log("- Your V3 bmad-agent content was NOT migrated (it's incompatible)");
|
||||
if (analysis.epicFiles.length > 0) {
|
||||
console.log(
|
||||
"- Epic files were found and copied - no PRD sharding needed!"
|
||||
);
|
||||
console.log('- Epic files were found and copied - no PRD sharding needed!');
|
||||
}
|
||||
if (analysis.frontEndArchFile) {
|
||||
console.log(
|
||||
"- Front-end architecture found - V4 uses full-stack-architecture.md, migration needed"
|
||||
'- Front-end architecture found - V4 uses full-stack-architecture.md, migration needed',
|
||||
);
|
||||
}
|
||||
if (analysis.uxSpecFile || analysis.uxPromptFile) {
|
||||
console.log(
|
||||
"- UX/UI design files found and copied - ready for use with V4"
|
||||
);
|
||||
console.log('- UX/UI design files found and copied - ready for use with V4');
|
||||
}
|
||||
|
||||
console.log(chalk.bold("\nNext Steps:"));
|
||||
console.log("1. Review your documents in the new docs/ folder");
|
||||
console.log(chalk.bold('\nNext Steps:'));
|
||||
console.log('1. Review your documents in the new docs/ folder');
|
||||
console.log(
|
||||
"2. Use @bmad-master agent to run the doc-migration-task to align your documents with V4 templates"
|
||||
'2. Use @bmad-master agent to run the doc-migration-task to align your documents with V4 templates',
|
||||
);
|
||||
if (analysis.epicFiles.length === 0) {
|
||||
console.log(
|
||||
"3. Use @bmad-master agent to shard the PRD to create epic files"
|
||||
);
|
||||
console.log('3. Use @bmad-master agent to shard the PRD to create epic files');
|
||||
}
|
||||
|
||||
console.log(
|
||||
chalk.dim(
|
||||
"\nYour V3 backup is preserved in .bmad-v3-backup/ and can be restored if needed."
|
||||
)
|
||||
chalk.dim('\nYour V3 backup is preserved in .bmad-v3-backup/ and can be restored if needed.'),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -652,67 +569,61 @@ class V3ToV4Upgrader {
|
||||
}
|
||||
}
|
||||
|
||||
async copyDirectory(src, dest) {
|
||||
await fs.mkdir(dest, { recursive: true });
|
||||
const entries = await fs.readdir(src, { withFileTypes: true });
|
||||
async copyDirectory(source, destination) {
|
||||
await fs.mkdir(destination, { recursive: true });
|
||||
const entries = await fs.readdir(source, { withFileTypes: true });
|
||||
|
||||
for (const entry of entries) {
|
||||
const srcPath = path.join(src, entry.name);
|
||||
const destPath = path.join(dest, entry.name);
|
||||
const sourcePath = path.join(source, entry.name);
|
||||
const destinationPath = path.join(destination, entry.name);
|
||||
|
||||
if (entry.isDirectory()) {
|
||||
await this.copyDirectory(srcPath, destPath);
|
||||
} else {
|
||||
await fs.copyFile(srcPath, destPath);
|
||||
}
|
||||
await (entry.isDirectory()
|
||||
? this.copyDirectory(sourcePath, destinationPath)
|
||||
: fs.copyFile(sourcePath, destinationPath));
|
||||
}
|
||||
}
|
||||
|
||||
async createPrdIndex(projectPath, analysis) {
|
||||
const prdIndexPath = path.join(projectPath, "docs", "prd", "index.md");
|
||||
const prdPath = path.join(
|
||||
projectPath,
|
||||
"docs",
|
||||
analysis.prdFile || "prd.md"
|
||||
);
|
||||
const prdIndexPath = path.join(projectPath, 'docs', 'prd', 'index.md');
|
||||
const prdPath = path.join(projectPath, 'docs', analysis.prdFile || 'prd.md');
|
||||
|
||||
let indexContent = "# Product Requirements Document\n\n";
|
||||
let indexContent = '# Product Requirements Document\n\n';
|
||||
|
||||
// Try to read the PRD to get the title and intro content
|
||||
if (analysis.prdFile && (await this.pathExists(prdPath))) {
|
||||
try {
|
||||
const prdContent = await fs.readFile(prdPath, "utf8");
|
||||
const lines = prdContent.split("\n");
|
||||
const prdContent = await fs.readFile(prdPath, 'utf8');
|
||||
const lines = prdContent.split('\n');
|
||||
|
||||
// Find the first heading
|
||||
const titleMatch = lines.find((line) => line.startsWith("# "));
|
||||
const titleMatch = lines.find((line) => line.startsWith('# '));
|
||||
if (titleMatch) {
|
||||
indexContent = titleMatch + "\n\n";
|
||||
indexContent = titleMatch + '\n\n';
|
||||
}
|
||||
|
||||
// Get any content before the first ## section
|
||||
let introContent = "";
|
||||
let introContent = '';
|
||||
let foundFirstSection = false;
|
||||
for (const line of lines) {
|
||||
if (line.startsWith("## ")) {
|
||||
if (line.startsWith('## ')) {
|
||||
foundFirstSection = true;
|
||||
break;
|
||||
}
|
||||
if (!line.startsWith("# ")) {
|
||||
introContent += line + "\n";
|
||||
if (!line.startsWith('# ')) {
|
||||
introContent += line + '\n';
|
||||
}
|
||||
}
|
||||
|
||||
if (introContent.trim()) {
|
||||
indexContent += introContent.trim() + "\n\n";
|
||||
indexContent += introContent.trim() + '\n\n';
|
||||
}
|
||||
} catch (error) {
|
||||
} catch {
|
||||
// If we can't read the PRD, just use default content
|
||||
}
|
||||
}
|
||||
|
||||
// Add sections list
|
||||
indexContent += "## Sections\n\n";
|
||||
indexContent += '## Sections\n\n';
|
||||
|
||||
// Sort epic files for consistent ordering
|
||||
const sortedEpics = [...analysis.epicFiles].sort();
|
||||
@@ -720,38 +631,36 @@ class V3ToV4Upgrader {
|
||||
for (const epicFile of sortedEpics) {
|
||||
// Extract epic name from filename
|
||||
const epicName = epicFile
|
||||
.replace(/\.md$/, "")
|
||||
.replace(/^epic-?/i, "")
|
||||
.replace(/-/g, " ")
|
||||
.replace(/^\d+\s*/, "") // Remove leading numbers
|
||||
.replace(/\.md$/, '')
|
||||
.replace(/^epic-?/i, '')
|
||||
.replaceAll('-', ' ')
|
||||
.replace(/^\d+\s*/, '') // Remove leading numbers
|
||||
.trim();
|
||||
|
||||
const displayName = epicName.charAt(0).toUpperCase() + epicName.slice(1);
|
||||
indexContent += `- [${
|
||||
displayName || epicFile.replace(".md", "")
|
||||
}](./${epicFile})\n`;
|
||||
indexContent += `- [${displayName || epicFile.replace('.md', '')}](./${epicFile})\n`;
|
||||
}
|
||||
|
||||
await fs.writeFile(prdIndexPath, indexContent);
|
||||
}
|
||||
|
||||
async createInstallManifest(projectPath) {
|
||||
const fileManager = require("../installer/lib/file-manager");
|
||||
const { glob } = require("glob");
|
||||
const fileManager = require('../installer/lib/file-manager');
|
||||
const { glob } = require('glob');
|
||||
|
||||
// Get all files in .bmad-core for the manifest
|
||||
const bmadCorePath = path.join(projectPath, ".bmad-core");
|
||||
const files = await glob("**/*", {
|
||||
const bmadCorePath = path.join(projectPath, '.bmad-core');
|
||||
const files = await glob('**/*', {
|
||||
cwd: bmadCorePath,
|
||||
nodir: true,
|
||||
ignore: ["**/.git/**", "**/node_modules/**"],
|
||||
ignore: ['**/.git/**', '**/node_modules/**'],
|
||||
});
|
||||
|
||||
// Prepend .bmad-core/ to file paths for manifest
|
||||
const manifestFiles = files.map((file) => path.join(".bmad-core", file));
|
||||
const manifestFiles = files.map((file) => path.join('.bmad-core', file));
|
||||
|
||||
const config = {
|
||||
installType: "full",
|
||||
installType: 'full',
|
||||
agent: null,
|
||||
ide: null, // Will be set if IDE setup is done later
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user