""" PromptResolver for resolving template macros. Implements FR-3: PromptResolver Behavior """ from typing import List, Optional from markitect.prompts.templates.models import PromptTemplate, ContentMacro, MacroKind from markitect.prompts.resolver.models import ( ResolutionContext, ResolutionResult, ResolutionError, ResolvedMacro, ) from markitect.prompts.resolver.strategy import ResolutionStrategy, ResolutionConfig from markitect.prompts.services.artifact_service import ArtifactService class PromptResolver: """ Resolver for prompt template macros. Implements FR-3: PromptResolver Behavior - Deterministic resolution order (FR-3.1) - Required macro validation (FR-3.2) - Optional macro fallback (FR-3.3) - Generate macro detection (FR-3.4) """ def __init__( self, artifact_service: ArtifactService, strategy: ResolutionStrategy, ): """ Initialize resolver. Args: artifact_service: Service for artifact lookup strategy: Resolution strategy for search order """ self.artifact_service = artifact_service self.strategy = strategy def resolve_template( self, template: PromptTemplate, config: ResolutionConfig, ) -> ResolutionResult: """ Resolve all macros in a template. Implements FR-3: PromptResolver Behavior Args: template: Template to resolve config: Resolution configuration Returns: ResolutionResult with resolved macros and status Raises: ValueError: If template hasn't been analyzed """ if not template.analyzed: raise ValueError( f"Template '{template.name}' must be analyzed before resolution. " "Call TemplateAnalyzer.analyze() first." ) # Get search order search_order = self.strategy.get_search_order(config) # Create resolution context context = ResolutionContext( template_id=template.id, space_id=config.space_id, search_order=search_order, ) # Track resolved content and dependencies resolved_content = {} dependency_artifacts = [] # Resolve each macro for macro in template.macros: resolved = self._resolve_macro(macro, search_order, context) if resolved.resolved and resolved.artifact: resolved_content[macro.target] = resolved.content dependency_artifacts.append(resolved.artifact.id) # Create result result = ResolutionResult( context=context, success=len(context.unresolved_required) == 0, resolved_content=resolved_content, dependency_artifacts=dependency_artifacts, needs_generation=len(context.generator_macros) > 0, ) return result def _resolve_macro( self, macro: ContentMacro, search_order: List[str], context: ResolutionContext, ) -> ResolvedMacro: """ Resolve a single macro. Implements: - FR-3.2: Required macro failure - FR-3.3: Optional macro empty fallback - FR-3.4: Generate macro detection Args: macro: Macro to resolve search_order: Ordered space IDs to search context: Resolution context Returns: ResolvedMacro with resolution result """ # Handle generate macros separately (FR-3.4) if macro.kind == MacroKind.GENERATE: context.add_generator(macro) return ResolvedMacro( macro=macro, artifact=None, resolved=False, # Will be resolved during generation phase space_id=None, content="", ) # Try to resolve in each space for space_id in search_order: artifact = self.artifact_service.repository.get_by_name( space_id, macro.target, ) if artifact: # Found! Get content (would need to load from storage in real impl) # For now, we'll use a placeholder content = f"[Content of {artifact.name} from {space_id}]" resolved = ResolvedMacro( macro=macro, artifact=artifact, resolved=True, space_id=space_id, content=content, ) context.add_resolved(resolved) return resolved # Not found in any space if macro.kind == MacroKind.REQUIRED: # FR-3.2: Required macros fail if not found context.add_unresolved_required(macro) return ResolvedMacro( macro=macro, artifact=None, resolved=False, space_id=None, content="", ) else: # FR-3.3: Optional macros resolve to empty context.add_unresolved_optional(macro) return ResolvedMacro( macro=macro, artifact=None, resolved=False, space_id=None, content="", # Empty content for optional ) def validate_resolution(self, result: ResolutionResult) -> None: """ Validate resolution result. Args: result: Resolution result to validate Raises: ResolutionError: If required macros are missing """ if not result.success: unresolved = result.context.unresolved_required targets = [m.target for m in unresolved] raise ResolutionError( f"Failed to resolve required macros: {', '.join(targets)}" ) def get_resolution_summary(self, result: ResolutionResult) -> dict: """ Get human-readable summary of resolution. Args: result: Resolution result Returns: Dictionary with summary information """ return { "status": result.status.value, "success": result.success, "resolved_count": len(result.context.resolved_macros), "unresolved_required": [ m.target for m in result.context.unresolved_required ], "unresolved_optional": [ m.target for m in result.context.unresolved_optional ], "needs_generation": result.needs_generation, "generator_count": len(result.context.generator_macros), "dependency_count": len(result.dependency_artifacts), "search_order": result.context.search_order, }