Complete implementation of Phase 8, the final phase of prompt dependency resolution infrastructure, adding full observability and traceability. ## Features (FR-11) ### FR-11.1: Complete Artifact Provenance Tracing - TraceabilityService: composition layer for full artifact lineage - Trace any artifact to producing PromptTemplate, input artifacts, generator runs, and quality validation results - ProvenanceTrace model with complete dependency chain reconstruction - RunSummary and ArtifactLineage models for structured trace output ### FR-11.2: Recomputation Query Infrastructure - PromptQueryService: cross-service complex queries - Run history queries with template and status filters - Stale artifact detection via impact debt analysis - Dependency graph statistics (nodes, edges, cycles, roots, leaves) - Content-based artifact lookups by digest ### Visualization Support - GraphExporter: DOT (Graphviz) and Mermaid format export - Supports all edge types (requires, generates, includes) - Handles isolated nodes, linear chains, diamonds, and complex graphs ### CLI Commands (prompt group) - `prompt trace <artifact_id>` - Full provenance trace as JSON - `prompt graph <artifact_id>` - Dependency graph (DOT/Mermaid) - `prompt runs` - List execution runs with filters - `prompt debt` - Show impact debt and stale artifacts - `prompt stats` - Dependency graph statistics ## Implementation Source files (8): - markitect/prompts/traceability/models.py - Trace data models - markitect/prompts/traceability/service.py - TraceabilityService - markitect/prompts/visualization/graph.py - Graph export - markitect/prompts/queries/operations.py - PromptQueryService - markitect/prompts/cli.py - Click CLI commands - Package __init__.py files (3) Tests (64 total, all passing): - tests/unit/prompts/test_traceability_service.py (21 tests) - tests/unit/prompts/test_visualization.py (14 tests) - tests/unit/prompts/test_query_operations.py (12 tests) - tests/integration/prompts/test_traceability_workflow.py (7 tests) - tests/integration/prompts/test_prompt_cli.py (10 tests) ## Architecture TraceabilityService is a composition layer that delegates to: - DependencyQueryService (transitive dependency lookups) - QualityValidator (validation history) - IncrementalExecutionEngine (impact debt queries) - Direct repository access (artifacts, edges) No duplicate data storage - all data comes from existing Phase 1-7 infrastructure (artifact repo, dependency repo, validation DB, debt DB). ## Verification All 2250 tests pass with 0 regressions. Phase 8 completes the full 8-phase implementation roadmap. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
148 lines
5.1 KiB
Python
148 lines
5.1 KiB
Python
"""
|
|
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))
|