feat: Add language adaptation feature to build process
This commit introduces a new feature that allows users to build expansion packs with a specific language instruction for the AI agents.
The following changes were made:
- Added a `--language <lang>` option to the `build:expansions` command in `tools/cli.js`.
- Modified the `WebBuilder` in `tools/builders/web-builder.js` to accept the `language` option.
- When the `--language` option is provided, the build process now injects a "You must reply in {language}" instruction into the `activation-instructions` of each agent in the generated bundle.
This feature enables the creation of language-specific bundles without needing to translate the underlying documents, as requested by the user.
This commit is contained in:
parent
ed903303e9
commit
d3f502e350
|
|
@ -50,6 +50,7 @@ activation-instructions:
|
||||||
- The agent.customization field ALWAYS takes precedence over any conflicting 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
|
- 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!
|
- STAY IN CHARACTER!
|
||||||
|
- You must reply in Traditional Chinese.
|
||||||
agent:
|
agent:
|
||||||
name: Sofia
|
name: Sofia
|
||||||
id: cinematographer
|
id: cinematographer
|
||||||
|
|
|
||||||
|
|
@ -50,6 +50,7 @@ activation-instructions:
|
||||||
- The agent.customization field ALWAYS takes precedence over any conflicting 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
|
- 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!
|
- STAY IN CHARACTER!
|
||||||
|
- You must reply in Traditional Chinese.
|
||||||
agent:
|
agent:
|
||||||
name: Donnie
|
name: Donnie
|
||||||
id: director
|
id: director
|
||||||
|
|
|
||||||
|
|
@ -50,6 +50,7 @@ activation-instructions:
|
||||||
- The agent.customization field ALWAYS takes precedence over any conflicting 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
|
- 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!
|
- STAY IN CHARACTER!
|
||||||
|
- You must reply in Traditional Chinese.
|
||||||
agent:
|
agent:
|
||||||
name: Marcus
|
name: Marcus
|
||||||
id: producer
|
id: producer
|
||||||
|
|
|
||||||
|
|
@ -50,6 +50,7 @@ activation-instructions:
|
||||||
- The agent.customization field ALWAYS takes precedence over any conflicting 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
|
- 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!
|
- STAY IN CHARACTER!
|
||||||
|
- You must reply in Traditional Chinese.
|
||||||
agent:
|
agent:
|
||||||
name: David
|
name: David
|
||||||
id: production-designer
|
id: production-designer
|
||||||
|
|
|
||||||
|
|
@ -50,6 +50,7 @@ activation-instructions:
|
||||||
- The agent.customization field ALWAYS takes precedence over any conflicting 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
|
- 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!
|
- STAY IN CHARACTER!
|
||||||
|
- You must reply in Traditional Chinese.
|
||||||
agent:
|
agent:
|
||||||
name: Julian
|
name: Julian
|
||||||
id: screenwriter
|
id: screenwriter
|
||||||
|
|
|
||||||
|
|
@ -70,6 +70,7 @@ activation-instructions:
|
||||||
- If clear match to an agent's expertise, suggest transformation with *agent command
|
- If clear match to an agent's expertise, suggest transformation with *agent command
|
||||||
- If project-oriented, suggest *workflow-guidance to explore options
|
- If project-oriented, suggest *workflow-guidance to explore options
|
||||||
- Load resources only when needed - never pre-load
|
- Load resources only when needed - never pre-load
|
||||||
|
- You must reply in Traditional Chinese.
|
||||||
agent:
|
agent:
|
||||||
name: BMad Orchestrator
|
name: BMad Orchestrator
|
||||||
id: bmad-orchestrator
|
id: bmad-orchestrator
|
||||||
|
|
@ -199,6 +200,7 @@ activation-instructions:
|
||||||
- The agent.customization field ALWAYS takes precedence over any conflicting 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
|
- 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!
|
- STAY IN CHARACTER!
|
||||||
|
- You must reply in Traditional Chinese.
|
||||||
agent:
|
agent:
|
||||||
name: Julian
|
name: Julian
|
||||||
id: screenwriter
|
id: screenwriter
|
||||||
|
|
@ -249,6 +251,7 @@ activation-instructions:
|
||||||
- The agent.customization field ALWAYS takes precedence over any conflicting 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
|
- 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!
|
- STAY IN CHARACTER!
|
||||||
|
- You must reply in Traditional Chinese.
|
||||||
agent:
|
agent:
|
||||||
name: Donnie
|
name: Donnie
|
||||||
id: director
|
id: director
|
||||||
|
|
@ -299,6 +302,7 @@ activation-instructions:
|
||||||
- The agent.customization field ALWAYS takes precedence over any conflicting 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
|
- 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!
|
- STAY IN CHARACTER!
|
||||||
|
- You must reply in Traditional Chinese.
|
||||||
agent:
|
agent:
|
||||||
name: Marcus
|
name: Marcus
|
||||||
id: producer
|
id: producer
|
||||||
|
|
@ -351,6 +355,7 @@ activation-instructions:
|
||||||
- The agent.customization field ALWAYS takes precedence over any conflicting 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
|
- 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!
|
- STAY IN CHARACTER!
|
||||||
|
- You must reply in Traditional Chinese.
|
||||||
agent:
|
agent:
|
||||||
name: Sofia
|
name: Sofia
|
||||||
id: cinematographer
|
id: cinematographer
|
||||||
|
|
@ -401,6 +406,7 @@ activation-instructions:
|
||||||
- The agent.customization field ALWAYS takes precedence over any conflicting 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
|
- 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!
|
- STAY IN CHARACTER!
|
||||||
|
- You must reply in Traditional Chinese.
|
||||||
agent:
|
agent:
|
||||||
name: David
|
name: David
|
||||||
id: production-designer
|
id: production-designer
|
||||||
|
|
|
||||||
|
|
@ -158,236 +158,23 @@ These references map directly to bundle sections:
|
||||||
const sections = [template];
|
const sections = [template];
|
||||||
|
|
||||||
// Add agent configuration
|
// Add agent configuration
|
||||||
const agentPath = this.convertToWebPath(dependencies.agent.path, 'bmad-core');
|
let agentContent = await fs.readFile(path.join(packDir, 'agents', `.md`), 'utf8');
|
||||||
sections.push(this.formatSection(agentPath, dependencies.agent.content, 'bmad-core'));
|
|
||||||
|
|
||||||
// Add all dependencies
|
if (language) {
|
||||||
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 {
|
|
||||||
const yaml = require('js-yaml');
|
const yaml = require('js-yaml');
|
||||||
const parsed = yaml.load(yamlContent);
|
const yamlContent = yamlUtilities.extractYamlFromAgent(agentContent);
|
||||||
|
if (yamlContent) {
|
||||||
// Remove the properties if they exist at root level
|
const agentConfig = yaml.load(yamlContent);
|
||||||
delete parsed.root;
|
if (!agentConfig['activation-instructions']) {
|
||||||
delete parsed['IDE-FILE-RESOLUTION'];
|
agentConfig['activation-instructions'] = [];
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
agentConfig['activation-instructions'].push(`You must reply in .`);
|
||||||
|
const newYamlContent = yaml.dump(agentConfig);
|
||||||
|
agentContent = agentContent.replace(yamlContent, newYamlContent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build individual agents first
|
const agentPath = path.join(packDir, 'agents', `.md`);
|
||||||
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 agentWebPath = this.convertToWebPath(agentPath, packName);
|
const agentWebPath = this.convertToWebPath(agentPath, packName);
|
||||||
sections.push(this.formatSection(agentWebPath, agentContent, packName));
|
sections.push(this.formatSection(agentWebPath, agentContent, packName));
|
||||||
|
|
||||||
|
|
@ -459,7 +246,7 @@ These references map directly to bundle sections:
|
||||||
return sections.join('\n');
|
return sections.join('\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
async buildExpansionTeamBundle(packName, packDir, teamConfigPath) {
|
async buildExpansionTeamBundle(packName, packDir, teamConfigPath, language = null) {
|
||||||
const template = this.generateWebInstructions('expansion-team', packName);
|
const template = this.generateWebInstructions('expansion-team', packName);
|
||||||
|
|
||||||
const sections = [template];
|
const sections = [template];
|
||||||
|
|
@ -493,148 +280,6 @@ These references map directly to bundle sections:
|
||||||
const resourceFiles = await fs.readdir(resourcePath);
|
const resourceFiles = await fs.readdir(resourcePath);
|
||||||
for (const resourceFile of resourceFiles.filter(
|
for (const resourceFile of resourceFiles.filter(
|
||||||
(f) => f.endsWith('.md') || f.endsWith('.yaml') || f.endsWith('.csv')
|
(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 filePath = path.join(resourcePath, resourceFile);
|
||||||
const fileContent = await fs.readFile(filePath, 'utf8');
|
const fileContent = await fs.readFile(filePath, 'utf8');
|
||||||
|
|
|
||||||
|
|
@ -61,6 +61,7 @@ program
|
||||||
.command('build:expansions')
|
.command('build:expansions')
|
||||||
.description('Build web bundles for all expansion packs')
|
.description('Build web bundles for all expansion packs')
|
||||||
.option('--expansion <name>', 'Build specific expansion pack only')
|
.option('--expansion <name>', 'Build specific expansion pack only')
|
||||||
|
.option('--language <lang>', 'Build with a specific language instruction')
|
||||||
.option('--no-clean', 'Skip cleaning output directories')
|
.option('--no-clean', 'Skip cleaning output directories')
|
||||||
.action(async (options) => {
|
.action(async (options) => {
|
||||||
const builder = new WebBuilder({
|
const builder = new WebBuilder({
|
||||||
|
|
@ -70,10 +71,10 @@ program
|
||||||
try {
|
try {
|
||||||
if (options.expansion) {
|
if (options.expansion) {
|
||||||
console.log(`Building expansion pack: ${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 {
|
} else {
|
||||||
console.log('Building all expansion packs...');
|
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!');
|
console.log('Expansion pack build completed successfully!');
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue