gitlink-cli/skills/gitlink-code-review/scripts/validate_review_report.py

76 lines
3.3 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
"""Validate that a code-review report gives evidence-backed Review advice."""
from __future__ import annotations
import argparse
import re
import sys
from pathlib import Path
REVIEW_PREFIX = "**Review 建议:**"
LEGACY_PREFIX = "**建议:**"
REVIEW_PATTERN = re.compile(
r"^\*\*Review 建议:\*\*\s*"
r"<span\b[^>]*><strong>([^<]+)</strong></span>\s*"
r"\*\*\[([^\]]+)\]\*\*\s*(\S.*)$"
)
REVIEW_DRAFT_HEADING = re.compile(r"^### PR #\d+ Review 建议草稿(未提交)\s*$")
EVIDENCE_MARKERS = ("依据", "测试", "Diff", "文件", "行号", "CR-", "RV-", "实现", "影响", "主线", "API", "命令", "已核验", "未发现", "通过", "失败", "缺少", "需要")
PLACEHOLDER_MARKERS = ("<结论>", "<具体", "<status-", "<decision_")
def validate_report(text: str, require_review: bool = False) -> list[str]:
errors: list[str] = []
review_lines: list[int] = []
lines = text.splitlines()
for line_number, line in enumerate(lines, start=1):
stripped = line.strip()
if stripped.startswith(LEGACY_PREFIX):
errors.append(f"line {line_number}: legacy conclusion-only field is not allowed")
if not stripped.startswith(REVIEW_PREFIX):
continue
review_lines.append(line_number)
match = REVIEW_PATTERN.match(stripped)
if not match:
errors.append(f"line {line_number}: Review decision requires a status tag and rationale")
continue
conclusion, status, rationale = match.groups()
if not conclusion.strip() or not status.strip() or len(rationale.strip()) < 30:
errors.append(f"line {line_number}: Review rationale is too short")
if not any(marker in rationale for marker in EVIDENCE_MARKERS):
errors.append(f"line {line_number}: Review rationale lacks evidence or an actionable next step")
if any(marker in rationale for marker in PLACEHOLDER_MARKERS):
errors.append(f"line {line_number}: unresolved template placeholder in rationale")
for index, line in enumerate(lines):
if REVIEW_DRAFT_HEADING.match(line.strip()) and not any(candidate.strip().startswith(REVIEW_PREFIX) for candidate in lines[index + 1:index + 7]):
errors.append(f"line {index + 1}: Review draft must start with a substantive Review suggestion")
if require_review and not review_lines:
errors.append("PR review mode requires at least one substantive Review decision")
return errors
def main() -> int:
parser = argparse.ArgumentParser(description="Validate substantive Review decisions.")
parser.add_argument("--report", required=True, type=Path)
parser.add_argument("--require-review", action="store_true")
args = parser.parse_args()
try:
text = args.report.read_text(encoding="utf-8", errors="strict")
except (OSError, UnicodeError) as exc:
print(f"review report validation failed: {exc}", file=sys.stderr)
return 2
errors = validate_report(text, args.require_review)
if errors:
print("review report validation failed:", file=sys.stderr)
for error in errors:
print(f"- {error}", file=sys.stderr)
return 1
print("review report validation passed")
return 0
if __name__ == "__main__":
raise SystemExit(main())