2025-06-19 12:55:16 -05:00
const fs = require ( "node:fs" ) . promises ;
const path = require ( "node:path" ) ;
const DependencyResolver = require ( "../lib/dependency-resolver" ) ;
2025-06-10 21:41:58 -05:00
class WebBuilder {
constructor ( options = { } ) {
this . rootDir = options . rootDir || process . cwd ( ) ;
2025-06-19 12:55:16 -05:00
this . outputDirs = options . outputDirs || [ path . join ( this . rootDir , "dist" ) ] ;
2025-06-10 21:41:58 -05:00
this . resolver = new DependencyResolver ( this . rootDir ) ;
2025-06-19 12:55:16 -05:00
this . templatePath = path . join (
this . rootDir ,
"bmad-core" ,
"utils" ,
"web-agent-startup-instructions.md"
) ;
2025-06-10 21:41:58 -05:00
}
2025-06-19 01:07:21 +02:00
parseYaml ( content ) {
2025-06-19 12:55:16 -05:00
const yaml = require ( "js-yaml" ) ;
2025-06-19 01:07:21 +02:00
return yaml . load ( content ) ;
}
2025-06-10 21:41:58 -05:00
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 ) {
2025-06-15 07:59:25 -07:00
console . debug ( ` Failed to clean directory ${ dir } : ` , error . message ) ;
2025-06-10 21:41:58 -05:00
// Directory might not exist, that's fine
}
}
}
async buildAgents ( ) {
const agents = await this . resolver . listAgents ( ) ;
2025-06-15 07:59:25 -07:00
2025-06-10 21:41:58 -05:00
for ( const agentId of agents ) {
console . log ( ` Building agent: ${ agentId } ` ) ;
const bundle = await this . buildAgentBundle ( agentId ) ;
2025-06-15 07:59:25 -07:00
2025-06-10 21:41:58 -05:00
// Write to all output directories
for ( const outputDir of this . outputDirs ) {
2025-06-19 12:55:16 -05:00
const outputPath = path . join ( outputDir , "agents" ) ;
2025-06-10 21:41:58 -05:00
await fs . mkdir ( outputPath , { recursive : true } ) ;
2025-06-11 08:13:36 -05:00
const outputFile = path . join ( outputPath , ` ${ agentId } .txt ` ) ;
2025-06-19 12:55:16 -05:00
await fs . writeFile ( outputFile , bundle , "utf8" ) ;
2025-06-10 21:41:58 -05:00
}
}
console . log ( ` Built ${ agents . length } agent bundles in ${ this . outputDirs . length } locations ` ) ;
}
async buildTeams ( ) {
const teams = await this . resolver . listTeams ( ) ;
2025-06-15 07:59:25 -07:00
2025-06-10 21:41:58 -05:00
for ( const teamId of teams ) {
console . log ( ` Building team: ${ teamId } ` ) ;
const bundle = await this . buildTeamBundle ( teamId ) ;
2025-06-15 07:59:25 -07:00
2025-06-10 21:41:58 -05:00
// Write to all output directories
for ( const outputDir of this . outputDirs ) {
2025-06-19 12:55:16 -05:00
const outputPath = path . join ( outputDir , "teams" ) ;
2025-06-10 21:41:58 -05:00
await fs . mkdir ( outputPath , { recursive : true } ) ;
2025-06-11 08:13:36 -05:00
const outputFile = path . join ( outputPath , ` ${ teamId } .txt ` ) ;
2025-06-19 12:55:16 -05:00
await fs . writeFile ( outputFile , bundle , "utf8" ) ;
2025-06-10 21:41:58 -05:00
}
}
console . log ( ` Built ${ teams . length } team bundles in ${ this . outputDirs . length } locations ` ) ;
}
async buildAgentBundle ( agentId ) {
const dependencies = await this . resolver . resolveAgentDependencies ( agentId ) ;
2025-06-19 12:55:16 -05:00
const template = await fs . readFile ( this . templatePath , "utf8" ) ;
2025-06-15 07:59:25 -07:00
2025-06-10 21:41:58 -05:00
const sections = [ template ] ;
2025-06-15 07:59:25 -07:00
2025-06-10 21:41:58 -05:00
// Add agent configuration
sections . push ( this . formatSection ( dependencies . agent . path , dependencies . agent . content ) ) ;
2025-06-15 07:59:25 -07:00
2025-06-10 21:41:58 -05:00
// Add all dependencies
for ( const resource of dependencies . resources ) {
sections . push ( this . formatSection ( resource . path , resource . content ) ) ;
}
2025-06-15 07:59:25 -07:00
2025-06-19 12:55:16 -05:00
return sections . join ( "\n" ) ;
2025-06-10 21:41:58 -05:00
}
async buildTeamBundle ( teamId ) {
const dependencies = await this . resolver . resolveTeamDependencies ( teamId ) ;
2025-06-19 12:55:16 -05:00
const template = await fs . readFile ( this . templatePath , "utf8" ) ;
2025-06-15 07:59:25 -07:00
2025-06-10 21:41:58 -05:00
const sections = [ template ] ;
2025-06-15 07:59:25 -07:00
2025-06-10 21:41:58 -05:00
// Add team configuration
sections . push ( this . formatSection ( dependencies . team . path , dependencies . team . content ) ) ;
2025-06-15 07:59:25 -07:00
2025-06-10 21:41:58 -05:00
// Add all agents
for ( const agent of dependencies . agents ) {
sections . push ( this . formatSection ( agent . path , agent . content ) ) ;
}
2025-06-15 07:59:25 -07:00
2025-06-10 21:41:58 -05:00
// Add all deduplicated resources
for ( const resource of dependencies . resources ) {
sections . push ( this . formatSection ( resource . path , resource . content ) ) ;
}
2025-06-15 07:59:25 -07:00
2025-06-19 12:55:16 -05:00
return sections . join ( "\n" ) ;
2025-06-10 21:41:58 -05:00
}
2025-06-19 13:21:26 -05:00
processAgentContent ( content ) {
// First, replace content before YAML with the template
const yamlMatch = content . match ( /```ya?ml\n([\s\S]*?)\n```/ ) ;
if ( ! yamlMatch ) return content ;
const yamlContent = yamlMatch [ 1 ] ;
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 \n CRITICAL: Read the full YML, 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 ;
}
}
2025-06-10 21:41:58 -05:00
formatSection ( path , content ) {
2025-06-19 12:55:16 -05:00
const separator = "====================" ;
2025-06-19 13:21:26 -05:00
// Process agent content if this is an agent file
if ( path . startsWith ( "agents#" ) ) {
content = this . processAgentContent ( content ) ;
}
2025-06-10 21:41:58 -05:00
return [
` ${ separator } START: ${ path } ${ separator } ` ,
content . trim ( ) ,
` ${ separator } END: ${ path } ${ separator } ` ,
2025-06-19 12:55:16 -05:00
"" ,
] . join ( "\n" ) ;
2025-06-10 21:41:58 -05:00
}
async validate ( ) {
2025-06-19 12:55:16 -05:00
console . log ( "Validating agent configurations..." ) ;
2025-06-10 21:41:58 -05:00
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 ;
}
}
2025-06-19 12:55:16 -05:00
console . log ( "\nValidating team configurations..." ) ;
2025-06-10 21:41:58 -05:00
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 ;
}
}
}
2025-06-16 18:34:12 -05:00
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 = { } ) {
2025-06-19 12:55:16 -05:00
const packDir = path . join ( this . rootDir , "expansion-packs" , packName ) ;
const outputDirs = [ path . join ( this . rootDir , "dist" , "expansion-packs" , packName ) ] ;
2025-06-16 18:34:12 -05:00
// 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
2025-06-19 12:55:16 -05:00
const agentsDir = path . join ( packDir , "agents" ) ;
2025-06-16 18:34:12 -05:00
try {
const agentFiles = await fs . readdir ( agentsDir ) ;
2025-06-19 12:55:16 -05:00
const agentMarkdownFiles = agentFiles . filter ( ( f ) => f . endsWith ( ".md" ) ) ;
2025-06-16 18:34:12 -05:00
if ( agentMarkdownFiles . length > 0 ) {
console . log ( ` Building individual agents for ${ packName } : ` ) ;
2025-06-19 12:55:16 -05:00
2025-06-16 18:34:12 -05:00
for ( const agentFile of agentMarkdownFiles ) {
2025-06-19 12:55:16 -05:00
const agentName = agentFile . replace ( ".md" , "" ) ;
2025-06-16 18:34:12 -05:00
console . log ( ` - ${ agentName } ` ) ;
2025-06-19 12:55:16 -05:00
2025-06-16 18:34:12 -05:00
// Build individual agent bundle
const bundle = await this . buildExpansionAgentBundle ( packName , packDir , agentName ) ;
2025-06-19 12:55:16 -05:00
2025-06-16 18:34:12 -05:00
// Write to all output directories
for ( const outputDir of outputDirs ) {
2025-06-19 12:55:16 -05:00
const agentsOutputDir = path . join ( outputDir , "agents" ) ;
2025-06-16 18:34:12 -05:00
await fs . mkdir ( agentsOutputDir , { recursive : true } ) ;
const outputFile = path . join ( agentsOutputDir , ` ${ agentName } .txt ` ) ;
2025-06-19 12:55:16 -05:00
await fs . writeFile ( outputFile , bundle , "utf8" ) ;
2025-06-16 18:34:12 -05:00
}
}
}
} catch ( error ) {
console . debug ( ` No agents directory found for ${ packName } ` ) ;
}
// Build team bundle
2025-06-19 12:55:16 -05:00
const agentTeamsDir = path . join ( packDir , "agent-teams" ) ;
2025-06-16 18:34:12 -05:00
try {
const teamFiles = await fs . readdir ( agentTeamsDir ) ;
2025-06-19 12:55:16 -05:00
const teamFile = teamFiles . find ( ( f ) => f . endsWith ( ".yml" ) ) ;
2025-06-16 18:34:12 -05:00
if ( teamFile ) {
console . log ( ` Building team bundle for ${ packName } ` ) ;
const teamConfigPath = path . join ( agentTeamsDir , teamFile ) ;
2025-06-19 12:55:16 -05:00
2025-06-16 18:34:12 -05:00
// Build expansion pack as a team bundle
const bundle = await this . buildExpansionTeamBundle ( packName , packDir , teamConfigPath ) ;
2025-06-19 12:55:16 -05:00
2025-06-16 18:34:12 -05:00
// Write to all output directories
for ( const outputDir of outputDirs ) {
2025-06-19 12:55:16 -05:00
const teamsOutputDir = path . join ( outputDir , "teams" ) ;
2025-06-16 18:34:12 -05:00
await fs . mkdir ( teamsOutputDir , { recursive : true } ) ;
2025-06-19 12:55:16 -05:00
const outputFile = path . join ( teamsOutputDir , teamFile . replace ( ".yml" , ".txt" ) ) ;
await fs . writeFile ( outputFile , bundle , "utf8" ) ;
2025-06-16 18:34:12 -05:00
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 ) {
2025-06-19 12:55:16 -05:00
const template = await fs . readFile ( this . templatePath , "utf8" ) ;
2025-06-16 18:34:12 -05:00
const sections = [ template ] ;
// Add agent configuration
2025-06-19 12:55:16 -05:00
const agentPath = path . join ( packDir , "agents" , ` ${ agentName } .md ` ) ;
const agentContent = await fs . readFile ( agentPath , "utf8" ) ;
2025-06-16 18:34:12 -05:00
sections . push ( this . formatSection ( ` agents# ${ agentName } ` , agentContent ) ) ;
2025-06-18 19:17:09 -05:00
// Resolve and add agent dependencies
2025-06-16 18:34:12 -05:00
const agentYaml = agentContent . match ( /```yaml\n([\s\S]*?)\n```/ ) ;
if ( agentYaml ) {
try {
2025-06-19 12:55:16 -05:00
const yaml = require ( "js-yaml" ) ;
2025-06-16 18:34:12 -05:00
const agentConfig = yaml . load ( agentYaml [ 1 ] ) ;
2025-06-19 12:55:16 -05:00
2025-06-16 18:34:12 -05:00
if ( agentConfig . dependencies ) {
2025-06-18 19:17:09 -05:00
// Add resources, first try expansion pack, then core
2025-06-16 18:34:12 -05:00
for ( const [ resourceType , resources ] of Object . entries ( agentConfig . dependencies ) ) {
if ( Array . isArray ( resources ) ) {
for ( const resourceName of resources ) {
2025-06-18 19:17:09 -05:00
let found = false ;
2025-06-19 12:55:16 -05:00
const extensions = [ ".md" , ".yml" , ".yaml" ] ;
2025-06-18 19:17:09 -05:00
// Try expansion pack first
for ( const ext of extensions ) {
const resourcePath = path . join ( packDir , resourceType , ` ${ resourceName } ${ ext } ` ) ;
try {
2025-06-19 12:55:16 -05:00
const resourceContent = await fs . readFile ( resourcePath , "utf8" ) ;
sections . push (
this . formatSection ( ` ${ resourceType } # ${ resourceName } ` , resourceContent )
) ;
2025-06-18 19:17:09 -05:00
found = true ;
break ;
} catch ( error ) {
// Not in expansion pack, continue
}
}
2025-06-19 12:55:16 -05:00
2025-06-18 19:17:09 -05:00
// If not found in expansion pack, try core
if ( ! found ) {
for ( const ext of extensions ) {
2025-06-19 12:55:16 -05:00
const corePath = path . join (
this . rootDir ,
"bmad-core" ,
resourceType ,
` ${ resourceName } ${ ext } `
) ;
2025-06-18 19:17:09 -05:00
try {
2025-06-19 12:55:16 -05:00
const coreContent = await fs . readFile ( corePath , "utf8" ) ;
sections . push (
this . formatSection ( ` ${ resourceType } # ${ resourceName } ` , coreContent )
) ;
2025-06-18 19:17:09 -05:00
found = true ;
break ;
} catch ( error ) {
// Not in core either, continue
}
}
}
2025-06-19 12:55:16 -05:00
2025-06-18 19:17:09 -05:00
if ( ! found ) {
2025-06-19 12:55:16 -05:00
console . warn (
` ⚠ Dependency ${ resourceType } # ${ resourceName } not found in expansion pack or core `
) ;
2025-06-16 18:34:12 -05:00
}
}
}
}
}
} catch ( error ) {
console . debug ( ` Failed to parse agent YAML for ${ agentName } : ` , error . message ) ;
}
}
2025-06-19 12:55:16 -05:00
return sections . join ( "\n" ) ;
2025-06-16 18:34:12 -05:00
}
async buildExpansionTeamBundle ( packName , packDir , teamConfigPath ) {
2025-06-19 12:55:16 -05:00
const template = await fs . readFile ( this . templatePath , "utf8" ) ;
2025-06-16 18:34:12 -05:00
const sections = [ template ] ;
2025-06-19 01:07:21 +02:00
// Add team configuration and parse to get agent list
2025-06-19 12:55:16 -05:00
const teamContent = await fs . readFile ( teamConfigPath , "utf8" ) ;
const teamFileName = path . basename ( teamConfigPath , ".yml" ) ;
2025-06-19 01:07:21 +02:00
const teamConfig = this . parseYaml ( teamContent ) ;
2025-06-16 18:34:12 -05:00
sections . push ( this . formatSection ( ` agent-teams# ${ teamFileName } ` , teamContent ) ) ;
2025-06-19 01:07:21 +02:00
// Get list of expansion pack agents
const expansionAgents = new Set ( ) ;
2025-06-19 12:55:16 -05:00
const agentsDir = path . join ( packDir , "agents" ) ;
2025-06-16 18:34:12 -05:00
try {
const agentFiles = await fs . readdir ( agentsDir ) ;
2025-06-19 12:55:16 -05:00
for ( const agentFile of agentFiles . filter ( ( f ) => f . endsWith ( ".md" ) ) ) {
const agentName = agentFile . replace ( ".md" , "" ) ;
2025-06-19 01:07:21 +02:00
expansionAgents . add ( agentName ) ;
2025-06-16 18:34:12 -05:00
}
} catch ( error ) {
console . warn ( ` ⚠ No agents directory found in ${ packName } ` ) ;
}
2025-06-18 19:17:09 -05:00
// Build a map of all available expansion pack resources for override checking
const expansionResources = new Map ( ) ;
2025-06-19 12:55:16 -05:00
const resourceDirs = [ "templates" , "tasks" , "checklists" , "workflows" , "data" ] ;
2025-06-18 19:17:09 -05:00
for ( const resourceDir of resourceDirs ) {
const resourcePath = path . join ( packDir , resourceDir ) ;
try {
const resourceFiles = await fs . readdir ( resourcePath ) ;
2025-06-19 12:55:16 -05:00
for ( const resourceFile of resourceFiles . filter (
( f ) => f . endsWith ( ".md" ) || f . endsWith ( ".yml" )
) ) {
const fileName = resourceFile . replace ( /\.(md|yml)$/ , "" ) ;
2025-06-18 19:17:09 -05:00
expansionResources . set ( ` ${ resourceDir } # ${ fileName } ` , true ) ;
}
} catch ( error ) {
// Directory might not exist, that's fine
}
}
2025-06-19 01:07:21 +02:00
// Process all agents listed in team configuration
const agentsToProcess = teamConfig . agents || [ ] ;
2025-06-19 12:55:16 -05:00
2025-06-19 01:07:21 +02:00
// Ensure bmad-orchestrator is always included for teams
2025-06-19 12:55:16 -05:00
if ( ! agentsToProcess . includes ( "bmad-orchestrator" ) ) {
2025-06-19 01:07:21 +02:00
console . warn ( ` ⚠ Team ${ teamFileName } missing bmad-orchestrator, adding automatically ` ) ;
2025-06-19 12:55:16 -05:00
agentsToProcess . unshift ( "bmad-orchestrator" ) ;
2025-06-19 01:07:21 +02:00
}
2025-06-18 19:17:09 -05:00
// Track all dependencies from all agents (deduplicated)
const allDependencies = new Map ( ) ;
2025-06-19 01:07:21 +02:00
for ( const agentId of agentsToProcess ) {
if ( expansionAgents . has ( agentId ) ) {
// Use expansion pack version (override)
const agentPath = path . join ( agentsDir , ` ${ agentId } .md ` ) ;
2025-06-19 12:55:16 -05:00
const agentContent = await fs . readFile ( agentPath , "utf8" ) ;
2025-06-19 01:07:21 +02:00
sections . push ( this . formatSection ( ` agents# ${ agentId } ` , agentContent ) ) ;
2025-06-18 19:17:09 -05:00
// 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 ) ;
}
}
2025-06-19 01:07:21 +02:00
} else {
// Use core BMAD version
try {
2025-06-19 12:55:16 -05:00
const coreAgentPath = path . join ( this . rootDir , "bmad-core" , "agents" , ` ${ agentId } .md ` ) ;
const coreAgentContent = await fs . readFile ( coreAgentPath , "utf8" ) ;
2025-06-19 01:07:21 +02:00
sections . push ( this . formatSection ( ` agents# ${ agentId } ` , coreAgentContent ) ) ;
2025-06-18 19:17:09 -05:00
// Parse and collect dependencies from core agent
const agentYaml = coreAgentContent . match ( /```yaml\n([\s\S]*?)\n```/ ) ;
if ( agentYaml ) {
try {
// Clean up the YAML to handle command descriptions after dashes
let yamlContent = agentYaml [ 1 ] ;
2025-06-19 12:55:16 -05:00
yamlContent = yamlContent . replace ( /^(\s*-)(\s*"[^"]+")(\s*-\s*.*)$/gm , "$1$2" ) ;
2025-06-18 19:17:09 -05:00
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 ) ;
}
}
2025-06-19 01:07:21 +02:00
} catch ( error ) {
console . warn ( ` ⚠ Agent ${ agentId } not found in core or expansion pack ` ) ;
}
}
}
2025-06-18 19:17:09 -05:00
// Add all collected dependencies from agents
// Always prefer expansion pack versions if they exist
for ( const [ key , dep ] of allDependencies ) {
let found = false ;
2025-06-19 12:55:16 -05:00
const extensions = [ ".md" , ".yml" , ".yaml" ] ;
2025-06-18 19:17:09 -05:00
// 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 {
2025-06-19 12:55:16 -05:00
const content = await fs . readFile ( expansionPath , "utf8" ) ;
2025-06-18 19:17:09 -05:00
sections . push ( this . formatSection ( key , content ) ) ;
console . log ( ` ✓ Using expansion override for ${ key } ` ) ;
found = true ;
break ;
} catch ( error ) {
// Try next extension
}
}
}
2025-06-19 12:55:16 -05:00
2025-06-18 19:17:09 -05:00
// If not found in expansion pack (or doesn't exist there), try core
if ( ! found ) {
for ( const ext of extensions ) {
2025-06-19 12:55:16 -05:00
const corePath = path . join ( this . rootDir , "bmad-core" , dep . type , ` ${ dep . name } ${ ext } ` ) ;
2025-06-18 19:17:09 -05:00
try {
2025-06-19 12:55:16 -05:00
const content = await fs . readFile ( corePath , "utf8" ) ;
2025-06-18 19:17:09 -05:00
sections . push ( this . formatSection ( key , content ) ) ;
found = true ;
break ;
} catch ( error ) {
// Not in core either, continue
}
}
}
2025-06-19 12:55:16 -05:00
2025-06-18 19:17:09 -05:00
if ( ! found ) {
console . warn ( ` ⚠ Dependency ${ key } not found in expansion pack or core ` ) ;
}
}
// Add remaining expansion pack resources not already included as dependencies
2025-06-16 18:34:12 -05:00
for ( const resourceDir of resourceDirs ) {
const resourcePath = path . join ( packDir , resourceDir ) ;
try {
const resourceFiles = await fs . readdir ( resourcePath ) ;
2025-06-19 12:55:16 -05:00
for ( const resourceFile of resourceFiles . filter (
( f ) => f . endsWith ( ".md" ) || f . endsWith ( ".yml" )
) ) {
2025-06-16 18:34:12 -05:00
const filePath = path . join ( resourcePath , resourceFile ) ;
2025-06-19 12:55:16 -05:00
const fileContent = await fs . readFile ( filePath , "utf8" ) ;
const fileName = resourceFile . replace ( /\.(md|yml)$/ , "" ) ;
2025-06-18 19:17:09 -05:00
// Only add if not already included as a dependency
const resourceKey = ` ${ resourceDir } # ${ fileName } ` ;
if ( ! allDependencies . has ( resourceKey ) ) {
sections . push ( this . formatSection ( resourceKey , fileContent ) ) ;
}
2025-06-16 18:34:12 -05:00
}
} catch ( error ) {
// Directory might not exist, that's fine
}
}
2025-06-19 12:55:16 -05:00
return sections . join ( "\n" ) ;
2025-06-16 18:34:12 -05:00
}
async listExpansionPacks ( ) {
2025-06-19 12:55:16 -05:00
const expansionPacksDir = path . join ( this . rootDir , "expansion-packs" ) ;
2025-06-16 18:34:12 -05:00
try {
const entries = await fs . readdir ( expansionPacksDir , { withFileTypes : true } ) ;
2025-06-19 12:55:16 -05:00
return entries . filter ( ( entry ) => entry . isDirectory ( ) ) . map ( ( entry ) => entry . name ) ;
2025-06-16 18:34:12 -05:00
} catch ( error ) {
2025-06-19 12:55:16 -05:00
console . warn ( "No expansion-packs directory found" ) ;
2025-06-16 18:34:12 -05:00
return [ ] ;
}
}
2025-06-10 21:41:58 -05:00
listAgents ( ) {
return this . resolver . listAgents ( ) ;
}
}
2025-06-19 12:55:16 -05:00
module . exports = WebBuilder ;