""" Models for resolution engine. Defines resolution context, results, and error types for macro resolution. """ from dataclasses import dataclass, field from typing import List, Dict, Optional, Any from enum import Enum from markitect.prompts.templates.models import ContentMacro, MacroKind from markitect.prompts.models import Artifact class ResolutionStatus(Enum): """Status of resolution operation.""" SUCCESS = "success" PARTIAL = "partial" # Some optional macros missing FAILED = "failed" # Required macros missing class ResolutionError(Exception): """Raised when macro resolution fails.""" def __init__(self, message: str, macro: Optional[ContentMacro] = None): super().__init__(message) self.macro = macro @dataclass class ResolvedMacro: """ Result of resolving a single macro. Attributes: macro: Original macro artifact: Resolved artifact (None if not found) resolved: Whether artifact was found space_id: Space where artifact was found content: Resolved content (empty string if not found for optional) """ macro: ContentMacro artifact: Optional[Artifact] = None resolved: bool = False space_id: Optional[str] = None content: str = "" def to_dict(self) -> Dict[str, Any]: """Convert to dictionary.""" return { "macro": self.macro.to_dict(), "artifact_id": self.artifact.id if self.artifact else None, "artifact_name": self.artifact.name if self.artifact else None, "resolved": self.resolved, "space_id": self.space_id, "content_preview": self.content[:100] + "..." if len(self.content) > 100 else self.content, } @dataclass class ResolutionContext: """ Context for macro resolution. Tracks resolution state, search order, and resolved artifacts. Attributes: template_id: ID of template being resolved space_id: Primary space ID search_order: Ordered list of space IDs to search resolved_macros: List of resolved macros unresolved_required: List of unresolved required macros unresolved_optional: List of unresolved optional macros generator_macros: List of generate macros (deferred) errors: List of resolution errors metadata: Additional context metadata """ template_id: str space_id: str search_order: List[str] = field(default_factory=list) resolved_macros: List[ResolvedMacro] = field(default_factory=list) unresolved_required: List[ContentMacro] = field(default_factory=list) unresolved_optional: List[ContentMacro] = field(default_factory=list) generator_macros: List[ContentMacro] = field(default_factory=list) errors: List[str] = field(default_factory=list) metadata: Dict[str, Any] = field(default_factory=dict) def add_resolved(self, resolved: ResolvedMacro) -> None: """Add a resolved macro to context.""" self.resolved_macros.append(resolved) def add_unresolved_required(self, macro: ContentMacro) -> None: """Record an unresolved required macro.""" self.unresolved_required.append(macro) self.errors.append( f"Required macro '{macro.target}' not found (line {macro.line_number})" ) def add_unresolved_optional(self, macro: ContentMacro) -> None: """Record an unresolved optional macro.""" self.unresolved_optional.append(macro) def add_generator(self, macro: ContentMacro) -> None: """Record a generator macro for deferred execution.""" self.generator_macros.append(macro) def has_errors(self) -> bool: """Check if any resolution errors occurred.""" return len(self.errors) > 0 def get_status(self) -> ResolutionStatus: """Get overall resolution status.""" if self.unresolved_required: return ResolutionStatus.FAILED elif self.unresolved_optional: return ResolutionStatus.PARTIAL else: return ResolutionStatus.SUCCESS def to_dict(self) -> Dict[str, Any]: """Convert to dictionary.""" return { "template_id": self.template_id, "space_id": self.space_id, "search_order": self.search_order, "resolved_count": len(self.resolved_macros), "unresolved_required": [m.target for m in self.unresolved_required], "unresolved_optional": [m.target for m in self.unresolved_optional], "generator_count": len(self.generator_macros), "status": self.get_status().value, "errors": self.errors, } @dataclass class ResolutionResult: """ Result of template resolution. Contains resolved content and metadata about the resolution process. Attributes: context: Resolution context with full state success: Whether all required macros were resolved resolved_content: Dictionary of macro -> resolved content dependency_artifacts: List of artifact IDs used needs_generation: Whether any generate macros were found """ context: ResolutionContext success: bool resolved_content: Dict[str, str] = field(default_factory=dict) dependency_artifacts: List[str] = field(default_factory=list) needs_generation: bool = False @property def status(self) -> ResolutionStatus: """Get resolution status.""" return self.context.get_status() def get_resolved_macro(self, target: str) -> Optional[ResolvedMacro]: """ Get resolved macro by target name. Args: target: Macro target name Returns: ResolvedMacro if found, None otherwise """ for resolved in self.context.resolved_macros: if resolved.macro.target == target: return resolved return None def to_dict(self) -> Dict[str, Any]: """Convert to dictionary.""" return { "success": self.success, "status": self.status.value, "context": self.context.to_dict(), "dependency_count": len(self.dependency_artifacts), "needs_generation": self.needs_generation, }