Padmanav commited on
Commit
a3859cd
·
1 Parent(s): 007ba8f

feat: add all 5 agents - analysis, bugs, tests, review, report

Browse files
app/agents/__init__.py ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ from app.agents import (
2
+ repo_analysis_agent,
3
+ bug_detection_agent,
4
+ test_generation_agent,
5
+ code_review_agent,
6
+ report_generator_agent
7
+ )
app/agents/bug_detection_agent.py ADDED
@@ -0,0 +1,86 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ from app.tools.static_analyzer import run_full_analysis
3
+ from app.tools.pytest_runner import run_tests
4
+ from app.tools.file_scanner import get_python_files
5
+ from app.core.llm import call_llm
6
+ from app.core.prompts import BUG_DETECTION_PROMPT
7
+ from app.models.issue import IssueReport, Bug, Warning, Suggestion
8
+
9
+
10
+ def run(local_path: str) -> IssueReport:
11
+ """
12
+ Detect bugs, warnings and suggestions in a repository.
13
+ """
14
+ print("Running Bug Detection Agent...")
15
+
16
+ # Step 1 - Run static analysis
17
+ static_results = run_full_analysis(local_path)
18
+
19
+ # Step 2 - Run tests
20
+ test_results = run_tests(local_path)
21
+
22
+ # Step 3 - Read sample of code for LLM
23
+ python_files = get_python_files(local_path)
24
+ code_samples = []
25
+ for file_path in python_files[:5]: # Limit to first 5 files
26
+ try:
27
+ with open(file_path, "r", encoding="utf-8", errors="ignore") as f:
28
+ content = f.read()[:2000] # Limit to 2000 chars per file
29
+ code_samples.append(f"### {file_path}\n{content}")
30
+ except Exception:
31
+ pass
32
+
33
+ # Step 4 - Build context for LLM
34
+ code_info = f"""
35
+ Static Analysis Results:
36
+ Ruff Issues: {len(static_results['ruff']['issues'])}
37
+ Bandit Issues: {len(static_results['bandit']['issues'])}
38
+
39
+ Ruff Output:
40
+ {chr(10).join(static_results['ruff']['issues'][:20])}
41
+
42
+ Bandit Output:
43
+ {chr(10).join(static_results['bandit']['issues'][:20])}
44
+
45
+ Test Results:
46
+ Passed: {test_results['passed']}
47
+ Failed: {test_results['failed']}
48
+ Errors: {test_results['errors']}
49
+
50
+ Code Samples:
51
+ {chr(10).join(code_samples)}
52
+ """
53
+
54
+ # Step 5 - Call LLM
55
+ prompt = BUG_DETECTION_PROMPT.format(code_info=code_info)
56
+ llm_response = call_llm(prompt)
57
+
58
+ # Step 6 - Parse response
59
+ try:
60
+ llm_data = json.loads(llm_response)
61
+ except json.JSONDecodeError:
62
+ llm_data = {}
63
+
64
+ # Step 7 - Build IssueReport
65
+ critical = [
66
+ Bug(**bug) if isinstance(bug, dict) else Bug(description=str(bug), file="unknown")
67
+ for bug in llm_data.get("critical", [])
68
+ ]
69
+ warnings = [
70
+ Warning(**w) if isinstance(w, dict) else Warning(description=str(w), file="unknown")
71
+ for w in llm_data.get("warnings", [])
72
+ ]
73
+ suggestions = [
74
+ Suggestion(**s) if isinstance(s, dict) else Suggestion(description=str(s), file="unknown")
75
+ for s in llm_data.get("suggestions", [])
76
+ ]
77
+
78
+ report = IssueReport(
79
+ critical=critical,
80
+ warnings=warnings,
81
+ suggestions=suggestions
82
+ )
83
+ report.calculate_totals()
84
+
85
+ print(f"Bug detection complete: {report.total_critical} critical, {report.total_warnings} warnings")
86
+ return report
app/agents/code_review_agent.py ADDED
@@ -0,0 +1,56 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ from app.tools.file_scanner import get_python_files
3
+ from app.core.llm import call_llm
4
+ from app.core.prompts import CODE_REVIEW_PROMPT
5
+ from app.models.review import ReviewSuggestions
6
+ from app.models.repository import RepositoryMetadata
7
+
8
+
9
+ def run(local_path: str, metadata: RepositoryMetadata) -> ReviewSuggestions:
10
+ """
11
+ Perform AI-powered code review on repository.
12
+ """
13
+ print("Running Code Review Agent...")
14
+
15
+ # Step 1 - Get Python files
16
+ python_files = get_python_files(local_path)
17
+
18
+ # Step 2 - Read source code samples
19
+ source_samples = []
20
+ for file_path in python_files[:4]:
21
+ if "test_" in file_path:
22
+ continue
23
+ try:
24
+ with open(file_path, "r", encoding="utf-8", errors="ignore") as f:
25
+ content = f.read()[:2500]
26
+ source_samples.append(f"### {file_path}\n{content}")
27
+ except Exception:
28
+ pass
29
+
30
+ if not source_samples:
31
+ return ReviewSuggestions(
32
+ summary="No source files found to review.",
33
+ overall_score=0.0
34
+ )
35
+
36
+ # Step 3 - Call LLM
37
+ source_code = "\n\n".join(source_samples)
38
+ prompt = CODE_REVIEW_PROMPT.format(source_code=source_code)
39
+ llm_response = call_llm(prompt)
40
+
41
+ # Step 4 - Parse response
42
+ try:
43
+ llm_data = json.loads(llm_response)
44
+ except json.JSONDecodeError:
45
+ llm_data = {}
46
+
47
+ review = ReviewSuggestions(
48
+ solid_violations=llm_data.get("solid_violations", []),
49
+ duplicate_code=llm_data.get("duplicate_code", []),
50
+ refactor_suggestions=llm_data.get("refactor_suggestions", []),
51
+ overall_score=float(llm_data.get("overall_score", 5.0)),
52
+ summary=llm_data.get("summary", "")
53
+ )
54
+
55
+ print(f"Code review complete: score {review.overall_score}/10")
56
+ return review
app/agents/repo_analysis_agent.py ADDED
@@ -0,0 +1,60 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ from app.tools.file_scanner import scan_repository, get_entry_points
3
+ from app.tools.ast_parser import parse_repository
4
+ from app.core.llm import call_llm
5
+ from app.core.prompts import REPO_ANALYSIS_PROMPT
6
+ from app.models.repository import RepositoryMetadata
7
+
8
+
9
+ def run(repo_url: str, local_path: str) -> RepositoryMetadata:
10
+ """
11
+ Analyze a repository and return structured metadata.
12
+ """
13
+ print("Running Repository Analysis Agent...")
14
+
15
+ # Step 1 - Scan files
16
+ scan_results = scan_repository(local_path)
17
+ entry_points = get_entry_points(local_path)
18
+ ast_results = parse_repository(local_path)
19
+
20
+ # Step 2 - Build context for LLM
21
+ repo_info = f"""
22
+ Repository URL: {repo_url}
23
+ Primary Language: {scan_results['primary_language']}
24
+ Total Files: {scan_results['total_files']}
25
+ Total Lines: {scan_results['total_lines']}
26
+ Languages Found: {scan_results['language_counts']}
27
+ Frameworks Detected: {scan_results['frameworks']}
28
+ Entry Points: {entry_points}
29
+ Classes Found: {sum(len(r['classes']) for r in ast_results)}
30
+ Functions Found: {sum(len(r['functions']) for r in ast_results)}
31
+ """
32
+
33
+ # Step 3 - Call LLM
34
+ prompt = REPO_ANALYSIS_PROMPT.format(repo_info=repo_info)
35
+ llm_response = call_llm(prompt)
36
+
37
+ # Step 4 - Parse response
38
+ try:
39
+ llm_data = json.loads(llm_response)
40
+ except json.JSONDecodeError:
41
+ llm_data = {}
42
+
43
+ # Step 5 - Build metadata
44
+ repo_name = repo_url.rstrip("/").split("/")[-1]
45
+
46
+ metadata = RepositoryMetadata(
47
+ url=repo_url,
48
+ name=repo_name,
49
+ local_path=local_path,
50
+ language=llm_data.get("language", scan_results["primary_language"]),
51
+ frameworks=llm_data.get("frameworks", scan_results["frameworks"]),
52
+ total_files=scan_results["total_files"],
53
+ total_lines=scan_results["total_lines"],
54
+ entry_points=[str(e) for e in entry_points],
55
+ architecture=llm_data.get("architecture", ""),
56
+ summary=llm_data.get("summary", "")
57
+ )
58
+
59
+ print(f"Analysis complete: {metadata.language}, {len(metadata.frameworks)} frameworks")
60
+ return metadata
app/agents/report_generator_agent.py ADDED
@@ -0,0 +1,72 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from datetime import datetime
2
+ from app.core.llm import call_llm
3
+ from app.core.prompts import REPORT_GENERATOR_PROMPT
4
+ from app.models.repository import RepositoryMetadata
5
+ from app.models.issue import IssueReport
6
+ from app.models.review import ReviewSuggestions, GeneratedTests
7
+ from app.models.report import EngineeringReport
8
+
9
+
10
+ def run(
11
+ metadata: RepositoryMetadata,
12
+ issues: IssueReport,
13
+ tests: GeneratedTests,
14
+ review: ReviewSuggestions
15
+ ) -> EngineeringReport:
16
+ """
17
+ Generate final engineering report combining all agent outputs.
18
+ """
19
+ print("Running Report Generator Agent...")
20
+
21
+ # Step 1 - Build context for LLM
22
+ analysis_summary = f"""
23
+ Repository: {metadata.name}
24
+ Language: {metadata.language}
25
+ Frameworks: {metadata.frameworks}
26
+ Total Files: {metadata.total_files}
27
+ Total Lines: {metadata.total_lines}
28
+ Summary: {metadata.summary}
29
+ """
30
+
31
+ bugs_summary = f"""
32
+ Critical Bugs: {issues.total_critical}
33
+ Warnings: {issues.total_warnings}
34
+ Suggestions: {issues.total_suggestions}
35
+ Critical Issues: {[b.description for b in issues.critical[:3]]}
36
+ """
37
+
38
+ tests_summary = f"""
39
+ Tests Generated: {tests.total_tests_generated}
40
+ Functions Covered: {tests.functions_covered[:5]}
41
+ Coverage Before: {tests.estimated_coverage_before}%
42
+ Coverage After: {tests.estimated_coverage_after}%
43
+ """
44
+
45
+ review_summary = f"""
46
+ Overall Score: {review.overall_score}/10
47
+ SOLID Violations: {review.solid_violations[:3]}
48
+ Refactor Suggestions: {review.refactor_suggestions[:3]}
49
+ Summary: {review.summary}
50
+ """
51
+
52
+ # Step 2 - Call LLM
53
+ prompt = REPORT_GENERATOR_PROMPT.format(
54
+ analysis=analysis_summary,
55
+ bugs=bugs_summary,
56
+ tests=tests_summary,
57
+ review=review_summary
58
+ )
59
+ full_report = call_llm(prompt, max_tokens=3000)
60
+
61
+ # Step 3 - Build final report
62
+ report = EngineeringReport(
63
+ repository=metadata,
64
+ issues=issues,
65
+ review=review,
66
+ tests=tests,
67
+ full_report=full_report,
68
+ generated_at=datetime.now().strftime("%Y-%m-%d %H:%M:%S")
69
+ )
70
+
71
+ print("Report generation complete.")
72
+ return report
app/agents/test_generation_agent.py ADDED
@@ -0,0 +1,65 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from app.tools.file_scanner import get_python_files
2
+ from app.tools.ast_parser import parse_python_file
3
+ from app.core.llm import call_llm
4
+ from app.core.prompts import TEST_GENERATION_PROMPT
5
+ from app.models.review import GeneratedTests
6
+
7
+
8
+ def run(local_path: str) -> GeneratedTests:
9
+ """
10
+ Generate pytest test cases for uncovered functions.
11
+ """
12
+ print("Running Test Generation Agent...")
13
+
14
+ # Step 1 - Get Python files
15
+ python_files = get_python_files(local_path)
16
+
17
+ # Step 2 - Find functions without tests
18
+ all_functions = []
19
+ source_code_samples = []
20
+
21
+ for file_path in python_files:
22
+ # Skip test files
23
+ if "test_" in file_path or "_test" in file_path:
24
+ continue
25
+
26
+ parsed = parse_python_file(file_path)
27
+ functions = parsed.get("functions", [])
28
+
29
+ if functions:
30
+ all_functions.extend([f["name"] for f in functions])
31
+
32
+ try:
33
+ with open(file_path, "r", encoding="utf-8", errors="ignore") as f:
34
+ content = f.read()[:3000]
35
+ source_code_samples.append(f"### {file_path}\n{content}")
36
+ except Exception:
37
+ pass
38
+
39
+ if len(source_code_samples) >= 3:
40
+ break
41
+
42
+ if not source_code_samples:
43
+ return GeneratedTests(
44
+ test_code="# No source files found to generate tests for",
45
+ total_tests_generated=0
46
+ )
47
+
48
+ # Step 3 - Call LLM
49
+ source_code = "\n\n".join(source_code_samples)
50
+ prompt = TEST_GENERATION_PROMPT.format(source_code=source_code)
51
+ llm_response = call_llm(prompt, max_tokens=3000)
52
+
53
+ # Step 4 - Count generated tests
54
+ test_count = llm_response.count("def test_")
55
+
56
+ result = GeneratedTests(
57
+ test_code=llm_response,
58
+ functions_covered=all_functions[:test_count],
59
+ estimated_coverage_before=40.0,
60
+ estimated_coverage_after=min(40.0 + (test_count * 3), 95.0),
61
+ total_tests_generated=test_count
62
+ )
63
+
64
+ print(f"Test generation complete: {test_count} tests generated")
65
+ return result