Files
yao-meta-skill/scripts/render_review_annotations.py
YAO 31ce04c655 Split meta skill CLI and review gates
Merge the beta-ready Yao Meta Skill architecture, report, evidence gate, and release-boundary updates.\n\nRelease boundary: beta/public testing is allowed; formal world-class, fully reviewed, or superiority claims remain blocked until the pending evidence gates are accepted.
2026-06-17 18:43:02 +08:00

403 lines
15 KiB
Python

#!/usr/bin/env python3
import argparse
import hashlib
import json
from datetime import date
from pathlib import Path
from typing import Any
ROOT = Path(__file__).resolve().parent.parent
DEFAULT_SOURCE = ROOT / "reports" / "review_annotations_input.json"
DEFAULT_OUTPUT_JSON = ROOT / "reports" / "review_annotations.json"
DEFAULT_OUTPUT_MD = ROOT / "reports" / "review_annotations.md"
VALID_GATES = {
"intent-canvas",
"trigger-lab",
"output-lab",
"context-budget",
"runtime-matrix",
"trust-report",
"python-compat",
"architecture-maintainability",
"permission-gates",
"permission-runtime",
"skill-atlas",
"operations-loop",
"review-waivers",
"world-class-evidence",
"registry-audit",
"release-notes",
}
VALID_SEVERITIES = {"info", "note", "warning", "blocker"}
VALID_STATUSES = {"open", "resolved", "deferred"}
def display_path(root: Path, path: Path) -> str:
try:
return str(path.resolve().relative_to(root.resolve()))
except ValueError:
try:
return str(path.resolve().relative_to(ROOT.resolve()))
except ValueError:
return str(path.resolve())
def load_json(path: Path) -> tuple[dict[str, Any], list[str]]:
if not path.exists():
return {"schema_version": "1.0", "annotations": []}, []
try:
payload = json.loads(path.read_text(encoding="utf-8"))
except json.JSONDecodeError as exc:
return {"schema_version": "1.0", "annotations": []}, [f"Invalid annotation JSON {display_path(ROOT, path)}: {exc}"]
if not isinstance(payload, dict):
return {"schema_version": "1.0", "annotations": []}, [f"Annotation file root must be an object: {display_path(ROOT, path)}"]
if not isinstance(payload.get("annotations", []), list):
return {"schema_version": "1.0", "annotations": []}, ["annotations must be a list"]
return payload, []
def template_payload() -> dict[str, Any]:
return {
"schema_version": "1.0",
"review_contract": {
"gate_key": "One Review Studio gate key, such as output-lab or trust-report.",
"target_path": "Relative path to the source or report being annotated.",
"line": "Optional positive integer line number.",
"severity": "info | note | warning | blocker.",
"status": "open | resolved | deferred.",
"body": "Reviewer-visible annotation text.",
},
"annotations": [],
}
def annotation_id(annotation: dict[str, Any]) -> str:
if annotation.get("id"):
return str(annotation["id"])
raw = "|".join(
[
str(annotation.get("gate_key", "")),
str(annotation.get("target_path", "")),
str(annotation.get("line", "")),
str(annotation.get("body", "")),
]
)
return "ann-" + hashlib.sha256(raw.encode("utf-8")).hexdigest()[:12]
def source_excerpt(path: Path, line: int | None) -> str:
if line is None or not path.exists() or not path.is_file():
return ""
try:
lines = path.read_text(encoding="utf-8", errors="replace").splitlines()
except OSError:
return ""
if line < 1 or line > len(lines):
return ""
return lines[line - 1].strip()[:220]
def normalize_annotation(skill_dir: Path, raw: dict[str, Any], index: int) -> tuple[dict[str, Any], list[str]]:
failures: list[str] = []
if not isinstance(raw, dict):
return {}, [f"annotation #{index} must be an object"]
gate_key = str(raw.get("gate_key", "")).strip()
if gate_key not in VALID_GATES:
failures.append(f"{annotation_id(raw)}: invalid gate_key {gate_key!r}")
severity = str(raw.get("severity", "note")).strip().lower()
if severity not in VALID_SEVERITIES:
failures.append(f"{annotation_id(raw)}: invalid severity {severity!r}")
status = str(raw.get("status", "open")).strip().lower()
if status not in VALID_STATUSES:
failures.append(f"{annotation_id(raw)}: invalid status {status!r}")
body = str(raw.get("body", "")).strip()
if not body:
failures.append(f"{annotation_id(raw)}: body is required")
target_path = str(raw.get("target_path", "")).strip()
if not target_path:
failures.append(f"{annotation_id(raw)}: target_path is required")
rel = Path(target_path)
if rel.is_absolute():
failures.append(f"{annotation_id(raw)}: target_path must be relative")
target = skill_dir / "__invalid_absolute_path__"
else:
target = (skill_dir / rel).resolve()
try:
target.relative_to(skill_dir.resolve())
except ValueError:
failures.append(f"{annotation_id(raw)}: target_path escapes skill directory")
target = skill_dir / "__invalid_escaped_path__"
line_value = raw.get("line", None)
line: int | None
if line_value is None or line_value == "":
line = None
else:
try:
line = int(line_value)
except (TypeError, ValueError):
line = None
failures.append(f"{annotation_id(raw)}: line must be a positive integer")
else:
if line < 1:
failures.append(f"{annotation_id(raw)}: line must be a positive integer")
normalized = {
"id": annotation_id(raw),
"gate_key": gate_key,
"target_path": target_path,
"line": line,
"severity": severity,
"status": status,
"reviewer": str(raw.get("reviewer", "")).strip(),
"created_at": str(raw.get("created_at", "")).strip(),
"body": body,
"suggested_action": str(raw.get("suggested_action", "")).strip(),
"evidence": str(raw.get("evidence", "")).strip(),
"target_exists": target.exists(),
"source_excerpt": source_excerpt(target, line),
}
return normalized, failures
def append_annotation(source: dict[str, Any], args: argparse.Namespace) -> dict[str, Any]:
annotations = list(source.get("annotations", []))
raw = {
"id": args.annotation_id,
"gate_key": args.gate_key,
"target_path": args.target_path,
"line": args.line,
"severity": args.severity,
"status": args.status,
"reviewer": args.reviewer,
"created_at": args.created_at or str(date.today()),
"body": args.body,
"suggested_action": args.suggested_action,
"evidence": args.evidence,
}
annotations.append({key: value for key, value in raw.items() if value is not None and value != ""})
updated = dict(source)
updated["annotations"] = annotations
updated.setdefault("schema_version", "1.0")
return updated
def build_summary(annotations: list[dict[str, Any]], failures: list[str]) -> dict[str, Any]:
open_items = [item for item in annotations if item["status"] == "open"]
resolved_items = [item for item in annotations if item["status"] == "resolved"]
deferred_items = [item for item in annotations if item["status"] == "deferred"]
open_blockers = [item for item in open_items if item["severity"] == "blocker"]
open_warnings = [item for item in open_items if item["severity"] == "warning"]
return {
"annotation_count": len(annotations),
"open_count": len(open_items),
"resolved_count": len(resolved_items),
"deferred_count": len(deferred_items),
"open_blocker_count": len(open_blockers),
"open_warning_count": len(open_warnings),
"linked_gate_count": len({item["gate_key"] for item in annotations if item["gate_key"]}),
"target_missing_count": sum(1 for item in annotations if not item["target_exists"]),
"failure_count": len(failures),
}
def render_markdown(payload: dict[str, Any]) -> str:
summary = payload["summary"]
lines = [
"# Review Annotations",
"",
"This report renders reviewer annotations attached to Review Studio gates and source/report paths.",
"",
f"- Annotations: `{summary['annotation_count']}`",
f"- Open: `{summary['open_count']}`",
f"- Resolved: `{summary['resolved_count']}`",
f"- Deferred: `{summary['deferred_count']}`",
f"- Open blockers: `{summary['open_blocker_count']}`",
f"- Open warnings: `{summary['open_warning_count']}`",
"",
]
if not payload["annotations"]:
lines.extend(["No reviewer annotations recorded yet.", ""])
else:
lines.extend(
[
"## Annotation Ledger",
"",
"| ID | Gate | Severity | Status | Target | Reviewer | Note |",
"| --- | --- | --- | --- | --- | --- | --- |",
]
)
for item in payload["annotations"]:
line = f":{item['line']}" if item.get("line") else ""
note = item["body"].replace("|", "\\|")
lines.append(
f"| {item['id']} | {item['gate_key']} | {item['severity']} | {item['status']} | "
f"{item['target_path']}{line} | {item.get('reviewer', '')} | {note} |"
)
if item.get("source_excerpt"):
excerpt = str(item["source_excerpt"]).replace("|", "\\|")
lines.append(f"| | | | | | excerpt | `{excerpt}` |")
if payload.get("failures"):
lines.extend(["", "## Failures", ""])
for failure in payload["failures"]:
lines.append(f"- {failure}")
lines.extend(
[
"",
"## Review Rule",
"",
"- Use annotations for reviewer comments tied to a gate or source line.",
"- Use waivers only for explicit acceptance of warning-level release risk.",
"- Open blocker annotations should block a release decision until resolved or deferred with rationale.",
]
)
return "\n".join(lines).strip() + "\n"
def render_review_annotations(
skill_dir: Path,
source_json: Path,
output_json: Path,
output_md: Path,
args: argparse.Namespace,
) -> dict[str, Any]:
skill_dir = skill_dir.resolve()
source, failures = load_json(source_json)
template_written = False
should_write_source = False
if args.write_template and not source_json.exists():
source_json.parent.mkdir(parents=True, exist_ok=True)
source_json.write_text(json.dumps(template_payload(), ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
source = template_payload()
template_written = True
if args.add_annotation:
source = append_annotation(source, args)
should_write_source = True
annotations: list[dict[str, Any]] = []
seen = set()
for index, raw in enumerate(source.get("annotations", []), start=1):
normalized, item_failures = normalize_annotation(skill_dir, raw, index)
failures.extend(item_failures)
if normalized:
if normalized["id"] in seen:
failures.append(f"{normalized['id']}: duplicate annotation id")
seen.add(normalized["id"])
annotations.append(normalized)
summary = build_summary(annotations, failures)
if should_write_source and not failures:
source_json.parent.mkdir(parents=True, exist_ok=True)
source_json.write_text(json.dumps(source, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
payload = {
"schema_version": "1.0",
"ok": not failures,
"skill_dir": display_path(ROOT, skill_dir),
"source": display_path(skill_dir, source_json),
"summary": summary,
"annotations": annotations,
"failures": failures,
"template_written": template_written,
"artifacts": {
"json": display_path(skill_dir, output_json),
"markdown": display_path(skill_dir, output_md),
},
}
output_json.parent.mkdir(parents=True, exist_ok=True)
output_md.parent.mkdir(parents=True, exist_ok=True)
output_json.write_text(json.dumps(payload, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
output_md.write_text(render_markdown(payload), encoding="utf-8")
return payload
def render_report(
skill_dir: Path,
annotations_json: Path | None = None,
output_json: Path | None = None,
output_md: Path | None = None,
) -> dict[str, Any]:
skill_dir = skill_dir.resolve()
args = argparse.Namespace(
write_template=False,
add_annotation=False,
annotation_id=None,
gate_key=None,
target_path=None,
line=None,
severity="note",
status="open",
reviewer=None,
created_at=None,
body=None,
suggested_action=None,
evidence=None,
)
return render_review_annotations(
skill_dir,
annotations_json or skill_dir / "reports" / "review_annotations_input.json",
output_json or skill_dir / "reports" / "review_annotations.json",
output_md or skill_dir / "reports" / "review_annotations.md",
args,
)
def main() -> None:
parser = argparse.ArgumentParser(description="Render reviewer annotations for Review Studio gates and source paths.")
parser.add_argument("skill_dir", nargs="?", default=".")
parser.add_argument("--annotations-json")
parser.add_argument("--output-json")
parser.add_argument("--output-md")
parser.add_argument("--write-template", action="store_true")
parser.add_argument("--add-annotation", action="store_true")
parser.add_argument("--annotation-id")
parser.add_argument("--gate-key", choices=sorted(VALID_GATES))
parser.add_argument("--target-path")
parser.add_argument("--line", type=int)
parser.add_argument("--severity", choices=sorted(VALID_SEVERITIES), default="note")
parser.add_argument("--status", choices=sorted(VALID_STATUSES), default="open")
parser.add_argument("--reviewer")
parser.add_argument("--created-at")
parser.add_argument("--body")
parser.add_argument("--suggested-action")
parser.add_argument("--evidence")
args = parser.parse_args()
skill_dir = Path(args.skill_dir).resolve()
annotations_json = Path(args.annotations_json).resolve() if args.annotations_json else skill_dir / "reports" / "review_annotations_input.json"
output_json = Path(args.output_json).resolve() if args.output_json else skill_dir / "reports" / "review_annotations.json"
output_md = Path(args.output_md).resolve() if args.output_md else skill_dir / "reports" / "review_annotations.md"
if args.add_annotation:
missing = [name for name in ("gate_key", "target_path", "body") if not getattr(args, name)]
if missing:
payload = {
"schema_version": "1.0",
"ok": False,
"skill_dir": display_path(ROOT, skill_dir),
"source": display_path(skill_dir, annotations_json),
"summary": build_summary([], missing),
"annotations": [],
"failures": [f"--add-annotation requires {', '.join(missing)}"],
"template_written": False,
"artifacts": {
"json": display_path(skill_dir, output_json),
"markdown": display_path(skill_dir, output_md),
},
}
print(json.dumps(payload, ensure_ascii=False, indent=2))
raise SystemExit(2)
payload = render_review_annotations(skill_dir, annotations_json, output_json, output_md, args)
print(json.dumps(payload, ensure_ascii=False, indent=2))
raise SystemExit(0 if payload["ok"] else 2)
if __name__ == "__main__":
main()