feat. Enforce repo review config

This commit is contained in:
Space-Banane
2026-05-22 22:37:53 +02:00
parent 71b4341cd4
commit 91401adbed
15 changed files with 254 additions and 28 deletions

View File

@@ -21,9 +21,11 @@ def parse_command(body: str) -> ParsedCommand | None:
if "--full" in tokens:
parsed.full = True
parsed.mode = "full"
parsed.mode_explicit = True
for mode in ("security", "performance", "tests"):
if mode in tokens:
parsed.mode = mode
parsed.mode_explicit = True
break
elif name == "fix":
parsed.branch_fix = "--branch" in tokens

View File

@@ -1,5 +1,6 @@
from __future__ import annotations
import base64
from dataclasses import dataclass
from typing import Any
from urllib.parse import quote
@@ -95,3 +96,24 @@ class GiteaClient:
encoded_name = quote(name, safe="")
payload = self._request("GET", f"/api/v1/repos/{encoded_owner}/{encoded_name}/issues/{pr_number}/comments")
return list(payload)
def get_file_content(self, repo: str, path: str, *, ref: str) -> str | None:
owner, name = self.split_repo(repo)
encoded_owner = quote(owner, safe="")
encoded_name = quote(name, safe="")
encoded_path = quote(path, safe="")
try:
payload = self._request(
"GET",
f"/api/v1/repos/{encoded_owner}/{encoded_name}/contents/{encoded_path}?ref={quote(ref, safe='')}",
)
except httpx.HTTPStatusError as exc:
if exc.response.status_code == 404:
return None
raise
content = payload.get("content")
encoding = payload.get("encoding")
if not isinstance(content, str) or encoding != "base64":
return None
decoded = base64.b64decode(content.encode("ascii"))
return decoded.decode("utf-8", errors="ignore")

View File

@@ -8,28 +8,32 @@ import yaml
@dataclass(slots=True)
class RepoReviewConfig:
configured: bool = True
enabled: bool = True
default_mode: str = "summary"
max_diff_bytes: int = 200000
include_tests: bool = True
focus: list[str] = field(default_factory=lambda: ["correctness", "security", "maintainability"])
ignore: list[str] = field(default_factory=list)
allow_fix: bool = False
def load_repo_review_config(repo_root: Path) -> RepoReviewConfig:
path = repo_root / ".codex-review.yml"
if not path.exists():
return RepoReviewConfig()
raw = yaml.safe_load(path.read_text(encoding="utf-8")) or {}
return RepoReviewConfig(configured=False)
return parse_repo_review_config_text(path.read_text(encoding="utf-8"), configured=True)
def parse_repo_review_config_text(text: str, *, configured: bool) -> RepoReviewConfig:
raw = yaml.safe_load(text) or {}
review = raw.get("review", {}) or {}
commands = raw.get("commands", {}) or {}
default_mode = str(review.get("default_mode", "summary")).strip().lower() or "summary"
return RepoReviewConfig(
configured=configured,
enabled=bool(raw.get("enabled", True)),
default_mode=str(review.get("default_mode", "summary")),
default_mode=default_mode,
max_diff_bytes=int(review.get("max_diff_bytes", 200000)),
include_tests=bool(review.get("include_tests", True)),
focus=list(review.get("focus", ["correctness", "security", "maintainability"])),
ignore=list(raw.get("ignore", [])),
allow_fix=bool(commands.get("allow_fix", False)),
)

View File

@@ -33,8 +33,9 @@ def format_unsupported_ack(command: ParsedCommand) -> str:
return f"⚠️ Command `@codex {command.name}` is not enabled on this repository."
def format_result_comment(head_sha: str, result: dict) -> str:
def format_result_comment(head_sha: str, result: dict, *, repo_configured: bool = True) -> str:
usage_note = _format_usage_note(result)
missing_config_note = _format_missing_config_note(repo_configured)
markdown_comment = result.get("markdown_comment")
if isinstance(markdown_comment, str) and markdown_comment.strip():
body = markdown_comment.strip()
@@ -71,6 +72,8 @@ def format_result_comment(head_sha: str, result: dict) -> str:
body = "\n".join(lines).strip()
if usage_note:
body = f"{body}\n\n{usage_note}"
if missing_config_note:
body = f"{body}\n\n{missing_config_note}"
return _inject_head_sha_marker(head_sha, body)
@@ -97,3 +100,9 @@ def _format_usage_note(result: dict) -> str:
if isinstance(total_tokens, int):
parts.append(f"total `{total_tokens}`")
return f"_Note: {', '.join(parts)} tokens used._"
def _format_missing_config_note(repo_configured: bool) -> str:
if repo_configured:
return ""
return ".codex-review.yml is not configured"

View File

@@ -275,6 +275,9 @@ def prepare_review_prompt(
tmpdir = Path(tmp)
repo_dir = checkout_pr(tmpdir, pr)
repo_cfg = load_repo_review_config(repo_dir)
if command.name == "review" and not command.mode_explicit:
configured_mode = repo_cfg.default_mode
command.mode = configured_mode if configured_mode in {"summary", "security", "performance", "tests", "full"} else "summary"
diff_context = collect_diff_context(repo_dir, pr, min(settings.max_diff_bytes, repo_cfg.max_diff_bytes))
diff_context["changed_files"] = _apply_ignore_patterns(diff_context["changed_files"], repo_cfg.ignore)
diff_context["diff"] = _redact_secrets_from_diff(diff_context["diff"])