UNPKG

aiwg

Version:

Deployment tool and support utility for AI context. Copies agents, skills, commands, rules, and behaviors into the paths each AI platform reads (Claude Code, Codex, Copilot, Cursor, Warp, OpenClaw, and 6 more) so one source of truth works across 10 platfo

235 lines 8.31 kB
/** * FrameworkConfigLoader - Load and merge framework configurations * * Handles loading framework-specific configs, merging with shared configs, * and managing configuration precedence. Supports JSON and YAML formats. * * FID-007 Framework-Scoped Workspaces configuration management. * * @module src/plugin/framework-config-loader * @version 1.0.0 * @since 2025-10-23 */ import * as fs from 'fs/promises'; import * as path from 'path'; // =========================== // FrameworkConfigLoader Class // =========================== export class FrameworkConfigLoader { projectRoot; configCache = new Map(); constructor(projectRoot) { this.projectRoot = path.resolve(projectRoot); } /** * Load framework configuration * * @param framework - Framework name * @param options - Load options * @returns Merged configuration */ async loadConfig(framework, options = {}) { const cacheKey = `${framework}-${options.environment || 'default'}`; // Check cache if (this.configCache.has(cacheKey)) { return this.configCache.get(cacheKey); } // Load shared config const sharedConfig = await this.loadSharedConfig(); // Load framework-specific config const frameworkConfig = await this.loadFrameworkConfig(framework); // Load environment-specific config let envConfig = {}; if (options.environment) { envConfig = await this.loadEnvironmentConfig(framework, options.environment); } // Load local overrides let localConfig = {}; if (options.includeLocal) { localConfig = await this.loadLocalConfig(framework); } // Merge configs (later configs override earlier) let merged = this.deepMerge(sharedConfig, frameworkConfig, envConfig, localConfig); // Apply merge strategy if requested if (options.respectMergeStrategy && sharedConfig.mergeStrategy) { merged = this.applyMergeStrategy(merged, sharedConfig.mergeStrategy); } // Cache result this.configCache.set(cacheKey, merged); return merged; } /** * Get specific config value * * @param framework - Framework name * @param key - Config key (supports dot notation) * @param defaultValue - Default value if key not found * @returns Config value */ getConfigValue(framework, key, defaultValue) { const config = this.configCache.get(`${framework}-default`); if (!config) { return defaultValue; } const keys = key.split('.'); let value = config; for (const k of keys) { if (value && typeof value === 'object' && k in value) { value = value[k]; } else { return defaultValue; } } return value !== undefined ? value : defaultValue; } /** * Save framework configuration * * @param framework - Framework name * @param config - Configuration to save */ async saveConfig(framework, config) { const configPath = path.join(this.projectRoot, '.aiwg', framework, 'settings.json'); // Ensure directory exists await fs.mkdir(path.dirname(configPath), { recursive: true }); // Write config await fs.writeFile(configPath, JSON.stringify(config, null, 2)); // Invalidate cache this.configCache.delete(`${framework}-default`); } // =========================== // Private Methods // =========================== async loadSharedConfig() { const workspacePath = path.join(this.projectRoot, '.aiwg', 'workspace.json'); try { const content = await fs.readFile(workspacePath, 'utf-8'); const workspace = JSON.parse(content); return workspace.shared || {}; } catch { return {}; } } async loadFrameworkConfig(framework) { // Try JSON first const jsonPath = path.join(this.projectRoot, '.aiwg', framework, 'settings.json'); try { const content = await fs.readFile(jsonPath, 'utf-8'); return JSON.parse(content); } catch { // Try YAML const yamlPath = path.join(this.projectRoot, '.aiwg', framework, 'config.yaml'); try { const content = await fs.readFile(yamlPath, 'utf-8'); return this.parseSimpleYaml(content); } catch { // Return default config return { framework }; } } } async loadEnvironmentConfig(framework, environment) { const envPath = path.join(this.projectRoot, '.aiwg', framework, `settings.${environment}.json`); try { const content = await fs.readFile(envPath, 'utf-8'); return JSON.parse(content); } catch { return {}; } } async loadLocalConfig(framework) { const localPath = path.join(this.projectRoot, '.aiwg', framework, 'settings.local.json'); try { const content = await fs.readFile(localPath, 'utf-8'); return JSON.parse(content); } catch { return {}; } } deepMerge(...objects) { const result = {}; for (const obj of objects) { if (!obj) continue; for (const key in obj) { if (obj.hasOwnProperty(key)) { const value = obj[key]; if (value !== null && typeof value === 'object' && !Array.isArray(value)) { // Merge objects recursively result[key] = this.deepMerge(result[key] || {}, value); } else { // Override with new value result[key] = value; } } } } return result; } applyMergeStrategy(config, strategy) { // Apply merge strategies (e.g., append arrays instead of override) const result = { ...config }; for (const key in strategy) { if (strategy[key] === 'append' && Array.isArray(config[key])) { // Append strategy already handled in initial merge // This is a placeholder for more complex strategies } } return result; } parseSimpleYaml(content) { const result = {}; const lines = content.split('\n'); let currentKey = null; for (const line of lines) { const trimmed = line.trim(); if (!trimmed || trimmed.startsWith('#')) continue; // Check for array items if (trimmed.startsWith('- ')) { const value = trimmed.slice(2).trim(); if (currentKey) { if (!Array.isArray(result[currentKey])) { result[currentKey] = []; } result[currentKey].push(value); } continue; } const colonIndex = trimmed.indexOf(':'); if (colonIndex === -1) continue; const key = trimmed.slice(0, colonIndex).trim(); const value = trimmed.slice(colonIndex + 1).trim(); if (value) { // Simple key-value - handle booleans and numbers if (value === 'true') { result[key] = true; } else if (value === 'false') { result[key] = false; } else if (!isNaN(Number(value))) { result[key] = Number(value); } else { result[key] = value; } currentKey = null; } else { // Key with no value - next lines will be array items or nested object currentKey = key; } } return result; } } //# sourceMappingURL=framework-config-loader.js.map