Spaces:
Running
Running
| import asyncio | |
| from app.core.llm import call_llm_for_json | |
| from app.core.logger import get_logger | |
| from app.core.prompts import AUTO_FIX_PROMPT, CODE_REVIEW_PROMPT | |
| from app.models.repository import RepositoryMetadata | |
| from app.models.review import AutoFix, ReviewSuggestions | |
| from app.tools.file_scanner import read_source_samples, get_python_files | |
| from app.tools.github_tool import get_changed_files | |
| import os | |
| logger = get_logger(__name__) | |
| async def run(local_path: str, metadata: RepositoryMetadata) -> ReviewSuggestions: | |
| """ | |
| Perform AI-powered code review on repository. | |
| """ | |
| logger.info("Starting Code Review Agent", extra={"path": local_path}) | |
| # Step 1 - Get Python files | |
| base_sha = metadata.base_sha | |
| head_sha = metadata.head_sha | |
| changed_files = [] | |
| if base_sha and head_sha: | |
| changed_files = get_changed_files(local_path, base_sha, head_sha) | |
| #Step 2 - Read source code samples | |
| all_files = changed_files or [ | |
| f for f in get_python_files(local_path) | |
| if not os.path.basename(f).startswith("test_") | |
| and not os.path.basename(f).endswith("_test.py") | |
| ] | |
| source_samples = read_source_samples( | |
| local_path, | |
| max_files=4, | |
| max_chars=2500, | |
| file_list=all_files, | |
| ) | |
| if not source_samples: | |
| return ReviewSuggestions( | |
| summary="No source files found to review.", overall_score=0.0 | |
| ) | |
| # Step 3 - Call LLM and parse JSON in one shot | |
| source_code = "\n\n".join(source_samples) | |
| prompt = CODE_REVIEW_PROMPT.format(source_code=source_code) | |
| llm_data = await call_llm_for_json(prompt, task="code_review") | |
| review = ReviewSuggestions( | |
| solid_violations=llm_data.get("solid_violations", []), | |
| duplicate_code=llm_data.get("duplicate_code", []), | |
| refactor_suggestions=llm_data.get("refactor_suggestions", []), | |
| overall_score=float(llm_data.get("overall_score", 5.0)), | |
| summary=llm_data.get("summary", ""), | |
| ) | |
| # Generate auto-fixes for top 3 violations (cap LLM calls) | |
| auto_fixes: list[AutoFix] = [] | |
| top_findings = (review.solid_violations + review.refactor_suggestions)[:3] | |
| if top_findings and source_samples: | |
| first_file_content = source_samples[0] # already read | |
| fix_tasks = [ | |
| call_llm_for_json( | |
| AUTO_FIX_PROMPT.format( | |
| source_code=first_file_content[:1500], finding=finding | |
| ) | |
| ) | |
| for finding in top_findings | |
| ] | |
| fix_results = await asyncio.gather(*fix_tasks, return_exceptions=True) | |
| for res in fix_results: | |
| if isinstance(res, dict) and res.get("fixed_snippet"): | |
| auto_fixes.append(AutoFix(**res)) | |
| review = review.model_copy(update={"auto_fixes": auto_fixes}) | |
| # Second pass — if score is poor, ask LLM for a prioritised fix list | |
| if review.overall_score < 5.0: | |
| logger.info( | |
| "Low score detected, running focused follow-up pass", | |
| extra={"score": review.overall_score}, | |
| ) | |
| followup_prompt = ( | |
| f"The following code scored {review.overall_score}/10 in a review.\n\n" | |
| f"Top violations: {review.solid_violations[:3]}\n\n" | |
| f'Return ONLY a JSON object with one key: "priority_fixes": a list of up to 3 strings, ' | |
| f"each describing the single most impactful change to make first." | |
| ) | |
| followup_data = await call_llm_for_json(followup_prompt) | |
| priority_fixes = followup_data.get("priority_fixes", []) | |
| if isinstance(priority_fixes, list) and priority_fixes: | |
| review = review.model_copy( | |
| update={ | |
| "refactor_suggestions": priority_fixes + review.refactor_suggestions | |
| } | |
| ) | |
| logger.info("Code review complete", extra={"score": review.overall_score}) | |
| return review | |