UNPKG

bmad-federated-knowledge

Version:

Git-Based Federated Knowledge System extension for BMAD-METHOD

283 lines (254 loc) 9.25 kB
const Joi = require('joi'); const yaml = require('yaml'); const fs = require('fs-extra'); const path = require('path'); /** * Configuration validator for BMAD Federated Knowledge System * Validates enhanced core-config.yaml with federated knowledge support */ class ConfigValidator { constructor() { this.federatedRepoSchema = Joi.object({ repo: Joi.string().uri().required() .description('Git repository URL (SSH or HTTPS)'), branch: Joi.string().default('main') .description('Branch to sync from'), local_cache: Joi.string().required() .description('Local cache directory path'), sync_policy: Joi.string().valid('daily', 'weekly', 'on_demand', 'manual').default('weekly') .description('Synchronization policy'), priority: Joi.number().integer().min(0).max(999).default(0) .description('Priority for conflict resolution (higher wins)'), auth: Joi.object({ type: Joi.string().valid('ssh', 'token', 'basic').default('ssh'), token: Joi.string().when('type', { is: 'token', then: Joi.required() }), username: Joi.string().when('type', { is: 'basic', then: Joi.required() }), password: Joi.string().when('type', { is: 'basic', then: Joi.required() }) }).optional(), filters: Joi.object({ include: Joi.array().items(Joi.string()).default([]), exclude: Joi.array().items(Joi.string()).default([]) }).optional(), metadata: Joi.object({ description: Joi.string(), maintainer: Joi.string(), tags: Joi.array().items(Joi.string()).default([]) }).optional() }); this.webSchema = Joi.object({ type: Joi.string().valid('web').required(), url: Joi.string().uri().required(), priority: Joi.number().min(0).max(999).default(0), metadata: Joi.object().optional() }); this.dbSchema = Joi.object({ type: Joi.string().valid('database').required(), connection: Joi.string().required(), query: Joi.string().default('SELECT * FROM knowledge'), priority: Joi.number().min(0).max(999).default(0), metadata: Joi.object().optional() }); this.enhancedConfigSchema = Joi.object({ bmad_config: Joi.object({ version: Joi.string().required(), // Existing local knowledge structure local_knowledge: Joi.object({ core_data: Joi.string().default('./.bmad-fks-core/core-data'), templates: Joi.string().default('./.bmad-fks-core/templates'), workflows: Joi.string().default('./.bmad-fks-core/workflows') }).optional(), // New federated knowledge structure federated_knowledge: Joi.object().pattern( Joi.string(), this.federatedRepoSchema ).optional(), // Global federated settings federated_settings: Joi.object({ cache_root: Joi.string().default('./.bmad-cache'), max_cache_size: Joi.string().default('1GB'), sync_timeout: Joi.number().default(300), retry_attempts: Joi.number().default(3), parallel_sync: Joi.boolean().default(true), conflict_resolution: Joi.string().valid('priority', 'manual', 'local_wins').default('priority') }).optional(), // Existing bmad configuration agents: Joi.object().optional(), dependencies: Joi.array().optional(), devLoadAlwaysFiles: Joi.array().optional() }).required() }); } async validateKnowledgeConfig(config) { switch (config.type) { case 'git': return this.gitSchema.validateAsync(config); case 'web': return this.webSchema.validateAsync(config); case 'database': return this.dbSchema.validateAsync(config); default: throw new Error(`Unknown knowledge source type: ${config.type}`); } } /** * Validate complete configuration * @param {Object|string} config - Configuration object or file path * @returns {Promise<Object>} Validated configuration */ async validate(config) { let configObj; if (typeof config === 'string') { configObj = await this.loadConfigFile(config); } else { configObj = config; } const { error, value } = this.enhancedConfigSchema.validate(configObj, { allowUnknown: true, stripUnknown: false }); if (error) { throw new Error(`Configuration validation failed: ${error.details.map(d => d.message).join(', ')}`); } return value; } /** * Validate repository configuration * @param {Object} repoConfig - Repository configuration * @returns {Promise<Object>} Validated repository configuration */ async validateRepositoryConfig(repoConfig) { const { error, value } = this.federatedRepoSchema.validate(repoConfig); if (error) { throw new Error(`Repository configuration validation failed: ${error.details.map(d => d.message).join(', ')}`); } return value; } /** * Load and parse YAML configuration file * @param {string} filePath - Path to configuration file * @returns {Promise<Object>} Parsed configuration */ async loadConfigFile(filePath) { try { const configPath = path.resolve(filePath); const configContent = await fs.readFile(configPath, 'utf8'); return yaml.parse(configContent); } catch (error) { throw new Error(`Failed to load configuration file ${filePath}: ${error.message}`); } } /** * Save configuration to YAML file * @param {Object} config - Configuration object * @param {string} filePath - Output file path * @returns {Promise<void>} */ async saveConfigFile(config, filePath) { try { const configPath = path.resolve(filePath); await fs.ensureDir(path.dirname(configPath)); const yamlContent = yaml.stringify(config, { indent: 2, lineWidth: 120, minContentWidth: 20 }); await fs.writeFile(configPath, yamlContent, 'utf8'); } catch (error) { throw new Error(`Failed to save configuration file ${filePath}: ${error.message}`); } } /** * Generate example configuration * @returns {Object} Example configuration object */ generateExampleConfig() { return { bmad_config: { version: "2.0", local_knowledge: { core_data: "./.bmad-fks-core/core-data", templates: "./.bmad-fks-core/templates", workflows: "./.bmad-fks-core/workflows" }, federated_knowledge: { org_standards: { repo: "https://github.com/goldbergyoni/nodebestpractices.git", branch: "master", local_cache: "./.bmad-fks-cache/org-standards", sync_policy: "daily", priority: 1, metadata: { description: "Organization-wide shared knowledge and standards (using Node.js best practices repo as example)", maintainer: "platform-team@company.com", tags: ["standards", "organization", "nodejs"] } }, industry_templates: { repo: "https://github.com/cookiecutter/cookiecutter.git", branch: "main", local_cache: "./.bmad-fks-cache/industry", sync_policy: "weekly", priority: 0, filters: { include: ["*"], exclude: ["*.tmp", "*.log"] }, metadata: { description: "Industry best practices and templates", maintainer: "bmad-community", tags: ["templates", "best-practices"] } }, // team_workflows: { // repo: "git@gitlab.company.com:teams/backend/bmad-workflows.git", // branch: "production", // local_cache: "./bmad-cache/team-backend", // sync_policy: "on_demand", // priority: 2, // auth: { // type: "token", // token: "${GITLAB_TOKEN}" // }, // metadata: { // description: "Team-specific workflows and configurations", // maintainer: "backend-team@company.com", // tags: ["workflows", "backend", "team"] // } //} }, federated_settings: { cache_root: "./bmad-cache", max_cache_size: "1GB", sync_timeout: 300, retry_attempts: 3, parallel_sync: true, conflict_resolution: "priority" } } }; } /** * Migrate legacy configuration to federated format * @param {Object} legacyConfig - Legacy configuration * @returns {Object} Migrated configuration */ migrateLegacyConfig(legacyConfig) { const migratedConfig = { bmad_config: { version: "2.0", ...legacyConfig.bmad_config, federated_knowledge: {}, federated_settings: { cache_root: "./bmad-cache", max_cache_size: "1GB", sync_timeout: 300, retry_attempts: 3, parallel_sync: true, conflict_resolution: "priority" } } }; return migratedConfig; } } module.exports = { ConfigValidator };