generated from coulomb/repo-seed
Add quality criteria registry
This commit is contained in:
15
src/repo_registry/acceptance/__init__.py
Normal file
15
src/repo_registry/acceptance/__init__.py
Normal file
@@ -0,0 +1,15 @@
|
||||
from repo_registry.acceptance.criteria import (
|
||||
active_quality_criteria_version,
|
||||
criteria_registry_dict,
|
||||
criteria_registry_json,
|
||||
criteria_registry_markdown,
|
||||
load_quality_criteria,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
"active_quality_criteria_version",
|
||||
"criteria_registry_dict",
|
||||
"criteria_registry_json",
|
||||
"criteria_registry_markdown",
|
||||
"load_quality_criteria",
|
||||
]
|
||||
148
src/repo_registry/acceptance/criteria.py
Normal file
148
src/repo_registry/acceptance/criteria.py
Normal file
@@ -0,0 +1,148 @@
|
||||
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],
|
||||
)
|
||||
Reference in New Issue
Block a user