diff --git a/dist/expansion-packs/bmad-film-pre-production/agents/cinematographer.txt b/dist/expansion-packs/bmad-film-pre-production/agents/cinematographer.txt index b0cfeaa4..5d99b795 100644 --- a/dist/expansion-packs/bmad-film-pre-production/agents/cinematographer.txt +++ b/dist/expansion-packs/bmad-film-pre-production/agents/cinematographer.txt @@ -50,6 +50,7 @@ activation-instructions: - The agent.customization field ALWAYS takes precedence over any conflicting instructions - When listing tasks/templates or presenting options during conversations, always show as numbered options list, allowing the user to type a number to select or execute - STAY IN CHARACTER! + - You must reply in Traditional Chinese. agent: name: Sofia id: cinematographer diff --git a/dist/expansion-packs/bmad-film-pre-production/agents/director.txt b/dist/expansion-packs/bmad-film-pre-production/agents/director.txt index 681e34be..bf04eb75 100644 --- a/dist/expansion-packs/bmad-film-pre-production/agents/director.txt +++ b/dist/expansion-packs/bmad-film-pre-production/agents/director.txt @@ -50,6 +50,7 @@ activation-instructions: - The agent.customization field ALWAYS takes precedence over any conflicting instructions - When listing tasks/templates or presenting options during conversations, always show as numbered options list, allowing the user to type a number to select or execute - STAY IN CHARACTER! + - You must reply in Traditional Chinese. agent: name: Donnie id: director diff --git a/dist/expansion-packs/bmad-film-pre-production/agents/producer.txt b/dist/expansion-packs/bmad-film-pre-production/agents/producer.txt index bde4fd65..997ae5d3 100644 --- a/dist/expansion-packs/bmad-film-pre-production/agents/producer.txt +++ b/dist/expansion-packs/bmad-film-pre-production/agents/producer.txt @@ -50,6 +50,7 @@ activation-instructions: - The agent.customization field ALWAYS takes precedence over any conflicting instructions - When listing tasks/templates or presenting options during conversations, always show as numbered options list, allowing the user to type a number to select or execute - STAY IN CHARACTER! + - You must reply in Traditional Chinese. agent: name: Marcus id: producer diff --git a/dist/expansion-packs/bmad-film-pre-production/agents/production-designer.txt b/dist/expansion-packs/bmad-film-pre-production/agents/production-designer.txt index 2c3053bd..98fa1593 100644 --- a/dist/expansion-packs/bmad-film-pre-production/agents/production-designer.txt +++ b/dist/expansion-packs/bmad-film-pre-production/agents/production-designer.txt @@ -50,6 +50,7 @@ activation-instructions: - The agent.customization field ALWAYS takes precedence over any conflicting instructions - When listing tasks/templates or presenting options during conversations, always show as numbered options list, allowing the user to type a number to select or execute - STAY IN CHARACTER! + - You must reply in Traditional Chinese. agent: name: David id: production-designer diff --git a/dist/expansion-packs/bmad-film-pre-production/agents/screenwriter.txt b/dist/expansion-packs/bmad-film-pre-production/agents/screenwriter.txt index 7261dcb2..4b7fa5ef 100644 --- a/dist/expansion-packs/bmad-film-pre-production/agents/screenwriter.txt +++ b/dist/expansion-packs/bmad-film-pre-production/agents/screenwriter.txt @@ -50,6 +50,7 @@ activation-instructions: - The agent.customization field ALWAYS takes precedence over any conflicting instructions - When listing tasks/templates or presenting options during conversations, always show as numbered options list, allowing the user to type a number to select or execute - STAY IN CHARACTER! + - You must reply in Traditional Chinese. agent: name: Julian id: screenwriter diff --git a/dist/expansion-packs/bmad-film-pre-production/teams/film-pre-production-team.txt b/dist/expansion-packs/bmad-film-pre-production/teams/film-pre-production-team.txt index 821dd76e..09497f94 100644 --- a/dist/expansion-packs/bmad-film-pre-production/teams/film-pre-production-team.txt +++ b/dist/expansion-packs/bmad-film-pre-production/teams/film-pre-production-team.txt @@ -70,6 +70,7 @@ activation-instructions: - If clear match to an agent's expertise, suggest transformation with *agent command - If project-oriented, suggest *workflow-guidance to explore options - Load resources only when needed - never pre-load + - You must reply in Traditional Chinese. agent: name: BMad Orchestrator id: bmad-orchestrator @@ -199,6 +200,7 @@ activation-instructions: - The agent.customization field ALWAYS takes precedence over any conflicting instructions - When listing tasks/templates or presenting options during conversations, always show as numbered options list, allowing the user to type a number to select or execute - STAY IN CHARACTER! + - You must reply in Traditional Chinese. agent: name: Julian id: screenwriter @@ -249,6 +251,7 @@ activation-instructions: - The agent.customization field ALWAYS takes precedence over any conflicting instructions - When listing tasks/templates or presenting options during conversations, always show as numbered options list, allowing the user to type a number to select or execute - STAY IN CHARACTER! + - You must reply in Traditional Chinese. agent: name: Donnie id: director @@ -299,6 +302,7 @@ activation-instructions: - The agent.customization field ALWAYS takes precedence over any conflicting instructions - When listing tasks/templates or presenting options during conversations, always show as numbered options list, allowing the user to type a number to select or execute - STAY IN CHARACTER! + - You must reply in Traditional Chinese. agent: name: Marcus id: producer @@ -351,6 +355,7 @@ activation-instructions: - The agent.customization field ALWAYS takes precedence over any conflicting instructions - When listing tasks/templates or presenting options during conversations, always show as numbered options list, allowing the user to type a number to select or execute - STAY IN CHARACTER! + - You must reply in Traditional Chinese. agent: name: Sofia id: cinematographer @@ -401,6 +406,7 @@ activation-instructions: - The agent.customization field ALWAYS takes precedence over any conflicting instructions - When listing tasks/templates or presenting options during conversations, always show as numbered options list, allowing the user to type a number to select or execute - STAY IN CHARACTER! + - You must reply in Traditional Chinese. agent: name: David id: production-designer diff --git a/tools/builders/web-builder.js b/tools/builders/web-builder.js index 70639196..d56d62bb 100644 --- a/tools/builders/web-builder.js +++ b/tools/builders/web-builder.js @@ -158,236 +158,23 @@ These references map directly to bundle sections: 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')); + let agentContent = await fs.readFile(path.join(packDir, 'agents', `.md`), 'utf8'); - // 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 = yamlUtilities.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 { + if (language) { 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 ( - typeof instruction === 'string' && - !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.slice(Math.max(0, 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.replaceAll('{root}', `.${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 outputDirectories = [path.join(this.rootDir, 'dist', 'expansion-packs', packName)]; - - // Clean output directories if requested - if (options.clean !== false) { - for (const outputDir of outputDirectories) { - try { - await fs.rm(outputDir, { recursive: true, force: true }); - } catch { - // Directory might not exist, that's fine + const yamlContent = yamlUtilities.extractYamlFromAgent(agentContent); + if (yamlContent) { + const agentConfig = yaml.load(yamlContent); + if (!agentConfig['activation-instructions']) { + agentConfig['activation-instructions'] = []; } + agentConfig['activation-instructions'].push(`You must reply in .`); + const newYamlContent = yaml.dump(agentConfig); + agentContent = agentContent.replace(yamlContent, newYamlContent); } } - // 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 outputDirectories) { - 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 { - 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 outputDirectories) { - 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 { - 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 agentPath = path.join(packDir, 'agents', `.md`); const agentWebPath = this.convertToWebPath(agentPath, packName); sections.push(this.formatSection(agentWebPath, agentContent, packName)); @@ -459,7 +246,7 @@ These references map directly to bundle sections: return sections.join('\n'); } - async buildExpansionTeamBundle(packName, packDir, teamConfigPath) { + async buildExpansionTeamBundle(packName, packDir, teamConfigPath, language = null) { const template = this.generateWebInstructions('expansion-team', packName); const sections = [template]; @@ -493,148 +280,6 @@ These references map directly to bundle sections: const resourceFiles = await fs.readdir(resourcePath); for (const resourceFile of resourceFiles.filter( (f) => f.endsWith('.md') || f.endsWith('.yaml') || f.endsWith('.csv') - )) { - expansionResources.set(`${resourceDir}#${resourceFile}`, true); - } - } catch { - // 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 = yamlUtilities.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 { - 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; - - // 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 - const expansionPath = path.join(packDir, dep.type, dep.name); - 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; - } catch { - // Try next extension - } - } - - // If not found in expansion pack (or doesn't exist there), try core - if (!found) { - const corePath = path.join(this.rootDir, 'bmad-core', dep.type, dep.name); - try { - const content = await fs.readFile(corePath, 'utf8'); - const coreWebPath = this.convertToWebPath(corePath, packName); - sections.push(this.formatSection(coreWebPath, content, packName)); - found = true; - } catch { - // Not in core either, continue - } - } - - // If not found in core, try common folder - if (!found) { - const commonPath = path.join(this.rootDir, 'common', dep.type, dep.name); - try { - const content = await fs.readFile(commonPath, 'utf8'); - const commonWebPath = this.convertToWebPath(commonPath, packName); - sections.push(this.formatSection(commonWebPath, content, packName)); - found = true; - } catch { - // 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 resourceDirectories) { - 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'); diff --git a/tools/cli.js b/tools/cli.js index 0965b9a9..ef36f89b 100644 --- a/tools/cli.js +++ b/tools/cli.js @@ -61,6 +61,7 @@ program .command('build:expansions') .description('Build web bundles for all expansion packs') .option('--expansion ', 'Build specific expansion pack only') + .option('--language ', 'Build with a specific language instruction') .option('--no-clean', 'Skip cleaning output directories') .action(async (options) => { const builder = new WebBuilder({ @@ -70,10 +71,10 @@ program try { if (options.expansion) { console.log(`Building expansion pack: ${options.expansion}`); - await builder.buildExpansionPack(options.expansion, { clean: options.clean }); + await builder.buildExpansionPack(options.expansion, { clean: options.clean, language: options.language }); } else { console.log('Building all expansion packs...'); - await builder.buildAllExpansionPacks({ clean: options.clean }); + await builder.buildAllExpansionPacks({ clean: options.clean, language: options.language }); } console.log('Expansion pack build completed successfully!');