@cloudkinetix/bmad-enhanced
Version:
Cloud-Kinetix enhanced fork of BMAD-METHOD - Breakthrough Method of Agile AI-driven Development with robust versioning and unified validation.
343 lines (281 loc) • 10.9 kB
JavaScript
const fs = require("fs-extra");
const path = require("path");
const yaml = require("js-yaml");
class ConfigLoader {
constructor() {
this.configPath = path.join(__dirname, "..", "config", "install.config.yaml");
this.ckDefaultsPath = path.join(__dirname, "..", "config", "ck-defaults.yaml");
this.config = null;
this.ckDefaults = null;
}
async load() {
if (this.config) return this.config;
try {
const configContent = await fs.readFile(this.configPath, "utf8");
this.config = yaml.load(configContent);
return this.config;
} catch (error) {
throw new Error(`Failed to load configuration: ${error.message}`);
}
}
async getInstallationOptions() {
const config = await this.load();
return config["installation-options"] || {};
}
async getAvailableAgents() {
const config = await this.load();
return config["available-agents"] || [];
}
async getAvailableExpansionPacks() {
const expansionPacksDir = this.getExpansionPacksPath();
try {
const entries = await fs.readdir(expansionPacksDir, { withFileTypes: true });
const expansionPacks = [];
for (const entry of entries) {
if (entry.isDirectory()) {
const manifestPath = path.join(expansionPacksDir, entry.name, "manifest.yaml");
try {
const manifestContent = await fs.readFile(manifestPath, "utf8");
const manifest = yaml.load(manifestContent);
expansionPacks.push({
id: entry.name,
name: manifest.name || entry.name,
description: manifest.description || "No description available",
version: manifest.version || "1.0.0",
author: manifest.author || "Unknown",
manifestPath: manifestPath,
dependencies: manifest.dependencies || [],
});
} catch (error) {
// Skip expansion packs without manifests (they might be incomplete)
if (process.env.VERBOSE) {
console.warn(
`Skipping expansion pack ${entry.name}: ${error.message}`
);
}
}
}
}
return expansionPacks;
} catch (error) {
console.warn(`Failed to read expansion packs directory: ${error.message}`);
return [];
}
}
async getAgentDependencies(agentId) {
// Use DependencyResolver to dynamically parse agent dependencies
const DependencyResolver = require("../lib/dependency-resolver");
const resolver = new DependencyResolver(this.getBmadCorePath());
try {
const agentDeps = await resolver.resolveAgentDependencies(agentId);
// Convert to flat list of file paths
const depPaths = [];
// Core files and utilities are included automatically by DependencyResolver
// Add agent file itself is already handled by installer
// Add all resolved resources
for (const resource of agentDeps.resources) {
const filePath = `.bmad-core/${resource.type}/${resource.id}.md`;
if (!depPaths.includes(filePath)) {
depPaths.push(filePath);
}
}
return depPaths;
} catch (error) {
console.warn(`Failed to dynamically resolve dependencies for ${agentId}: ${error.message}`);
// Fall back to static config
const config = await this.load();
const dependencies = config["agent-dependencies"] || {};
const coreFiles = dependencies["core-files"] || [];
const agentDeps = dependencies[agentId] || [];
return [...coreFiles, ...agentDeps];
}
}
async getIdeConfiguration(ide) {
const config = await this.load();
const ideConfigs = config["ide-configurations"] || {};
return ideConfigs[ide] || null;
}
async getAvailableIDEs() {
const config = await this.load();
const ideConfigs = config["ide-configurations"] || {};
return Object.keys(ideConfigs);
}
getBmadCorePath() {
// Get the path to bmad-core relative to the installer
// Now that lib is in ck-layer/lib, we need to go up one more level
const devPath = path.join(__dirname, "..", "..", "..", "bmad-core");
const npmPath = path.join(__dirname, "..", "..", "bmad-core");
// Check if we're in development environment
const fs = require("fs");
if (fs.existsSync(devPath)) {
return devPath;
}
return npmPath;
}
getExpansionPacksPath() {
// Get the path to expansion-packs relative to the installer
// Now that lib is in ck-layer/lib, we need to go up one more level
const devPath = path.join(__dirname, "..", "..", "..", "expansion-packs");
const npmPath = path.join(__dirname, "..", "..", "expansion-packs");
// Check if we're in development environment
const fs = require("fs");
if (fs.existsSync(devPath)) {
return devPath;
}
return npmPath;
}
getDistPath() {
// Get the path to dist directory relative to the installer
// Now that lib is in ck-layer/lib, we need to go up appropriate levels
return path.join(__dirname, "..", "..", "..", "dist");
}
getAgentPath(agentId) {
return path.join(this.getBmadCorePath(), "agents", `${agentId}.md`);
}
async getAvailableTeams() {
const teamsDir = path.join(this.getBmadCorePath(), "agent-teams");
try {
const entries = await fs.readdir(teamsDir, { withFileTypes: true });
const teams = [];
for (const entry of entries) {
if (entry.isFile() && entry.name.startsWith("team-") && entry.name.endsWith(".yaml")) {
const teamPath = path.join(teamsDir, entry.name);
try {
const teamContent = await fs.readFile(teamPath, "utf8");
const teamConfig = yaml.load(teamContent);
if (teamConfig.bundle) {
teams.push({
id: path.basename(entry.name, ".yaml").replace("team-", ""),
name: teamConfig.bundle.name || entry.name,
description: teamConfig.bundle.description || "Team configuration",
icon: teamConfig.bundle.icon || "📋",
});
}
} catch (error) {
console.warn(`Warning: Could not load team config ${entry.name}: ${error.message}`);
}
}
}
return teams;
} catch (error) {
console.warn(`Warning: Could not scan teams directory: ${error.message}`);
return [];
}
}
getTeamPath(teamId) {
return path.join(this.getBmadCorePath(), "agent-teams", `team-${teamId}.yaml`);
}
async getTeamDependencies(teamId) {
// Use DependencyResolver to dynamically parse team dependencies
const DependencyResolver = require("../lib/dependency-resolver");
const resolver = new DependencyResolver(this.getBmadCorePath());
try {
const teamDeps = await resolver.resolveTeamDependencies(teamId);
// Convert to flat list of file paths
const depPaths = [];
// Add team config file - resolver already handles the team- prefix
const teamFileName = teamId.startsWith("team-") ? teamId : `team-${teamId}`;
depPaths.push(path.join(".bmad-core", "agent-teams", `${teamFileName}.yaml`));
// Add all agents
for (const agent of teamDeps.agents) {
const filePath = path.join(".bmad-core", "agents", `${agent.id}.md`);
if (!depPaths.includes(filePath)) {
depPaths.push(filePath);
}
}
// Add all resolved resources
for (const resource of teamDeps.resources) {
const filePath = path.join(
".bmad-core",
resource.type,
`${resource.id}.${resource.type === "workflows" ? "yml" : "md"}`
);
if (!depPaths.includes(filePath)) {
depPaths.push(filePath);
}
}
return depPaths;
} catch (error) {
throw new Error(`Failed to resolve team dependencies for ${teamId}: ${error.message}`);
}
}
async loadCKDefaults() {
if (this.ckDefaults) return this.ckDefaults;
try {
const defaultsContent = await fs.readFile(this.ckDefaultsPath, "utf8");
this.ckDefaults = yaml.load(defaultsContent);
return this.ckDefaults;
} catch (error) {
// If defaults file doesn't exist, return sensible defaults
console.warn(`Could not load CK defaults, using built-in defaults: ${error.message}`);
this.ckDefaults = {
"ides": [], // Empty = install all
"expansion-packs": [], // Empty = install all
"excluded-ides": [] // None excluded by default
};
return this.ckDefaults;
}
}
async getDefaultIDEs() {
const defaults = await this.loadCKDefaults();
// New format with default-settings
if (defaults["default-settings"]) {
return defaults["default-settings"]["ides"] || [];
}
// Old format for backward compatibility
return defaults["ides"] || [];
}
async getDefaultExpansionPacks() {
const defaults = await this.loadCKDefaults();
// New format with default-settings
if (defaults["default-settings"]) {
return defaults["default-settings"]["expansion-packs"] || [];
}
// Old format for backward compatibility
return defaults["expansion-packs"] || [];
}
async getExcludedIDEs() {
const defaults = await this.loadCKDefaults();
// New format with default-settings
if (defaults["default-settings"]) {
return defaults["default-settings"]["excluded-ides"] || [];
}
// Old format for backward compatibility
return defaults["excluded-ides"] || [];
}
async getSmartFeatureDetection() {
// Smart feature detection based on expansion pack keywords
return {
'jira': ['jira-sync'],
'gitlab': ['gitlab-integration'],
'cicd': ['cicd-automation'],
'ai-agent': ['ai-agent-tools'],
'parallel': ['parallel-development']
};
}
async getInstallationProfile(profileName) {
const defaults = await this.loadCKDefaults();
// Check if profiles section exists
if (defaults.profiles && defaults.profiles[profileName]) {
const profile = defaults.profiles[profileName];
// Normalize field names to match expected format
return {
name: profile.name,
description: profile.description,
ides: profile.ides,
expansionPacks: profile["expansion-packs"] || profile.expansionPacks || [],
excludedIdes: profile["excluded-ides"] || profile.excludedIdes || []
};
}
return null;
}
async getAvailableProfiles() {
const defaults = await this.loadCKDefaults();
return defaults.profiles ? Object.keys(defaults.profiles) : [];
}
async getDefaultProfileName() {
const defaults = await this.loadCKDefaults();
return defaults.metadata?.["default-profile"] || null;
}
}
module.exports = new ConfigLoader();