# Hephaestus Development Patterns

## Architecture Patterns

### MCP Server Architecture
```python
# Standard server structure
class HephaestusMCPServer:
    def __init__(self, mcp: FastMCP):
        self.mcp = mcp
        self._register_tools()
    
    def _register_tools(self):
        # Register all Anvil development tools
        pass
    
    def run(self, host: str = "localhost", port: int = 8000, transport: str = "stdio"):
        # Start MCP server with specified transport
        pass
```

### Tool Registration Pattern
```python
def _register_tools(self):
    """Register all Anvil development tools with MCP server."""
    
    @self.mcp.tool()
    def create_modern_anvil_app(project_name: str, **options) -> str:
        """Create a new modern Anvil application with optimal patterns."""
        return self._create_modern_app(project_name, options)
    
    @self.mcp.tool()
    def analyse_project_context(project_path: str) -> dict:
        """Analyze existing Anvil project to determine patterns and modernisation opportunities."""
        return self._analyze_project(project_path)
```

## Code Generation Patterns

### Context-Aware Generation
```python
@dataclass
class ProjectContext:
    anvil_version: str
    theme_type: str  # "material-3", "classic", "custom"
    routing_system: str  # "official", "anvil-extras", "none"
    has_reactive: bool
    data_layer: str  # "model-classes", "anvil-extras-orm", "raw-tables"
    layout_system: str  # "layouts", "traditional", "mixed"
    modernisation_score: float  # 0.0-1.0
    generation_mode: str  # "modern", "hybrid", "legacy-compatible"

class ContextAwareGenerator:
    def generate(self, context: ProjectContext, **params):
        if context.generation_mode == "modern":
            return self._generate_modern(**params)
        elif context.generation_mode == "hybrid":
            return self._generate_hybrid(context, **params)
        else:
            return self._generate_compatible(context, **params)
```

### YAML Form Generation Pattern
```python
class AnvilFormGenerator:
    def generate_form(self, form_config: dict) -> str:
        """Generate Anvil form YAML from configuration."""
        
        yaml_structure = {
            "container": {
                "type": form_config.get("container_type", "ColumnPanel"),
                "properties": self._generate_container_properties(form_config),
                "layout_properties": self._generate_container_layout(form_config),
                "event_bindings": self._generate_container_events(form_config)
            },
            "components": self._generate_components(form_config.get("components", []))
        }
        
        return self._to_yaml(yaml_structure)
    
    def _generate_components(self, components: list) -> list:
        """Generate component list with proper validation."""
        generated = []
        for comp in components:
            if self._validate_component_type(comp["type"]):
                generated.append({
                    "name": comp["name"],
                    "type": comp["type"],
                    "properties": comp.get("properties", {}),
                    "layout_properties": comp.get("layout_properties", {}),
                    "data_bindings": comp.get("data_bindings", []),
                    "event_bindings": comp.get("event_bindings", {})
                })
        return generated
```

### Model Class Generation Pattern
```python
class ModelClassGenerator:
    def generate_model_class(self, model_config: dict) -> str:
        """Generate Anvil model class from configuration."""
        
        class_definition = [
            f"@anvil.server.model_class",
            f"class {model_config['name']}:"
        ]
        
        # Generate fields
        for field in model_config.get("fields", []):
            field_def = self._generate_field_definition(field)
            class_definition.append(f"    {field_def}")
        
        # Generate methods
        for method in model_config.get("methods", []):
            method_def = self._generate_method_definition(method)
            class_definition.append(f"\n    {method_def}")
        
        return "\n".join(class_definition)
    
    def _generate_field_definition(self, field: dict) -> str:
        """Generate field definition with proper type hints."""
        name = field["name"]
        field_type = field["type"]
        default = field.get("default")
        optional = field.get("optional", False)
        
        if optional:
            field_type = f"Optional[{field_type}]"
        
        if default is not None:
            return f"{name} = field({field_type}, default={default})"
        else:
            return f"{name} = field({field_type})"
```

## Error Handling Patterns

### Validation Pattern
```python
class ValidationError(Exception):
    def __init__(self, message: str, field: str = None, value: any = None):
        self.message = message
        self.field = field
        self.value = value
        super().__init__(self.message)

class ComponentValidator:
    def validate_component(self, component: dict) -> bool:
        """Validate component configuration against Anvil API."""
        
        # Check required fields
        if "type" not in component:
            raise ValidationError("Component type is required")
        
        if "name" not in component and not self._is_container_type(component["type"]):
            raise ValidationError("Component name is required for non-containers")
        
        # Validate component type
        if not self._is_valid_component_type(component["type"]):
            raise ValidationError(f"Invalid component type: {component['type']}")
        
        # Validate properties
        self._validate_properties(component)
        
        return True
```

### Error Response Pattern
```python
class ToolErrorHandler:
    def handle_tool_error(self, error: Exception, context: dict) -> dict:
        """Handle tool errors with appropriate responses."""
        
        if isinstance(error, ValidationError):
            return {
                "success": False,
                "error_type": "validation_error",
                "message": error.message,
                "field": error.field,
                "suggestion": self._get_validation_suggestion(error)
            }
        elif isinstance(error, FileNotFoundError):
            return {
                "success": False,
                "error_type": "file_not_found",
                "message": str(error),
                "suggestion": "Check that the file path is correct and the file exists"
            }
        else:
            return {
                "success": False,
                "error_type": "unexpected_error",
                "message": str(error),
                "suggestion": "Please check the input parameters and try again"
            }
```

