"""Features B & C — Klartext-Erklärung von Scan-Funden / CI-Fehlern. Aufruf: python ai_explain.py kind = "security" -> erklärt Trivy/Gitleaks-Funde (GitLab-Duo-Parität) kind = "ci-failure" -> erklärt ein fehlgeschlagenes Job-Log Postet das Ergebnis als idempotenten PR-Kommentar (eigener Marker je kind). Ohne PR-Kontext (z.B. push ohne PR) wird nur ins Job-Log geschrieben. """ import sys import context import gitea_api import llm KINDS = { "security": { "marker": "", "heading": "## 🛡️ Security-Scan — Erklärung der Funde", "system": ( "Du bist ein Security-Engineer. Erkläre die folgenden Scanner-Funde " "(Trivy/Gitleaks) auf Deutsch in Klartext: was wurde gefunden, warum " "ist es ein Risiko, und wie behebt man es konkret. Priorisiere nach " "Schweregrad. Keine Funde erfinden." ), "intro": "Der Security-Scan ist fehlgeschlagen. Hier die Funde im Klartext:", }, "ci-failure": { "marker": "", "heading": "## 🔧 CI-Fehler — Analyse", "system": ( "Du bist ein erfahrener CI/CD-Engineer. Analysiere das folgende Log " "eines fehlgeschlagenen CI-Jobs auf Deutsch: nenne die wahrscheinliche " "Ursache und einen konkreten Fix. Knapp und konkret." ), "intro": "Ein CI-Job ist fehlgeschlagen. Wahrscheinliche Ursache:", }, } LOG_CAP = 16000 # Zeichen aus dem Log (Ende ist meist am aussagekräftigsten) def main() -> int: if len(sys.argv) != 3 or sys.argv[1] not in KINDS: print("Usage: ai_explain.py ", file=sys.stderr) return 2 kind = KINDS[sys.argv[1]] with open(sys.argv[2], encoding="utf-8", errors="replace") as fh: text = fh.read() if len(text) > LOG_CAP: text = "[... Anfang gekürzt ...]\n" + text[-LOG_CAP:] answer = llm.complete(kind["system"], f"{kind['intro']}\n\n```\n{text}\n```", max_tokens=1200) body = f"{kind['heading']}\n\n{answer}" owner, repo = context.repo_slug() pr = context.pull_request() if pr and pr["index"]: cid = gitea_api.upsert_comment(owner, repo, pr["index"], kind["marker"], body) print(f"[ai_explain] Kommentar gepostet/aktualisiert (id={cid}).") else: print("[ai_explain] Kein PR-Kontext — Ausgabe nur im Log:\n") print(body) return 0 if __name__ == "__main__": sys.exit(main())