doc updates, build folder renamed to tools, readme clarity for v4

This commit is contained in:
Brian Madison
2025-06-08 10:36:23 -05:00
parent 6c4ff90c50
commit aa3d8eba67
25 changed files with 20397 additions and 520 deletions

View File

@@ -0,0 +1,262 @@
/**
* BMAD v4 Dependency Resolver
* Analyzes agent configurations and resolves resource dependencies
*/
const fs = require('fs');
const path = require('path');
const yaml = require('js-yaml');
class DependencyResolver {
constructor(rootPath = process.cwd()) {
this.rootPath = rootPath;
this.agentsPath = path.join(rootPath, 'agents');
this.corePath = path.join(rootPath, 'bmad-core');
this.cache = new Map();
}
/**
* Load and parse an agent configuration
*/
loadAgentConfig(agentId) {
if (this.cache.has(agentId)) {
return this.cache.get(agentId);
}
const configPath = path.join(this.agentsPath, `${agentId}.yml`);
if (!fs.existsSync(configPath)) {
throw new Error(`Agent configuration not found: ${configPath}`);
}
const configContent = fs.readFileSync(configPath, 'utf8');
const rawConfig = yaml.load(configContent);
// Extract agent config from nested structure if present
const config = rawConfig.agent || rawConfig;
// Merge other root-level fields that might be needed
if (rawConfig.dependencies) {
config.dependencies = rawConfig.dependencies;
}
// Validate required fields
this.validateAgentConfig(config, agentId);
this.cache.set(agentId, config);
return config;
}
/**
* Validate agent configuration structure
*/
validateAgentConfig(config, agentId) {
const required = ['name', 'id'];
for (const field of required) {
if (!config[field]) {
throw new Error(`Missing required field '${field}' in agent ${agentId}`);
}
}
if (config.id !== agentId) {
throw new Error(`Agent ID mismatch: expected '${agentId}', got '${config.id}'`);
}
// Ensure persona exists
if (!config.persona) {
// Default to agent id if no persona specified
config.persona = config.id;
}
// Ensure dependencies exist with defaults
if (!config.dependencies) {
config.dependencies = {
tasks: [],
templates: [],
checklists: [],
data: []
};
}
}
/**
* Resolve dependencies for a single agent
*/
resolveAgentDependencies(agentId, environment = 'web') {
const config = this.loadAgentConfig(agentId);
const dependencies = {
agent: agentId,
config: config,
resources: {
tasks: config.dependencies?.tasks || [],
templates: config.dependencies?.templates || [],
checklists: config.dependencies?.checklists || [],
data: config.dependencies?.data || [],
utils: config.dependencies?.utils || []
}
};
// Validate that all required resources exist
this.validateResourceExistence(dependencies.resources);
return dependencies;
}
/**
* Resolve dependencies for multiple agents (bundle)
*/
resolveBundleDependencies(agentIds, environment = 'web', optimize = true) {
const agentDependencies = [];
const allResources = {
tasks: new Set(),
templates: new Set(),
checklists: new Set(),
data: new Set(),
utils: new Set()
};
// Collect dependencies for each agent
for (const agentId of agentIds) {
const deps = this.resolveAgentDependencies(agentId, environment);
agentDependencies.push(deps);
// Aggregate all resources
Object.keys(allResources).forEach(type => {
deps.resources[type].forEach(resource => {
allResources[type].add(resource);
});
});
}
const result = {
agents: agentDependencies,
bundleResources: {
tasks: Array.from(allResources.tasks),
templates: Array.from(allResources.templates),
checklists: Array.from(allResources.checklists),
data: Array.from(allResources.data),
utils: Array.from(allResources.utils)
},
optimized: optimize
};
if (optimize) {
result.statistics = this.calculateOptimizationStats(agentDependencies, result.bundleResources);
}
return result;
}
/**
* Calculate optimization statistics
*/
calculateOptimizationStats(agentDeps, bundleResources) {
const totalAgents = agentDeps.length;
const totalResources = Object.values(bundleResources).reduce((sum, arr) => sum + arr.length, 0);
// Calculate how many resources would be needed without optimization
const unoptimizedTotal = agentDeps.reduce((sum, agent) => {
return sum + Object.values(agent.resources).reduce((agentSum, arr) => agentSum + arr.length, 0);
}, 0);
const savings = unoptimizedTotal - totalResources;
const savingsPercentage = unoptimizedTotal > 0 ? (savings / unoptimizedTotal * 100).toFixed(1) : 0;
return {
totalAgents,
totalUniqueResources: totalResources,
unoptimizedResourceCount: unoptimizedTotal,
resourcesSaved: savings,
optimizationPercentage: savingsPercentage
};
}
/**
* Validate that all required resources exist in core
*/
validateResourceExistence(resources) {
const resourceTypes = ['tasks', 'templates', 'checklists', 'data', 'utils'];
for (const type of resourceTypes) {
const resourceDir = path.join(this.corePath, type);
for (const resource of resources[type] || []) {
const resourcePath = path.join(resourceDir, `${resource}.md`);
const altPath = path.join(resourceDir, `${resource}.yml`);
if (!fs.existsSync(resourcePath) && !fs.existsSync(altPath)) {
throw new Error(`Resource not found: ${type}/${resource} (checked .md and .yml)`);
}
}
}
}
/**
* Get all available agents
*/
getAvailableAgents() {
if (!fs.existsSync(this.agentsPath)) {
return [];
}
return fs.readdirSync(this.agentsPath)
.filter(file => {
return (file.endsWith('.yml') || file.endsWith('.yaml')) &&
fs.statSync(path.join(this.agentsPath, file)).isFile();
})
.map(file => path.basename(file, path.extname(file)));
}
/**
* Generate dependency graph for visualization
*/
generateDependencyGraph(agentIds = null) {
const agents = agentIds || this.getAvailableAgents();
const graph = {
nodes: [],
edges: []
};
for (const agentId of agents) {
const config = this.loadAgentConfig(agentId);
// Add agent node
graph.nodes.push({
id: agentId,
type: 'agent',
label: config.name,
description: config.description
});
// Add resource nodes and edges
Object.entries(config.requires).forEach(([type, resources]) => {
resources.forEach(resource => {
const resourceId = `${type}:${resource}`;
// Add resource node if not exists
if (!graph.nodes.find(n => n.id === resourceId)) {
graph.nodes.push({
id: resourceId,
type: type,
label: resource,
category: type
});
}
// Add edge
graph.edges.push({
from: agentId,
to: resourceId,
type: 'requires'
});
});
});
}
return graph;
}
}
module.exports = DependencyResolver;