76 lines
2.7 KiB
Python
76 lines
2.7 KiB
Python
"""Features B & C — Klartext-Erklärung von Scan-Funden / CI-Fehlern.
|
|
|
|
Aufruf:
|
|
python ai_explain.py <kind> <textdatei>
|
|
|
|
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": "<!-- ci-ai:security -->",
|
|
"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": "<!-- ci-ai:ci-failure -->",
|
|
"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 <security|ci-failure> <textdatei>", file=sys.stderr)
|
|
return 2
|
|
|
|
kind = KINDS[sys.argv[1]]
|
|
ok, why = llm.configured()
|
|
if not ok:
|
|
print(f"[ai_explain] KI inaktiv ({why}) — Erklärung sauber übersprungen, kein Fehler.")
|
|
return 0
|
|
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())
|