""" Context compiler for assembling resolved prompts. Compiles resolved macros into final prompt context. """ from dataclasses import dataclass, field from datetime import datetime from typing import Dict, List, Optional from markitect.prompts.templates.models import PromptTemplate from markitect.prompts.resolver.models import ResolutionResult from markitect.prompts.models import calculate_content_digest @dataclass class CompiledPrompt: """ Compiled prompt ready for execution. Contains the final assembled prompt with all macros resolved. Attributes: template_id: Source template ID template_name: Source template name content: Compiled prompt content with macros substituted content_digest: SHA-256 digest of compiled content resolution_result: Original resolution result dependency_digests: Map of artifact name -> content digest compiled_at: Compilation timestamp metadata: Additional metadata """ template_id: str template_name: str content: str content_digest: str resolution_result: ResolutionResult dependency_digests: Dict[str, str] = field(default_factory=dict) compiled_at: datetime = field(default_factory=datetime.utcnow) metadata: Dict[str, str] = field(default_factory=dict) def to_dict(self) -> dict: """Convert to dictionary.""" return { "template_id": self.template_id, "template_name": self.template_name, "content_digest": self.content_digest, "content_length": len(self.content), "dependency_count": len(self.dependency_digests), "compiled_at": self.compiled_at.isoformat(), "resolution_status": self.resolution_result.status.value, } class ContextCompiler: """ Compiler for assembling resolved context into final prompt. Takes resolution results and produces CompiledPrompt with all macros substituted. """ def compile( self, template: PromptTemplate, template_content: str, resolution_result: ResolutionResult, ) -> CompiledPrompt: """ Compile template with resolved macros into final prompt. Args: template: Source template template_content: Original template content resolution_result: Resolution result with resolved macros Returns: CompiledPrompt with macros substituted Raises: ValueError: If resolution failed """ if not resolution_result.success: raise ValueError( f"Cannot compile template '{template.name}': " f"Resolution failed with unresolved required macros" ) # Start with original template content compiled_content = template_content # Track dependency digests dependency_digests = {} # Substitute each resolved macro for resolved in resolution_result.context.resolved_macros: if resolved.resolved and resolved.artifact: # Replace macro with resolved content compiled_content = compiled_content.replace( resolved.macro.raw_text, resolved.content, ) # Track dependency dependency_digests[resolved.artifact.name] = resolved.artifact.content_digest # Substitute unresolved optional macros with empty string for macro in resolution_result.context.unresolved_optional: compiled_content = compiled_content.replace(macro.raw_text, "") # Calculate digest of compiled content content_digest = calculate_content_digest(compiled_content) return CompiledPrompt( template_id=template.id, template_name=template.name, content=compiled_content, content_digest=content_digest, resolution_result=resolution_result, dependency_digests=dependency_digests, ) def compile_partial( self, template: PromptTemplate, template_content: str, resolution_result: ResolutionResult, placeholder: str = "[UNRESOLVED]", ) -> CompiledPrompt: """ Compile template even with unresolved required macros. Useful for debugging or preview. Unresolved required macros are replaced with placeholder text. Args: template: Source template template_content: Original template content resolution_result: Resolution result (may have failures) placeholder: Text to use for unresolved macros Returns: CompiledPrompt with partial resolution """ # Start with original template content compiled_content = template_content # Track dependency digests dependency_digests = {} # Substitute resolved macros for resolved in resolution_result.context.resolved_macros: if resolved.resolved and resolved.artifact: compiled_content = compiled_content.replace( resolved.macro.raw_text, resolved.content, ) dependency_digests[resolved.artifact.name] = resolved.artifact.content_digest # Substitute unresolved required with placeholder for macro in resolution_result.context.unresolved_required: placeholder_text = f"{placeholder}:{macro.target}" compiled_content = compiled_content.replace( macro.raw_text, placeholder_text, ) # Substitute unresolved optional with empty for macro in resolution_result.context.unresolved_optional: compiled_content = compiled_content.replace(macro.raw_text, "") content_digest = calculate_content_digest(compiled_content) return CompiledPrompt( template_id=template.id, template_name=template.name, content=compiled_content, content_digest=content_digest, resolution_result=resolution_result, dependency_digests=dependency_digests, metadata={"partial": "true", "placeholder": placeholder}, ) def get_compilation_info(self, compiled: CompiledPrompt) -> dict: """ Get information about compilation. Args: compiled: Compiled prompt Returns: Dictionary with compilation metadata """ return { "template_id": compiled.template_id, "template_name": compiled.template_name, "content_length": len(compiled.content), "content_digest": compiled.content_digest, "dependencies": list(compiled.dependency_digests.keys()), "dependency_count": len(compiled.dependency_digests), "compiled_at": compiled.compiled_at.isoformat(), "is_partial": compiled.metadata.get("partial") == "true", }