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
232 lines • 7.53 kB
JavaScript
/**
* Configuration Loader
*
* Manages loading, validating, and merging AIWG CLI configurations from multiple sources.
* Supports .aiwgrc.json, package.json, and command-line overrides.
*/
import { readFile, access } from 'fs/promises';
import { existsSync } from 'fs';
import { resolve, dirname } from 'path';
import { constants } from 'fs';
/**
* Configuration Loader class
*/
export class ConfigLoader {
configCache = null;
/**
* Load configuration from file or defaults
*/
async load(configPath) {
// If cached, return it
if (this.configCache) {
return this.configCache;
}
// Start with defaults
let config = this.getDefaults();
// Try to load from .aiwgrc.json
if (configPath) {
const fileConfig = await this.loadFromFile(configPath);
if (fileConfig) {
config = this.merge([config, fileConfig]);
}
}
else {
// Look for .aiwgrc.json in current directory and parent directories
const foundConfig = await this.findConfigFile(process.cwd());
if (foundConfig) {
config = this.merge([config, foundConfig]);
}
}
// Try to load from package.json
const pkgPath = resolve(process.cwd(), 'package.json');
if (existsSync(pkgPath)) {
const pkgConfig = await this.loadFromPackageJson(pkgPath);
if (pkgConfig) {
config = this.merge([config, pkgConfig]);
}
}
// Validate merged config
const validation = this.validate(config);
if (!validation.valid) {
throw new Error(`Invalid configuration: ${validation.errors.join(', ')}`);
}
// Cache it
this.configCache = config;
return config;
}
/**
* Load configuration from .aiwgrc.json file
*/
async loadFromFile(filePath) {
try {
await access(filePath, constants.R_OK);
const content = await readFile(filePath, 'utf-8');
const config = JSON.parse(content);
return config;
}
catch (error) {
if (error.code === 'ENOENT') {
return null;
}
throw new Error(`Failed to load config from ${filePath}: ${error}`);
}
}
/**
* Load configuration from package.json "aiwg" field
*/
async loadFromPackageJson(pkgPath) {
try {
const content = await readFile(pkgPath, 'utf-8');
const pkg = JSON.parse(content);
return pkg.aiwg || null;
}
catch {
return null;
}
}
/**
* Find config file by walking up directory tree
*/
async findConfigFile(startDir) {
let currentDir = startDir;
const root = resolve('/');
while (currentDir !== root) {
const configPath = resolve(currentDir, '.aiwgrc.json');
if (existsSync(configPath)) {
return await this.loadFromFile(configPath);
}
const parentDir = dirname(currentDir);
if (parentDir === currentDir) {
break; // Reached root
}
currentDir = parentDir;
}
return null;
}
/**
* Merge multiple partial configs into one complete config
*/
merge(configs) {
const result = this.getDefaults();
for (const config of configs) {
if (config.version)
result.version = config.version;
if (config.validation) {
result.validation = { ...result.validation, ...config.validation };
}
if (config.optimization) {
result.optimization = { ...result.optimization, ...config.optimization };
}
if (config.output) {
result.output = { ...result.output, ...config.output };
}
if (config.watch) {
result.watch = { ...result.watch, ...config.watch };
}
if (config.hooks) {
result.hooks = { ...result.hooks, ...config.hooks };
}
}
return result;
}
/**
* Validate configuration object
*/
validate(config) {
const errors = [];
const warnings = [];
// Validate version
if (!config.version || !config.version.match(/^\d+\.\d+$/)) {
errors.push('Invalid version format (expected "1.0")');
}
// Validate validation config
if (config.validation.threshold < 0 || config.validation.threshold > 100) {
errors.push('Validation threshold must be between 0 and 100');
}
if (config.validation.context &&
!['academic', 'technical', 'executive', 'casual'].includes(config.validation.context)) {
errors.push('Invalid validation context');
}
// Validate optimization config
const validStrategies = ['specificity', 'examples', 'constraints', 'voice', 'anti_pattern'];
for (const strategy of config.optimization.strategies) {
if (!validStrategies.includes(strategy)) {
warnings.push(`Unknown optimization strategy: ${strategy}`);
}
}
// Validate output config
if (!['text', 'json', 'html', 'junit'].includes(config.output.format)) {
errors.push('Invalid output format (must be: text, json, html, or junit)');
}
// Validate watch config
if (config.watch.debounce < 0) {
errors.push('Watch debounce must be >= 0');
}
if (config.watch.patterns.length === 0) {
warnings.push('No watch patterns specified');
}
return {
valid: errors.length === 0,
errors,
warnings
};
}
/**
* Get default configuration
*/
getDefaults() {
return {
version: '1.0',
validation: {
enabled: true,
threshold: 70,
context: undefined,
failOnCritical: true,
rules: []
},
optimization: {
enabled: true,
autoApply: false,
strategies: ['specificity', 'examples', 'constraints', 'anti_pattern'],
createBackup: true
},
output: {
format: 'text',
destination: undefined,
verbose: false,
colors: true
},
watch: {
enabled: false,
patterns: ['**/*.md', '**/*.txt'],
debounce: 500,
ignorePatterns: ['**/node_modules/**', '**/.git/**', '**/dist/**']
},
hooks: {
preCommit: false,
prePush: false,
hookPath: '.git/hooks'
}
};
}
/**
* Generate example config file content
*/
generateExample() {
const example = this.getDefaults();
return JSON.stringify(example, null, 2);
}
/**
* Clear cached configuration
*/
clearCache() {
this.configCache = null;
}
/**
* Override specific config values
*/
override(config, overrides) {
return this.merge([config, overrides]);
}
}
//# sourceMappingURL=config-loader.js.map