UNPKG

@moikas/code-audit-mcp

Version:

AI-powered code auditing via MCP using local Ollama models for security, performance, and quality analysis

457 lines 14.7 kB
/** * Configuration utility for Code Audit MCP * Provides type-safe configuration management with global and project-specific overrides */ import Conf from 'conf'; import { join } from 'path'; import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs'; import { homedir } from 'os'; /** * Custom error class for configuration issues */ export class ConfigError extends Error { configPath; constructor(message, configPath) { super(message); this.configPath = configPath; this.name = 'ConfigError'; } } /** * Default configuration */ const DEFAULT_CONFIG = { ollama: { host: 'http://localhost:11434', timeout: 30000, models: { primary: 'codellama:7b', fallback: ['granite-code:8b', 'qwen2.5-coder:7b'], }, }, audit: { rules: { security: true, performance: true, quality: true, documentation: true, testing: true, architecture: true, completeness: true, }, output: { format: 'json', includeMetrics: true, verbosity: 'normal', }, filters: { excludePatterns: [ 'node_modules/**', 'dist/**', 'build/**', '**/*.min.js', '**/*.d.ts', 'coverage/**', '.git/**', ], includePatterns: [ '**/*.ts', '**/*.js', '**/*.jsx', '**/*.tsx', '**/*.py', '**/*.java', '**/*.go', '**/*.rs', ], maxFileSize: 1048576, // 1MB }, }, server: { port: 3000, transport: 'stdio', logLevel: 'info', shutdown: { gracefulTimeout: 5000, forceTimeout: 10000, }, }, updates: { checkInterval: 86400000, // 24 hours autoUpdate: false, prerelease: false, }, telemetry: { enabled: false, anonymousId: '', }, }; /** * Configuration manager class */ class ConfigManager { globalConfig; projectConfigPath = null; projectConfig = null; configVersion = '1.0.0'; constructor() { // Initialize global configuration using 'conf' package this.globalConfig = new Conf({ projectName: 'code-audit-mcp', projectVersion: this.configVersion, defaults: DEFAULT_CONFIG, configName: 'config', configFileMode: 0o600, // Secure file permissions serialize: (value) => JSON.stringify(value, null, 2), // Note: schema validation disabled for now due to complexity }); this.initializeGlobalConfig(); this.detectProjectConfig(); } /** * Get configuration schema for validation */ getConfigSchema() { return { type: 'object', properties: { ollama: { type: 'object', properties: { host: { type: 'string' }, timeout: { type: 'number', minimum: 1000 }, models: { type: 'object', properties: { primary: { type: 'string' }, fallback: { type: 'array', items: { type: 'string' } }, }, required: ['primary', 'fallback'], }, }, required: ['host', 'timeout', 'models'], }, audit: { type: 'object', properties: { rules: { type: 'object', properties: { security: { type: 'boolean' }, performance: { type: 'boolean' }, quality: { type: 'boolean' }, documentation: { type: 'boolean' }, testing: { type: 'boolean' }, architecture: { type: 'boolean' }, completeness: { type: 'boolean' }, }, required: [ 'security', 'performance', 'quality', 'documentation', 'testing', 'architecture', 'completeness', ], }, }, }, }, }; } /** * Initialize global configuration directory and files */ initializeGlobalConfig() { const configDir = join(homedir(), '.code-audit'); if (!existsSync(configDir)) { mkdirSync(configDir, { recursive: true, mode: 0o755 }); } // Generate anonymous telemetry ID if not exists if (!this.globalConfig.get('telemetry.anonymousId')) { const anonymousId = this.generateAnonymousId(); this.globalConfig.set('telemetry.anonymousId', anonymousId); } } /** * Generate anonymous telemetry ID */ generateAnonymousId() { return ('audit_' + Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15)); } /** * Detect project-specific configuration */ detectProjectConfig() { const cwd = process.cwd(); const possiblePaths = [ join(cwd, '.code-audit.json'), join(cwd, '.code-audit', 'config.json'), join(cwd, 'package.json'), // Check for config in package.json ]; for (const configPath of possiblePaths) { if (existsSync(configPath)) { try { let config = null; if (configPath.endsWith('package.json')) { const packageJson = JSON.parse(readFileSync(configPath, 'utf8')); config = packageJson['code-audit']; } else { config = JSON.parse(readFileSync(configPath, 'utf8')); } if (config) { this.projectConfigPath = configPath; this.projectConfig = config; break; } } catch { // Skip invalid config files } } } } /** * Get merged configuration (global + project overrides) */ getConfig() { const globalConfig = this.globalConfig.store; if (this.projectConfig) { return this.mergeConfigs(globalConfig, this.projectConfig); } return globalConfig; } /** * Get specific configuration value with support for nested keys */ get(key) { const config = this.getConfig(); return this.getNestedValue(config, key); } /** * Set configuration value with support for nested keys */ set(key, value, isGlobal = true) { this.validateConfigValue(key, value); if (isGlobal) { this.setNestedValue(this.globalConfig.store, key, value); this.globalConfig.store = { ...this.globalConfig.store }; } else { throw new Error('Project-specific configuration setting not yet implemented'); } } /** * Reset configuration to defaults */ async reset(confirmCallback) { if (confirmCallback && !(await confirmCallback())) { return false; } this.globalConfig.clear(); this.globalConfig.store = { ...DEFAULT_CONFIG }; this.initializeGlobalConfig(); return true; } /** * Get configuration file paths */ getConfigPaths() { return { global: this.globalConfig.path, ...(this.projectConfigPath && { project: this.projectConfigPath }), }; } /** * Validate configuration value */ validateConfigValue(key, value) { // Basic validation for common configuration keys const validations = { 'ollama.host': (val) => typeof val === 'string' && val.length > 0, 'ollama.timeout': (val) => typeof val === 'number' && val >= 1000, 'audit.output.format': (val) => typeof val === 'string' && ['json', 'markdown', 'html'].includes(val), 'audit.output.verbosity': (val) => typeof val === 'string' && ['minimal', 'normal', 'detailed'].includes(val), 'server.transport': (val) => typeof val === 'string' && ['stdio', 'http'].includes(val), 'server.logLevel': (val) => typeof val === 'string' && ['error', 'warn', 'info', 'debug'].includes(val), 'server.port': (val) => typeof val === 'number' && val >= 1 && val <= 65535, }; const validator = validations[key]; if (validator && !validator(value)) { throw new Error(`Invalid value for configuration key '${key}': ${value}`); } } /** * Merge configurations with deep merge */ mergeConfigs(global, project) { const result = { ...global }; // Type-safe merge if (project.ollama) { result.ollama = { ...result.ollama, ...project.ollama }; } if (project.audit) { result.audit = { ...result.audit, rules: project.audit.rules ? { ...result.audit.rules, ...project.audit.rules } : result.audit.rules, output: project.audit.output ? { ...result.audit.output, ...project.audit.output } : result.audit.output, filters: project.audit.filters ? { ...result.audit.filters, ...project.audit.filters } : result.audit.filters, }; } if (project.server) { result.server = { ...result.server, ...project.server }; } if (project.updates) { result.updates = { ...result.updates, ...project.updates }; } if (project.telemetry) { result.telemetry = { ...result.telemetry, ...project.telemetry }; } return result; } /** * Get nested configuration value */ getNestedValue(obj, path) { return path .split('.') .reduce((current, key) => current?.[key], obj); } /** * Set nested configuration value */ setNestedValue(obj, path, value) { const keys = path.split('.'); const lastKey = keys.pop(); const target = keys.reduce((current, key) => { const curr = current; if (!curr[key] || typeof curr[key] !== 'object') { curr[key] = {}; } return curr[key]; }, obj); target[lastKey] = value; } /** * Migrate configuration to newer version if needed */ async migrateConfig() { const currentVersion = this.globalConfig.get('$version') || '1.0.0'; if (currentVersion === this.configVersion) { return false; // No migration needed } // Add migration logic here for future versions this.globalConfig.set('$version', this.configVersion); return true; } /** * Export configuration for backup */ exportConfig() { return { global: this.globalConfig.store, ...(this.projectConfig && { project: this.projectConfig }), }; } /** * Import configuration from backup */ importConfig(config) { this.globalConfig.store = config.global; if (config.project && this.projectConfigPath) { writeFileSync(this.projectConfigPath, JSON.stringify(config.project, null, 2)); this.projectConfig = config.project; } } /** * Check if configuration is valid */ validateConfig() { const errors = []; const config = this.getConfig(); try { // Check required fields if (!config.ollama?.host) { errors.push('ollama.host is required'); } if (!config.ollama?.models?.primary) { errors.push('ollama.models.primary is required'); } // Check value ranges if (config.ollama?.timeout && config.ollama.timeout < 1000) { errors.push('ollama.timeout must be at least 1000ms'); } if (config.server?.port && (config.server.port < 1 || config.server.port > 65535)) { errors.push('server.port must be between 1 and 65535'); } } catch (error) { errors.push(`Configuration validation failed: ${error instanceof Error ? error.message : 'Unknown error'}`); } return { isValid: errors.length === 0, errors, }; } } // Singleton instance let configManager = null; /** * Get configuration manager instance */ export function getConfigManager() { if (!configManager) { configManager = new ConfigManager(); } return configManager; } /** * Get merged configuration */ export async function getConfig() { return getConfigManager().getConfig(); } /** * Get specific configuration value */ export async function getConfigValue(key) { return getConfigManager().get(key); } /** * Set configuration value */ export async function setConfigValue(key, value, isGlobal = true) { return getConfigManager().set(key, value, isGlobal); } /** * Reset configuration to defaults */ export async function resetConfig(confirmCallback) { return getConfigManager().reset(confirmCallback); } /** * Get default configuration */ export function getDefaultConfig() { return JSON.parse(JSON.stringify(DEFAULT_CONFIG)); } /** * Check if project has local configuration */ export function hasProjectConfig() { return getConfigManager().getConfigPaths().project !== undefined; } //# sourceMappingURL=config.js.map