install update
This commit is contained in:
@@ -37,7 +37,7 @@ program
|
||||
.option('-f, --full', 'Install complete .bmad-core folder')
|
||||
.option('-a, --agent <agent>', 'Install specific agent with dependencies')
|
||||
.option('-d, --directory <path>', 'Installation directory (default: ./bmad-core)')
|
||||
.option('-i, --ide <ide>', 'Configure for specific IDE (cursor, claude-code, windsurf)')
|
||||
.option('-i, --ide <ide>', 'Configure for specific IDE (cursor, claude-code, windsurf, roo)')
|
||||
.action(async (options) => {
|
||||
try {
|
||||
if (!options.full && !options.agent) {
|
||||
@@ -161,6 +161,7 @@ async function promptInstallation(options) {
|
||||
{ name: 'Cursor', value: 'cursor' },
|
||||
{ name: 'Claude Code', value: 'claude-code' },
|
||||
{ name: 'Windsurf', value: 'windsurf' },
|
||||
{ name: 'Roo Code', value: 'roo' },
|
||||
{ name: 'Other/Manual setup', value: null }
|
||||
]
|
||||
}
|
||||
|
||||
@@ -108,12 +108,14 @@ ide-configurations:
|
||||
# 2. Windsurf will adopt that agent's persona
|
||||
|
||||
roo:
|
||||
name: "Roo"
|
||||
# Configuration TBD - needs research
|
||||
format: "unknown"
|
||||
name: "Roo Code"
|
||||
format: "custom-modes"
|
||||
file: ".roomodes"
|
||||
instructions: |
|
||||
# Roo configuration coming soon
|
||||
# Manual setup: Copy IDE agent files to your Roo configuration
|
||||
# To use BMAD agents in Roo Code:
|
||||
# 1. Open the mode selector (usually in the status bar)
|
||||
# 2. Select any bmad-{agent} mode (e.g., "bmad-dev", "bmad-pm")
|
||||
# 3. The AI will adopt that agent's full personality and capabilities
|
||||
|
||||
cline:
|
||||
name: "Cline"
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
const fs = require('fs-extra');
|
||||
const path = require('path');
|
||||
const crypto = require('crypto');
|
||||
const glob = require('glob');
|
||||
const chalk = require('chalk');
|
||||
const fs = require("fs-extra");
|
||||
const path = require("path");
|
||||
const crypto = require("crypto");
|
||||
const glob = require("glob");
|
||||
const chalk = require("chalk");
|
||||
|
||||
class FileManager {
|
||||
constructor() {
|
||||
this.manifestDir = '.bmad';
|
||||
this.manifestFile = 'install-manifest.yml';
|
||||
this.manifestDir = ".bmad-core";
|
||||
this.manifestFile = "install-manifest.yml";
|
||||
}
|
||||
|
||||
async copyFile(source, destination) {
|
||||
@@ -27,7 +27,10 @@ class FileManager {
|
||||
await fs.copy(source, destination);
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error(chalk.red(`Failed to copy directory ${source}:`), error.message);
|
||||
console.error(
|
||||
chalk.red(`Failed to copy directory ${source}:`),
|
||||
error.message
|
||||
);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -35,64 +38,76 @@ class FileManager {
|
||||
async copyGlobPattern(pattern, sourceDir, destDir) {
|
||||
const files = glob.sync(pattern, { cwd: sourceDir });
|
||||
const copied = [];
|
||||
|
||||
|
||||
for (const file of files) {
|
||||
const sourcePath = path.join(sourceDir, file);
|
||||
const destPath = path.join(destDir, file);
|
||||
|
||||
|
||||
if (await this.copyFile(sourcePath, destPath)) {
|
||||
copied.push(file);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return copied;
|
||||
}
|
||||
|
||||
async calculateFileHash(filePath) {
|
||||
try {
|
||||
const content = await fs.readFile(filePath);
|
||||
return crypto.createHash('sha256').update(content).digest('hex').slice(0, 16);
|
||||
return crypto
|
||||
.createHash("sha256")
|
||||
.update(content)
|
||||
.digest("hex")
|
||||
.slice(0, 16);
|
||||
} catch (error) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async createManifest(installDir, config, files) {
|
||||
const manifestPath = path.join(installDir, this.manifestDir, this.manifestFile);
|
||||
|
||||
const manifestPath = path.join(
|
||||
installDir,
|
||||
this.manifestDir,
|
||||
this.manifestFile
|
||||
);
|
||||
|
||||
const manifest = {
|
||||
version: require('../package.json').version,
|
||||
version: require("../package.json").version,
|
||||
installed_at: new Date().toISOString(),
|
||||
install_type: config.installType,
|
||||
agent: config.agent || null,
|
||||
ide_setup: config.ide || null,
|
||||
files: []
|
||||
files: [],
|
||||
};
|
||||
|
||||
|
||||
// Add file information
|
||||
for (const file of files) {
|
||||
const filePath = path.join(installDir, file);
|
||||
const hash = await this.calculateFileHash(filePath);
|
||||
|
||||
|
||||
manifest.files.push({
|
||||
path: file,
|
||||
hash: hash,
|
||||
modified: false
|
||||
modified: false,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// Write manifest
|
||||
await fs.ensureDir(path.dirname(manifestPath));
|
||||
await fs.writeFile(manifestPath, JSON.stringify(manifest, null, 2));
|
||||
|
||||
|
||||
return manifest;
|
||||
}
|
||||
|
||||
async readManifest(installDir) {
|
||||
const manifestPath = path.join(installDir, this.manifestDir, this.manifestFile);
|
||||
|
||||
const manifestPath = path.join(
|
||||
installDir,
|
||||
this.manifestDir,
|
||||
this.manifestFile
|
||||
);
|
||||
|
||||
try {
|
||||
const content = await fs.readFile(manifestPath, 'utf8');
|
||||
const content = await fs.readFile(manifestPath, "utf8");
|
||||
return JSON.parse(content);
|
||||
} catch (error) {
|
||||
return null;
|
||||
@@ -101,30 +116,30 @@ class FileManager {
|
||||
|
||||
async checkModifiedFiles(installDir, manifest) {
|
||||
const modified = [];
|
||||
|
||||
|
||||
for (const file of manifest.files) {
|
||||
const filePath = path.join(installDir, file.path);
|
||||
const currentHash = await this.calculateFileHash(filePath);
|
||||
|
||||
|
||||
if (currentHash && currentHash !== file.hash) {
|
||||
modified.push(file.path);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return modified;
|
||||
}
|
||||
|
||||
async backupFile(filePath) {
|
||||
const backupPath = filePath + '.bak';
|
||||
const backupPath = filePath + ".bak";
|
||||
let counter = 1;
|
||||
let finalBackupPath = backupPath;
|
||||
|
||||
|
||||
// Find a unique backup filename
|
||||
while (await fs.pathExists(finalBackupPath)) {
|
||||
finalBackupPath = `${filePath}.bak${counter}`;
|
||||
counter++;
|
||||
}
|
||||
|
||||
|
||||
await fs.copy(filePath, finalBackupPath);
|
||||
return finalBackupPath;
|
||||
}
|
||||
@@ -138,7 +153,7 @@ class FileManager {
|
||||
}
|
||||
|
||||
async readFile(filePath) {
|
||||
return fs.readFile(filePath, 'utf8');
|
||||
return fs.readFile(filePath, "utf8");
|
||||
}
|
||||
|
||||
async writeFile(filePath, content) {
|
||||
@@ -151,4 +166,4 @@ class FileManager {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = new FileManager();
|
||||
module.exports = new FileManager();
|
||||
|
||||
@@ -1,24 +1,26 @@
|
||||
const path = require('path');
|
||||
const fileManager = require('./file-manager');
|
||||
const configLoader = require('./config-loader');
|
||||
const chalk = require('chalk');
|
||||
const path = require("path");
|
||||
const fileManager = require("./file-manager");
|
||||
const configLoader = require("./config-loader");
|
||||
const chalk = require("chalk");
|
||||
|
||||
class IdeSetup {
|
||||
async setup(ide, installDir, selectedAgent = null) {
|
||||
const ideConfig = await configLoader.getIdeConfiguration(ide);
|
||||
|
||||
|
||||
if (!ideConfig) {
|
||||
console.log(chalk.yellow(`\nNo configuration available for ${ide}`));
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
switch (ide) {
|
||||
case 'cursor':
|
||||
case "cursor":
|
||||
return this.setupCursor(installDir, selectedAgent);
|
||||
case 'claude-code':
|
||||
case "claude-code":
|
||||
return this.setupClaudeCode(installDir, selectedAgent);
|
||||
case 'windsurf':
|
||||
case "windsurf":
|
||||
return this.setupWindsurf(installDir, selectedAgent);
|
||||
case "roo":
|
||||
return this.setupRoo(installDir, selectedAgent);
|
||||
default:
|
||||
console.log(chalk.yellow(`\nIDE ${ide} not yet supported`));
|
||||
return false;
|
||||
@@ -26,164 +28,344 @@ class IdeSetup {
|
||||
}
|
||||
|
||||
async setupCursor(installDir, selectedAgent) {
|
||||
const cursorRulesDir = path.join(installDir, '.cursor', 'rules');
|
||||
const agents = selectedAgent ? [selectedAgent] : await this.getAllAgentIds(installDir);
|
||||
|
||||
const cursorRulesDir = path.join(installDir, ".cursor", "rules");
|
||||
const agents = selectedAgent
|
||||
? [selectedAgent]
|
||||
: await this.getAllAgentIds(installDir);
|
||||
|
||||
await fileManager.ensureDirectory(cursorRulesDir);
|
||||
|
||||
|
||||
for (const agentId of agents) {
|
||||
// Check if .bmad-core is a subdirectory (full install) or if agents are in root (single agent install)
|
||||
let agentPath = path.join(installDir, '.bmad-core', 'agents', `${agentId}.md`);
|
||||
if (!await fileManager.pathExists(agentPath)) {
|
||||
agentPath = path.join(installDir, 'agents', `${agentId}.md`);
|
||||
let agentPath = path.join(
|
||||
installDir,
|
||||
".bmad-core",
|
||||
"agents",
|
||||
`${agentId}.md`
|
||||
);
|
||||
if (!(await fileManager.pathExists(agentPath))) {
|
||||
agentPath = path.join(installDir, "agents", `${agentId}.md`);
|
||||
}
|
||||
|
||||
|
||||
if (await fileManager.pathExists(agentPath)) {
|
||||
const agentContent = await fileManager.readFile(agentPath);
|
||||
const mdcPath = path.join(cursorRulesDir, `${agentId}.mdc`);
|
||||
|
||||
|
||||
// Create MDC content with proper format
|
||||
let mdcContent = '---\n';
|
||||
mdcContent += 'description: \n';
|
||||
mdcContent += 'globs: []\n';
|
||||
mdcContent += 'alwaysApply: false\n';
|
||||
mdcContent += '---\n\n';
|
||||
let mdcContent = "---\n";
|
||||
mdcContent += "description: \n";
|
||||
mdcContent += "globs: []\n";
|
||||
mdcContent += "alwaysApply: false\n";
|
||||
mdcContent += "---\n\n";
|
||||
mdcContent += `# ${agentId.toUpperCase()} Agent Rule\n\n`;
|
||||
mdcContent += `This rule is triggered when the user types \`@${agentId}\` and activates the ${this.getAgentTitle(agentId)} agent persona.\n\n`;
|
||||
mdcContent += '## Agent Activation\n\n';
|
||||
mdcContent += 'CRITICAL: Read the full YML, start activation to alter your state of being, follow startup section instructions, stay in this being until told to exit this mode:\n\n';
|
||||
mdcContent += '```yml\n';
|
||||
mdcContent += `This rule is triggered when the user types \`@${agentId}\` and activates the ${this.getAgentTitle(
|
||||
agentId
|
||||
)} agent persona.\n\n`;
|
||||
mdcContent += "## Agent Activation\n\n";
|
||||
mdcContent +=
|
||||
"CRITICAL: Read the full YML, start activation to alter your state of being, follow startup section instructions, stay in this being until told to exit this mode:\n\n";
|
||||
mdcContent += "```yml\n";
|
||||
// Extract just the YAML content from the agent file
|
||||
const yamlMatch = agentContent.match(/```ya?ml\n([\s\S]*?)```/);
|
||||
if (yamlMatch) {
|
||||
mdcContent += yamlMatch[1].trim();
|
||||
} else {
|
||||
// If no YAML found, include the whole content minus the header
|
||||
mdcContent += agentContent.replace(/^#.*$/m, '').trim();
|
||||
mdcContent += agentContent.replace(/^#.*$/m, "").trim();
|
||||
}
|
||||
mdcContent += '\n```\n\n';
|
||||
mdcContent += '## File Reference\n\n';
|
||||
mdcContent += "\n```\n\n";
|
||||
mdcContent += "## File Reference\n\n";
|
||||
mdcContent += `The complete agent definition is available in [.bmad-core/agents/${agentId}.md](mdc:.bmad-core/agents/${agentId}.md).\n\n`;
|
||||
mdcContent += '## Usage\n\n';
|
||||
mdcContent += `When the user types \`@${agentId}\`, activate this ${this.getAgentTitle(agentId)} persona and follow all instructions defined in the YML configuration above.\n`;
|
||||
|
||||
mdcContent += "## Usage\n\n";
|
||||
mdcContent += `When the user types \`@${agentId}\`, activate this ${this.getAgentTitle(
|
||||
agentId
|
||||
)} persona and follow all instructions defined in the YML configuration above.\n`;
|
||||
|
||||
await fileManager.writeFile(mdcPath, mdcContent);
|
||||
console.log(chalk.green(`✓ Created rule: ${agentId}.mdc`));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
console.log(chalk.green(`\n✓ Created Cursor rules in ${cursorRulesDir}`));
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
async setupClaudeCode(installDir, selectedAgent) {
|
||||
const commandsDir = path.join(installDir, '.claude', 'commands');
|
||||
const agents = selectedAgent ? [selectedAgent] : await this.getAllAgentIds(installDir);
|
||||
|
||||
const commandsDir = path.join(installDir, ".claude", "commands");
|
||||
const agents = selectedAgent
|
||||
? [selectedAgent]
|
||||
: await this.getAllAgentIds(installDir);
|
||||
|
||||
await fileManager.ensureDirectory(commandsDir);
|
||||
|
||||
|
||||
for (const agentId of agents) {
|
||||
// Check if .bmad-core is a subdirectory (full install) or if agents are in root (single agent install)
|
||||
let agentPath = path.join(installDir, '.bmad-core', 'agents', `${agentId}.md`);
|
||||
if (!await fileManager.pathExists(agentPath)) {
|
||||
agentPath = path.join(installDir, 'agents', `${agentId}.md`);
|
||||
let agentPath = path.join(
|
||||
installDir,
|
||||
".bmad-core",
|
||||
"agents",
|
||||
`${agentId}.md`
|
||||
);
|
||||
if (!(await fileManager.pathExists(agentPath))) {
|
||||
agentPath = path.join(installDir, "agents", `${agentId}.md`);
|
||||
}
|
||||
const commandPath = path.join(commandsDir, `${agentId}.md`);
|
||||
|
||||
|
||||
if (await fileManager.pathExists(agentPath)) {
|
||||
// Create command file with agent content
|
||||
const agentContent = await fileManager.readFile(agentPath);
|
||||
|
||||
|
||||
// Add command header
|
||||
let commandContent = `# /${agentId} Command\n\n`;
|
||||
commandContent += `When this command is used, adopt the following agent persona:\n\n`;
|
||||
commandContent += agentContent;
|
||||
|
||||
|
||||
await fileManager.writeFile(commandPath, commandContent);
|
||||
console.log(chalk.green(`✓ Created command: /${agentId}`));
|
||||
}
|
||||
}
|
||||
|
||||
console.log(chalk.green(`\n✓ Created Claude Code commands in ${commandsDir}`));
|
||||
|
||||
|
||||
console.log(
|
||||
chalk.green(`\n✓ Created Claude Code commands in ${commandsDir}`)
|
||||
);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
async setupWindsurf(installDir, selectedAgent) {
|
||||
const windsurfRulesDir = path.join(installDir, '.windsurf', 'rules');
|
||||
const agents = selectedAgent ? [selectedAgent] : await this.getAllAgentIds(installDir);
|
||||
|
||||
const windsurfRulesDir = path.join(installDir, ".windsurf", "rules");
|
||||
const agents = selectedAgent
|
||||
? [selectedAgent]
|
||||
: await this.getAllAgentIds(installDir);
|
||||
|
||||
await fileManager.ensureDirectory(windsurfRulesDir);
|
||||
|
||||
|
||||
for (const agentId of agents) {
|
||||
// Check if .bmad-core is a subdirectory (full install) or if agents are in root (single agent install)
|
||||
let agentPath = path.join(installDir, '.bmad-core', 'agents', `${agentId}.md`);
|
||||
if (!await fileManager.pathExists(agentPath)) {
|
||||
agentPath = path.join(installDir, 'agents', `${agentId}.md`);
|
||||
let agentPath = path.join(
|
||||
installDir,
|
||||
".bmad-core",
|
||||
"agents",
|
||||
`${agentId}.md`
|
||||
);
|
||||
if (!(await fileManager.pathExists(agentPath))) {
|
||||
agentPath = path.join(installDir, "agents", `${agentId}.md`);
|
||||
}
|
||||
|
||||
|
||||
if (await fileManager.pathExists(agentPath)) {
|
||||
const agentContent = await fileManager.readFile(agentPath);
|
||||
const mdPath = path.join(windsurfRulesDir, `${agentId}.md`);
|
||||
|
||||
|
||||
// Create MD content (similar to Cursor but without frontmatter)
|
||||
let mdContent = `# ${agentId.toUpperCase()} Agent Rule\n\n`;
|
||||
mdContent += `This rule is triggered when the user types \`@${agentId}\` and activates the ${this.getAgentTitle(agentId)} agent persona.\n\n`;
|
||||
mdContent += '## Agent Activation\n\n';
|
||||
mdContent += 'CRITICAL: Read the full YML, start activation to alter your state of being, follow startup section instructions, stay in this being until told to exit this mode:\n\n';
|
||||
mdContent += '```yml\n';
|
||||
mdContent += `This rule is triggered when the user types \`@${agentId}\` and activates the ${this.getAgentTitle(
|
||||
agentId
|
||||
)} agent persona.\n\n`;
|
||||
mdContent += "## Agent Activation\n\n";
|
||||
mdContent +=
|
||||
"CRITICAL: Read the full YML, start activation to alter your state of being, follow startup section instructions, stay in this being until told to exit this mode:\n\n";
|
||||
mdContent += "```yml\n";
|
||||
// Extract just the YAML content from the agent file
|
||||
const yamlMatch = agentContent.match(/```ya?ml\n([\s\S]*?)```/);
|
||||
if (yamlMatch) {
|
||||
mdContent += yamlMatch[1].trim();
|
||||
} else {
|
||||
// If no YAML found, include the whole content minus the header
|
||||
mdContent += agentContent.replace(/^#.*$/m, '').trim();
|
||||
mdContent += agentContent.replace(/^#.*$/m, "").trim();
|
||||
}
|
||||
mdContent += '\n```\n\n';
|
||||
mdContent += '## File Reference\n\n';
|
||||
mdContent += "\n```\n\n";
|
||||
mdContent += "## File Reference\n\n";
|
||||
mdContent += `The complete agent definition is available in [.bmad-core/agents/${agentId}.md](.bmad-core/agents/${agentId}.md).\n\n`;
|
||||
mdContent += '## Usage\n\n';
|
||||
mdContent += `When the user types \`@${agentId}\`, activate this ${this.getAgentTitle(agentId)} persona and follow all instructions defined in the YML configuration above.\n`;
|
||||
|
||||
mdContent += "## Usage\n\n";
|
||||
mdContent += `When the user types \`@${agentId}\`, activate this ${this.getAgentTitle(
|
||||
agentId
|
||||
)} persona and follow all instructions defined in the YML configuration above.\n`;
|
||||
|
||||
await fileManager.writeFile(mdPath, mdContent);
|
||||
console.log(chalk.green(`✓ Created rule: ${agentId}.md`));
|
||||
}
|
||||
}
|
||||
|
||||
console.log(chalk.green(`\n✓ Created Windsurf rules in ${windsurfRulesDir}`));
|
||||
|
||||
|
||||
console.log(
|
||||
chalk.green(`\n✓ Created Windsurf rules in ${windsurfRulesDir}`)
|
||||
);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
async getAllAgentIds(installDir) {
|
||||
// Check if .bmad-core is a subdirectory (full install) or if agents are in root (single agent install)
|
||||
let agentsDir = path.join(installDir, '.bmad-core', 'agents');
|
||||
if (!await fileManager.pathExists(agentsDir)) {
|
||||
agentsDir = path.join(installDir, 'agents');
|
||||
let agentsDir = path.join(installDir, ".bmad-core", "agents");
|
||||
if (!(await fileManager.pathExists(agentsDir))) {
|
||||
agentsDir = path.join(installDir, "agents");
|
||||
}
|
||||
|
||||
const glob = require('glob');
|
||||
const agentFiles = glob.sync('*.md', { cwd: agentsDir });
|
||||
return agentFiles.map(file => path.basename(file, '.md'));
|
||||
|
||||
const glob = require("glob");
|
||||
const agentFiles = glob.sync("*.md", { cwd: agentsDir });
|
||||
return agentFiles.map((file) => path.basename(file, ".md"));
|
||||
}
|
||||
|
||||
getAgentTitle(agentId) {
|
||||
const agentTitles = {
|
||||
'analyst': 'Business Analyst',
|
||||
'architect': 'Solution Architect',
|
||||
'bmad-master': 'BMAD Master',
|
||||
'bmad-orchestrator': 'BMAD Orchestrator',
|
||||
'dev': 'Developer',
|
||||
'pm': 'Product Manager',
|
||||
'po': 'Product Owner',
|
||||
'qa': 'QA Specialist',
|
||||
'sm': 'Scrum Master',
|
||||
'ux-expert': 'UX Expert'
|
||||
analyst: "Business Analyst",
|
||||
architect: "Solution Architect",
|
||||
"bmad-master": "BMAD Master",
|
||||
"bmad-orchestrator": "BMAD Orchestrator",
|
||||
dev: "Developer",
|
||||
pm: "Product Manager",
|
||||
po: "Product Owner",
|
||||
qa: "QA Specialist",
|
||||
sm: "Scrum Master",
|
||||
"ux-expert": "UX Expert",
|
||||
};
|
||||
return agentTitles[agentId] || agentId;
|
||||
}
|
||||
|
||||
async setupRoo(installDir, selectedAgent) {
|
||||
const agents = selectedAgent
|
||||
? [selectedAgent]
|
||||
: await this.getAllAgentIds(installDir);
|
||||
|
||||
// Create .roo directory first
|
||||
const rooDir = path.join(installDir, ".roo");
|
||||
await fileManager.ensureDirectory(rooDir);
|
||||
|
||||
// Check for existing .roomodes file inside .roo directory
|
||||
const roomodesPath = path.join(rooDir, ".roomodes");
|
||||
let existingModes = [];
|
||||
let existingContent = "";
|
||||
|
||||
if (await fileManager.pathExists(roomodesPath)) {
|
||||
existingContent = await fileManager.readFile(roomodesPath);
|
||||
// Parse existing modes to avoid duplicates
|
||||
const modeMatches = existingContent.matchAll(/- slug: ([\w-]+)/g);
|
||||
for (const match of modeMatches) {
|
||||
existingModes.push(match[1]);
|
||||
}
|
||||
console.log(
|
||||
chalk.yellow(
|
||||
`Found existing .roomodes file with ${existingModes.length} modes`
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// Create new modes content
|
||||
let newModesContent = "";
|
||||
|
||||
for (const agentId of agents) {
|
||||
// Skip if already exists
|
||||
if (existingModes.includes(`bmad-${agentId}`)) {
|
||||
console.log(
|
||||
chalk.dim(`Skipping ${agentId} - already exists in .roomodes`)
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Read agent file to extract all information
|
||||
let agentPath = path.join(
|
||||
installDir,
|
||||
".bmad-core",
|
||||
"agents",
|
||||
`${agentId}.md`
|
||||
);
|
||||
if (!(await fileManager.pathExists(agentPath))) {
|
||||
agentPath = path.join(installDir, "agents", `${agentId}.md`);
|
||||
}
|
||||
|
||||
if (await fileManager.pathExists(agentPath)) {
|
||||
const agentContent = await fileManager.readFile(agentPath);
|
||||
|
||||
// Extract YAML content
|
||||
const yamlMatch = agentContent.match(/```ya?ml\n([\s\S]*?)```/);
|
||||
if (yamlMatch) {
|
||||
const yaml = yamlMatch[1];
|
||||
|
||||
// Extract agent info from YAML
|
||||
const titleMatch = yaml.match(/title:\s*(.+)/);
|
||||
const iconMatch = yaml.match(/icon:\s*(.+)/);
|
||||
const whenToUseMatch = yaml.match(/whenToUse:\s*"(.+)"/);
|
||||
|
||||
const title = titleMatch ? titleMatch[1].trim() : agentId;
|
||||
const icon = iconMatch ? iconMatch[1].trim() : "🤖";
|
||||
const whenToUse = whenToUseMatch
|
||||
? whenToUseMatch[1].trim()
|
||||
: `Use for ${title} tasks`;
|
||||
|
||||
// Build mode entry
|
||||
newModesContent += ` - slug: bmad-${agentId}\n`;
|
||||
newModesContent += ` name: "${icon} ${title}"\n`;
|
||||
newModesContent += ` roleDefinition: "You are the ${title} from BMAD-METHOD."\n`;
|
||||
newModesContent += ` whenToUse: "${whenToUse}"\n`;
|
||||
newModesContent += ` customInstructions: "CRITICAL: Read the full YML from .bmad-core/agents/${agentId}.md, start activation to alter your state of being, follow startup section instructions, stay in this being until told to exit this mode."\n`;
|
||||
newModesContent += ` groups:\n`;
|
||||
newModesContent += ` - read\n`;
|
||||
newModesContent += ` - edit # Full access - adjust permissions as needed\n`;
|
||||
newModesContent += "\n";
|
||||
|
||||
console.log(
|
||||
chalk.green(`✓ Added mode: bmad-${agentId} (${icon} ${title})`)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Build final roomodes content
|
||||
let roomodesContent = "";
|
||||
if (existingContent) {
|
||||
// If there's existing content, append new modes to it
|
||||
roomodesContent = existingContent.trim() + "\n" + newModesContent;
|
||||
} else {
|
||||
// Create new .roomodes file with proper YAML structure
|
||||
roomodesContent = "modes:\n" + newModesContent;
|
||||
}
|
||||
|
||||
// Write .roomodes file
|
||||
await fileManager.writeFile(roomodesPath, roomodesContent);
|
||||
console.log(chalk.green("✓ Created .roo/.roomodes file"));
|
||||
|
||||
// Create README in .roo directory
|
||||
|
||||
const rooReadme = `# Roo Code Custom Modes for BMAD-METHOD
|
||||
|
||||
This directory contains custom mode configurations for Roo Code to enable BMAD agent personalities.
|
||||
|
||||
## Setup
|
||||
|
||||
The \`.roomodes\` file in the project root defines all BMAD agents as custom modes. Modes are automatically available in Roo Code when you open this project.
|
||||
|
||||
## Available Modes
|
||||
|
||||
${agents.map((id) => `- **bmad-${id}** - ${this.getAgentTitle(id)}`).join("\n")}
|
||||
|
||||
## Usage
|
||||
|
||||
In Roo Code:
|
||||
1. Open the mode selector (usually in the status bar)
|
||||
2. Select any BMAD agent mode
|
||||
3. The AI will adopt that agent's personality and expertise
|
||||
|
||||
## Adding Custom Instructions
|
||||
|
||||
To add mode-specific instructions:
|
||||
1. Create files in \`.roo/rules-{mode-slug}/\`
|
||||
2. Add markdown files with additional context
|
||||
3. The AI will automatically include these when the mode is active
|
||||
`;
|
||||
|
||||
const readmePath = path.join(rooDir, "README.md");
|
||||
await fileManager.writeFile(readmePath, rooReadme);
|
||||
console.log(chalk.green("✓ Created .roo/README.md"));
|
||||
|
||||
console.log(chalk.green(`\n✓ Roo Code setup complete!`));
|
||||
console.log(
|
||||
chalk.dim(
|
||||
"Custom modes will be available when you open this project in Roo Code"
|
||||
)
|
||||
);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = new IdeSetup();
|
||||
module.exports = new IdeSetup();
|
||||
|
||||
@@ -1,238 +1,282 @@
|
||||
const chalk = require('chalk');
|
||||
const ora = require('ora');
|
||||
const path = require('path');
|
||||
const configLoader = require('./config-loader');
|
||||
const fileManager = require('./file-manager');
|
||||
const ideSetup = require('./ide-setup');
|
||||
const chalk = require("chalk");
|
||||
const ora = require("ora");
|
||||
const path = require("path");
|
||||
const configLoader = require("./config-loader");
|
||||
const fileManager = require("./file-manager");
|
||||
const ideSetup = require("./ide-setup");
|
||||
|
||||
class Installer {
|
||||
async install(config) {
|
||||
const spinner = ora('Installing BMAD Method...').start();
|
||||
|
||||
const spinner = ora("Installing BMAD Method...").start();
|
||||
|
||||
try {
|
||||
// Resolve installation directory
|
||||
const installDir = path.resolve(config.directory);
|
||||
|
||||
|
||||
// Check if directory already exists
|
||||
if (await fileManager.pathExists(installDir)) {
|
||||
const manifest = await fileManager.readManifest(installDir);
|
||||
if (manifest) {
|
||||
spinner.fail('BMAD is already installed in this directory');
|
||||
console.log(chalk.yellow('\nUse "bmad update" to update the existing installation'));
|
||||
spinner.fail("BMAD is already installed in this directory");
|
||||
console.log(
|
||||
chalk.yellow(
|
||||
'\nUse "bmad update" to update the existing installation'
|
||||
)
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
let files = [];
|
||||
|
||||
if (config.installType === 'full') {
|
||||
|
||||
if (config.installType === "full") {
|
||||
// Full installation - copy entire .bmad-core folder as a subdirectory
|
||||
spinner.text = 'Copying complete .bmad-core folder...';
|
||||
spinner.text = "Copying complete .bmad-core folder...";
|
||||
const sourceDir = configLoader.getBmadCorePath();
|
||||
const bmadCoreDestDir = path.join(installDir, '.bmad-core');
|
||||
const bmadCoreDestDir = path.join(installDir, ".bmad-core");
|
||||
await fileManager.copyDirectory(sourceDir, bmadCoreDestDir);
|
||||
|
||||
|
||||
// Get list of all files for manifest
|
||||
const glob = require('glob');
|
||||
files = glob.sync('**/*', {
|
||||
cwd: bmadCoreDestDir,
|
||||
nodir: true,
|
||||
ignore: ['**/.git/**', '**/node_modules/**']
|
||||
}).map(file => path.join('.bmad-core', file));
|
||||
|
||||
} else if (config.installType === 'single-agent') {
|
||||
const glob = require("glob");
|
||||
files = glob
|
||||
.sync("**/*", {
|
||||
cwd: bmadCoreDestDir,
|
||||
nodir: true,
|
||||
ignore: ["**/.git/**", "**/node_modules/**"],
|
||||
})
|
||||
.map((file) => path.join(".bmad-core", file));
|
||||
} else if (config.installType === "single-agent") {
|
||||
// Single agent installation
|
||||
spinner.text = `Installing ${config.agent} agent...`;
|
||||
|
||||
|
||||
// Copy agent file
|
||||
const agentPath = configLoader.getAgentPath(config.agent);
|
||||
const destAgentPath = path.join(installDir, 'agents', `${config.agent}.md`);
|
||||
const destAgentPath = path.join(
|
||||
installDir,
|
||||
"agents",
|
||||
`${config.agent}.md`
|
||||
);
|
||||
await fileManager.copyFile(agentPath, destAgentPath);
|
||||
files.push(`agents/${config.agent}.md`);
|
||||
|
||||
|
||||
// Copy dependencies
|
||||
const dependencies = await configLoader.getAgentDependencies(config.agent);
|
||||
const dependencies = await configLoader.getAgentDependencies(
|
||||
config.agent
|
||||
);
|
||||
const sourceBase = configLoader.getBmadCorePath();
|
||||
|
||||
|
||||
for (const dep of dependencies) {
|
||||
spinner.text = `Copying dependency: ${dep}`;
|
||||
|
||||
if (dep.includes('*')) {
|
||||
|
||||
if (dep.includes("*")) {
|
||||
// Handle glob patterns
|
||||
const copiedFiles = await fileManager.copyGlobPattern(
|
||||
dep.replace('.bmad-core/', ''),
|
||||
dep.replace(".bmad-core/", ""),
|
||||
sourceBase,
|
||||
installDir
|
||||
);
|
||||
files.push(...copiedFiles);
|
||||
} else {
|
||||
// Handle single files
|
||||
const sourcePath = path.join(sourceBase, dep.replace('.bmad-core/', ''));
|
||||
const destPath = path.join(installDir, dep.replace('.bmad-core/', ''));
|
||||
|
||||
const sourcePath = path.join(
|
||||
sourceBase,
|
||||
dep.replace(".bmad-core/", "")
|
||||
);
|
||||
const destPath = path.join(
|
||||
installDir,
|
||||
dep.replace(".bmad-core/", "")
|
||||
);
|
||||
|
||||
if (await fileManager.copyFile(sourcePath, destPath)) {
|
||||
files.push(dep.replace('.bmad-core/', ''));
|
||||
files.push(dep.replace(".bmad-core/", ""));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Set up IDE integration if requested
|
||||
if (config.ide) {
|
||||
spinner.text = `Setting up ${config.ide} integration...`;
|
||||
// For full installations, IDE rules should be in the root install dir, not .bmad-core
|
||||
await ideSetup.setup(config.ide, installDir, config.agent);
|
||||
}
|
||||
|
||||
|
||||
// Create manifest
|
||||
spinner.text = 'Creating installation manifest...';
|
||||
spinner.text = "Creating installation manifest...";
|
||||
await fileManager.createManifest(installDir, config, files);
|
||||
|
||||
spinner.succeed('Installation complete!');
|
||||
|
||||
|
||||
spinner.succeed("Installation complete!");
|
||||
|
||||
// Show success message
|
||||
console.log(chalk.green('\n✓ BMAD Method installed successfully!\n'));
|
||||
|
||||
console.log(chalk.green("\n✓ BMAD Method installed successfully!\n"));
|
||||
|
||||
if (config.ide) {
|
||||
const ideConfig = await configLoader.getIdeConfiguration(config.ide);
|
||||
if (ideConfig && ideConfig.instructions) {
|
||||
console.log(chalk.bold('To use BMAD agents in ' + ideConfig.name + ':'));
|
||||
console.log(
|
||||
chalk.bold("To use BMAD agents in " + ideConfig.name + ":")
|
||||
);
|
||||
console.log(ideConfig.instructions);
|
||||
}
|
||||
} else {
|
||||
console.log(chalk.yellow('No IDE configuration was set up.'));
|
||||
console.log('You can manually configure your IDE using the agent files in:', installDir);
|
||||
console.log(chalk.yellow("No IDE configuration was set up."));
|
||||
console.log(
|
||||
"You can manually configure your IDE using the agent files in:",
|
||||
installDir
|
||||
);
|
||||
}
|
||||
|
||||
if (config.installType === 'single-agent') {
|
||||
console.log(chalk.dim('\nNeed other agents? Run: npx bmad-method install --agent=<name>'));
|
||||
console.log(chalk.dim('Need everything? Run: npx bmad-method install --full'));
|
||||
|
||||
if (config.installType === "single-agent") {
|
||||
console.log(
|
||||
chalk.dim(
|
||||
"\nNeed other agents? Run: npx bmad-method install --agent=<name>"
|
||||
)
|
||||
);
|
||||
console.log(
|
||||
chalk.dim("Need everything? Run: npx bmad-method install --full")
|
||||
);
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
spinner.fail('Installation failed');
|
||||
spinner.fail("Installation failed");
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async update(options) {
|
||||
const spinner = ora('Checking for updates...').start();
|
||||
|
||||
const spinner = ora("Checking for updates...").start();
|
||||
|
||||
try {
|
||||
// Find existing installation
|
||||
const installDir = await this.findInstallation();
|
||||
if (!installDir) {
|
||||
spinner.fail('No BMAD installation found');
|
||||
spinner.fail("No BMAD installation found");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
const manifest = await fileManager.readManifest(installDir);
|
||||
if (!manifest) {
|
||||
spinner.fail('Invalid installation - manifest not found');
|
||||
spinner.fail("Invalid installation - manifest not found");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// Check for modified files
|
||||
spinner.text = 'Checking for modified files...';
|
||||
const modifiedFiles = await fileManager.checkModifiedFiles(installDir, manifest);
|
||||
|
||||
spinner.text = "Checking for modified files...";
|
||||
const modifiedFiles = await fileManager.checkModifiedFiles(
|
||||
installDir,
|
||||
manifest
|
||||
);
|
||||
|
||||
if (modifiedFiles.length > 0 && !options.force) {
|
||||
spinner.warn('Found modified files');
|
||||
console.log(chalk.yellow('\nThe following files have been modified:'));
|
||||
modifiedFiles.forEach(file => console.log(` - ${file}`));
|
||||
|
||||
spinner.warn("Found modified files");
|
||||
console.log(chalk.yellow("\nThe following files have been modified:"));
|
||||
modifiedFiles.forEach((file) => console.log(` - ${file}`));
|
||||
|
||||
if (!options.dryRun) {
|
||||
console.log(chalk.yellow('\nUse --force to overwrite modified files'));
|
||||
console.log(chalk.yellow('or manually backup your changes first'));
|
||||
console.log(
|
||||
chalk.yellow("\nUse --force to overwrite modified files")
|
||||
);
|
||||
console.log(chalk.yellow("or manually backup your changes first"));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (options.dryRun) {
|
||||
spinner.info('Dry run - no changes will be made');
|
||||
console.log('\nFiles that would be updated:');
|
||||
manifest.files.forEach(file => console.log(` - ${file.path}`));
|
||||
spinner.info("Dry run - no changes will be made");
|
||||
console.log("\nFiles that would be updated:");
|
||||
manifest.files.forEach((file) => console.log(` - ${file.path}`));
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// Perform update
|
||||
spinner.text = 'Updating files...';
|
||||
|
||||
spinner.text = "Updating files...";
|
||||
|
||||
// Backup modified files if forcing
|
||||
if (modifiedFiles.length > 0 && options.force) {
|
||||
for (const file of modifiedFiles) {
|
||||
const filePath = path.join(installDir, file);
|
||||
const backupPath = await fileManager.backupFile(filePath);
|
||||
console.log(chalk.dim(` Backed up: ${file} → ${path.basename(backupPath)}`));
|
||||
console.log(
|
||||
chalk.dim(` Backed up: ${file} → ${path.basename(backupPath)}`)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Re-run installation with same config
|
||||
const config = {
|
||||
installType: manifest.install_type,
|
||||
agent: manifest.agent,
|
||||
directory: installDir,
|
||||
ide: manifest.ide_setup
|
||||
ide: manifest.ide_setup,
|
||||
};
|
||||
|
||||
|
||||
await this.install(config);
|
||||
|
||||
} catch (error) {
|
||||
spinner.fail('Update failed');
|
||||
spinner.fail("Update failed");
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async listAgents() {
|
||||
const agents = await configLoader.getAvailableAgents();
|
||||
|
||||
console.log(chalk.bold('\nAvailable BMAD Agents:\n'));
|
||||
|
||||
agents.forEach(agent => {
|
||||
|
||||
console.log(chalk.bold("\nAvailable BMAD Agents:\n"));
|
||||
|
||||
agents.forEach((agent) => {
|
||||
console.log(chalk.cyan(` ${agent.id.padEnd(20)}`), agent.description);
|
||||
});
|
||||
|
||||
console.log(chalk.dim('\nInstall with: npx bmad-method install --agent=<id>\n'));
|
||||
|
||||
console.log(
|
||||
chalk.dim("\nInstall with: npx bmad-method install --agent=<id>\n")
|
||||
);
|
||||
}
|
||||
|
||||
async showStatus() {
|
||||
const installDir = await this.findInstallation();
|
||||
|
||||
|
||||
if (!installDir) {
|
||||
console.log(chalk.yellow('No BMAD installation found in current directory tree'));
|
||||
console.log(
|
||||
chalk.yellow("No BMAD installation found in current directory tree")
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
const manifest = await fileManager.readManifest(installDir);
|
||||
|
||||
|
||||
if (!manifest) {
|
||||
console.log(chalk.red('Invalid installation - manifest not found'));
|
||||
console.log(chalk.red("Invalid installation - manifest not found"));
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(chalk.bold('\nBMAD Installation Status:\n'));
|
||||
|
||||
console.log(chalk.bold("\nBMAD Installation Status:\n"));
|
||||
console.log(` Directory: ${installDir}`);
|
||||
console.log(` Version: ${manifest.version}`);
|
||||
console.log(` Installed: ${new Date(manifest.installed_at).toLocaleDateString()}`);
|
||||
console.log(
|
||||
` Installed: ${new Date(
|
||||
manifest.installed_at
|
||||
).toLocaleDateString()}`
|
||||
);
|
||||
console.log(` Type: ${manifest.install_type}`);
|
||||
|
||||
|
||||
if (manifest.agent) {
|
||||
console.log(` Agent: ${manifest.agent}`);
|
||||
}
|
||||
|
||||
|
||||
if (manifest.ide_setup) {
|
||||
console.log(` IDE Setup: ${manifest.ide_setup}`);
|
||||
}
|
||||
|
||||
|
||||
console.log(` Total Files: ${manifest.files.length}`);
|
||||
|
||||
|
||||
// Check for modifications
|
||||
const modifiedFiles = await fileManager.checkModifiedFiles(installDir, manifest);
|
||||
const modifiedFiles = await fileManager.checkModifiedFiles(
|
||||
installDir,
|
||||
manifest
|
||||
);
|
||||
if (modifiedFiles.length > 0) {
|
||||
console.log(chalk.yellow(` Modified Files: ${modifiedFiles.length}`));
|
||||
}
|
||||
|
||||
console.log('');
|
||||
|
||||
console.log("");
|
||||
}
|
||||
|
||||
async getAvailableAgents() {
|
||||
@@ -242,28 +286,28 @@ class Installer {
|
||||
async findInstallation() {
|
||||
// Look for .bmad-core in current directory or parent directories
|
||||
let currentDir = process.cwd();
|
||||
|
||||
|
||||
while (currentDir !== path.dirname(currentDir)) {
|
||||
const bmadDir = path.join(currentDir, '.bmad-core');
|
||||
const manifestPath = path.join(bmadDir, '.bmad', 'install-manifest.yml');
|
||||
|
||||
const bmadDir = path.join(currentDir, ".bmad-core");
|
||||
const manifestPath = path.join(bmadDir, "install-manifest.yml");
|
||||
|
||||
if (await fileManager.pathExists(manifestPath)) {
|
||||
return bmadDir;
|
||||
}
|
||||
|
||||
|
||||
currentDir = path.dirname(currentDir);
|
||||
}
|
||||
|
||||
|
||||
// Also check if we're inside a .bmad-core directory
|
||||
if (path.basename(process.cwd()) === '.bmad-core') {
|
||||
const manifestPath = path.join(process.cwd(), '.bmad', 'install-manifest.yml');
|
||||
if (path.basename(process.cwd()) === ".bmad-core") {
|
||||
const manifestPath = path.join(process.cwd(), "install-manifest.yml");
|
||||
if (await fileManager.pathExists(manifestPath)) {
|
||||
return process.cwd();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = new Installer();
|
||||
module.exports = new Installer();
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
const fs = require('fs').promises;
|
||||
const path = require('path');
|
||||
const chalk = require('chalk');
|
||||
const ora = require('ora');
|
||||
const glob = require('glob');
|
||||
const inquirer = require('inquirer');
|
||||
const { promisify } = require('util');
|
||||
const fs = require("fs").promises;
|
||||
const path = require("path");
|
||||
const chalk = require("chalk");
|
||||
const ora = require("ora");
|
||||
const glob = require("glob");
|
||||
const inquirer = require("inquirer");
|
||||
const { promisify } = require("util");
|
||||
const globAsync = promisify(glob);
|
||||
|
||||
class V3ToV4Upgrader {
|
||||
@@ -16,17 +16,25 @@ class V3ToV4Upgrader {
|
||||
try {
|
||||
// Keep readline open throughout the process
|
||||
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);
|
||||
@@ -34,11 +42,15 @@ 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;
|
||||
}
|
||||
|
||||
@@ -47,15 +59,17 @@ class V3ToV4Upgrader {
|
||||
await this.showPreflightCheck(analysis, options);
|
||||
|
||||
if (!options.dryRun) {
|
||||
const { confirm } = await inquirer.prompt([{
|
||||
type: 'confirm',
|
||||
name: 'confirm',
|
||||
message: 'Continue with upgrade?',
|
||||
default: true
|
||||
}]);
|
||||
|
||||
const { confirm } = await inquirer.prompt([
|
||||
{
|
||||
type: "confirm",
|
||||
name: "confirm",
|
||||
message: "Continue with upgrade?",
|
||||
default: true,
|
||||
},
|
||||
]);
|
||||
|
||||
if (!confirm) {
|
||||
console.log('Upgrade cancelled.');
|
||||
console.log("Upgrade cancelled.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -85,7 +99,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);
|
||||
}
|
||||
}
|
||||
@@ -95,56 +109,58 @@ class V3ToV4Upgrader {
|
||||
return path.resolve(providedPath);
|
||||
}
|
||||
|
||||
const { projectPath } = await inquirer.prompt([{
|
||||
type: 'input',
|
||||
name: 'projectPath',
|
||||
message: 'Please enter the path to your V3 project:',
|
||||
default: process.cwd()
|
||||
}]);
|
||||
|
||||
const { projectPath } = await inquirer.prompt([
|
||||
{
|
||||
type: "input",
|
||||
name: "projectPath",
|
||||
message: "Please enter the path to your V3 project:",
|
||||
default: process.cwd(),
|
||||
},
|
||||
]);
|
||||
|
||||
return path.resolve(projectPath);
|
||||
}
|
||||
|
||||
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);
|
||||
@@ -155,7 +171,11 @@ 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);
|
||||
@@ -166,7 +186,11 @@ class V3ToV4Upgrader {
|
||||
}
|
||||
|
||||
// Find Front-end Architecture (V3 specific)
|
||||
const frontEndCandidates = ['front-end-architecture.md', 'frontend-architecture.md', 'ui-architecture.md'];
|
||||
const frontEndCandidates = [
|
||||
"front-end-architecture.md",
|
||||
"frontend-architecture.md",
|
||||
"ui-architecture.md",
|
||||
];
|
||||
let frontEndArchFile = null;
|
||||
for (const candidate of frontEndCandidates) {
|
||||
const candidatePath = path.join(docsPath, candidate);
|
||||
@@ -177,19 +201,19 @@ class V3ToV4Upgrader {
|
||||
}
|
||||
|
||||
// Find epic files
|
||||
const epicFiles = await globAsync('epic*.md', { cwd: docsPath });
|
||||
const epicFiles = await globAsync("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 globAsync('*.md', { cwd: storiesPath });
|
||||
storyFiles = await globAsync("*.md", { cwd: storiesPath });
|
||||
}
|
||||
|
||||
// Count custom files in bmad-agent
|
||||
const bmadAgentFiles = await globAsync('**/*.md', {
|
||||
const bmadAgentFiles = await globAsync("**/*.md", {
|
||||
cwd: bmadAgentPath,
|
||||
ignore: ['node_modules/**']
|
||||
ignore: ["node_modules/**"],
|
||||
});
|
||||
|
||||
return {
|
||||
@@ -198,106 +222,152 @@ class V3ToV4Upgrader {
|
||||
frontEndArchFile,
|
||||
epicFiles,
|
||||
storyFiles,
|
||||
customFileCount: bmadAgentFiles.length
|
||||
customFileCount: bmadAgentFiles.length,
|
||||
};
|
||||
}
|
||||
|
||||
async showPreflightCheck(analysis, options) {
|
||||
console.log(chalk.bold('\nProject Analysis:'));
|
||||
console.log(`- PRD found: ${analysis.prdFile ? `docs/${analysis.prdFile}` : chalk.yellow('Not found')}`);
|
||||
console.log(`- Architecture found: ${analysis.archFile ? `docs/${analysis.archFile}` : chalk.yellow('Not found')}`);
|
||||
console.log(chalk.bold("\nProject Analysis:"));
|
||||
console.log(
|
||||
`- PRD found: ${
|
||||
analysis.prdFile
|
||||
? `docs/${analysis.prdFile}`
|
||||
: chalk.yellow("Not found")
|
||||
}`
|
||||
);
|
||||
console.log(
|
||||
`- Architecture 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(`- Epic files found: ${analysis.epicFiles.length} files (epic*.md)`);
|
||||
console.log(`- Stories found: ${analysis.storyFiles.length} files in docs/stories/`);
|
||||
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.'));
|
||||
console.log(chalk.green('Since epic files exist, you won\'t need to shard the PRD after upgrade.'));
|
||||
console.log(
|
||||
chalk.green(
|
||||
"\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."
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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');
|
||||
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'));
|
||||
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
|
||||
const sourcePath = path.join(__dirname, '..', '..', '.bmad-core');
|
||||
const destPath = path.join(projectPath, '.bmad-core');
|
||||
const sourcePath = path.join(__dirname, "..", "..", ".bmad-core");
|
||||
const destPath = 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'));
|
||||
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"));
|
||||
|
||||
console.log(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.'));
|
||||
// Create install manifest for future updates
|
||||
await this.createInstallManifest(projectPath);
|
||||
console.log(chalk.green("✓ Created install manifest"));
|
||||
|
||||
spinner.succeed('V4 structure installed successfully');
|
||||
console.log(
|
||||
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."
|
||||
)
|
||||
);
|
||||
|
||||
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
|
||||
@@ -314,7 +384,9 @@ class V3ToV4Upgrader {
|
||||
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}`));
|
||||
console.log(
|
||||
chalk.green(`✓ Copied Architecture to docs/${analysis.archFile}`)
|
||||
);
|
||||
copiedCount++;
|
||||
}
|
||||
|
||||
@@ -323,28 +395,40 @@ class V3ToV4Upgrader {
|
||||
const src = path.join(backupDocsPath, analysis.frontEndArchFile);
|
||||
const dest = path.join(newDocsPath, analysis.frontEndArchFile);
|
||||
await fs.copyFile(src, dest);
|
||||
console.log(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'));
|
||||
console.log(
|
||||
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"
|
||||
)
|
||||
);
|
||||
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 src = path.join(backupDocsPath, "stories", storyFile);
|
||||
const dest = path.join(storiesDir, storyFile);
|
||||
await fs.copyFile(src, dest);
|
||||
}
|
||||
console.log(chalk.green(`✓ Copied ${analysis.storyFiles.length} story files to docs/stories/`));
|
||||
console.log(
|
||||
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) {
|
||||
@@ -352,92 +436,133 @@ class V3ToV4Upgrader {
|
||||
const dest = path.join(prdDir, epicFile);
|
||||
await fs.copyFile(src, dest);
|
||||
}
|
||||
console.log(chalk.green(`✓ Found and copied ${analysis.epicFiles.length} epic files to docs/prd/`));
|
||||
|
||||
console.log(
|
||||
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('\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.'));
|
||||
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."
|
||||
)
|
||||
);
|
||||
copiedCount += analysis.epicFiles.length;
|
||||
}
|
||||
|
||||
spinner.succeed(`Migrated ${copiedCount} documents successfully`);
|
||||
} catch (error) {
|
||||
spinner.fail('Document migration failed');
|
||||
spinner.fail("Document migration failed");
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async setupIDE(projectPath) {
|
||||
const { ide } = await inquirer.prompt([{
|
||||
type: 'list',
|
||||
name: 'ide',
|
||||
message: 'Which IDE are you using?',
|
||||
choices: [
|
||||
{ name: 'Cursor', value: 'cursor' },
|
||||
{ name: 'Claude Code', value: 'claude-code' },
|
||||
{ name: 'Windsurf', value: 'windsurf' },
|
||||
{ name: 'VS Code', value: 'skip' },
|
||||
{ name: 'Other/Skip', value: 'skip' }
|
||||
]
|
||||
}]);
|
||||
const { ide } = await inquirer.prompt([
|
||||
{
|
||||
type: "list",
|
||||
name: "ide",
|
||||
message: "Which IDE are you using?",
|
||||
choices: [
|
||||
{ name: "Cursor", value: "cursor" },
|
||||
{ name: "Claude Code", value: "claude-code" },
|
||||
{ name: "Windsurf", value: "windsurf" },
|
||||
{ name: "Roo Code", value: "roo" },
|
||||
{ name: "VS Code", value: "skip" },
|
||||
{ name: "Other/Skip", value: "skip" },
|
||||
],
|
||||
},
|
||||
]);
|
||||
|
||||
const selectedIde = ide === 'skip' ? null : ide;
|
||||
const selectedIde = ide === "skip" ? null : ide;
|
||||
|
||||
if (selectedIde) {
|
||||
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 {
|
||||
await ideSetup.setup(selectedIde, projectPath);
|
||||
spinner.succeed('IDE setup complete!');
|
||||
spinner.succeed("IDE setup complete!");
|
||||
|
||||
const ideMessages = {
|
||||
'cursor': 'Rules created in .cursor/rules/',
|
||||
'claude-code': 'Commands created in .claude/commands/',
|
||||
'windsurf': 'Rules created in .windsurf/rules/'
|
||||
cursor: "Rules created in .cursor/rules/",
|
||||
"claude-code": "Commands created in .claude/commands/",
|
||||
windsurf: "Rules created in .windsurf/rules/",
|
||||
roo: "Custom modes created in .roomodes",
|
||||
};
|
||||
|
||||
console.log(chalk.green(`- ${ideMessages[selectedIde]}`));
|
||||
} catch (error) {
|
||||
spinner.fail('IDE setup failed');
|
||||
console.error(chalk.yellow('IDE setup failed, but upgrade is complete.'));
|
||||
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)`);
|
||||
|
||||
const totalDocs = (analysis.prdFile ? 1 : 0) +
|
||||
(analysis.archFile ? 1 : 0) +
|
||||
(analysis.frontEndArchFile ? 1 : 0) +
|
||||
analysis.storyFiles.length;
|
||||
console.log(`- Documents migrated: ${totalDocs} files${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)');
|
||||
const totalDocs =
|
||||
(analysis.prdFile ? 1 : 0) +
|
||||
(analysis.archFile ? 1 : 0) +
|
||||
(analysis.frontEndArchFile ? 1 : 0) +
|
||||
analysis.storyFiles.length;
|
||||
console.log(
|
||||
`- Documents migrated: ${totalDocs} files${
|
||||
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)"
|
||||
);
|
||||
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');
|
||||
console.log(
|
||||
"- Front-end architecture found - V4 uses full-stack-architecture.md, migration needed"
|
||||
);
|
||||
}
|
||||
|
||||
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');
|
||||
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"
|
||||
);
|
||||
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.'));
|
||||
console.log(
|
||||
chalk.dim(
|
||||
"\nYour V3 backup is preserved in .bmad-v3-backup/ and can be restored if needed."
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
async pathExists(filePath) {
|
||||
@@ -466,65 +591,97 @@ class V3ToV4Upgrader {
|
||||
}
|
||||
|
||||
async createPrdIndex(projectPath, analysis) {
|
||||
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';
|
||||
|
||||
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";
|
||||
|
||||
// Try to read the PRD to get the title and intro content
|
||||
if (analysis.prdFile && await this.pathExists(prdPath)) {
|
||||
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) {
|
||||
// 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();
|
||||
|
||||
|
||||
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, "")
|
||||
.replace(/-/g, " ")
|
||||
.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 { promisify } = require("util");
|
||||
const globAsync = promisify(glob);
|
||||
|
||||
// Get all files in .bmad-core for the manifest
|
||||
const bmadCorePath = path.join(projectPath, ".bmad-core");
|
||||
const files = await globAsync("**/*", {
|
||||
cwd: bmadCorePath,
|
||||
nodir: true,
|
||||
ignore: ["**/.git/**", "**/node_modules/**"],
|
||||
});
|
||||
|
||||
// Prepend .bmad-core/ to file paths for manifest
|
||||
const manifestFiles = files.map((file) => path.join(".bmad-core", file));
|
||||
|
||||
const config = {
|
||||
installType: "full",
|
||||
agent: null,
|
||||
ide: null, // Will be set if IDE setup is done later
|
||||
};
|
||||
|
||||
await fileManager.createManifest(projectPath, config, manifestFiles);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = V3ToV4Upgrader;
|
||||
module.exports = V3ToV4Upgrader;
|
||||
|
||||
Reference in New Issue
Block a user