Files
skill-seekers-reference/src/skill_seekers/cli/config_enhancer.py
yusyus 4e8ad835ed style: Format code with ruff formatter
- Auto-format 11 files to comply with ruff formatting standards
- Fixes CI/CD formatter check failures

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-03 21:37:54 +03:00

506 lines
18 KiB
Python

#!/usr/bin/env python3
"""
Configuration Enhancer - AI-powered enhancement for config extraction results.
Provides dual-mode AI enhancement (API + LOCAL) for configuration analysis:
- Explain what each setting does
- Suggest best practices and improvements
- Security analysis (hardcoded secrets, exposed credentials)
- Migration suggestions (consolidate configs)
- Context-aware documentation
Similar to GuideEnhancer (C3.3) but for configuration files.
"""
import json
import logging
import os
import subprocess
import sys
import tempfile
from dataclasses import dataclass, field
from pathlib import Path
# Configure logging
logging.basicConfig(level=logging.INFO, format="%(levelname)s - %(message)s")
logger = logging.getLogger(__name__)
# Optional anthropic import
ANTHROPIC_AVAILABLE = False
try:
import anthropic
ANTHROPIC_AVAILABLE = True
except ImportError:
pass
@dataclass
class ConfigEnhancement:
"""AI-generated enhancement for a configuration"""
explanation: str = "" # What this setting does
best_practice: str = "" # Suggested improvement
security_concern: str = "" # Security issue (if any)
migration_suggestion: str = "" # Consolidation opportunity
context: str = "" # Pattern context and usage
@dataclass
class EnhancedConfigFile:
"""Configuration file with AI enhancements"""
file_path: str
config_type: str
purpose: str
enhancement: ConfigEnhancement
setting_enhancements: dict[str, ConfigEnhancement] = field(default_factory=dict)
class ConfigEnhancer:
"""
AI enhancement for configuration extraction results.
Supports dual-mode operation:
- API mode: Uses Claude API (requires ANTHROPIC_API_KEY)
- LOCAL mode: Uses Claude Code CLI (no API key needed)
- AUTO mode: Automatically detects best available mode
"""
def __init__(self, mode: str = "auto"):
"""
Initialize ConfigEnhancer.
Args:
mode: Enhancement mode - "api", "local", or "auto" (default)
"""
self.mode = self._detect_mode(mode)
self.api_key = os.environ.get("ANTHROPIC_API_KEY")
self.client = None
if self.mode == "api" and ANTHROPIC_AVAILABLE and self.api_key:
# Support custom base_url for GLM-4.7 and other Claude-compatible APIs
client_kwargs = {"api_key": self.api_key}
base_url = os.environ.get("ANTHROPIC_BASE_URL")
if base_url:
client_kwargs["base_url"] = base_url
logger.info(f"✅ Using custom API base URL: {base_url}")
self.client = anthropic.Anthropic(**client_kwargs)
def _detect_mode(self, requested_mode: str) -> str:
"""
Detect best enhancement mode.
Args:
requested_mode: User-requested mode
Returns:
Actual mode to use
"""
if requested_mode in ["api", "local"]:
return requested_mode
# Auto-detect
if os.environ.get("ANTHROPIC_API_KEY") and ANTHROPIC_AVAILABLE:
logger.info("🤖 AI enhancement: API mode (Claude API detected)")
return "api"
else:
logger.info("🤖 AI enhancement: LOCAL mode (using Claude Code CLI)")
return "local"
def enhance_config_result(self, result: dict) -> dict:
"""
Enhance entire configuration extraction result.
Args:
result: ConfigExtractionResult as dict
Returns:
Enhanced result with AI insights
"""
logger.info(f"🔄 Enhancing {len(result.get('config_files', []))} config files...")
if self.mode == "api":
return self._enhance_via_api(result)
else:
return self._enhance_via_local(result)
# =========================================================================
# API MODE - Direct Claude API calls
# =========================================================================
def _enhance_via_api(self, result: dict) -> dict:
"""Enhance configs using Claude API"""
if not self.client:
logger.error("❌ API mode requested but no API key available")
return result
try:
# Create enhancement prompt
prompt = self._create_enhancement_prompt(result)
# Call Claude API
logger.info("📡 Calling Claude API for config analysis...")
response = self.client.messages.create(
model="claude-sonnet-4-20250514",
max_tokens=8000,
messages=[{"role": "user", "content": prompt}],
)
# Parse response
enhanced_result = self._parse_api_response(response.content[0].text, result)
logger.info("✅ API enhancement complete")
return enhanced_result
except Exception as e:
logger.error(f"❌ API enhancement failed: {e}")
return result
def _create_enhancement_prompt(self, result: dict) -> str:
"""Create prompt for Claude API"""
config_files = result.get("config_files", [])
# Summarize configs for prompt
config_summary = []
for cf in config_files[:10]: # Limit to first 10 files
settings_summary = []
for setting in cf.get("settings", [])[:5]: # First 5 settings per file
# Support both "type" (from config_extractor) and "value_type" (legacy)
value_type = setting.get("type", setting.get("value_type", "unknown"))
settings_summary.append(f" - {setting['key']}: {setting['value']} ({value_type})")
# Support both "type" (from config_extractor) and "config_type" (legacy)
config_type = cf.get("type", cf.get("config_type", "unknown"))
config_summary.append(f"""
File: {cf["relative_path"]} ({config_type})
Purpose: {cf["purpose"]}
Settings:
{chr(10).join(settings_summary)}
Patterns: {", ".join(cf.get("patterns", []))}
""")
prompt = f"""Analyze these configuration files and provide AI-enhanced insights.
CONFIGURATION FILES ({len(config_files)} total, showing first 10):
{chr(10).join(config_summary)}
YOUR TASK: Provide comprehensive analysis in JSON format with these 5 enhancements:
1. **EXPLANATIONS**: For each config file, explain its purpose and key settings
2. **BEST PRACTICES**: Suggest improvements (better structure, naming, organization)
3. **SECURITY ANALYSIS**: Identify hardcoded secrets, exposed credentials, security issues
4. **MIGRATION SUGGESTIONS**: Identify opportunities to consolidate or standardize configs
5. **CONTEXT**: Explain the detected patterns and when to use them
OUTPUT FORMAT (strict JSON):
{{
"file_enhancements": [
{{
"file_path": "path/to/config.json",
"explanation": "This file configures the database connection...",
"best_practice": "Consider using environment variables for host/port",
"security_concern": "⚠️ DATABASE_PASSWORD is hardcoded - move to .env",
"migration_suggestion": "Consolidate with config.yml (overlapping settings)",
"context": "Standard PostgreSQL configuration pattern"
}}
],
"overall_insights": {{
"config_count": {len(config_files)},
"security_issues_found": 3,
"consolidation_opportunities": ["Merge .env and config.json database settings"],
"recommended_actions": ["Move secrets to environment variables", "Standardize on YAML format"]
}}
}}
Focus on actionable insights that help developers understand and improve their configuration.
"""
return prompt
def _parse_api_response(self, response_text: str, original_result: dict) -> dict:
"""Parse Claude API response and merge with original result"""
try:
# Extract JSON from response
import re
json_match = re.search(r"\{.*\}", response_text, re.DOTALL)
if not json_match:
logger.warning("⚠️ No JSON found in API response")
return original_result
enhancements = json.loads(json_match.group())
# Merge enhancements into original result
original_result["ai_enhancements"] = enhancements
# Add enhancement flags to config files
file_enhancements = {
e["file_path"]: e for e in enhancements.get("file_enhancements", [])
}
for cf in original_result.get("config_files", []):
file_path = cf.get("relative_path", cf.get("file_path"))
if file_path in file_enhancements:
cf["ai_enhancement"] = file_enhancements[file_path]
return original_result
except json.JSONDecodeError as e:
logger.error(f"❌ Failed to parse API response as JSON: {e}")
return original_result
# =========================================================================
# LOCAL MODE - Claude Code CLI
# =========================================================================
def _enhance_via_local(self, result: dict) -> dict:
"""Enhance configs using Claude Code CLI"""
try:
# Create a temporary directory for this enhancement session
with tempfile.TemporaryDirectory(prefix="config_enhance_") as temp_dir:
temp_path = Path(temp_dir)
# Define output file path (absolute path that Claude will write to)
output_file = temp_path / "config_enhancement.json"
# Create prompt file with the output path embedded
prompt_file = temp_path / "enhance_prompt.md"
prompt_content = self._create_local_prompt(result, output_file)
prompt_file.write_text(prompt_content)
logger.info("🖥️ Launching Claude Code CLI for config analysis...")
logger.info("⏱️ This will take 30-60 seconds...")
# Run Claude Code CLI
result_data = self._run_claude_cli(prompt_file, output_file, temp_path)
if result_data:
# Merge LOCAL enhancements
result["ai_enhancements"] = result_data
logger.info("✅ LOCAL enhancement complete")
return result
else:
logger.warning("⚠️ LOCAL enhancement produced no results")
return result
except Exception as e:
logger.error(f"❌ LOCAL enhancement failed: {e}")
return result
def _create_local_prompt(self, result: dict, output_file: Path) -> str:
"""Create prompt file for Claude Code CLI
Args:
result: Config extraction result dict
output_file: Absolute path where Claude should write the JSON output
Returns:
Prompt content string
"""
config_files = result.get("config_files", [])
# Format config data for Claude (limit to 15 files for reasonable prompt size)
config_data = []
for cf in config_files[:15]:
# Support both "type" (from config_extractor) and "config_type" (legacy)
config_type = cf.get("type", cf.get("config_type", "unknown"))
settings_preview = []
for s in cf.get("settings", [])[:3]: # Show first 3 settings
settings_preview.append(
f" - {s.get('key', 'unknown')}: {str(s.get('value', ''))[:50]}"
)
config_data.append(f"""
### {cf["relative_path"]} ({config_type})
- Purpose: {cf["purpose"]}
- Patterns: {", ".join(cf.get("patterns", [])) or "none detected"}
- Settings: {len(cf.get("settings", []))} total
{chr(10).join(settings_preview) if settings_preview else " (no settings)"}
""")
prompt = f"""# Configuration Analysis Task
IMPORTANT: You MUST write the output to this EXACT file path:
{output_file}
## Configuration Files ({len(config_files)} total, showing first 15)
{chr(10).join(config_data)}
## Your Task
Analyze these configuration files and write a JSON file to the path specified above.
The JSON must have this EXACT structure:
```json
{{
"file_enhancements": [
{{
"file_path": "relative/path/to/config.json",
"explanation": "Brief explanation of what this config file does",
"best_practice": "Suggested improvement or 'None'",
"security_concern": "Security issue if any, or 'None'",
"migration_suggestion": "Consolidation opportunity or 'None'",
"context": "What pattern or purpose this serves"
}}
],
"overall_insights": {{
"config_count": {len(config_files)},
"security_issues_found": 0,
"consolidation_opportunities": ["List of suggestions"],
"recommended_actions": ["List of actions"]
}}
}}
```
## Instructions
1. Use the Write tool to create the JSON file at: {output_file}
2. Include an enhancement entry for each config file shown above
3. Focus on actionable insights:
- Explain what each config does in 1-2 sentences
- Identify any hardcoded secrets or security issues
- Suggest consolidation if configs have overlapping settings
- Note any missing best practices
DO NOT explain your work - just write the JSON file directly.
"""
return prompt
def _run_claude_cli(
self, prompt_file: Path, output_file: Path, working_dir: Path
) -> dict | None:
"""Run Claude Code CLI and wait for completion
Args:
prompt_file: Path to the prompt markdown file
output_file: Expected path where Claude will write the JSON output
working_dir: Working directory to run Claude from
Returns:
Parsed JSON dict if successful, None otherwise
"""
import time
try:
start_time = time.time()
# Run claude command with --dangerously-skip-permissions to bypass all prompts
# This allows Claude to write files without asking for confirmation
logger.info(f" Running: claude --dangerously-skip-permissions {prompt_file.name}")
logger.info(f" Output expected at: {output_file}")
result = subprocess.run(
["claude", "--dangerously-skip-permissions", str(prompt_file)],
capture_output=True,
text=True,
timeout=300, # 5 minute timeout
cwd=str(working_dir),
)
elapsed = time.time() - start_time
logger.info(f" Claude finished in {elapsed:.1f} seconds")
if result.returncode != 0:
logger.error(f"❌ Claude CLI failed (exit code {result.returncode})")
if result.stderr:
logger.error(f" Error: {result.stderr[:200]}")
return None
# Check if the expected output file was created
if output_file.exists():
try:
with open(output_file) as f:
data = json.load(f)
if "file_enhancements" in data or "overall_insights" in data:
logger.info(f"✅ Found enhancement data in {output_file.name}")
return data
else:
logger.warning("⚠️ Output file exists but missing expected keys")
except json.JSONDecodeError as e:
logger.error(f"❌ Failed to parse output JSON: {e}")
return None
# Fallback: Look for any JSON files created in the working directory
logger.info(" Looking for JSON files in working directory...")
current_time = time.time()
potential_files = []
for json_file in working_dir.glob("*.json"):
# Check if created recently (within last 2 minutes)
if current_time - json_file.stat().st_mtime < 120:
potential_files.append(json_file)
# Try to load the most recent JSON file with expected structure
for json_file in sorted(potential_files, key=lambda f: f.stat().st_mtime, reverse=True):
try:
with open(json_file) as f:
data = json.load(f)
if "file_enhancements" in data or "overall_insights" in data:
logger.info(f"✅ Found enhancement data in {json_file.name}")
return data
except Exception:
continue
logger.warning("⚠️ Could not find enhancement output file")
logger.info(f" Expected file: {output_file}")
logger.info(f" Files in dir: {list(working_dir.glob('*'))}")
return None
except subprocess.TimeoutExpired:
logger.error("❌ Claude CLI timeout (5 minutes)")
return None
except FileNotFoundError:
logger.error("'claude' command not found. Is Claude Code CLI installed?")
logger.error(" Install with: npm install -g @anthropic-ai/claude-code")
return None
except Exception as e:
logger.error(f"❌ Error running Claude CLI: {e}")
return None
def main():
"""Command-line interface for config enhancement"""
import argparse
parser = argparse.ArgumentParser(description="AI-enhance configuration extraction results")
parser.add_argument("result_file", help="Path to config extraction JSON result file")
parser.add_argument(
"--mode",
choices=["auto", "api", "local"],
default="auto",
help="Enhancement mode (default: auto)",
)
parser.add_argument(
"--output", help="Output file for enhanced results (default: <input>_enhanced.json)"
)
args = parser.parse_args()
# Load result file
try:
with open(args.result_file) as f:
result = json.load(f)
except Exception as e:
logger.error(f"❌ Failed to load result file: {e}")
return 1
# Enhance
enhancer = ConfigEnhancer(mode=args.mode)
enhanced_result = enhancer.enhance_config_result(result)
# Save
output_file = args.output or args.result_file.replace(".json", "_enhanced.json")
try:
with open(output_file, "w") as f:
json.dump(enhanced_result, f, indent=2)
logger.info(f"✅ Enhanced results saved to: {output_file}")
except Exception as e:
logger.error(f"❌ Failed to save results: {e}")
return 1
return 0
if __name__ == "__main__":
sys.exit(main())