702 lines
27 KiB
JavaScript
702 lines
27 KiB
JavaScript
const fs = require("node:fs").promises;
|
|
const path = require("node:path");
|
|
const DependencyResolver = require("../lib/dependency-resolver");
|
|
const yamlUtils = require("../lib/yaml-utils");
|
|
|
|
class WebBuilder {
|
|
constructor(options = {}) {
|
|
this.rootDir = options.rootDir || process.cwd();
|
|
this.outputDirs = options.outputDirs || [path.join(this.rootDir, "dist")];
|
|
this.resolver = new DependencyResolver(this.rootDir);
|
|
this.templatePath = path.join(
|
|
this.rootDir,
|
|
"tools",
|
|
"md-assets",
|
|
"web-agent-startup-instructions.md"
|
|
);
|
|
}
|
|
|
|
parseYaml(content) {
|
|
const yaml = require("js-yaml");
|
|
return yaml.load(content);
|
|
}
|
|
|
|
convertToWebPath(filePath, bundleRoot = 'bmad-core') {
|
|
// Convert absolute paths to web bundle paths with dot prefix
|
|
// All resources get installed under the bundle root, so use that path
|
|
const relativePath = path.relative(this.rootDir, filePath);
|
|
const pathParts = relativePath.split(path.sep);
|
|
|
|
let resourcePath;
|
|
if (pathParts[0] === 'expansion-packs') {
|
|
// For expansion packs, remove 'expansion-packs/packname' and use the rest
|
|
resourcePath = pathParts.slice(2).join('/');
|
|
} else {
|
|
// For bmad-core, common, etc., remove the first part
|
|
resourcePath = pathParts.slice(1).join('/');
|
|
}
|
|
|
|
return `.${bundleRoot}/${resourcePath}`;
|
|
}
|
|
|
|
generateWebInstructions(bundleType, packName = null) {
|
|
// Generate dynamic web instructions based on bundle type
|
|
const rootExample = packName ? `.${packName}` : '.bmad-core';
|
|
const examplePath = packName ? `.${packName}/folder/filename.md` : '.bmad-core/folder/filename.md';
|
|
const personasExample = packName ? `.${packName}/personas/analyst.md` : '.bmad-core/personas/analyst.md';
|
|
const tasksExample = packName ? `.${packName}/tasks/create-story.md` : '.bmad-core/tasks/create-story.md';
|
|
const utilsExample = packName ? `.${packName}/utils/template-format.md` : '.bmad-core/utils/template-format.md';
|
|
const tasksRef = packName ? `.${packName}/tasks/create-story.md` : '.bmad-core/tasks/create-story.md';
|
|
|
|
return `# Web Agent Bundle Instructions
|
|
|
|
You are now operating as a specialized AI agent from the BMad-Method framework. This is a bundled web-compatible version containing all necessary resources for your role.
|
|
|
|
## Important Instructions
|
|
|
|
1. **Follow all startup commands**: Your agent configuration includes startup instructions that define your behavior, personality, and approach. These MUST be followed exactly.
|
|
|
|
2. **Resource Navigation**: This bundle contains all resources you need. Resources are marked with tags like:
|
|
|
|
- \`==================== START: ${examplePath} ====================\`
|
|
- \`==================== END: ${examplePath} ====================\`
|
|
|
|
When you need to reference a resource mentioned in your instructions:
|
|
|
|
- Look for the corresponding START/END tags
|
|
- The format is always the full path with dot prefix (e.g., \`${personasExample}\`, \`${tasksExample}\`)
|
|
- If a section is specified (e.g., \`{root}/tasks/create-story.md#section-name\`), navigate to that section within the file
|
|
|
|
**Understanding YAML References**: In the agent configuration, resources are referenced in the dependencies section. For example:
|
|
|
|
\`\`\`yaml
|
|
dependencies:
|
|
utils:
|
|
- template-format
|
|
tasks:
|
|
- create-story
|
|
\`\`\`
|
|
|
|
These references map directly to bundle sections:
|
|
|
|
- \`utils: template-format\` → Look for \`==================== START: ${utilsExample} ====================\`
|
|
- \`tasks: create-story\` → Look for \`==================== START: ${tasksRef} ====================\`
|
|
|
|
3. **Execution Context**: You are operating in a web environment. All your capabilities and knowledge are contained within this bundle. Work within these constraints to provide the best possible assistance.
|
|
|
|
4. **Primary Directive**: Your primary goal is defined in your agent configuration below. Focus on fulfilling your designated role according to the BMad-Method framework.
|
|
|
|
---
|
|
|
|
`;
|
|
}
|
|
|
|
async cleanOutputDirs() {
|
|
for (const dir of this.outputDirs) {
|
|
try {
|
|
await fs.rm(dir, { recursive: true, force: true });
|
|
console.log(`Cleaned: ${path.relative(this.rootDir, dir)}`);
|
|
} catch (error) {
|
|
console.debug(`Failed to clean directory ${dir}:`, error.message);
|
|
// Directory might not exist, that's fine
|
|
}
|
|
}
|
|
}
|
|
|
|
async buildAgents() {
|
|
const agents = await this.resolver.listAgents();
|
|
|
|
for (const agentId of agents) {
|
|
console.log(` Building agent: ${agentId}`);
|
|
const bundle = await this.buildAgentBundle(agentId);
|
|
|
|
// Write to all output directories
|
|
for (const outputDir of this.outputDirs) {
|
|
const outputPath = path.join(outputDir, "agents");
|
|
await fs.mkdir(outputPath, { recursive: true });
|
|
const outputFile = path.join(outputPath, `${agentId}.txt`);
|
|
await fs.writeFile(outputFile, bundle, "utf8");
|
|
}
|
|
}
|
|
|
|
console.log(`Built ${agents.length} agent bundles in ${this.outputDirs.length} locations`);
|
|
}
|
|
|
|
async buildTeams() {
|
|
const teams = await this.resolver.listTeams();
|
|
|
|
for (const teamId of teams) {
|
|
console.log(` Building team: ${teamId}`);
|
|
const bundle = await this.buildTeamBundle(teamId);
|
|
|
|
// Write to all output directories
|
|
for (const outputDir of this.outputDirs) {
|
|
const outputPath = path.join(outputDir, "teams");
|
|
await fs.mkdir(outputPath, { recursive: true });
|
|
const outputFile = path.join(outputPath, `${teamId}.txt`);
|
|
await fs.writeFile(outputFile, bundle, "utf8");
|
|
}
|
|
}
|
|
|
|
console.log(`Built ${teams.length} team bundles in ${this.outputDirs.length} locations`);
|
|
}
|
|
|
|
async buildAgentBundle(agentId) {
|
|
const dependencies = await this.resolver.resolveAgentDependencies(agentId);
|
|
const template = this.generateWebInstructions('agent');
|
|
|
|
const sections = [template];
|
|
|
|
// Add agent configuration
|
|
const agentPath = this.convertToWebPath(dependencies.agent.path, 'bmad-core');
|
|
sections.push(this.formatSection(agentPath, dependencies.agent.content, 'bmad-core'));
|
|
|
|
// Add all dependencies
|
|
for (const resource of dependencies.resources) {
|
|
const resourcePath = this.convertToWebPath(resource.path, 'bmad-core');
|
|
sections.push(this.formatSection(resourcePath, resource.content, 'bmad-core'));
|
|
}
|
|
|
|
return sections.join("\n");
|
|
}
|
|
|
|
async buildTeamBundle(teamId) {
|
|
const dependencies = await this.resolver.resolveTeamDependencies(teamId);
|
|
const template = this.generateWebInstructions('team');
|
|
|
|
const sections = [template];
|
|
|
|
// Add team configuration
|
|
const teamPath = this.convertToWebPath(dependencies.team.path, 'bmad-core');
|
|
sections.push(this.formatSection(teamPath, dependencies.team.content, 'bmad-core'));
|
|
|
|
// Add all agents
|
|
for (const agent of dependencies.agents) {
|
|
const agentPath = this.convertToWebPath(agent.path, 'bmad-core');
|
|
sections.push(this.formatSection(agentPath, agent.content, 'bmad-core'));
|
|
}
|
|
|
|
// Add all deduplicated resources
|
|
for (const resource of dependencies.resources) {
|
|
const resourcePath = this.convertToWebPath(resource.path, 'bmad-core');
|
|
sections.push(this.formatSection(resourcePath, resource.content, 'bmad-core'));
|
|
}
|
|
|
|
return sections.join("\n");
|
|
}
|
|
|
|
processAgentContent(content) {
|
|
// First, replace content before YAML with the template
|
|
const yamlContent = yamlUtils.extractYamlFromAgent(content);
|
|
if (!yamlContent) return content;
|
|
|
|
const yamlMatch = content.match(/```ya?ml\n([\s\S]*?)\n```/);
|
|
if (!yamlMatch) return content;
|
|
|
|
const yamlStartIndex = content.indexOf(yamlMatch[0]);
|
|
const yamlEndIndex = yamlStartIndex + yamlMatch[0].length;
|
|
|
|
// Parse YAML and remove root and IDE-FILE-RESOLUTION properties
|
|
try {
|
|
const yaml = require("js-yaml");
|
|
const parsed = yaml.load(yamlContent);
|
|
|
|
// Remove the properties if they exist at root level
|
|
delete parsed.root;
|
|
delete parsed["IDE-FILE-RESOLUTION"];
|
|
delete parsed["REQUEST-RESOLUTION"];
|
|
|
|
// Also remove from activation-instructions if they exist
|
|
if (parsed["activation-instructions"] && Array.isArray(parsed["activation-instructions"])) {
|
|
parsed["activation-instructions"] = parsed["activation-instructions"].filter(
|
|
(instruction) => {
|
|
return (
|
|
!instruction.startsWith("IDE-FILE-RESOLUTION:") &&
|
|
!instruction.startsWith("REQUEST-RESOLUTION:")
|
|
);
|
|
}
|
|
);
|
|
}
|
|
|
|
// Reconstruct the YAML
|
|
const cleanedYaml = yaml.dump(parsed, { lineWidth: -1 });
|
|
|
|
// Get the agent name from the YAML for the header
|
|
const agentName = parsed.agent?.id || "agent";
|
|
|
|
// Build the new content with just the agent header and YAML
|
|
const newHeader = `# ${agentName}\n\nCRITICAL: Read the full YAML, start activation to alter your state of being, follow startup section instructions, stay in this being until told to exit this mode:\n\n`;
|
|
const afterYaml = content.substring(yamlEndIndex);
|
|
|
|
return newHeader + "```yaml\n" + cleanedYaml.trim() + "\n```" + afterYaml;
|
|
} catch (error) {
|
|
console.warn("Failed to process agent YAML:", error.message);
|
|
// If parsing fails, return original content
|
|
return content;
|
|
}
|
|
}
|
|
|
|
formatSection(path, content, bundleRoot = 'bmad-core') {
|
|
const separator = "====================";
|
|
|
|
// Process agent content if this is an agent file
|
|
if (path.includes("/agents/")) {
|
|
content = this.processAgentContent(content);
|
|
}
|
|
|
|
// Replace {root} references with the actual bundle root
|
|
content = this.replaceRootReferences(content, bundleRoot);
|
|
|
|
return [
|
|
`${separator} START: ${path} ${separator}`,
|
|
content.trim(),
|
|
`${separator} END: ${path} ${separator}`,
|
|
"",
|
|
].join("\n");
|
|
}
|
|
|
|
replaceRootReferences(content, bundleRoot) {
|
|
// Replace {root} with the appropriate bundle root path
|
|
return content.replace(/\{root\}/g, `.${bundleRoot}`);
|
|
}
|
|
|
|
async validate() {
|
|
console.log("Validating agent configurations...");
|
|
const agents = await this.resolver.listAgents();
|
|
for (const agentId of agents) {
|
|
try {
|
|
await this.resolver.resolveAgentDependencies(agentId);
|
|
console.log(` ✓ ${agentId}`);
|
|
} catch (error) {
|
|
console.log(` ✗ ${agentId}: ${error.message}`);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
console.log("\nValidating team configurations...");
|
|
const teams = await this.resolver.listTeams();
|
|
for (const teamId of teams) {
|
|
try {
|
|
await this.resolver.resolveTeamDependencies(teamId);
|
|
console.log(` ✓ ${teamId}`);
|
|
} catch (error) {
|
|
console.log(` ✗ ${teamId}: ${error.message}`);
|
|
throw error;
|
|
}
|
|
}
|
|
}
|
|
|
|
async buildAllExpansionPacks(options = {}) {
|
|
const expansionPacks = await this.listExpansionPacks();
|
|
|
|
for (const packName of expansionPacks) {
|
|
console.log(` Building expansion pack: ${packName}`);
|
|
await this.buildExpansionPack(packName, options);
|
|
}
|
|
|
|
console.log(`Built ${expansionPacks.length} expansion pack bundles`);
|
|
}
|
|
|
|
async buildExpansionPack(packName, options = {}) {
|
|
const packDir = path.join(this.rootDir, "expansion-packs", packName);
|
|
const outputDirs = [path.join(this.rootDir, "dist", "expansion-packs", packName)];
|
|
|
|
// Clean output directories if requested
|
|
if (options.clean !== false) {
|
|
for (const outputDir of outputDirs) {
|
|
try {
|
|
await fs.rm(outputDir, { recursive: true, force: true });
|
|
} catch (error) {
|
|
// Directory might not exist, that's fine
|
|
}
|
|
}
|
|
}
|
|
|
|
// Build individual agents first
|
|
const agentsDir = path.join(packDir, "agents");
|
|
try {
|
|
const agentFiles = await fs.readdir(agentsDir);
|
|
const agentMarkdownFiles = agentFiles.filter((f) => f.endsWith(".md"));
|
|
|
|
if (agentMarkdownFiles.length > 0) {
|
|
console.log(` Building individual agents for ${packName}:`);
|
|
|
|
for (const agentFile of agentMarkdownFiles) {
|
|
const agentName = agentFile.replace(".md", "");
|
|
console.log(` - ${agentName}`);
|
|
|
|
// Build individual agent bundle
|
|
const bundle = await this.buildExpansionAgentBundle(packName, packDir, agentName);
|
|
|
|
// Write to all output directories
|
|
for (const outputDir of outputDirs) {
|
|
const agentsOutputDir = path.join(outputDir, "agents");
|
|
await fs.mkdir(agentsOutputDir, { recursive: true });
|
|
const outputFile = path.join(agentsOutputDir, `${agentName}.txt`);
|
|
await fs.writeFile(outputFile, bundle, "utf8");
|
|
}
|
|
}
|
|
}
|
|
} catch (error) {
|
|
console.debug(` No agents directory found for ${packName}`);
|
|
}
|
|
|
|
// Build team bundle
|
|
const agentTeamsDir = path.join(packDir, "agent-teams");
|
|
try {
|
|
const teamFiles = await fs.readdir(agentTeamsDir);
|
|
const teamFile = teamFiles.find((f) => f.endsWith(".yaml"));
|
|
|
|
if (teamFile) {
|
|
console.log(` Building team bundle for ${packName}`);
|
|
const teamConfigPath = path.join(agentTeamsDir, teamFile);
|
|
|
|
// Build expansion pack as a team bundle
|
|
const bundle = await this.buildExpansionTeamBundle(packName, packDir, teamConfigPath);
|
|
|
|
// Write to all output directories
|
|
for (const outputDir of outputDirs) {
|
|
const teamsOutputDir = path.join(outputDir, "teams");
|
|
await fs.mkdir(teamsOutputDir, { recursive: true });
|
|
const outputFile = path.join(teamsOutputDir, teamFile.replace(".yaml", ".txt"));
|
|
await fs.writeFile(outputFile, bundle, "utf8");
|
|
console.log(` ✓ Created bundle: ${path.relative(this.rootDir, outputFile)}`);
|
|
}
|
|
} else {
|
|
console.warn(` ⚠ No team configuration found in ${packName}/agent-teams/`);
|
|
}
|
|
} catch (error) {
|
|
console.warn(` ⚠ No agent-teams directory found for ${packName}`);
|
|
}
|
|
}
|
|
|
|
async buildExpansionAgentBundle(packName, packDir, agentName) {
|
|
const template = this.generateWebInstructions('expansion-agent', packName);
|
|
const sections = [template];
|
|
|
|
// Add agent configuration
|
|
const agentPath = path.join(packDir, "agents", `${agentName}.md`);
|
|
const agentContent = await fs.readFile(agentPath, "utf8");
|
|
const agentWebPath = this.convertToWebPath(agentPath, packName);
|
|
sections.push(this.formatSection(agentWebPath, agentContent, packName));
|
|
|
|
// Resolve and add agent dependencies
|
|
const yamlContent = yamlUtils.extractYamlFromAgent(agentContent);
|
|
if (yamlContent) {
|
|
try {
|
|
const yaml = require("js-yaml");
|
|
const agentConfig = yaml.load(yamlContent);
|
|
|
|
if (agentConfig.dependencies) {
|
|
// Add resources, first try expansion pack, then core
|
|
for (const [resourceType, resources] of Object.entries(agentConfig.dependencies)) {
|
|
if (Array.isArray(resources)) {
|
|
for (const resourceName of resources) {
|
|
let found = false;
|
|
const extensions = [".md", ".yaml"];
|
|
|
|
// Try expansion pack first
|
|
for (const ext of extensions) {
|
|
const resourcePath = path.join(packDir, resourceType, `${resourceName}${ext}`);
|
|
try {
|
|
const resourceContent = await fs.readFile(resourcePath, "utf8");
|
|
const resourceWebPath = this.convertToWebPath(resourcePath, packName);
|
|
sections.push(
|
|
this.formatSection(resourceWebPath, resourceContent, packName)
|
|
);
|
|
found = true;
|
|
break;
|
|
} catch (error) {
|
|
// Not in expansion pack, continue
|
|
}
|
|
}
|
|
|
|
// If not found in expansion pack, try core
|
|
if (!found) {
|
|
for (const ext of extensions) {
|
|
const corePath = path.join(
|
|
this.rootDir,
|
|
"bmad-core",
|
|
resourceType,
|
|
`${resourceName}${ext}`
|
|
);
|
|
try {
|
|
const coreContent = await fs.readFile(corePath, "utf8");
|
|
const coreWebPath = this.convertToWebPath(corePath, packName);
|
|
sections.push(
|
|
this.formatSection(coreWebPath, coreContent, packName)
|
|
);
|
|
found = true;
|
|
break;
|
|
} catch (error) {
|
|
// Not in core either, continue
|
|
}
|
|
}
|
|
}
|
|
|
|
// If not found in core, try common folder
|
|
if (!found) {
|
|
for (const ext of extensions) {
|
|
const commonPath = path.join(
|
|
this.rootDir,
|
|
"common",
|
|
resourceType,
|
|
`${resourceName}${ext}`
|
|
);
|
|
try {
|
|
const commonContent = await fs.readFile(commonPath, "utf8");
|
|
const commonWebPath = this.convertToWebPath(commonPath, packName);
|
|
sections.push(
|
|
this.formatSection(commonWebPath, commonContent, packName)
|
|
);
|
|
found = true;
|
|
break;
|
|
} catch (error) {
|
|
// Not in common either, continue
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!found) {
|
|
console.warn(
|
|
` ⚠ Dependency ${resourceType}#${resourceName} not found in expansion pack or core`
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} catch (error) {
|
|
console.debug(`Failed to parse agent YAML for ${agentName}:`, error.message);
|
|
}
|
|
}
|
|
|
|
return sections.join("\n");
|
|
}
|
|
|
|
async buildExpansionTeamBundle(packName, packDir, teamConfigPath) {
|
|
const template = this.generateWebInstructions('expansion-team', packName);
|
|
|
|
const sections = [template];
|
|
|
|
// Add team configuration and parse to get agent list
|
|
const teamContent = await fs.readFile(teamConfigPath, "utf8");
|
|
const teamFileName = path.basename(teamConfigPath, ".yaml");
|
|
const teamConfig = this.parseYaml(teamContent);
|
|
const teamWebPath = this.convertToWebPath(teamConfigPath, packName);
|
|
sections.push(this.formatSection(teamWebPath, teamContent, packName));
|
|
|
|
// Get list of expansion pack agents
|
|
const expansionAgents = new Set();
|
|
const agentsDir = path.join(packDir, "agents");
|
|
try {
|
|
const agentFiles = await fs.readdir(agentsDir);
|
|
for (const agentFile of agentFiles.filter((f) => f.endsWith(".md"))) {
|
|
const agentName = agentFile.replace(".md", "");
|
|
expansionAgents.add(agentName);
|
|
}
|
|
} catch (error) {
|
|
console.warn(` ⚠ No agents directory found in ${packName}`);
|
|
}
|
|
|
|
// Build a map of all available expansion pack resources for override checking
|
|
const expansionResources = new Map();
|
|
const resourceDirs = ["templates", "tasks", "checklists", "workflows", "data"];
|
|
for (const resourceDir of resourceDirs) {
|
|
const resourcePath = path.join(packDir, resourceDir);
|
|
try {
|
|
const resourceFiles = await fs.readdir(resourcePath);
|
|
for (const resourceFile of resourceFiles.filter(
|
|
(f) => f.endsWith(".md") || f.endsWith(".yaml")
|
|
)) {
|
|
const fileName = resourceFile.replace(/\.(md|yaml)$/, "");
|
|
expansionResources.set(`${resourceDir}#${fileName}`, true);
|
|
}
|
|
} catch (error) {
|
|
// Directory might not exist, that's fine
|
|
}
|
|
}
|
|
|
|
// Process all agents listed in team configuration
|
|
const agentsToProcess = teamConfig.agents || [];
|
|
|
|
// Ensure bmad-orchestrator is always included for teams
|
|
if (!agentsToProcess.includes("bmad-orchestrator")) {
|
|
console.warn(` ⚠ Team ${teamFileName} missing bmad-orchestrator, adding automatically`);
|
|
agentsToProcess.unshift("bmad-orchestrator");
|
|
}
|
|
|
|
// Track all dependencies from all agents (deduplicated)
|
|
const allDependencies = new Map();
|
|
|
|
for (const agentId of agentsToProcess) {
|
|
if (expansionAgents.has(agentId)) {
|
|
// Use expansion pack version (override)
|
|
const agentPath = path.join(agentsDir, `${agentId}.md`);
|
|
const agentContent = await fs.readFile(agentPath, "utf8");
|
|
const expansionAgentWebPath = this.convertToWebPath(agentPath, packName);
|
|
sections.push(this.formatSection(expansionAgentWebPath, agentContent, packName));
|
|
|
|
// Parse and collect dependencies from expansion agent
|
|
const agentYaml = agentContent.match(/```yaml\n([\s\S]*?)\n```/);
|
|
if (agentYaml) {
|
|
try {
|
|
const agentConfig = this.parseYaml(agentYaml[1]);
|
|
if (agentConfig.dependencies) {
|
|
for (const [resourceType, resources] of Object.entries(agentConfig.dependencies)) {
|
|
if (Array.isArray(resources)) {
|
|
for (const resourceName of resources) {
|
|
const key = `${resourceType}#${resourceName}`;
|
|
if (!allDependencies.has(key)) {
|
|
allDependencies.set(key, { type: resourceType, name: resourceName });
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} catch (error) {
|
|
console.debug(`Failed to parse agent YAML for ${agentId}:`, error.message);
|
|
}
|
|
}
|
|
} else {
|
|
// Use core BMad version
|
|
try {
|
|
const coreAgentPath = path.join(this.rootDir, "bmad-core", "agents", `${agentId}.md`);
|
|
const coreAgentContent = await fs.readFile(coreAgentPath, "utf8");
|
|
const coreAgentWebPath = this.convertToWebPath(coreAgentPath, packName);
|
|
sections.push(this.formatSection(coreAgentWebPath, coreAgentContent, packName));
|
|
|
|
// Parse and collect dependencies from core agent
|
|
const yamlContent = yamlUtils.extractYamlFromAgent(coreAgentContent, true);
|
|
if (yamlContent) {
|
|
try {
|
|
const agentConfig = this.parseYaml(yamlContent);
|
|
if (agentConfig.dependencies) {
|
|
for (const [resourceType, resources] of Object.entries(agentConfig.dependencies)) {
|
|
if (Array.isArray(resources)) {
|
|
for (const resourceName of resources) {
|
|
const key = `${resourceType}#${resourceName}`;
|
|
if (!allDependencies.has(key)) {
|
|
allDependencies.set(key, { type: resourceType, name: resourceName });
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} catch (error) {
|
|
console.debug(`Failed to parse agent YAML for ${agentId}:`, error.message);
|
|
}
|
|
}
|
|
} catch (error) {
|
|
console.warn(` ⚠ Agent ${agentId} not found in core or expansion pack`);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Add all collected dependencies from agents
|
|
// Always prefer expansion pack versions if they exist
|
|
for (const [key, dep] of allDependencies) {
|
|
let found = false;
|
|
const extensions = [".md", ".yaml"];
|
|
|
|
// Always check expansion pack first, even if the dependency came from a core agent
|
|
if (expansionResources.has(key)) {
|
|
// We know it exists in expansion pack, find and load it
|
|
for (const ext of extensions) {
|
|
const expansionPath = path.join(packDir, dep.type, `${dep.name}${ext}`);
|
|
try {
|
|
const content = await fs.readFile(expansionPath, "utf8");
|
|
const expansionWebPath = this.convertToWebPath(expansionPath, packName);
|
|
sections.push(this.formatSection(expansionWebPath, content, packName));
|
|
console.log(` ✓ Using expansion override for ${key}`);
|
|
found = true;
|
|
break;
|
|
} catch (error) {
|
|
// Try next extension
|
|
}
|
|
}
|
|
}
|
|
|
|
// If not found in expansion pack (or doesn't exist there), try core
|
|
if (!found) {
|
|
for (const ext of extensions) {
|
|
const corePath = path.join(this.rootDir, "bmad-core", dep.type, `${dep.name}${ext}`);
|
|
try {
|
|
const content = await fs.readFile(corePath, "utf8");
|
|
const coreWebPath = this.convertToWebPath(corePath, packName);
|
|
sections.push(this.formatSection(coreWebPath, content, packName));
|
|
found = true;
|
|
break;
|
|
} catch (error) {
|
|
// Not in core either, continue
|
|
}
|
|
}
|
|
}
|
|
|
|
// If not found in core, try common folder
|
|
if (!found) {
|
|
for (const ext of extensions) {
|
|
const commonPath = path.join(this.rootDir, "common", dep.type, `${dep.name}${ext}`);
|
|
try {
|
|
const content = await fs.readFile(commonPath, "utf8");
|
|
const commonWebPath = this.convertToWebPath(commonPath, packName);
|
|
sections.push(this.formatSection(commonWebPath, content, packName));
|
|
found = true;
|
|
break;
|
|
} catch (error) {
|
|
// Not in common either, continue
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!found) {
|
|
console.warn(` ⚠ Dependency ${key} not found in expansion pack or core`);
|
|
}
|
|
}
|
|
|
|
// Add remaining expansion pack resources not already included as dependencies
|
|
for (const resourceDir of resourceDirs) {
|
|
const resourcePath = path.join(packDir, resourceDir);
|
|
try {
|
|
const resourceFiles = await fs.readdir(resourcePath);
|
|
for (const resourceFile of resourceFiles.filter(
|
|
(f) => f.endsWith(".md") || f.endsWith(".yaml")
|
|
)) {
|
|
const filePath = path.join(resourcePath, resourceFile);
|
|
const fileContent = await fs.readFile(filePath, "utf8");
|
|
const fileName = resourceFile.replace(/\.(md|yaml)$/, "");
|
|
|
|
// Only add if not already included as a dependency
|
|
const resourceKey = `${resourceDir}#${fileName}`;
|
|
if (!allDependencies.has(resourceKey)) {
|
|
const fullResourcePath = path.join(resourcePath, resourceFile);
|
|
const resourceWebPath = this.convertToWebPath(fullResourcePath, packName);
|
|
sections.push(this.formatSection(resourceWebPath, fileContent, packName));
|
|
}
|
|
}
|
|
} catch (error) {
|
|
// Directory might not exist, that's fine
|
|
}
|
|
}
|
|
|
|
return sections.join("\n");
|
|
}
|
|
|
|
async listExpansionPacks() {
|
|
const expansionPacksDir = path.join(this.rootDir, "expansion-packs");
|
|
try {
|
|
const entries = await fs.readdir(expansionPacksDir, { withFileTypes: true });
|
|
return entries.filter((entry) => entry.isDirectory()).map((entry) => entry.name);
|
|
} catch (error) {
|
|
console.warn("No expansion-packs directory found");
|
|
return [];
|
|
}
|
|
}
|
|
|
|
listAgents() {
|
|
return this.resolver.listAgents();
|
|
}
|
|
}
|
|
|
|
module.exports = WebBuilder;
|