import os import json import httpx import logging logger = logging.getLogger(__name__) GEMINI_API_KEY = os.getenv("GEMINI_API_KEY") GEMINI_API_URL = f"https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent?key={GEMINI_API_KEY}" async def generate_change_summary(files: list) -> str: """ Generate a concise summary of what changed in the PR using AI. """ logger.info(f"📝 Generating change summary for {len(files)} files...") # Build a summary of changes changes_text = "" for f in files: status = f.get("status", "modified") filename = f.get("filename", "unknown") additions = f.get("additions", 0) deletions = f.get("deletions", 0) changes_text += f"- {status.upper()}: {filename} (+{additions}/-{deletions})\n" prompt = f"""You are a code reviewer. Summarize what changed in this pull request in 1-2 short sentences. Focus on WHAT was changed, not HOW. Be concise and clear. Files changed: {changes_text} Respond with ONLY the summary text (no markdown, no extra formatting):""" headers = {"Content-Type": "application/json"} payload = { "contents": [{"parts": [{"text": prompt}]}], "generationConfig": { "temperature": 0.5, "maxOutputTokens": 150 } } try: async with httpx.AsyncClient(timeout=30.0) as client: response = await client.post(GEMINI_API_URL, headers=headers, json=payload) response.raise_for_status() data = response.json() summary = data["candidates"][0]["content"]["parts"][0]["text"].strip() logger.info(f"✅ Generated summary: {summary[:100]}...") return summary except Exception as e: logger.warning(f"⚠️ Failed to generate summary: {str(e)}") return f"Modified {len(files)} file(s)" async def analyze_code(file_name: str, patch: str) -> list: """ Analyze a single file diff using Google Gemini AI model. Returns a list of structured comments. """ logger.info(f"🤖 Sending to Gemini AI: {file_name} ({len(patch)} chars)") prompt = f"""You are a senior code reviewer focused on finding REAL issues. Review the following diff from `{file_name}` and provide feedback ONLY for: - Security vulnerabilities - Bugs or logic errors - Performance issues - Code that will break in production - Missing error handling for critical operations - Resource leaks (memory, connections, files) DO NOT comment on: - Code style or formatting - Comments or documentation - Variable naming (unless critically confusing) - Minor suggestions or preferences - Things that are already working fine Respond ONLY with a JSON array (no markdown, no explanation): [ {{ "line": 42, "severity": "high", "comment": "Potential SQL injection vulnerability - use parameterized queries" }} ] Severity levels: "high" (critical bugs/security), "medium" (bugs/performance), "low" (minor issues) If no REAL issues found, return an empty array: [] Code Diff: {patch} """ headers = { "Content-Type": "application/json" } payload = { "contents": [ { "parts": [ {"text": prompt} ] } ], "generationConfig": { "temperature": 0.3, "maxOutputTokens": 2048 } } try: async with httpx.AsyncClient(timeout=60.0) as client: logger.info("⏳ Waiting for Gemini response...") response = await client.post(GEMINI_API_URL, headers=headers, json=payload) response.raise_for_status() data = response.json() logger.info("✅ Gemini response received") # Extract text from Gemini response structure text_output = data["candidates"][0]["content"]["parts"][0]["text"].strip() logger.info(f"📄 Response length: {len(text_output)} chars") # Defensive: handle non-JSON outputs try: # Remove markdown code blocks if present if text_output.startswith("```json"): text_output = text_output.replace("```json", "").replace("```", "").strip() elif text_output.startswith("```"): text_output = text_output.replace("```", "").strip() parsed = json.loads(text_output) logger.info(f"✅ Parsed {len(parsed)} review comments") return parsed except Exception as e: logger.warning(f"⚠️ Failed to parse JSON, returning raw text: {str(e)}") logger.warning(f"Raw response: {text_output[:200]}") return [{"line": 1, "severity": "info", "comment": text_output}] except Exception as e: logger.error(f"❌ Gemini API error: {str(e)}") raise