UNPKG

ai-github-changelog-generator-cli-mcp

Version:

AI-powered changelog generator with MCP server support - works with OpenAI GPT-4.1 and Azure OpenAI with reasoning models

424 lines (362 loc) 14.9 kB
#!/usr/bin/env node /** * Configuration Management for AI Changelog Generator * Handles configuration loading, validation, and defaults * Updated for GPT-4.1 series and Azure OpenAI v1 API */ const fs = require('fs'); const path = require('path'); class ConfigManager { constructor(configPath = null) { this.configPath = configPath || this.findConfigFile(); this.config = this.loadConfig(); this.validate(); } findConfigFile() { const possiblePaths = [ '.env.local', '.changelog.config.js', 'changelog.config.json', path.join(process.cwd(), '.env.local') ]; for (const configPath of possiblePaths) { if (fs.existsSync(configPath)) { return configPath; } } return '.env.local'; // Default } loadConfig() { const defaults = { // AI Provider Settings AI_PROVIDER: process.env.AI_PROVIDER || 'auto', OPENAI_API_KEY: process.env.OPENAI_API_KEY, // Azure OpenAI Settings AZURE_OPENAI_ENDPOINT: process.env.AZURE_OPENAI_ENDPOINT, AZURE_OPENAI_KEY: process.env.AZURE_OPENAI_KEY, AZURE_OPENAI_DEPLOYMENT_NAME: process.env.AZURE_OPENAI_DEPLOYMENT_NAME || 'gpt-4.1', // Updated to current API version AZURE_OPENAI_API_VERSION: process.env.AZURE_OPENAI_API_VERSION || '2025-04-01-preview', AZURE_OPENAI_USE_V1_API: process.env.AZURE_OPENAI_USE_V1_API !== 'false', // Legacy API version for backward compatibility AZURE_OPENAI_LEGACY_API_VERSION: process.env.AZURE_OPENAI_LEGACY_API_VERSION || '2024-10-21', // Model Configuration - Updated GPT-4.1 Series AI_MODEL: process.env.AI_MODEL || 'gpt-4.1', AI_MODEL_SIMPLE: process.env.AI_MODEL_SIMPLE || 'gpt-4.1-mini', AI_MODEL_COMPLEX: process.env.AI_MODEL_COMPLEX || 'gpt-4.1', AI_MODEL_REASONING: process.env.AI_MODEL_REASONING || 'o4-mini', // Latest compact reasoning model AI_MODEL_ADVANCED_REASONING: process.env.AI_MODEL_ADVANCED_REASONING || 'o4', // Latest advanced reasoning model AI_MODEL_REASONING_LEGACY: process.env.AI_MODEL_REASONING_LEGACY || 'o3-mini', // Previous generation compact AI_MODEL_ADVANCED_REASONING_LEGACY: process.env.AI_MODEL_ADVANCED_REASONING_LEGACY || 'o3', // Previous generation advanced AI_MODEL_NANO: process.env.AI_MODEL_NANO || 'gpt-4.1-nano', // Changelog Settings CHANGELOG_FILE: process.env.CHANGELOG_FILE || 'AI_CHANGELOG.md', CHANGELOG_FORMAT: process.env.CHANGELOG_FORMAT || 'standard', DEFAULT_COMMIT_COUNT: parseInt(process.env.DEFAULT_COMMIT_COUNT || '20'), // Output Settings INCLUDE_COMMIT_HASH: process.env.INCLUDE_COMMIT_HASH !== 'false', INCLUDE_AUTHOR: process.env.INCLUDE_AUTHOR !== 'false', INCLUDE_DATE: process.env.INCLUDE_DATE !== 'false', GROUP_BY_TYPE: process.env.GROUP_BY_TYPE !== 'false', // Git Settings EXCLUDE_MERGE_COMMITS: process.env.EXCLUDE_MERGE_COMMITS !== 'false', EXCLUDE_WIP_COMMITS: process.env.EXCLUDE_WIP_COMMITS !== 'false', // AI Settings AI_TEMPERATURE: parseFloat(process.env.AI_TEMPERATURE || '0.3'), AI_MAX_TOKENS: parseInt(process.env.AI_MAX_TOKENS || '1000'), AI_TIMEOUT: parseInt(process.env.AI_TIMEOUT || '30000'), // Performance Settings ENABLE_PROMPT_CACHING: process.env.ENABLE_PROMPT_CACHING !== 'false', AI_MODEL_SELECTION_STRATEGY: process.env.AI_MODEL_SELECTION_STRATEGY || 'adaptive', // MCP Server Settings MCP_SERVER_PORT: parseInt(process.env.MCP_SERVER_PORT || '3000'), MCP_SERVER_TIMEOUT: parseInt(process.env.MCP_SERVER_TIMEOUT || '30000') }; // Load from config file if it exists if (this.configPath && fs.existsSync(this.configPath)) { if (this.configPath.endsWith('.json')) { try { const fileConfig = JSON.parse(fs.readFileSync(this.configPath, 'utf8')); return { ...defaults, ...fileConfig }; } catch (error) { console.warn(`⚠️ Failed to parse JSON config file: ${error.message}`); } } else if (this.configPath.endsWith('.js')) { try { const fileConfig = require(path.resolve(this.configPath)); return { ...defaults, ...fileConfig }; } catch (error) { console.warn(`⚠️ Failed to load JS config file: ${error.message}`); } } } return defaults; } validate() { const errors = []; const warnings = []; // Validate AI provider configuration if (this.config.AI_PROVIDER === 'openai' && !this.config.OPENAI_API_KEY) { errors.push('OPENAI_API_KEY is required when AI_PROVIDER is set to "openai"'); } if (this.config.AI_PROVIDER === 'azure') { if (!this.config.AZURE_OPENAI_ENDPOINT) { errors.push('AZURE_OPENAI_ENDPOINT is required when AI_PROVIDER is set to "azure"'); } if (!this.config.AZURE_OPENAI_KEY) { errors.push('AZURE_OPENAI_KEY is required when AI_PROVIDER is set to "azure"'); } } // Validate numeric values if (this.config.DEFAULT_COMMIT_COUNT < 1) { errors.push('DEFAULT_COMMIT_COUNT must be a positive integer'); } if (this.config.AI_TEMPERATURE < 0 || this.config.AI_TEMPERATURE > 2) { errors.push('AI_TEMPERATURE must be between 0 and 2'); } if (this.config.AI_MAX_TOKENS < 1) { errors.push('AI_MAX_TOKENS must be a positive integer'); } // Check for outdated API versions if (this.config.AZURE_OPENAI_API_VERSION && this.config.AZURE_OPENAI_API_VERSION < '2025-04-01-preview') { warnings.push(`AZURE_OPENAI_API_VERSION is outdated. Current recommended: 2025-04-01-preview`); } // Check model availability warnings if (this.config.AI_MODEL && !this.config.AI_MODEL.includes('gpt-4.1')) { warnings.push('Consider using GPT-4.1 series for better coding performance and 75% cost reduction with prompt caching'); } // Provide helpful suggestions if (this.config.AZURE_OPENAI_ENDPOINT && !this.config.AZURE_OPENAI_USE_V1_API) { warnings.push('Consider enabling AZURE_OPENAI_USE_V1_API=true for always up-to-date API features'); } if (errors.length > 0) { console.error('❌ Configuration errors:'); errors.forEach(error => console.error(` - ${error}`)); console.error(' Please fix these errors before continuing.\n'); } if (warnings.length > 0) { console.warn('⚠️ Configuration suggestions:'); warnings.forEach(warning => console.warn(` - ${warning}`)); console.warn(' Consider updating for better performance.\n'); } return errors.length === 0; } get(key) { return this.config[key]; } getAll() { return { ...this.config }; } isAIAvailable() { return !!(this.config.OPENAI_API_KEY || (this.config.AZURE_OPENAI_ENDPOINT && this.config.AZURE_OPENAI_KEY)); } getOptimalModelConfig() { const provider = this.config.AI_PROVIDER; const hasAzure = !!(this.config.AZURE_OPENAI_ENDPOINT && this.config.AZURE_OPENAI_KEY); const hasOpenAI = !!this.config.OPENAI_API_KEY; if (provider === 'azure' || (provider === 'auto' && hasAzure)) { return { provider: 'azure', models: { nano: this.config.AI_MODEL_NANO, simple: this.config.AI_MODEL_SIMPLE, default: this.config.AI_MODEL, complex: this.config.AI_MODEL_COMPLEX, reasoning: this.config.AI_MODEL_REASONING, advanced_reasoning: this.config.AI_MODEL_ADVANCED_REASONING, reasoning_legacy: this.config.AI_MODEL_REASONING_LEGACY, advanced_reasoning_legacy: this.config.AI_MODEL_ADVANCED_REASONING_LEGACY }, features: { reasoning: true, promptCaching: true, largeContext: true } }; } if (provider === 'openai' || (provider === 'auto' && hasOpenAI)) { return { provider: 'openai', models: { nano: this.config.AI_MODEL_NANO, simple: this.config.AI_MODEL_SIMPLE, default: this.config.AI_MODEL, complex: this.config.AI_MODEL_COMPLEX }, features: { reasoning: false, // o-series not available on OpenAI promptCaching: this.config.AI_MODEL.includes('gpt-4.1'), largeContext: this.config.AI_MODEL.includes('gpt-4.1') } }; } return null; } createSampleConfig() { const sampleConfig = `# AI Changelog Generator Configuration # Copy this to .env.local and fill in your values # AI Provider (auto, openai, azure) AI_PROVIDER=auto # OpenAI Configuration OPENAI_API_KEY=your-openai-api-key-here # Azure OpenAI Configuration - Updated June 2025 AZURE_OPENAI_ENDPOINT=https://your-resource.openai.azure.com AZURE_OPENAI_KEY=your-azure-openai-key-here AZURE_OPENAI_DEPLOYMENT_NAME=gpt-4.1 AZURE_OPENAI_API_VERSION=2025-04-01-preview AZURE_OPENAI_USE_V1_API=true # Model Configuration - GPT-4.1 Series (June 2025) # Default: GPT-4.1 (1M token context, 21% better coding, 26% cheaper) AI_MODEL=gpt-4.1 # Simple: GPT-4.1 Mini (efficient for small changes) AI_MODEL_SIMPLE=gpt-4.1-mini # Complex: GPT-4.1 (best for large refactors) AI_MODEL_COMPLEX=gpt-4.1 # Reasoning: o4-mini (latest compact reasoning, Azure-only) AI_MODEL_REASONING=o4-mini # Advanced Reasoning: o4 (latest advanced reasoning, Azure-only) AI_MODEL_ADVANCED_REASONING=o4 # Legacy Reasoning: o3-mini (previous generation compact, Azure-only) AI_MODEL_REASONING_LEGACY=o3-mini # Legacy Advanced Reasoning: o3 (previous generation advanced, Azure-only) AI_MODEL_ADVANCED_REASONING_LEGACY=o3 # Nano: GPT-4.1 Nano (ultra-efficient for minimal changes) AI_MODEL_NANO=gpt-4.1-nano # Changelog Settings CHANGELOG_FILE=AI_CHANGELOG.md CHANGELOG_FORMAT=standard DEFAULT_COMMIT_COUNT=20 # Output Settings INCLUDE_COMMIT_HASH=true INCLUDE_AUTHOR=true INCLUDE_DATE=true GROUP_BY_TYPE=true # Git Settings EXCLUDE_MERGE_COMMITS=true EXCLUDE_WIP_COMMITS=true # AI Settings AI_TEMPERATURE=0.3 AI_MAX_TOKENS=1000 AI_TIMEOUT=30000 # Performance Settings ENABLE_PROMPT_CACHING=true AI_MODEL_SELECTION_STRATEGY=adaptive # MCP Server Settings MCP_SERVER_PORT=3000 MCP_SERVER_TIMEOUT=30000 `; fs.writeFileSync('.env.local.example', sampleConfig); console.log('✅ Sample configuration created: .env.local.example'); console.log('💡 Copy this file to .env.local and update with your values'); console.log('🚀 Now using GPT-4.1 series with 1M token context and enhanced capabilities'); console.log('💰 Enable prompt caching for 75% cost reduction on repeated content'); console.log('🧠 Use Azure OpenAI for reasoning models (o3/o3-mini, o4/o4-mini) and advanced features'); } // New method to get model recommendations based on commit characteristics getModelRecommendation(commitInfo = {}) { const config = this.getOptimalModelConfig(); if (!config) return null; const { files = 0, lines = 0, breaking = false, complex = false } = commitInfo; // Very large architectural changes if (files > 100 || lines > 5000 || (breaking && files > 20)) { if (config.features.reasoning) { return { model: config.models.advanced_reasoning, reason: 'Large architectural change - using advanced reasoning model', features: ['reasoning', 'enhanced safety', 'architectural analysis'] }; } return { model: config.models.complex, reason: 'Large change - using complex model', features: ['large context', 'detailed analysis'] }; } // Complex changes if (files > 20 || lines > 1000 || complex) { if (config.features.reasoning && (breaking || complex)) { return { model: config.models.reasoning, reason: 'Complex change with breaking/architectural elements', features: ['reasoning', 'pattern analysis'] }; } return { model: config.models.complex, reason: 'Complex change requiring detailed analysis', features: ['full capabilities', 'context understanding'] }; } // Simple changes if (files < 5 && lines < 200) { return { model: config.models.simple, reason: 'Simple change - using efficient model', features: ['cost effective', 'fast processing'] }; } // Minimal changes if (files < 3 && lines < 50) { return { model: config.models.nano, reason: 'Minimal change - using ultra-efficient model', features: ['ultra efficient', '90% cost savings'] }; } // Default return { model: config.models.default, reason: 'Standard change analysis', features: ['balanced performance', 'optimal for most cases'] }; } } module.exports = ConfigManager; // CLI usage if (require.main === module) { const command = process.argv[2]; if (command === 'create-sample') { const config = new ConfigManager(); config.createSampleConfig(); } else if (command === 'validate') { const config = new ConfigManager(); console.log('🔍 Validating configuration...'); if (config.validate()) { console.log('✅ Configuration is valid'); // Show optimal configuration const optimal = config.getOptimalModelConfig(); if (optimal) { console.log(`\n🎯 Optimal Configuration:`); console.log(` Provider: ${optimal.provider.toUpperCase()}`); console.log(` Features: ${Object.entries(optimal.features).filter(([k,v]) => v).map(([k]) => k).join(', ')}`); } } else { console.log('❌ Configuration has issues (see errors above)'); } } else if (command === 'model-test') { const config = new ConfigManager(); const testCases = [ { files: 1, lines: 20, description: 'Minor fix' }, { files: 8, lines: 150, description: 'Feature addition' }, { files: 25, lines: 800, description: 'Major refactor' }, { files: 120, lines: 5000, breaking: true, description: 'Breaking architecture change' } ]; console.log('🎯 Model Recommendations:'); testCases.forEach(testCase => { const rec = config.getModelRecommendation(testCase); console.log(`\n📝 ${testCase.description}:`); console.log(` Model: ${rec?.model || 'none'}`); console.log(` Reason: ${rec?.reason || 'no recommendation'}`); if (rec?.features) { console.log(` Features: ${rec.features.join(', ')}`); } }); } else { console.log('Usage:'); console.log(' node config.js create-sample # Create sample config file'); console.log(' node config.js validate # Validate current config'); console.log(' node config.js model-test # Test model selection logic'); } }