NomenAK da0a356eec feat: Implement YAML-first declarative intelligence architecture
Revolutionary transformation from hardcoded Python intelligence to hot-reloadable
YAML patterns, enabling dynamic configuration without code changes.

## Phase 1: Foundation Intelligence Complete

### YAML Intelligence Patterns (6 files)
- intelligence_patterns.yaml: Multi-dimensional pattern recognition with adaptive learning
- mcp_orchestration.yaml: Server selection decision trees with load balancing
- hook_coordination.yaml: Parallel execution patterns with dependency resolution
- performance_intelligence.yaml: Resource zones and auto-optimization triggers
- validation_intelligence.yaml: Health scoring and proactive diagnostic patterns
- user_experience.yaml: Project detection and smart UX adaptations

### Python Infrastructure Enhanced (4 components)
- intelligence_engine.py: Generic YAML pattern interpreter with hot-reload
- learning_engine.py: Enhanced with YAML intelligence integration
- yaml_loader.py: Added intelligence configuration helper methods
- validate_system.py: New YAML-driven validation with health scoring

### Key Features Implemented
- Hot-reload intelligence: Update patterns without code changes or restarts
- Declarative configuration: All intelligence logic expressed in YAML
- Graceful fallbacks: System works correctly even with missing YAML files
- Multi-pattern coordination: Intelligent recommendations from multiple sources
- Health scoring: Component-weighted validation with predictive diagnostics
- Generic architecture: Single engine consumes all intelligence pattern types

### Testing Results
 All components integrate correctly
 Hot-reload mechanism functional
 Graceful error handling verified
 YAML-driven validation operational
 Health scoring system working (detected real system issues)

This enables users to modify intelligence behavior by editing YAML files,
add new pattern types without coding, and hot-reload improvements in real-time.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-06 13:26:04 +02:00

422 lines
16 KiB
Python

