chore: add code formatting config and pre-commit hooks (#450)

This commit is contained in:
manjaroblack
2025-08-16 19:08:39 -05:00
committed by GitHub
parent 51284d6ecf
commit ed539432fb
130 changed files with 11886 additions and 10939 deletions

View File

@@ -1,23 +1,23 @@
const fs = require("node:fs").promises;
const path = require("node:path");
const DependencyResolver = require("../lib/dependency-resolver");
const yamlUtils = require("../lib/yaml-utils");
const fs = require('node:fs').promises;
const path = require('node:path');
const DependencyResolver = require('../lib/dependency-resolver');
const yamlUtilities = require('../lib/yaml-utils');
class WebBuilder {
constructor(options = {}) {
this.rootDir = options.rootDir || process.cwd();
this.outputDirs = options.outputDirs || [path.join(this.rootDir, "dist")];
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"
'tools',
'md-assets',
'web-agent-startup-instructions.md',
);
}
parseYaml(content) {
const yaml = require("js-yaml");
const yaml = require('js-yaml');
return yaml.load(content);
}
@@ -26,7 +26,7 @@ class WebBuilder {
// 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
@@ -35,18 +35,28 @@ class WebBuilder {
// 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';
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 utilitiesExample = packName
? `.${packName}/utils/template-format.md`
: '.bmad-core/utils/template-format.md';
const tasksReference = packName
? `.${packName}/tasks/create-story.md`
: '.bmad-core/tasks/create-story.md';
return `# Web Agent Bundle Instructions
@@ -79,8 +89,8 @@ dependencies:
These references map directly to bundle sections:
- \`utils: template-format\` → Look for \`==================== START: ${utilsExample} ====================\`
- \`tasks: create-story\` → Look for \`==================== START: ${tasksRef} ====================\`
- \`utils: template-format\` → Look for \`==================== START: ${utilitiesExample} ====================\`
- \`tasks: create-story\` → Look for \`==================== START: ${tasksReference} ====================\`
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.
@@ -112,10 +122,10 @@ These references map directly to bundle sections:
// Write to all output directories
for (const outputDir of this.outputDirs) {
const outputPath = path.join(outputDir, "agents");
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");
await fs.writeFile(outputFile, bundle, 'utf8');
}
}
@@ -131,10 +141,10 @@ These references map directly to bundle sections:
// Write to all output directories
for (const outputDir of this.outputDirs) {
const outputPath = path.join(outputDir, "teams");
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");
await fs.writeFile(outputFile, bundle, 'utf8');
}
}
@@ -157,7 +167,7 @@ These references map directly to bundle sections:
sections.push(this.formatSection(resourcePath, resource.content, 'bmad-core'));
}
return sections.join("\n");
return sections.join('\n');
}
async buildTeamBundle(teamId) {
@@ -182,40 +192,40 @@ These references map directly to bundle sections:
sections.push(this.formatSection(resourcePath, resource.content, 'bmad-core'));
}
return sections.join("\n");
return sections.join('\n');
}
processAgentContent(content) {
// First, replace content before YAML with the template
const yamlContent = yamlUtils.extractYamlFromAgent(content);
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);
// Remove the properties if they exist at root level
delete parsed.root;
delete parsed["IDE-FILE-RESOLUTION"];
delete parsed["REQUEST-RESOLUTION"];
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(
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:")
!instruction.startsWith('IDE-FILE-RESOLUTION:') &&
!instruction.startsWith('REQUEST-RESOLUTION:')
);
}
},
);
}
@@ -223,25 +233,25 @@ These references map directly to bundle sections:
const cleanedYaml = yaml.dump(parsed, { lineWidth: -1 });
// Get the agent name from the YAML for the header
const agentName = parsed.agent?.id || "agent";
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);
const afterYaml = content.slice(Math.max(0, yamlEndIndex));
return newHeader + "```yaml\n" + cleanedYaml.trim() + "\n```" + afterYaml;
return newHeader + '```yaml\n' + cleanedYaml.trim() + '\n```' + afterYaml;
} catch (error) {
console.warn("Failed to process agent YAML:", error.message);
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 = "====================";
const separator = '====================';
// Process agent content if this is an agent file
if (path.includes("/agents/")) {
if (path.includes('/agents/')) {
content = this.processAgentContent(content);
}
@@ -252,17 +262,17 @@ These references map directly to bundle sections:
`${separator} START: ${path} ${separator}`,
content.trim(),
`${separator} END: ${path} ${separator}`,
"",
].join("\n");
'',
].join('\n');
}
replaceRootReferences(content, bundleRoot) {
// Replace {root} with the appropriate bundle root path
return content.replace(/\{root\}/g, `.${bundleRoot}`);
return content.replaceAll('{root}', `.${bundleRoot}`);
}
async validate() {
console.log("Validating agent configurations...");
console.log('Validating agent configurations...');
const agents = await this.resolver.listAgents();
for (const agentId of agents) {
try {
@@ -274,7 +284,7 @@ These references map directly to bundle sections:
}
}
console.log("\nValidating team configurations...");
console.log('\nValidating team configurations...');
const teams = await this.resolver.listTeams();
for (const teamId of teams) {
try {
@@ -299,54 +309,54 @@ These references map directly to bundle sections:
}
async buildExpansionPack(packName, options = {}) {
const packDir = path.join(this.rootDir, "expansion-packs", packName);
const outputDirs = [path.join(this.rootDir, "dist", "expansion-packs", packName)];
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 outputDirs) {
for (const outputDir of outputDirectories) {
try {
await fs.rm(outputDir, { recursive: true, force: true });
} catch (error) {
} catch {
// Directory might not exist, that's fine
}
}
}
// Build individual agents first
const agentsDir = path.join(packDir, "agents");
const agentsDir = path.join(packDir, 'agents');
try {
const agentFiles = await fs.readdir(agentsDir);
const agentMarkdownFiles = agentFiles.filter((f) => f.endsWith(".md"));
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", "");
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");
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");
await fs.writeFile(outputFile, bundle, 'utf8');
}
}
}
} catch (error) {
} catch {
console.debug(` No agents directory found for ${packName}`);
}
// Build team bundle
const agentTeamsDir = path.join(packDir, "agent-teams");
const agentTeamsDir = path.join(packDir, 'agent-teams');
try {
const teamFiles = await fs.readdir(agentTeamsDir);
const teamFile = teamFiles.find((f) => f.endsWith(".yaml"));
const teamFile = teamFiles.find((f) => f.endsWith('.yaml'));
if (teamFile) {
console.log(` Building team bundle for ${packName}`);
@@ -356,17 +366,17 @@ These references map directly to bundle sections:
const bundle = await this.buildExpansionTeamBundle(packName, packDir, teamConfigPath);
// Write to all output directories
for (const outputDir of outputDirs) {
const teamsOutputDir = path.join(outputDir, "teams");
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");
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) {
} catch {
console.warn(` ⚠ No agent-teams directory found for ${packName}`);
}
}
@@ -376,16 +386,16 @@ These references map directly to bundle sections:
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', `${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);
const yamlContent = yamlUtilities.extractYamlFromAgent(agentContent);
if (yamlContent) {
try {
const yaml = require("js-yaml");
const yaml = require('js-yaml');
const agentConfig = yaml.load(yamlContent);
if (agentConfig.dependencies) {
@@ -398,59 +408,43 @@ These references map directly to bundle sections:
// Try expansion pack first
const resourcePath = path.join(packDir, resourceType, resourceName);
try {
const resourceContent = await fs.readFile(resourcePath, "utf8");
const resourceContent = await fs.readFile(resourcePath, 'utf8');
const resourceWebPath = this.convertToWebPath(resourcePath, packName);
sections.push(
this.formatSection(resourceWebPath, resourceContent, packName)
);
sections.push(this.formatSection(resourceWebPath, resourceContent, packName));
found = true;
} catch (error) {
} catch {
// Not in expansion pack, continue
}
// If not found in expansion pack, try core
if (!found) {
const corePath = path.join(
this.rootDir,
"bmad-core",
resourceType,
resourceName
);
const corePath = path.join(this.rootDir, 'bmad-core', resourceType, resourceName);
try {
const coreContent = await fs.readFile(corePath, "utf8");
const coreContent = await fs.readFile(corePath, 'utf8');
const coreWebPath = this.convertToWebPath(corePath, packName);
sections.push(
this.formatSection(coreWebPath, coreContent, packName)
);
sections.push(this.formatSection(coreWebPath, coreContent, packName));
found = true;
} catch (error) {
} catch {
// Not in core either, continue
}
}
// If not found in core, try common folder
if (!found) {
const commonPath = path.join(
this.rootDir,
"common",
resourceType,
resourceName
);
const commonPath = path.join(this.rootDir, 'common', resourceType, resourceName);
try {
const commonContent = await fs.readFile(commonPath, "utf8");
const commonContent = await fs.readFile(commonPath, 'utf8');
const commonWebPath = this.convertToWebPath(commonPath, packName);
sections.push(
this.formatSection(commonWebPath, commonContent, packName)
);
sections.push(this.formatSection(commonWebPath, commonContent, packName));
found = true;
} catch (error) {
} catch {
// Not in common either, continue
}
}
if (!found) {
console.warn(
` ⚠ Dependency ${resourceType}#${resourceName} not found in expansion pack or core`
` ⚠ Dependency ${resourceType}#${resourceName} not found in expansion pack or core`,
);
}
}
@@ -462,7 +456,7 @@ These references map directly to bundle sections:
}
}
return sections.join("\n");
return sections.join('\n');
}
async buildExpansionTeamBundle(packName, packDir, teamConfigPath) {
@@ -471,38 +465,38 @@ These references map directly to bundle sections:
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 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");
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", "");
for (const agentFile of agentFiles.filter((f) => f.endsWith('.md'))) {
const agentName = agentFile.replace('.md', '');
expansionAgents.add(agentName);
}
} catch (error) {
} catch {
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 resourceDirectories = ['templates', 'tasks', 'checklists', 'workflows', 'data'];
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")
(f) => f.endsWith('.md') || f.endsWith('.yaml'),
)) {
expansionResources.set(`${resourceDir}#${resourceFile}`, true);
}
} catch (error) {
} catch {
// Directory might not exist, that's fine
}
}
@@ -511,9 +505,9 @@ These references map directly to bundle sections:
const agentsToProcess = teamConfig.agents || [];
// Ensure bmad-orchestrator is always included for teams
if (!agentsToProcess.includes("bmad-orchestrator")) {
if (!agentsToProcess.includes('bmad-orchestrator')) {
console.warn(` ⚠ Team ${teamFileName} missing bmad-orchestrator, adding automatically`);
agentsToProcess.unshift("bmad-orchestrator");
agentsToProcess.unshift('bmad-orchestrator');
}
// Track all dependencies from all agents (deduplicated)
@@ -523,7 +517,7 @@ These references map directly to bundle sections:
if (expansionAgents.has(agentId)) {
// Use expansion pack version (override)
const agentPath = path.join(agentsDir, `${agentId}.md`);
const agentContent = await fs.readFile(agentPath, "utf8");
const agentContent = await fs.readFile(agentPath, 'utf8');
const expansionAgentWebPath = this.convertToWebPath(agentPath, packName);
sections.push(this.formatSection(expansionAgentWebPath, agentContent, packName));
@@ -551,13 +545,13 @@ These references map directly to bundle sections:
} 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 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);
const yamlContent = yamlUtilities.extractYamlFromAgent(coreAgentContent, true);
if (yamlContent) {
try {
const agentConfig = this.parseYaml(yamlContent);
@@ -577,7 +571,7 @@ These references map directly to bundle sections:
console.debug(`Failed to parse agent YAML for ${agentId}:`, error.message);
}
}
} catch (error) {
} catch {
console.warn(` ⚠ Agent ${agentId} not found in core or expansion pack`);
}
}
@@ -593,38 +587,38 @@ These references map directly to bundle sections:
// 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 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 (error) {
} 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);
const corePath = path.join(this.rootDir, 'bmad-core', dep.type, dep.name);
try {
const content = await fs.readFile(corePath, "utf8");
const content = await fs.readFile(corePath, 'utf8');
const coreWebPath = this.convertToWebPath(corePath, packName);
sections.push(this.formatSection(coreWebPath, content, packName));
found = true;
} catch (error) {
} 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);
const commonPath = path.join(this.rootDir, 'common', dep.type, dep.name);
try {
const content = await fs.readFile(commonPath, "utf8");
const content = await fs.readFile(commonPath, 'utf8');
const commonWebPath = this.convertToWebPath(commonPath, packName);
sections.push(this.formatSection(commonWebPath, content, packName));
found = true;
} catch (error) {
} catch {
// Not in common either, continue
}
}
@@ -635,16 +629,16 @@ These references map directly to bundle sections:
}
// Add remaining expansion pack resources not already included as dependencies
for (const resourceDir of resourceDirs) {
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")
(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)$/, "");
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}`;
@@ -654,21 +648,21 @@ These references map directly to bundle sections:
sections.push(this.formatSection(resourceWebPath, fileContent, packName));
}
}
} catch (error) {
} catch {
// Directory might not exist, that's fine
}
}
return sections.join("\n");
return sections.join('\n');
}
async listExpansionPacks() {
const expansionPacksDir = path.join(this.rootDir, "expansion-packs");
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");
} catch {
console.warn('No expansion-packs directory found');
return [];
}
}