## Performance Patterns

### Caching Pattern
```python
from functools import lru_cache
import hashlib

class CachedGenerator:
    def __init__(self):
        self._cache = {}
    
    def generate_with_cache(self, key: str, generator_func, *args, **kwargs):
        """Generate content with caching based on input hash."""
        
        cache_key = self._generate_cache_key(key, args, kwargs)
        
        if cache_key in self._cache:
            return self._cache[cache_key]
        
        result = generator_func(*args, **kwargs)
        self._cache[cache_key] = result
        return result
    
    def _generate_cache_key(self, prefix: str, args: tuple, kwargs: dict) -> str:
        """Generate deterministic cache key from inputs."""
        content = f"{prefix}:{str(args)}:{str(sorted(kwargs.items()))}"
        return hashlib.md5(content.encode()).hexdigest()
```

### Lazy Loading Pattern
```python
class LazyProjectAnalyzer:
    def __init__(self, project_path: str):
        self.project_path = project_path
        self._context = None
        self._analysis_complete = False
    
    @property
    def context(self) -> ProjectContext:
        """Lazy load project context."""
        if not self._analysis_complete:
            self._analyze_project()
        return self._context
    
    def _analyze_project(self):
        """Perform project analysis only when needed."""
        # Expensive analysis operations
        self._context = self._perform_analysis()
        self._analysis_complete = True
```

## Testing Patterns

### Mock Pattern for MCP Tools
```python
import pytest
from unittest.mock import Mock, patch

class TestAnvilTools:
    @pytest.fixture
    def mock_mcp_server(self):
        """Mock MCP server for testing."""
        mcp = Mock()
        server = HephaestusMCPServer(mcp)
        return server
    
    @pytest.fixture
    def sample_project_context(self):
        """Sample project context for testing."""
        return ProjectContext(
            theme_type="material-3",
            routing_system="official",
            has_reactive=True,
            data_layer="model-classes",
            layout_system="layouts",
            modernisation_score=0.9,
            generation_mode="modern"
        )
    
    def test_create_modern_app(self, mock_mcp_server, sample_project_context):
        """Test modern app creation."""
        with patch.object(mock_mcp_server, '_analyze_project') as mock_analyze:
            mock_analyze.return_value = sample_project_context
            
            result = mock_mcp_server._create_modern_app("test_app", {})
            
            assert result["success"] is True
            assert "project_path" in result
            assert "generated_files" in result
```

### Integration Test Pattern
```python
class TestMCPIntegration:
    @pytest.fixture
    def mcp_server(self):
        """Real MCP server for integration testing."""
        mcp = FastMCP("test-hephaestus")
        server = HephaestusMCPServer(mcp)
        return server
    
    def test_tool_registration(self, mcp_server):
        """Test that all tools are properly registered."""
        tools = mcp_server.mcp.list_tools()
        
        expected_tools = [
            "create_modern_anvil_app",
            "analyse_project_context",
            "create_reactive_page"
        ]
        
        for tool_name in expected_tools:
            assert any(tool.name == tool_name for tool in tools)
```

## Development Commands

Always use `uv run` instead of system python for running Python commands, tests, and scripts:
- `uv run pytest tests/` for running tests
- `uv run python src/server.py` for running the server
- `uv run ruff check src/` for linting
- `uv run ruff format src/` for formatting

## Configuration Patterns

### Configuration Management
```python
from dataclasses import dataclass
from pathlib import Path
import yaml

@dataclass
class HephaestusConfig:
    default_theme: str = "material-3"
    default_routing: str = "official"
    enable_reactive: bool = True
    output_directory: str = "./generated"
    template_directory: str = "./templates"
    
    @classmethod
    def from_file(cls, config_path: Path) -> "HephaestusConfig":
        """Load configuration from YAML file."""
        if not config_path.exists():
            return cls()
        
        with open(config_path, 'r') as f:
            config_data = yaml.safe_load(f)
        
        return cls(**config_data)
    
    def to_file(self, config_path: Path):
        """Save configuration to YAML file."""
        with open(config_path, 'w') as f:
            yaml.dump(self.__dict__, f, default_flow_style=False)
```

## Logging Patterns

### Structured Logging
```python
import logging
import json
from datetime import datetime

class StructuredLogger:
    def __init__(self, name: str):
        self.logger = logging.getLogger(name)
    
    def log_tool_execution(self, tool_name: str, params: dict, result: dict, execution_time: float):
        """Log tool execution with structured data."""
        log_entry = {
            "timestamp": datetime.utcnow().isoformat(),
            "tool": tool_name,
            "parameters": params,
            "result": result,
            "execution_time_seconds": execution_time,
            "success": result.get("success", False)
        }
        
        self.logger.info(json.dumps(log_entry))
    
    def log_error(self, error: Exception, context: dict):
        """Log error with context information."""
        log_entry = {
            "timestamp": datetime.utcnow().isoformat(),
            "error_type": type(error).__name__,
            "error_message": str(error),
            "context": context
        }
        
        self.logger.error(json.dumps(log_entry))
```