111 lines
4.0 KiB
Python
111 lines
4.0 KiB
Python
from __future__ import annotations
|
||
|
||
from gitea_codex_bot.types import ParsedCommand
|
||
|
||
|
||
def _inject_head_sha_marker(head_sha: str, body: str) -> str:
|
||
marker = f"<!-- codex-review:head_sha={head_sha} -->"
|
||
stripped = body.strip()
|
||
if not stripped:
|
||
return marker
|
||
if stripped.startswith("<!-- codex-review:head_sha="):
|
||
lines = stripped.splitlines()
|
||
if lines:
|
||
lines[0] = marker
|
||
return "\n".join(lines).strip()
|
||
return f"{marker}\n{stripped}"
|
||
|
||
|
||
def format_queue_ack(head_sha: str) -> str:
|
||
short_sha = head_sha[:7]
|
||
return f"👀 Codex review queued for commit `{short_sha}`."
|
||
|
||
|
||
def format_cooldown_ack(seconds: int) -> str:
|
||
return f"⏳ Cooldown active. Please wait {seconds}s before requesting another review on this PR."
|
||
|
||
|
||
def format_disabled_ack() -> str:
|
||
return "🚫 Review is disabled by `.codex-review.yml` for this repository."
|
||
|
||
|
||
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, *, 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()
|
||
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)
|
||
|
||
verdict = result.get("verdict", "has_issues")
|
||
confidence = float(result.get("confidence", 0.0))
|
||
summary = str(result.get("summary", "No summary returned."))
|
||
findings = result.get("findings", []) or []
|
||
|
||
lines = [f"<!-- codex-review:head_sha={head_sha} -->", "## Codex Review", "", f"Verdict: `{verdict}`", f"Confidence: `{confidence:.2f}`", "", summary, ""]
|
||
if not findings:
|
||
lines.append("No blocking issues found.")
|
||
else:
|
||
lines.append("Findings:")
|
||
for idx, finding in enumerate(findings, start=1):
|
||
severity = finding.get("severity", "unknown")
|
||
file_path = finding.get("file", "unknown")
|
||
line_start = finding.get("line_start", "?")
|
||
line_end = finding.get("line_end", line_start)
|
||
title = finding.get("title", "Issue")
|
||
body = finding.get("body", "")
|
||
suggestion = finding.get("suggestion", "")
|
||
lines.extend(
|
||
[
|
||
f"{idx}. `{file_path}:{line_start}-{line_end}` ({severity})",
|
||
f" {title}",
|
||
f" {body}",
|
||
f" Suggestion: {suggestion}" if suggestion else " Suggestion: n/a",
|
||
]
|
||
)
|
||
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)
|
||
|
||
|
||
def _format_usage_note(result: dict) -> str:
|
||
meta = result.get("_meta")
|
||
if not isinstance(meta, dict):
|
||
return ""
|
||
|
||
model = meta.get("model")
|
||
model_text = model.strip() if isinstance(model, str) and model.strip() else "unknown"
|
||
|
||
usage = meta.get("usage")
|
||
if not isinstance(usage, dict):
|
||
return f"_Note: model `{model_text}`._"
|
||
|
||
input_tokens = usage.get("input_tokens")
|
||
output_tokens = usage.get("output_tokens")
|
||
total_tokens = usage.get("total_tokens")
|
||
parts = [f"model `{model_text}`"]
|
||
if isinstance(input_tokens, int):
|
||
parts.append(f"input `{input_tokens}`")
|
||
if isinstance(output_tokens, int):
|
||
parts.append(f"output `{output_tokens}`")
|
||
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"
|