#!/usr/bin/env python3 """Inspect or merge a reviewed NestQuest feature PR into dev only.""" from __future__ import annotations import argparse import json import os from pathlib import Path import re import sys import urllib.error import urllib.request API = "https://code.cubecraftlabs.com/api/v1/repos/Maniacs_Mansion/NestQuest" CREDENTIALS = Path("/home/overseer/.llms/credentials.env") FULL_SHA = re.compile(r"^[0-9a-f]{40}$") def token_from_file() -> str: for line in CREDENTIALS.read_text().splitlines(): if line.startswith("GITEA_APPROVER_AGENT_TOKEN="): return line.split("=", 1)[1].strip().strip("\"'") raise RuntimeError("GITEA_APPROVER_AGENT_TOKEN is unavailable") def request(path: str, token: str, payload: dict | None = None) -> dict: body = None if payload is None else json.dumps(payload).encode() headers = {"Authorization": "token " + token, "Accept": "application/json"} if body is not None: headers["Content-Type"] = "application/json" call = urllib.request.Request(API + path, data=body, headers=headers) try: with urllib.request.urlopen(call, timeout=20) as response: raw = response.read() return json.loads(raw) if raw else {} except urllib.error.HTTPError as error: raise RuntimeError(f"Gitea returned HTTP {error.code}") from None def inspect(pr_number: int, token: str) -> dict: pr = request(f"/pulls/{pr_number}", token) head = (pr.get("head") or {}).get("sha") base = (pr.get("base") or {}).get("ref") if not isinstance(head, str) or not FULL_SHA.fullmatch(head): raise RuntimeError("Gitea returned an invalid PR head") status = request(f"/commits/{head}/status", token) return { "number": pr.get("number"), "base": base, "head": head, "state": pr.get("state"), "merged": pr.get("merged"), "mergeable": pr.get("mergeable"), "check_count": status.get("total_count") or 0, "check_state": status.get("state"), } def validate_review(path: Path, head: str) -> None: if not path.is_file() or not path.resolve().is_relative_to(Path("/tmp/opencode").resolve()): raise RuntimeError("review evidence must be a file under /tmp/opencode") text = path.read_text(errors="replace") reviewed = re.findall(r"(?m)^REVIEWED_HEAD:\s*([0-9a-f]{40})\s*$", text) verdicts = re.findall(r"(?m)^REVIEW_VERDICT:\s*(APPROVED|CHANGES_REQUIRED)\s*$", text) if len(reviewed) != 1 or len(verdicts) != 1 or reviewed[0] != head or verdicts[0] != "APPROVED": raise RuntimeError("independent review does not approve this exact head") def main() -> int: parser = argparse.ArgumentParser(description=__doc__) parser.add_argument("operation", choices=("inspect", "merge")) parser.add_argument("pr", type=int) parser.add_argument("reviewed_head", nargs="?") parser.add_argument("review_file", nargs="?", type=Path) args = parser.parse_args() if args.pr < 1: parser.error("PR number must be positive") if args.operation == "merge" and (not args.reviewed_head or not args.review_file): parser.error("merge requires reviewed_head and review_file") token = token_from_file() before = inspect(args.pr, token) if args.operation == "inspect": print(json.dumps(before, sort_keys=True)) return 0 if not FULL_SHA.fullmatch(args.reviewed_head): parser.error("reviewed head must be a full SHA") validate_review(args.review_file, args.reviewed_head) if before["base"] != "dev" or before["head"] != args.reviewed_head: raise RuntimeError("PR target or current head differs from the approved review") if before["state"] != "open" or before["merged"] or before["mergeable"] is not True: raise RuntimeError("PR is closed, merged, or not mergeable") if before["check_count"] and before["check_state"] != "success": raise RuntimeError("a configured commit check has not passed") # Gitea rechecks branch protection at the merge endpoint. The caller must # independently verify Maestro and task-level evidence before invoking us. request(f"/pulls/{args.pr}/merge", token, {"Do": "squash"}) after = inspect(args.pr, token) if after["merged"] is not True: raise RuntimeError("merge request returned, but the PR is not merged") print(json.dumps(after, sort_keys=True)) return 0 if __name__ == "__main__": try: raise SystemExit(main()) except (OSError, ValueError, RuntimeError) as error: print(f"approval gate failed: {error}", file=sys.stderr) raise SystemExit(1)