""" Complex cross-service query operations for prompt infrastructure. Combines artifact repository, dependency repository, and run data into higher-level queries like run history, impact analysis, and stats. """ from typing import Any, Dict, List, Optional from markitect.prompts.dependencies.graph import GraphBuilder from markitect.prompts.dependencies.queries import DependencyQueryService from markitect.prompts.dependencies.repository import IDependencyRepository from markitect.prompts.execution.models import PromptRun from markitect.prompts.incremental.engine import IncrementalExecutionEngine from markitect.prompts.repositories.interfaces import IArtifactRepository class PromptQueryService: """ Complex cross-service queries over prompt infrastructure. Provides higher-level queries that span multiple data sources: run history, stale artifact detection, dependency statistics, and content-based artifact lookups. """ def __init__( self, artifact_repo: IArtifactRepository, dependency_repo: IDependencyRepository, db_path: Optional[str] = None, ): """ Initialize with data sources. Args: artifact_repo: Artifact repository dependency_repo: Dependency edge repository db_path: Optional database path for debt queries """ self._artifact_repo = artifact_repo self._dependency_repo = dependency_repo self._db_path = db_path self._query_service = DependencyQueryService(dependency_repo) self._graph_builder = GraphBuilder(dependency_repo) self._engine = ( IncrementalExecutionEngine(db_path, self._query_service) if db_path else None ) # Run registry: external code registers runs for querying self._runs: Dict[str, PromptRun] = {} def register_run(self, run: PromptRun) -> None: """Register a run for query lookups.""" self._runs[run.id] = run def get_run_history( self, template_id: Optional[str] = None, status: Optional[str] = None, limit: int = 50, ) -> List[Dict[str, Any]]: """ Query run history with optional filters. Args: template_id: Optional template ID filter status: Optional status filter limit: Maximum results to return Returns: List of run dictionaries sorted by start time (newest first) """ runs = list(self._runs.values()) if template_id: runs = [r for r in runs if r.template_id == template_id] if status: runs = [r for r in runs if r.status.value == status] runs.sort(key=lambda r: r.started_at, reverse=True) runs = runs[:limit] return [r.to_dict() for r in runs] def get_stale_artifacts(self) -> List[Dict[str, Any]]: """ Find artifacts with outstanding impact debt. Returns: List of dicts with artifact info and debt details """ if not self._engine: return [] all_debt = self._engine.get_all_debt() if not all_debt: return [] # Group debt by artifact debt_by_artifact: Dict[str, list] = {} for d in all_debt: debt_by_artifact.setdefault(d.artifact_id, []).append(d) results = [] for artifact_id, debts in debt_by_artifact.items(): artifact = self._artifact_repo.get_by_id(artifact_id) results.append({ "artifact_id": artifact_id, "artifact_name": artifact.name if artifact else "unknown", "debt_count": len(debts), "total_magnitude": sum(d.change_magnitude for d in debts), "reasons": list({d.suppression_reason for d in debts}), }) return results def get_dependency_stats(self) -> Dict[str, Any]: """ Get summary statistics about the dependency graph. Returns: Dictionary with graph statistics """ graph = self._graph_builder.build_graph() nodes = graph.nodes edge_count = graph.edge_count # Find root nodes (no predecessors) and leaf nodes (no successors) roots = [n for n in nodes if not graph.get_predecessors(n)] leaves = [n for n in nodes if not graph.get_successors(n)] # Check for cycles has_cycles = graph.has_cycle() return { "total_nodes": len(nodes), "total_edges": edge_count, "root_count": len(roots), "leaf_count": len(leaves), "has_cycles": has_cycles, } def find_artifacts_by_digest(self, digest: str) -> List[Dict[str, Any]]: """ Find all artifacts with a given content digest. Args: digest: Content digest to search for Returns: List of artifact dictionaries """ artifacts = self._artifact_repo.get_by_digest(digest) return [ { "artifact_id": a.id, "name": a.name, "space_id": a.space_id, "artifact_type": a.artifact_type.value, } for a in artifacts ]