generated from coulomb/repo-seed
149 lines
4.9 KiB
Python
149 lines
4.9 KiB
Python
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', '<missing>')}"
|
|
)
|
|
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', '<unknown>')} 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', '<unknown>')} 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],
|
|
)
|