Compare commits

...

6 Commits

Author SHA1 Message Date
manjaroblack
ed4db108df feat: enhance status command to support expansion-only installations and add debug mode 2025-08-19 11:49:07 -05:00
manjaroblack
b00fd55fbf chore: revert version from 4.39.2 to 4.39.1 in package manifests 2025-08-18 20:40:10 -05:00
manjaroblack
28f48af7f0 chore: remove unnecessary whitespace and comments from workflow files 2025-08-18 20:38:25 -05:00
manjaroblack
31c4744ed3 fix: remove unnecessary divider lines in creative writing workflow files 2025-08-18 20:35:30 -05:00
manjaroblack
17e7d14cc2 fix: honor original working directory when running npx installer and searching for task files
(cherry picked from commit 6a5a597fe39bc75379f54bedc1616bfab283dfa1)
2025-08-18 19:34:09 -05:00
github-actions[bot]
7e2780cd3e release: bump to v4.39.2 2025-08-17 16:08:37 +00:00
5 changed files with 144 additions and 68 deletions

View File

@@ -40,6 +40,8 @@
"release:minor": "gh workflow run \"Manual Release\" -f version_bump=minor",
"release:patch": "gh workflow run \"Manual Release\" -f version_bump=patch",
"release:watch": "gh run watch",
"status": "node tools/installer/bin/bmad.js status",
"status:debug": "BMAD_DEBUG=1 node tools/installer/bin/bmad.js status",
"validate": "node tools/cli.js validate",
"version:all": "node tools/bump-all-versions.js",
"version:all:major": "node tools/bump-all-versions.js major",

View File

