massive v4 framework WIP part 1
This commit is contained in:
296
build/lib/bundle-optimizer.js
Normal file
296
build/lib/bundle-optimizer.js
Normal file
@@ -0,0 +1,296 @@
|
||||
/**
|
||||
* BMAD v4 Bundle Optimizer
|
||||
* Optimizes bundles by deduplicating resources and minimizing size
|
||||
*/
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
class BundleOptimizer {
|
||||
constructor(rootPath = process.cwd()) {
|
||||
this.rootPath = rootPath;
|
||||
this.corePath = path.join(rootPath, 'bmad-core');
|
||||
}
|
||||
|
||||
/**
|
||||
* Optimize a bundle by loading and processing resources
|
||||
*/
|
||||
optimizeBundle(bundleConfig, agentDependencies) {
|
||||
const optimizedBundle = {
|
||||
metadata: {
|
||||
name: bundleConfig.name,
|
||||
version: bundleConfig.version,
|
||||
environment: bundleConfig.target_environment,
|
||||
generatedAt: new Date().toISOString(),
|
||||
optimization: bundleConfig.optimize || false
|
||||
},
|
||||
agents: {},
|
||||
resources: {
|
||||
personas: {},
|
||||
tasks: {},
|
||||
templates: {},
|
||||
checklists: {},
|
||||
data: {},
|
||||
utils: {}
|
||||
},
|
||||
sections: [],
|
||||
statistics: {}
|
||||
};
|
||||
|
||||
// Process each agent
|
||||
agentDependencies.agents.forEach(agentDep => {
|
||||
optimizedBundle.agents[agentDep.agent] = {
|
||||
name: agentDep.config.name,
|
||||
id: agentDep.config.id,
|
||||
description: agentDep.config.description,
|
||||
capabilities: agentDep.config.capabilities || [],
|
||||
workflow: agentDep.config.workflow || []
|
||||
};
|
||||
});
|
||||
|
||||
// Load and process resources
|
||||
this.loadResources(optimizedBundle, agentDependencies.bundleResources);
|
||||
|
||||
// Create optimized sections for web output
|
||||
if (bundleConfig.target_environment === 'web') {
|
||||
this.createWebSections(optimizedBundle, bundleConfig);
|
||||
}
|
||||
|
||||
// Calculate statistics
|
||||
optimizedBundle.statistics = this.calculateBundleStats(optimizedBundle, agentDependencies);
|
||||
|
||||
return optimizedBundle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load resources from core directory
|
||||
*/
|
||||
loadResources(bundle, resourceLists) {
|
||||
const resourceTypes = ['tasks', 'templates', 'checklists', 'data', 'utils'];
|
||||
|
||||
resourceTypes.forEach(type => {
|
||||
const resourceDir = path.join(this.corePath, type);
|
||||
|
||||
(resourceLists[type] || []).forEach(resourceName => {
|
||||
const content = this.loadResourceFile(resourceDir, resourceName);
|
||||
if (content) {
|
||||
bundle.resources[type][resourceName] = {
|
||||
name: resourceName,
|
||||
content: content,
|
||||
size: content.length
|
||||
};
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Load personas for agents
|
||||
const personaDir = path.join(this.corePath, 'personas');
|
||||
Object.keys(bundle.agents).forEach(agentId => {
|
||||
const personaContent = this.loadResourceFile(personaDir, agentId);
|
||||
if (personaContent) {
|
||||
bundle.resources.personas[agentId] = {
|
||||
name: agentId,
|
||||
content: personaContent,
|
||||
size: personaContent.length
|
||||
};
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a resource file from disk
|
||||
*/
|
||||
loadResourceFile(dir, name) {
|
||||
const extensions = ['.md', '.yml', '.yaml'];
|
||||
|
||||
for (const ext of extensions) {
|
||||
const filePath = path.join(dir, `${name}${ext}`);
|
||||
if (fs.existsSync(filePath)) {
|
||||
return fs.readFileSync(filePath, 'utf8');
|
||||
}
|
||||
}
|
||||
|
||||
console.warn(`Resource file not found: ${name} in ${dir}`);
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create web-formatted sections with markers
|
||||
*/
|
||||
createWebSections(bundle, bundleConfig) {
|
||||
const sections = [];
|
||||
|
||||
// Create personas section
|
||||
if (Object.keys(bundle.resources.personas).length > 0) {
|
||||
const personasContent = Object.entries(bundle.resources.personas)
|
||||
.map(([id, persona]) =>
|
||||
`==================== START: personas#${id} ====================\n` +
|
||||
persona.content +
|
||||
`\n==================== END: personas#${id} ====================`
|
||||
).join('\n\n');
|
||||
|
||||
sections.push({
|
||||
name: 'personas',
|
||||
filename: 'personas.txt',
|
||||
content: personasContent,
|
||||
size: personasContent.length
|
||||
});
|
||||
}
|
||||
|
||||
// Create other resource sections
|
||||
['tasks', 'templates', 'checklists', 'data', 'utils'].forEach(type => {
|
||||
const resources = bundle.resources[type];
|
||||
if (Object.keys(resources).length > 0) {
|
||||
const sectionContent = Object.entries(resources)
|
||||
.map(([name, resource]) =>
|
||||
`==================== START: ${type}#${name} ====================\n` +
|
||||
resource.content +
|
||||
`\n==================== END: ${type}#${name} ====================`
|
||||
).join('\n\n');
|
||||
|
||||
sections.push({
|
||||
name: type,
|
||||
filename: `${type}.txt`,
|
||||
content: sectionContent,
|
||||
size: sectionContent.length
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
bundle.sections = sections;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create standalone agent bundle
|
||||
*/
|
||||
createStandaloneAgent(agentId, environment = 'web') {
|
||||
const DependencyResolver = require('./dependency-resolver');
|
||||
const resolver = new DependencyResolver(this.rootPath);
|
||||
|
||||
const agentDep = resolver.resolveAgentDependencies(agentId, environment);
|
||||
const bundleConfig = {
|
||||
name: `${agentDep.config.name} Standalone`,
|
||||
version: agentDep.config.version,
|
||||
target_environment: environment,
|
||||
optimize: true
|
||||
};
|
||||
|
||||
// Create bundle with just this agent
|
||||
const agentDependencies = {
|
||||
agents: [agentDep],
|
||||
bundleResources: agentDep.resources
|
||||
};
|
||||
|
||||
const optimizedBundle = this.optimizeBundle(bundleConfig, agentDependencies);
|
||||
|
||||
// For standalone agents, create a single combined content
|
||||
if (environment === 'web') {
|
||||
optimizedBundle.standaloneContent = this.createStandaloneContent(optimizedBundle, agentId);
|
||||
}
|
||||
|
||||
return optimizedBundle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create single-file content for standalone agent
|
||||
*/
|
||||
createStandaloneContent(bundle, agentId) {
|
||||
const agent = bundle.agents[agentId];
|
||||
const persona = bundle.resources.personas[agentId];
|
||||
|
||||
let content = `# ${agent.name}\n\n`;
|
||||
content += `${agent.description}\n\n`;
|
||||
|
||||
if (persona) {
|
||||
content += `## Agent Persona\n\n${persona.content}\n\n`;
|
||||
}
|
||||
|
||||
// Add required resources inline
|
||||
const resourceTypes = ['tasks', 'templates', 'checklists', 'data', 'utils'];
|
||||
resourceTypes.forEach(type => {
|
||||
const resources = bundle.resources[type];
|
||||
if (Object.keys(resources).length > 0) {
|
||||
content += `## ${type.charAt(0).toUpperCase() + type.slice(1)}\n\n`;
|
||||
Object.entries(resources).forEach(([name, resource]) => {
|
||||
content += `### ${name}\n\n${resource.content}\n\n`;
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate bundle statistics
|
||||
*/
|
||||
calculateBundleStats(bundle, agentDependencies) {
|
||||
const stats = {
|
||||
agents: Object.keys(bundle.agents).length,
|
||||
totalResources: 0,
|
||||
resourcesByType: {},
|
||||
totalSize: 0,
|
||||
sizeByType: {},
|
||||
averageResourceSize: 0
|
||||
};
|
||||
|
||||
// Count resources and calculate sizes
|
||||
Object.entries(bundle.resources).forEach(([type, resources]) => {
|
||||
const count = Object.keys(resources).length;
|
||||
stats.resourcesByType[type] = count;
|
||||
stats.totalResources += count;
|
||||
|
||||
const typeSize = Object.values(resources).reduce((sum, r) => sum + (r.size || 0), 0);
|
||||
stats.sizeByType[type] = typeSize;
|
||||
stats.totalSize += typeSize;
|
||||
});
|
||||
|
||||
if (stats.totalResources > 0) {
|
||||
stats.averageResourceSize = Math.round(stats.totalSize / stats.totalResources);
|
||||
}
|
||||
|
||||
// Add web-specific stats
|
||||
if (bundle.sections) {
|
||||
stats.webSections = bundle.sections.length;
|
||||
stats.webTotalSize = bundle.sections.reduce((sum, s) => sum + s.size, 0);
|
||||
}
|
||||
|
||||
return stats;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate bundle against size constraints
|
||||
*/
|
||||
validateBundle(bundle, constraints = {}) {
|
||||
const issues = [];
|
||||
const stats = bundle.statistics;
|
||||
|
||||
// Check max bundle size
|
||||
if (constraints.maxBundleSize && stats.totalSize > constraints.maxBundleSize) {
|
||||
issues.push({
|
||||
type: 'size_exceeded',
|
||||
message: `Bundle size ${stats.totalSize} exceeds limit ${constraints.maxBundleSize}`,
|
||||
severity: 'error'
|
||||
});
|
||||
}
|
||||
|
||||
// Check web section sizes
|
||||
if (bundle.sections) {
|
||||
bundle.sections.forEach(section => {
|
||||
if (constraints.maxSectionSize && section.size > constraints.maxSectionSize) {
|
||||
issues.push({
|
||||
type: 'section_size_exceeded',
|
||||
message: `Section ${section.name} size ${section.size} exceeds limit ${constraints.maxSectionSize}`,
|
||||
severity: 'warning'
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
valid: issues.filter(i => i.severity === 'error').length === 0,
|
||||
issues: issues
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = BundleOptimizer;
|
||||
Reference in New Issue
Block a user