from __future__ import annotations import json from dataclasses import asdict, dataclass from pathlib import Path from typing import Any CRITERIA_SCHEMA_VERSION = "quality-criteria-registry/v1" DEFAULT_CRITERIA_PATH = ( Path(__file__).resolve().parents[3] / "docs" / "quality-criteria" / "acceptance-quality-criteria.v1.json" ) REQUIRED_CRITERION_FIELDS = { "id", "title", "category", "severity", "applies_to", "description", "deterministic_action", "deterministic_action_when", "reviewer_guidance", } @dataclass(frozen=True) class QualityCriterion: id: str title: str category: str severity: str applies_to: list[str] description: str deterministic_action: str deterministic_action_when: str reviewer_guidance: str agentic_guidance: str = "" examples: list[str] | None = None @dataclass(frozen=True) class QualityCriteriaRegistry: schema_version: str criteria_version: str status: str updated_at: str criteria: list[QualityCriterion] def load_quality_criteria(path: str | Path | None = None) -> QualityCriteriaRegistry: criteria_path = Path(path) if path is not None else DEFAULT_CRITERIA_PATH payload = json.loads(criteria_path.read_text(encoding="utf-8")) return _registry_from_payload(payload) def active_quality_criteria_version(path: str | Path | None = None) -> str: return load_quality_criteria(path).criteria_version def criteria_registry_dict(registry: QualityCriteriaRegistry) -> dict[str, Any]: return asdict(registry) def criteria_registry_json(registry: QualityCriteriaRegistry) -> str: return json.dumps(criteria_registry_dict(registry), indent=2, sort_keys=True) + "\n" def criteria_registry_markdown(registry: QualityCriteriaRegistry) -> str: lines = [ f"# Quality Criteria Registry: {registry.criteria_version}", "", f"- Schema: `{registry.schema_version}`", f"- Status: `{registry.status}`", f"- Updated: `{registry.updated_at}`", "", ] for criterion in registry.criteria: lines.extend( [ f"## {criterion.id}: {criterion.title}", "", f"- Category: `{criterion.category}`", f"- Severity: `{criterion.severity}`", f"- Applies to: `{', '.join(criterion.applies_to)}`", f"- Deterministic action: `{criterion.deterministic_action}`", "", criterion.description, "", f"Deterministic trigger: {criterion.deterministic_action_when}", "", f"Reviewer guidance: {criterion.reviewer_guidance}", "", ] ) return "\n".join(lines) def _registry_from_payload(payload: dict[str, Any]) -> QualityCriteriaRegistry: if payload.get("schema_version") != CRITERIA_SCHEMA_VERSION: raise ValueError( "unsupported quality criteria schema: " f"{payload.get('schema_version', '')}" ) criteria_payload = payload.get("criteria") if not isinstance(criteria_payload, list) or not criteria_payload: raise ValueError("quality criteria registry must contain criteria") criteria = [_criterion_from_payload(item) for item in criteria_payload] ids = [criterion.id for criterion in criteria] if len(ids) != len(set(ids)): raise ValueError("quality criteria ids must be unique") return QualityCriteriaRegistry( schema_version=str(payload.get("schema_version", "")), criteria_version=str(payload.get("criteria_version", "")), status=str(payload.get("status", "")), updated_at=str(payload.get("updated_at", "")), criteria=criteria, ) def _criterion_from_payload(payload: dict[str, Any]) -> QualityCriterion: missing = sorted(REQUIRED_CRITERION_FIELDS - set(payload)) if missing: raise ValueError( f"quality criterion {payload.get('id', '')} missing fields: " f"{', '.join(missing)}" ) applies_to = payload.get("applies_to") if not isinstance(applies_to, list) or not applies_to: raise ValueError( f"quality criterion {payload.get('id', '')} must list applies_to" ) examples = payload.get("examples") or [] return QualityCriterion( id=str(payload["id"]), title=str(payload["title"]), category=str(payload["category"]), severity=str(payload["severity"]), applies_to=[str(item) for item in applies_to], description=str(payload["description"]), deterministic_action=str(payload["deterministic_action"]), deterministic_action_when=str(payload["deterministic_action_when"]), reviewer_guidance=str(payload["reviewer_guidance"]), agentic_guidance=str(payload.get("agentic_guidance", "")), examples=[str(item) for item in examples], )