83 lines
2.8 KiB
Python
83 lines
2.8 KiB
Python
#!/usr/bin/env python3
|
|
"""Review Studio gate keys, scoring, and contract checks."""
|
|
|
|
from collections import Counter
|
|
from typing import Any
|
|
|
|
|
|
SCRIPT_INTERFACE = "internal-module"
|
|
SCRIPT_INTERFACE_REASON = "Imported by review_studio_gates.py for stable gate scoring and contract checks."
|
|
|
|
|
|
GATE_WEIGHTS = {
|
|
"trigger-lab": 15,
|
|
"output-lab": 20,
|
|
"context-budget": 10,
|
|
"runtime-matrix": 10,
|
|
"trust-report": 10,
|
|
"python-compat": 10,
|
|
"architecture-maintainability": 10,
|
|
"permission-gates": 10,
|
|
"permission-runtime": 10,
|
|
"skill-atlas": 10,
|
|
"operations-loop": 10,
|
|
"review-waivers": 10,
|
|
"world-class-evidence": 10,
|
|
"registry-audit": 10,
|
|
"release-notes": 10,
|
|
"intent-canvas": 10,
|
|
}
|
|
REVIEW_STUDIO_GATE_KEYS = frozenset(GATE_WEIGHTS)
|
|
|
|
|
|
def status_label(status: str) -> str:
|
|
return {"pass": "通过", "warn": "关注", "block": "阻断"}.get(status, status)
|
|
|
|
|
|
def add_blockers_from_gate(gates: list[dict[str, str]]) -> tuple[list[dict[str, str]], list[dict[str, str]]]:
|
|
blockers = [item for item in gates if item["status"] == "block"]
|
|
warnings = [item for item in gates if item["status"] == "warn"]
|
|
return blockers, warnings
|
|
|
|
|
|
def gate_contract(gates: list[dict[str, str]]) -> dict[str, Any]:
|
|
rendered_gate_keys = [str(item.get("key", "")) for item in gates]
|
|
rendered_gate_set = set(rendered_gate_keys)
|
|
expected_gate_set = set(REVIEW_STUDIO_GATE_KEYS)
|
|
duplicate_gate_keys = sorted(key for key, count in Counter(rendered_gate_keys).items() if count > 1)
|
|
missing_gate_keys = sorted(expected_gate_set - rendered_gate_set)
|
|
unknown_gate_keys = sorted(rendered_gate_set - expected_gate_set)
|
|
unweighted_gate_keys = sorted(rendered_gate_set - set(GATE_WEIGHTS))
|
|
return {
|
|
"ok": not (missing_gate_keys or unknown_gate_keys or duplicate_gate_keys or unweighted_gate_keys),
|
|
"expected_gate_count": len(expected_gate_set),
|
|
"actual_gate_count": len(rendered_gate_keys),
|
|
"expected_gate_keys": sorted(expected_gate_set),
|
|
"rendered_gate_keys": rendered_gate_keys,
|
|
"missing_gate_keys": missing_gate_keys,
|
|
"unknown_gate_keys": unknown_gate_keys,
|
|
"duplicate_gate_keys": duplicate_gate_keys,
|
|
"unweighted_gate_keys": unweighted_gate_keys,
|
|
}
|
|
|
|
|
|
def min_output_cases(maturity: str) -> int:
|
|
if maturity in {"library", "governed"}:
|
|
return 5
|
|
if maturity == "production":
|
|
return 3
|
|
return 1
|
|
|
|
|
|
def weighted_score(gates: list[dict[str, str]]) -> int:
|
|
earned = 0.0
|
|
total = 0.0
|
|
for item in gates:
|
|
weight = GATE_WEIGHTS.get(item["key"], 5)
|
|
total += weight
|
|
if item["status"] == "pass":
|
|
earned += weight
|
|
elif item["status"] == "warn":
|
|
earned += weight * 0.6
|
|
return int(round(earned / total * 100)) if total else 0
|