from __future__ import annotations from dataclasses import asdict from pathlib import Path from fastapi import Depends, FastAPI, HTTPException from pydantic import BaseModel, Field from repo_registry.core.service import RegistryService from repo_registry.repo_ingestion.git import GitIngestionService from repo_registry.storage.sqlite import NotFoundError, RegistryStore class Settings(BaseModel): database_path: str = Field(default="var/repo-registry.sqlite3") checkout_root: str = Field(default="var/checkouts") def get_settings() -> Settings: return Settings() def get_service(settings: Settings = Depends(get_settings)) -> RegistryService: database_path = Path(settings.database_path) database_path.parent.mkdir(parents=True, exist_ok=True) store = RegistryStore(database_path) store.initialize() return RegistryService(store, ingestion=GitIngestionService(settings.checkout_root)) class RepositoryCreate(BaseModel): url: str name: str | None = None description: str | None = None branch: str = "main" class AbilityCreate(BaseModel): name: str description: str = "" confidence: float = Field(default=1.0, ge=0.0, le=1.0) class CapabilityCreate(BaseModel): ability_id: int name: str description: str = "" inputs: list[str] = Field(default_factory=list) outputs: list[str] = Field(default_factory=list) confidence: float = Field(default=1.0, ge=0.0, le=1.0) class FeatureCreate(BaseModel): capability_id: int name: str type: str location: str = "" confidence: float = Field(default=1.0, ge=0.0, le=1.0) class EvidenceCreate(BaseModel): capability_id: int type: str reference: str strength: str = "medium" class AnalysisRunCreate(BaseModel): source_path: str | None = None class CandidateGraphApproval(BaseModel): notes: str = "" app = FastAPI(title="Repository Ability Registry", version="0.1.0") from repo_registry.web_ui.views import router as ui_router app.include_router(ui_router) @app.get("/health") def health() -> dict[str, str]: return {"status": "ok"} @app.post("/repos", status_code=201) def create_repository( payload: RepositoryCreate, service: RegistryService = Depends(get_service), ) -> dict[str, object]: try: repository = service.register_repository(**payload.model_dump()) except (RuntimeError, ValueError) as exc: raise HTTPException(status_code=400, detail=str(exc)) from exc return asdict(repository) @app.get("/repos") def list_repositories( service: RegistryService = Depends(get_service), ) -> list[dict[str, object]]: return [asdict(repository) for repository in service.list_repositories()] @app.get("/repos/{repository_id}") def get_repository( repository_id: int, service: RegistryService = Depends(get_service), ) -> dict[str, object]: try: return asdict(service.get_repository(repository_id)) except NotFoundError as exc: raise HTTPException(status_code=404, detail=str(exc)) from exc @app.post("/repos/{repository_id}/analysis-runs", status_code=201) def create_analysis_run( repository_id: int, payload: AnalysisRunCreate, service: RegistryService = Depends(get_service), ) -> dict[str, object]: try: summary = service.analyze_repository( repository_id, source_path=payload.source_path, ) except NotFoundError as exc: raise HTTPException(status_code=404, detail=str(exc)) from exc return asdict(summary) @app.get("/repos/{repository_id}/analysis-runs") def list_analysis_runs( repository_id: int, service: RegistryService = Depends(get_service), ) -> list[dict[str, object]]: try: return [asdict(run) for run in service.list_analysis_runs(repository_id)] except NotFoundError as exc: raise HTTPException(status_code=404, detail=str(exc)) from exc @app.get("/repos/{repository_id}/observed-facts") def list_observed_facts( repository_id: int, analysis_run_id: int | None = None, service: RegistryService = Depends(get_service), ) -> list[dict[str, object]]: try: return [ asdict(fact) for fact in service.list_observed_facts(repository_id, analysis_run_id) ] except NotFoundError as exc: raise HTTPException(status_code=404, detail=str(exc)) from exc @app.get("/repos/{repository_id}/analysis-runs/{analysis_run_id}/candidate-graph") def get_candidate_graph( repository_id: int, analysis_run_id: int, service: RegistryService = Depends(get_service), ) -> dict[str, object]: try: return asdict(service.candidate_graph(repository_id, analysis_run_id)) except NotFoundError as exc: raise HTTPException(status_code=404, detail=str(exc)) from exc @app.post("/repos/{repository_id}/analysis-runs/{analysis_run_id}/candidate-graph/approve") def approve_candidate_graph( repository_id: int, analysis_run_id: int, payload: CandidateGraphApproval, service: RegistryService = Depends(get_service), ) -> dict[str, object]: try: return asdict( service.approve_candidate_graph( repository_id, analysis_run_id, notes=payload.notes, ) ) except NotFoundError as exc: raise HTTPException(status_code=404, detail=str(exc)) from exc @app.post("/repos/{repository_id}/abilities", status_code=201) def create_ability( repository_id: int, payload: AbilityCreate, service: RegistryService = Depends(get_service), ) -> dict[str, int]: try: ability_id = service.add_ability(repository_id, **payload.model_dump()) except NotFoundError as exc: raise HTTPException(status_code=404, detail=str(exc)) from exc return {"id": ability_id} @app.post("/repos/{repository_id}/capabilities", status_code=201) def create_capability( repository_id: int, payload: CapabilityCreate, service: RegistryService = Depends(get_service), ) -> dict[str, int]: try: capability_id = service.add_capability(repository_id, **payload.model_dump()) except NotFoundError as exc: raise HTTPException(status_code=404, detail=str(exc)) from exc return {"id": capability_id} @app.post("/repos/{repository_id}/features", status_code=201) def create_feature( repository_id: int, payload: FeatureCreate, service: RegistryService = Depends(get_service), ) -> dict[str, int]: try: feature_id = service.add_feature(repository_id, **payload.model_dump()) except NotFoundError as exc: raise HTTPException(status_code=404, detail=str(exc)) from exc return {"id": feature_id} @app.post("/repos/{repository_id}/evidence", status_code=201) def create_evidence( repository_id: int, payload: EvidenceCreate, service: RegistryService = Depends(get_service), ) -> dict[str, int]: try: evidence_id = service.add_evidence(repository_id, **payload.model_dump()) except NotFoundError as exc: raise HTTPException(status_code=404, detail=str(exc)) from exc return {"id": evidence_id} @app.get("/repos/{repository_id}/ability-map") def get_ability_map( repository_id: int, service: RegistryService = Depends(get_service), ) -> dict[str, object]: try: return asdict(service.ability_map(repository_id)) except NotFoundError as exc: raise HTTPException(status_code=404, detail=str(exc)) from exc @app.get("/search") def search( q: str, service: RegistryService = Depends(get_service), ) -> list[dict[str, object]]: return [asdict(result) for result in service.search(q)]