""" CLI commands for prompt dependency resolution. Provides Click commands for tracing provenance, visualizing graphs, querying run history, and inspecting impact debt. """ import json import os import click from markitect.prompts.dependencies.graph import GraphBuilder from markitect.prompts.dependencies.repository import SQLiteDependencyRepository from markitect.prompts.queries.operations import PromptQueryService from markitect.prompts.repositories.sqlite import SQLiteArtifactRepository from markitect.prompts.traceability.service import TraceabilityService from markitect.prompts.visualization.graph import GraphExporter def _default_db_path(): """Return the default database path.""" return os.path.expanduser("~/.markitect/markitect.db") def _get_repos(database): """Create artifact and dependency repos from database path.""" db = database or _default_db_path() artifact_repo = SQLiteArtifactRepository(db) dep_repo = SQLiteDependencyRepository(db) return artifact_repo, dep_repo, db @click.group(name="prompt") def prompt_commands(): """Prompt dependency resolution commands.""" pass @prompt_commands.command("trace") @click.argument("artifact_id") @click.option("--database", type=click.Path(), help="Database file path") def trace_artifact(artifact_id, database): """Trace provenance of an artifact.""" artifact_repo, dep_repo, db = _get_repos(database) service = TraceabilityService(artifact_repo, dep_repo, db_path=db) artifact = artifact_repo.get_by_id(artifact_id) if not artifact: click.echo(f"Artifact '{artifact_id}' not found.", err=True) raise SystemExit(1) trace = service.trace_artifact(artifact_id) click.echo(json.dumps(trace.to_dict(), indent=2, default=str)) @prompt_commands.command("graph") @click.argument("artifact_id") @click.option( "--format", "fmt", type=click.Choice(["dot", "mermaid"]), default="mermaid", help="Output format", ) @click.option("--database", type=click.Path(), help="Database file path") def show_graph(artifact_id, fmt, database): """Visualize dependency graph for an artifact.""" artifact_repo, dep_repo, db = _get_repos(database) builder = GraphBuilder(dep_repo) # Build graph from all edges involving this artifact from markitect.prompts.dependencies.queries import DependencyQueryService query_svc = DependencyQueryService(dep_repo) deps = query_svc.find_transitive_dependencies(artifact_id) dependents = query_svc.find_transitive_dependents(artifact_id) all_ids = deps | dependents | {artifact_id} graph = builder.build_graph(all_ids) if fmt == "dot": click.echo(GraphExporter.to_dot(graph, title=f"Dependencies: {artifact_id}")) else: click.echo( GraphExporter.to_mermaid(graph, title=f"Dependencies: {artifact_id}") ) @prompt_commands.command("runs") @click.option("--template", help="Filter by template ID") @click.option( "--status", type=click.Choice(["pending", "running", "success", "failed", "skipped"]), help="Filter by status", ) @click.option("--limit", default=20, type=int, help="Maximum results") @click.option("--database", type=click.Path(), help="Database file path") def list_runs(template, status, limit, database): """List prompt execution runs.""" artifact_repo, dep_repo, db = _get_repos(database) query_svc = PromptQueryService(artifact_repo, dep_repo, db_path=db) runs = query_svc.get_run_history( template_id=template, status=status, limit=limit ) if not runs: click.echo("No runs found.") return click.echo(json.dumps(runs, indent=2, default=str)) @prompt_commands.command("debt") @click.option("--artifact", help="Filter by artifact ID") @click.option("--database", type=click.Path(), help="Database file path") def show_debt(artifact, database): """Show impact debt (suppressed recomputations).""" artifact_repo, dep_repo, db = _get_repos(database) query_svc = PromptQueryService(artifact_repo, dep_repo, db_path=db) if artifact: # Get debt for specific artifact via traceability service trace_svc = TraceabilityService(artifact_repo, dep_repo, db_path=db) debts = trace_svc.get_impact_debt(artifact) if not debts: click.echo(f"No impact debt for artifact '{artifact}'.") return click.echo(json.dumps(debts, indent=2, default=str)) else: stale = query_svc.get_stale_artifacts() if not stale: click.echo("No stale artifacts found.") return click.echo(json.dumps(stale, indent=2, default=str)) @prompt_commands.command("stats") @click.option("--database", type=click.Path(), help="Database file path") def show_stats(database): """Show dependency graph statistics.""" artifact_repo, dep_repo, db = _get_repos(database) query_svc = PromptQueryService(artifact_repo, dep_repo, db_path=db) stats = query_svc.get_dependency_stats() click.echo(json.dumps(stats, indent=2))