"""
Unified Configuration Loader for SuperClaude-Lite
High-performance configuration loading with support for both JSON and YAML formats,
caching, hot-reload capabilities, and comprehensive error handling.
Supports:
- Claude Code settings.json (JSON format)
- SuperClaude superclaude-config.json (JSON format)
- YAML configuration files
- Unified configuration interface for hooks
"""
import os
import json
import yaml
import time
import hashlib
from typing import Dict, Any, Optional, Union
from pathlib import Path
class UnifiedConfigLoader:
"""
Intelligent configuration loader with support for JSON and YAML formats.
Features:
- Dual-configuration support (Claude Code + SuperClaude)
- File modification detection for hot-reload
- In-memory caching for performance (<10ms access)
- Comprehensive error handling and validation
- Environment variable interpolation
- Include/merge support for modular configs
- Unified configuration interface
"""
def __init__(self, project_root: Union[str, Path]):
self.project_root = Path(project_root)
self.config_dir = self.project_root / "config"
# Configuration file paths
self.claude_settings_path = self.project_root / "settings.json"
self.superclaude_config_path = self.project_root / "superclaude-config.json"
# Cache for all configuration sources
self._cache: Dict[str, Dict[str, Any]] = {}
self._file_hashes: Dict[str, str] = {}
self._last_check: Dict[str, float] = {}
self.check_interval = 1.0 # Check files every 1 second max
# Configuration source registry
self._config_sources = {
'claude_settings': self.claude_settings_path,
'superclaude_config': self.superclaude_config_path
}
def load_config(self, config_name: str, force_reload: bool = False) -> Dict[str, Any]:
"""
Load configuration with intelligent caching (supports JSON and YAML).
Args:
config_name: Name of config file or special config identifier
- For YAML: config file name without .yaml extension
- For JSON: 'claude_settings' or 'superclaude_config'
force_reload: Force reload even if cached
Returns:
Parsed configuration dictionary
Raises:
FileNotFoundError: If config file doesn't exist
ValueError: If config parsing fails
"""
# Handle special configuration sources
if config_name in self._config_sources:
return self._load_json_config(config_name, force_reload)
# Handle YAML configuration files
config_path = self.config_dir / f"{config_name}.yaml"
if not config_path.exists():
raise FileNotFoundError(f"Configuration file not found: {config_path}")
# Check if we need to reload
if not force_reload and self._should_use_cache(config_name, config_path):
return self._cache[config_name]
# Load and parse the YAML configuration
try:
with open(config_path, 'r', encoding='utf-8') as f:
content = f.read()
# Environment variable interpolation
content = self._interpolate_env_vars(content)
# Parse YAML
config = yaml.safe_load(content)
# Handle includes/merges
config = self._process_includes(config, config_path.parent)
# Update cache
self._cache[config_name] = config
self._file_hashes[config_name] = self._compute_hash(config_path)
self._last_check[config_name] = time.time()
return config
except yaml.YAMLError as e:
raise ValueError(f"YAML parsing error in {config_path}: {e}")
except Exception as e:
raise RuntimeError(f"Error loading config {config_name}: {e}")
def _load_json_config(self, config_name: str, force_reload: bool = False) -> Dict[str, Any]:
"""Load JSON configuration file."""
config_path = self._config_sources[config_name]
if not config_path.exists():
raise FileNotFoundError(f"Configuration file not found: {config_path}")
# Check if we need to reload
if not force_reload and self._should_use_cache(config_name, config_path):
return self._cache[config_name]
# Load and parse the JSON configuration
try:
with open(config_path, 'r', encoding='utf-8') as f:
content = f.read()
# Environment variable interpolation
content = self._interpolate_env_vars(content)
# Parse JSON
config = json.loads(content)
# Update cache
self._cache[config_name] = config
self._file_hashes[config_name] = self._compute_hash(config_path)
self._last_check[config_name] = time.time()
return config
except json.JSONDecodeError as e:
raise ValueError(f"JSON parsing error in {config_path}: {e}")
except Exception as e:
raise RuntimeError(f"Error loading JSON config {config_name}: {e}")
def get_section(self, config_name: str, section_path: str, default: Any = None) -> Any:
"""
Get specific section from configuration using dot notation.
Args:
config_name: Configuration file name or identifier
section_path: Dot-separated path (e.g., 'routing.ui_components')
default: Default value if section not found
Returns:
Configuration section value or default
"""
config = self.load_config(config_name)
try:
result = config
for key in section_path.split('.'):
result = result[key]
return result
except (KeyError, TypeError):
return default
def get_hook_config(self, hook_name: str, section_path: str = None, default: Any = None) -> Any:
"""
Get hook-specific configuration from SuperClaude config.
Args:
hook_name: Hook name (e.g., 'session_start', 'pre_tool_use')
section_path: Optional dot-separated path within hook config
default: Default value if not found
Returns:
Hook configuration or specific section
"""
base_path = f"hook_configurations.{hook_name}"
if section_path:
full_path = f"{base_path}.{section_path}"
else:
full_path = base_path
return self.get_section('superclaude_config', full_path, default)
def get_claude_hooks(self) -> Dict[str, Any]:
"""Get Claude Code hook definitions from settings.json."""
return self.get_section('claude_settings', 'hooks', {})
def get_superclaude_config(self, section_path: str = None, default: Any = None) -> Any:
"""
Get SuperClaude framework configuration.
Args:
section_path: Optional dot-separated path (e.g., 'global_configuration.performance_monitoring')
default: Default value if not found
Returns:
Configuration section or full config if no path specified
"""
if section_path:
return self.get_section('superclaude_config', section_path, default)
else:
return self.load_config('superclaude_config')
def get_mcp_server_config(self, server_name: str = None) -> Dict[str, Any]:
"""
Get MCP server configuration.
Args:
server_name: Optional specific server name
Returns:
MCP server configuration
"""
if server_name:
return self.get_section('superclaude_config', f'mcp_server_integration.servers.{server_name}', {})
else:
return self.get_section('superclaude_config', 'mcp_server_integration', {})
def get_performance_targets(self) -> Dict[str, Any]:
"""Get performance targets for all components."""
return self.get_section('superclaude_config', 'global_configuration.performance_monitoring', {})
def is_hook_enabled(self, hook_name: str) -> bool:
"""Check if a specific hook is enabled."""
return self.get_hook_config(hook_name, 'enabled', False)
def reload_all(self) -> None:
"""Force reload of all cached configurations."""
for config_name in list(self._cache.keys()):
self.load_config(config_name, force_reload=True)
def _should_use_cache(self, config_name: str, config_path: Path) -> bool:
"""Check if cached version is still valid."""
if config_name not in self._cache:
return False
# Rate limit file checks
now = time.time()
if now - self._last_check.get(config_name, 0) < self.check_interval:
return True
# Check if file changed
current_hash = self._compute_hash(config_path)
return current_hash == self._file_hashes.get(config_name)
def _compute_hash(self, file_path: Path) -> str:
"""Compute file hash for change detection."""
stat = file_path.stat()
return hashlib.md5(f"{stat.st_mtime}:{stat.st_size}".encode()).hexdigest()
def _interpolate_env_vars(self, content: str) -> str:
"""Replace environment variables in YAML content."""
import re
def replace_env_var(match):
var_name = match.group(1)
default_value = match.group(2) if match.group(2) else ""
return os.getenv(var_name, default_value)
# Support ${VAR} and ${VAR:default} syntax
pattern = r'\$\{([^}:]+)(?::([^}]*))?\}'
return re.sub(pattern, replace_env_var, content)
def _process_includes(self, config: Dict[str, Any], base_dir: Path) -> Dict[str, Any]:
"""Process include directives in configuration."""
if not isinstance(config, dict):
return config
# Handle special include key
if '__include__' in config:
includes = config.pop('__include__')
if isinstance(includes, str):
includes = [includes]
for include_file in includes:
include_path = base_dir / include_file
if include_path.exists():
with open(include_path, 'r', encoding='utf-8') as f:
included_config = yaml.safe_load(f.read())
if isinstance(included_config, dict):
# Merge included config (current config takes precedence)
included_config.update(config)
config = included_config
return config
def get_intelligence_config(self, intelligence_type: str, section_path: str = None, default: Any = None) -> Any:
"""
Get intelligence configuration from YAML patterns.
Args:
intelligence_type: Type of intelligence config (e.g., 'intelligence_patterns', 'mcp_orchestration')
section_path: Optional dot-separated path within intelligence config
default: Default value if not found
Returns:
Intelligence configuration or specific section
"""
try:
config = self.load_config(intelligence_type)
if section_path:
result = config
for key in section_path.split('.'):
result = result[key]
return result
else:
return config
except (FileNotFoundError, KeyError, TypeError):
return default
def get_pattern_dimensions(self) -> Dict[str, Any]:
"""Get pattern recognition dimensions from intelligence patterns."""
return self.get_intelligence_config(
'intelligence_patterns',
'learning_intelligence.pattern_recognition.dimensions',
{'primary': ['context_type', 'complexity_score', 'operation_type'], 'secondary': []}
)
def get_mcp_orchestration_rules(self) -> Dict[str, Any]:
"""Get MCP server orchestration rules."""
return self.get_intelligence_config(
'mcp_orchestration',
'server_selection.decision_tree',
[]
)
def get_hook_coordination_patterns(self) -> Dict[str, Any]:
"""Get hook coordination execution patterns."""
return self.get_intelligence_config(
'hook_coordination',
'execution_patterns',
{}
)
def get_performance_zones(self) -> Dict[str, Any]:
"""Get performance management resource zones."""
return self.get_intelligence_config(
'performance_intelligence',
'resource_management.resource_zones',
{}
)
def get_validation_health_config(self) -> Dict[str, Any]:
"""Get validation and health scoring configuration."""
return self.get_intelligence_config(
'validation_intelligence',
'health_scoring',
{}
)
def get_ux_project_patterns(self) -> Dict[str, Any]:
"""Get user experience project detection patterns."""
return self.get_intelligence_config(
'user_experience',
'project_detection.detection_patterns',
{}
)
def get_intelligence_summary(self) -> Dict[str, Any]:
"""Get summary of all available intelligence configurations."""
intelligence_types = [
'intelligence_patterns',
'mcp_orchestration',
'hook_coordination',
'performance_intelligence',
'validation_intelligence',
'user_experience'
]
summary = {}
for intelligence_type in intelligence_types:
try:
config = self.load_config(intelligence_type)
summary[intelligence_type] = {
'loaded': True,
'version': config.get('version', 'unknown'),
'last_updated': config.get('last_updated', 'unknown'),
'sections': list(config.keys()) if isinstance(config, dict) else []
}
except Exception:
summary[intelligence_type] = {
'loaded': False,
'error': 'Failed to load configuration'
}
return summary
def reload_intelligence_configs(self) -> Dict[str, bool]:
"""Force reload all intelligence configurations and return status."""
intelligence_types = [
'intelligence_patterns',
'mcp_orchestration',
'hook_coordination',
'performance_intelligence',
'validation_intelligence',
'user_experience'
]
reload_status = {}
for intelligence_type in intelligence_types:
try:
self.load_config(intelligence_type, force_reload=True)
reload_status[intelligence_type] = True
except Exception as e:
reload_status[intelligence_type] = False
print(f"Warning: Could not reload {intelligence_type}: {e}")
return reload_status
# Global instance for shared use across hooks
# Use Claude installation directory instead of current working directory
import os
config_loader = UnifiedConfigLoader(os.path.expanduser("~/.claude"))