@@ -26,9 +26,12 @@ if (isNpxExecution) {
}
try {
// Honor the directory where the user invoked npx from
const originalCwd = process.env.INIT_CWD || process.env.PWD || process.cwd();
execSync(`node "${bmadScriptPath}" ${arguments_.join(' ')}`, {
stdio: 'inherit',
cwd: path.dirname(__dirname),
cwd: originalCwd,
env: { ...process.env, BMAD_ORIGINAL_CWD: originalCwd },
});
} catch (error) {
process.exit(error.status || 1);

View File

@@ -208,6 +208,7 @@ async function promptInstallation() {
console.log(chalk.bold.magenta('🚀 Universal AI Agent Framework for Any Domain'));
console.log(chalk.bold.blue(`✨ Installer v${version}\n`));
const originalCwd = process.env.INIT_CWD || process.env.PWD || process.cwd();
const answers = {};
// Ask for installation directory first
@@ -216,7 +217,7 @@ async function promptInstallation() {
type: 'input',
name: 'directory',
message: 'Enter the full path to your project directory where BMad should be installed:',
default: path.resolve('.'),
default: originalCwd,
validate: (input) => {
if (!input.trim()) {
return 'Please enter a valid project path';

View File

@@ -520,15 +520,14 @@ class IdeSetup extends BaseIdeSetup {
}
if (await fileManager.pathExists(tasksDir)) {
const glob = require('glob');
const taskFiles = glob.sync('*.md', { cwd: tasksDir });
const taskFiles = await resourceLocator.findFiles('*.md', { cwd: tasksDir });
allTaskIds.push(...taskFiles.map((file) => path.basename(file, '.md')));
}
// Check common tasks
const commonTasksDir = path.join(installDir, 'common', 'tasks');
if (await fileManager.pathExists(commonTasksDir)) {
const commonTaskFiles = glob.sync('*.md', { cwd: commonTasksDir });
const commonTaskFiles = await resourceLocator.findFiles('*.md', { cwd: commonTasksDir });
allTaskIds.push(...commonTaskFiles.map((file) => path.basename(file, '.md')));
}

View File

@@ -27,7 +27,8 @@ class Installer {
try {
// Store the original CWD where npx was executed
const originalCwd = process.env.INIT_CWD || process.env.PWD || process.cwd();
const originalCwd =
process.env.BMAD_ORIGINAL_CWD || process.env.INIT_CWD || process.env.PWD || process.cwd();
// Resolve installation directory relative to where the user ran the command
let installDir = path.isAbsolute(config.directory)
@@ -1005,7 +1006,8 @@ class Installer {
}
async showStatus() {
const installDir = await this.findInstallation();
// Try to locate an installation root. Include expansion-only installs.
const installDir = await this.findInstallation(true);
if (!installDir) {
console.log(chalk.yellow('No BMad installation found in current directory tree'));
@@ -1013,12 +1015,15 @@ class Installer {
}
const manifest = await fileManager.readManifest(installDir);
const expansionPacks = await this.detectExpansionPacks(installDir);
const hasExpansionPacks = expansionPacks && Object.keys(expansionPacks).length > 0;
if (!manifest) {
if (!manifest && !hasExpansionPacks) {
console.log(chalk.red('Invalid installation - manifest not found'));
return;
}
if (manifest) {
console.log(chalk.bold('\nBMad Installation Status:\n'));
console.log(` Directory: ${installDir}`);
console.log(` Version: ${manifest.version}`);
@@ -1035,13 +1040,36 @@ class Installer {
console.log(` Total Files: ${manifest.files.length}`);
// Check for modifications
// Check for modifications in core
const modifiedFiles = await fileManager.checkModifiedFiles(installDir, manifest);
if (modifiedFiles.length > 0) {
console.log(chalk.yellow(` Modified Files: ${modifiedFiles.length}`));
}
// If there are also expansion packs, list them
if (hasExpansionPacks) {
console.log('\nExpansion Packs:');
for (const [packId, info] of Object.entries(expansionPacks)) {
const version = info.manifest?.version || 'unknown';
console.log(` - ${packId} (${version})`);
}
}
console.log('');
return;
}
// Expansion-only installation (no .bmad-core manifest)
if (hasExpansionPacks) {
console.log(chalk.bold('\nBMad Expansion-only Installation Status:\n'));
console.log(` Directory: ${installDir}`);
console.log(` Expansion Packs: ${Object.keys(expansionPacks).length}`);
for (const [packId, info] of Object.entries(expansionPacks)) {
const version = info.manifest?.version || 'unknown';
console.log(`${packId} (${version})`);
}
console.log('');
return;
}
}
async getAvailableAgents() {
@@ -1792,33 +1820,23 @@ class Installer {
for (const folder of dotFolders) {
const folderPath = path.join(installDir, folder);
const stats = await fileManager.pathExists(folderPath);
const exists = await fileManager.pathExists(folderPath);
if (!exists) continue;
if (stats) {
// Check if it has a manifest
// Only consider folders with an explicit BMad expansion manifest
const manifestPath = path.join(folderPath, 'install-manifest.yaml');
if (await fileManager.pathExists(manifestPath)) {
if (!(await fileManager.pathExists(manifestPath))) continue;
const manifest = await fileManager.readExpansionPackManifest(installDir, folder.slice(1));
if (manifest) {
// Validate manifest to avoid false positives from unrelated dot-folders
if (!manifest || typeof manifest.expansion_pack_id !== 'string') continue;
expansionPacks[folder.slice(1)] = {
path: folderPath,
manifest: manifest,
manifest,
hasManifest: true,
};
}
} else {
// Check if it has a config.yaml (expansion pack without manifest)
const configPath = path.join(folderPath, 'config.yaml');
if (await fileManager.pathExists(configPath)) {
expansionPacks[folder.slice(1)] = {
path: folderPath,
manifest: null,
hasManifest: false,
};
}
}
}
}
return expansionPacks;
}
@@ -1943,27 +1961,80 @@ class Installer {
}
}
async findInstallation() {
// Look for .bmad-core in current directory or parent directories
let currentDir = process.cwd();
async findInstallation(includeExpansionOnly = false) {
// Start from where the user invoked the CLI (npx wrapper sets BMAD_ORIGINAL_CWD)
const startDir =
process.env.BMAD_ORIGINAL_CWD || process.env.INIT_CWD || process.env.PWD || process.cwd();
while (currentDir !== path.dirname(currentDir)) {
const bmadDir = path.join(currentDir, '.bmad-core');
const manifestPath = path.join(bmadDir, 'install-manifest.yaml');
const debug = (message) => {
if (process.env.BMAD_DEBUG) console.log(chalk.dim(`[bmad:findInstallation] ${message}`));
};
if (await fileManager.pathExists(manifestPath)) {
return currentDir; // Return parent directory, not .bmad-core itself
let currentDir = path.resolve(startDir);
const visited = new Set();
while (true) {
if (visited.has(currentDir)) break;
visited.add(currentDir);
debug(`checking: ${currentDir}`);
// Case: we are inside .bmad-core
if (path.basename(currentDir) === '.bmad-core') {
const parentDir = path.dirname(currentDir);
const coreManifest = path.join(parentDir, '.bmad-core', 'install-manifest.yaml');
if (await fileManager.pathExists(coreManifest)) {
debug(`found core manifest (from inside .bmad-core): ${coreManifest}`);
return parentDir;
}
}
currentDir = path.dirname(currentDir);
// Case: we are inside an expansion pack dot-folder
if (includeExpansionOnly) {
const base = path.basename(currentDir);
if (base.startsWith('.') && base !== '.git' && base !== '.bmad-core') {
const packManifest = path.join(currentDir, 'install-manifest.yaml');
if (await fileManager.pathExists(packManifest)) {
const parentDir = path.dirname(currentDir);
// Validate it's a BMad expansion manifest
try {
const manifest = await fileManager.readExpansionPackManifest(
parentDir,
base.slice(1),
);
if (manifest && typeof manifest.expansion_pack_id === 'string') {
debug(`found expansion pack folder at ${currentDir}; root is ${parentDir}`);
return parentDir;
}
} catch {
// ignore invalid manifests
}
}
}
}
// Also check if we're inside a .bmad-core directory
if (path.basename(process.cwd()) === '.bmad-core') {
const manifestPath = path.join(process.cwd(), 'install-manifest.yaml');
if (await fileManager.pathExists(manifestPath)) {
return path.dirname(process.cwd()); // Return parent directory
// Detection: full installation (.bmad-core with manifest)
const coreManifestPath = path.join(currentDir, '.bmad-core', 'install-manifest.yaml');
if (await fileManager.pathExists(coreManifestPath)) {
debug(`found core manifest at ${coreManifestPath}`);
return currentDir;
}
// Detection: expansion-only installation (any expansion dot-folder with manifest/config)
if (includeExpansionOnly) {
try {
const expansions = await this.detectExpansionPacks(currentDir);
if (expansions && Object.keys(expansions).length > 0) {
debug(`found expansion-only installation at ${currentDir}`);
return currentDir;
}
} catch {
// ignore errors during detection and continue ascending
}
}
const parent = path.dirname(currentDir);
if (parent === currentDir) break; // reached filesystem root
currentDir = parent;
}
return null;