BMAD-METHOD/tools/claude-code-hooks/lib/config.js

147 lines
3.8 KiB
JavaScript

/**
* Configuration management for BMAD hooks
* Uses only Node.js built-in modules
*/
const fs = require('fs').promises;
const path = require('path');
const { homedir } = require('os');
class Config {
constructor() {
this.defaults = {
enabled: true,
hooks: {
contextLoader: true,
writeValidator: true,
progressTracker: true,
sessionSummary: true
},
quality: {
strictMode: true,
allowMocksInTests: true,
minQualityScore: 'B'
},
performance: {
cacheEnabled: true,
cacheTTL: 300000, // 5 minutes
maxContextLength: 2000,
maxExecutionTime: 500
},
modes: {
current: 'balanced' // strict, balanced, relaxed
}
};
this.presets = {
strict: {
quality: {
strictMode: true,
allowMocksInTests: false,
minQualityScore: 'A'
}
},
balanced: {
quality: {
strictMode: true,
allowMocksInTests: true,
minQualityScore: 'B'
}
},
relaxed: {
quality: {
strictMode: false,
allowMocksInTests: true,
minQualityScore: 'C'
}
}
};
}
async load() {
try {
// Check for local project config
const localConfig = await this.loadFile('.bmad-hooks.json');
if (localConfig) return localConfig;
// Check for bmad-config.json in .claude directory (avoids validation errors)
const bmadConfigPath = path.join(process.cwd(), '.claude', 'bmad-config.json');
const bmadConfig = await this.loadFile(bmadConfigPath);
if (bmadConfig) {
return this.mergeWithDefaults(bmadConfig);
}
// Check project-level Claude Code settings (legacy support)
const projectSettingsPath = path.join(process.cwd(), '.claude', 'settings.json');
const projectSettings = await this.loadFile(projectSettingsPath);
if (projectSettings && projectSettings['bmad-hooks']) {
return this.mergeWithDefaults(projectSettings['bmad-hooks']);
}
// Fall back to global Claude Code settings
const globalSettingsPath = path.join(homedir(), '.claude', 'settings.json');
const globalSettings = await this.loadFile(globalSettingsPath);
if (globalSettings && globalSettings['bmad-hooks']) {
return this.mergeWithDefaults(globalSettings['bmad-hooks']);
}
return this.defaults;
} catch (error) {
return this.defaults;
}
}
async loadFile(filePath) {
try {
const content = await fs.readFile(filePath, 'utf8');
return JSON.parse(content);
} catch (error) {
return null;
}
}
mergeWithDefaults(config) {
// Apply preset if specified
const presetName = config.preset || (config.modes && config.modes.current);
if (presetName && this.presets[presetName]) {
config = this.deepMerge(config, this.presets[presetName]);
}
return this.deepMerge(this.defaults, config);
}
deepMerge(target, source) {
const result = { ...target };
for (const key in source) {
if (source[key] && typeof source[key] === 'object' && !Array.isArray(source[key])) {
result[key] = this.deepMerge(result[key] || {}, source[key]);
} else {
result[key] = source[key];
}
}
return result;
}
// Runtime control commands support
static async handleCommand(command) {
if (command === '*hooks-disable') {
process.env.BMAD_HOOKS_DISABLED = 'true';
return true;
}
if (command === '*hooks-enable') {
delete process.env.BMAD_HOOKS_DISABLED;
return true;
}
return false;
}
static isDisabled() {
return process.env.BMAD_HOOKS_DISABLED === 'true';
}
}
module.exports = Config;