"""Feature A — KI-PR-Review + Summary. Holt den PR-Diff über die Gitea-API, lässt ihn vom konfigurierten LLM zusammenfassen + reviewen und postet das Ergebnis als EINEN idempotenten Kommentar (Marker -> editiert sich bei jedem Push, statt zu spammen). Erwartete Env-Vars: siehe llm.py + gitea_api.py, plus die Actions-Kontext- Variablen (GITHUB_REPOSITORY, GITHUB_EVENT_PATH). """ import sys import context import gitea_api import llm MARKER = "" DIFF_CAP = 40000 # Zeichen — darüber wird der Diff gekürzt (Token-/Kostenschutz) # Dateien, die für ein Review wenig Sinn ergeben (Lockfiles, Binär, generiert). SKIP_TOKENS = ( ".lock", "package-lock.json", "yarn.lock", "pnpm-lock.yaml", ".min.js", ".min.css", ".map", ".png", ".jpg", ".jpeg", ".gif", ".ico", ".pdf", ".woff", ".woff2", ".ttf", "/vendor/", "/node_modules/", "/dist/", "/build/", ) SYSTEM = ( "Du bist ein erfahrener, pragmatischer Code-Reviewer. Antworte auf Deutsch, " "knapp und konkret. Erfinde nichts, was nicht im Diff steht." ) PROMPT = """\ Reviewe den folgenden Pull-Request. PR-Titel: {title} PR-Beschreibung: {body} Gib genau diese zwei Abschnitte aus (Markdown): ### Zusammenfassung 2–4 Sätze: Was ändert dieser PR und warum. ### Review Stichpunkte zu: möglichen Bugs, Sicherheits-/Edge-Cases, fehlenden Tests, Stil/Lesbarkeit. Nur echte, im Diff belegbare Punkte — wenn alles gut aussieht, sag das ehrlich. Markiere ernste Probleme mit ⚠️. --- DIFF --- {diff} """ def filter_diff(diff: str) -> str: """Entfernt uninteressante Dateien (per `diff --git`-Sektion).""" if not diff: return "" sections = diff.split("diff --git ") kept = [] for sec in sections[1:]: # sections[0] ist der Vorspann (leer) header = sec.split("\n", 1)[0].lower() if any(tok in header for tok in SKIP_TOKENS): continue kept.append("diff --git " + sec) return "".join(kept).strip() def main() -> int: owner, repo = context.repo_slug() pr = context.pull_request() if not pr or not pr["index"]: print("[ai_review] Kein Pull-Request im Event — übersprungen.") return 0 if pr["author"].lower().endswith("-bot") or pr["author"].lower() == "ci-ai": print("[ai_review] PR vom Bot selbst — übersprungen.") return 0 diff = filter_diff(gitea_api.get_pr_diff(owner, repo, pr["index"])) if not diff: print("[ai_review] Leerer/gefilterter Diff — nichts zu reviewen.") return 0 truncated = False if len(diff) > DIFF_CAP: diff = diff[:DIFF_CAP] truncated = True answer = llm.complete( SYSTEM, PROMPT.format(title=pr["title"], body=pr["body"][:1000], diff=diff), max_tokens=1800, ) note = "\n\n_⚠️ Diff war groß und wurde gekürzt — Review nur über den Anfang._" if truncated else "" body = f"## 🤖 KI-Review\n\n{answer}{note}" cid = gitea_api.upsert_comment(owner, repo, pr["index"], MARKER, body) print(f"[ai_review] Kommentar gepostet/aktualisiert (id={cid}).") return 0 if __name__ == "__main__": sys.exit(main())