From ac485a015af8db0c290be2be2f0cf0f85b70b058 Mon Sep 17 00:00:00 2001 From: maoqin Date: Thu, 21 Aug 2025 14:54:01 +0100 Subject: [PATCH 1/3] add sonarqube api tool --- .../src/core/agents/antipattern_scanner.py | 15 +++++++++++++-- .../static/prompt/antipattern_scanner.yaml | 4 ++++ 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/AntiPattern_Remediator/src/core/agents/antipattern_scanner.py b/AntiPattern_Remediator/src/core/agents/antipattern_scanner.py index a1312a0..50b92d8 100644 --- a/AntiPattern_Remediator/src/core/agents/antipattern_scanner.py +++ b/AntiPattern_Remediator/src/core/agents/antipattern_scanner.py @@ -2,9 +2,14 @@ Antipattern scanner agent for detecting code smells and antipatterns """ +import sys +import os +sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..', '..', '..')) + from ..state import AgentState from colorama import Fore, Style from ..prompt import PromptManager +from sonarqube_tool import SonarQubeAPI class AntipatternScanner: @@ -22,7 +27,12 @@ def retrieve_context(self, state: AgentState): search_query = f"Java antipatterns code analysis: {state['code'][:50]}" # Use retriever_tool to get relevant context context = self.tool.invoke({"query": search_query}) - state["context"] = context + api = SonarQubeAPI() + issues = api.get_issues_for_file(project_key="commons-collections", file_path="src/main/java/org/apache/commons/collections4/collection/SynchronizedCollection.java") + solutions = [] + for issue in issues["issues"]: + solutions.append(api.get_rules_and_fix_method(rule_key=issue['rule'])) + state["context"] = {"sonarqube_issues": issues, "search_context": context, "solutions": solutions} print(Fore.GREEN + f"Successfully retrieved relevant context" + Style.RESET_ALL) except Exception as e: print(Fore.RED + f"Error retrieving context: {e}" + Style.RESET_ALL) @@ -39,7 +49,8 @@ def analyze_antipatterns(self, state: AgentState): formatted_messages = prompt_template.format_messages( code=state['code'], - context=state['context'], + context=state['context'].get('search_context', ''), + sonarqube_issues=state['context'].get('solutions', ''), msgs=msgs ) diff --git a/AntiPattern_Remediator/static/prompt/antipattern_scanner.yaml b/AntiPattern_Remediator/static/prompt/antipattern_scanner.yaml index 052d3d3..33587d3 100644 --- a/AntiPattern_Remediator/static/prompt/antipattern_scanner.yaml +++ b/AntiPattern_Remediator/static/prompt/antipattern_scanner.yaml @@ -22,6 +22,10 @@ antipattern_scanner: ```java {code} ``` + Found issues: + ```json + {sonarqube_issues} + ``` Additional context from codebase: {context} From 5eebde8156f44a186bd04800556490e3c13431c8 Mon Sep 17 00:00:00 2001 From: Vamsi Date: Mon, 25 Aug 2025 13:27:23 +0100 Subject: [PATCH 2/3] Add Full Repository Run Functionality - Added full repo workflow and required utility functions - Updated Anti-pattern scanner to include SonarQube context - Updated code transformer to strip docstrings from refactored code while using Granite 3.3:8b --- AntiPattern_Remediator/full_repo_workflow.py | 293 ++++++++++++++++++ AntiPattern_Remediator/main.py | 104 ++++--- .../src/core/agents/antipattern_scanner.py | 23 +- .../src/core/agents/code_transformer.py | 26 ++ AntiPattern_Remediator/src/core/state.py | 1 + .../static/prompt/antipattern_scanner.yaml | 2 + .../static/prompt/code_transformer.yaml | 5 + AntiPattern_Remediator/workflow/__init__.py | 11 + .../workflow/backup_manager.py | 54 ++++ .../workflow/file_operations.py | 40 +++ .../workflow/results_manager.py | 155 +++++++++ .../workflow/workflow_utils.py | 70 +++++ 12 files changed, 746 insertions(+), 38 deletions(-) create mode 100644 AntiPattern_Remediator/full_repo_workflow.py create mode 100644 AntiPattern_Remediator/workflow/__init__.py create mode 100644 AntiPattern_Remediator/workflow/backup_manager.py create mode 100644 AntiPattern_Remediator/workflow/file_operations.py create mode 100644 AntiPattern_Remediator/workflow/results_manager.py create mode 100644 AntiPattern_Remediator/workflow/workflow_utils.py diff --git a/AntiPattern_Remediator/full_repo_workflow.py b/AntiPattern_Remediator/full_repo_workflow.py new file mode 100644 index 0000000..69ddb5a --- /dev/null +++ b/AntiPattern_Remediator/full_repo_workflow.py @@ -0,0 +1,293 @@ +""" +Full Repository Workflow - Process files with 100% test coverage from JaCoCo results +""" +from colorama import Fore, Style +from pathlib import Path +import os + +# Import workflow utilities +from workflow.workflow_utils import parse_antipattern_results, get_repository_paths_from_files +from workflow.backup_manager import create_repository_backup +from workflow.results_manager import save_intermediate_results, create_processing_summary +from workflow.file_operations import read_java_file, save_refactored_code + + + +def read_jacoco_results(jacoco_results_dir: str = "../jacoco_results") -> list: + """Read the list of files with 100% coverage from JaCoCo results.""" + results_path = Path(jacoco_results_dir) + + if not results_path.exists(): + print(Fore.RED + f"JaCoCo results directory not found: {results_path}" + Style.RESET_ALL) + return [] + + # Find all result files + all_files = list(results_path.glob("*.txt")) + if not all_files: + print(Fore.RED + f"No JaCoCo result files found in: {results_path}" + Style.RESET_ALL) + return [] + + # Separate combined file from individual repo files + combined_file = results_path / "all_100_percent_coverage_files.txt" + repo_files = [f for f in all_files if f.name != "all_100_percent_coverage_files.txt"] + + print(Fore.CYAN + "\nAvailable JaCoCo result files:" + Style.RESET_ALL) + print("0) All repositories (combined)") + + # Show individual repository options + for i, repo_file in enumerate(repo_files, 1): + # Extract repo name from filename (remove _100_percent_coverage.txt suffix) + repo_name = repo_file.stem.replace("_100_percent_coverage", "") + print(f"{i}) {repo_name}") + + # Get user choice + while True: + try: + choice = input(f"\nSelect repository to process (0-{len(repo_files)}): ").strip() + choice_num = int(choice) + + if choice_num == 0: + # Use combined file + selected_file = combined_file + repo_name = "All repositories" + break + elif 1 <= choice_num <= len(repo_files): + # Use specific repo file + selected_file = repo_files[choice_num - 1] + repo_name = selected_file.stem.replace("_100_percent_coverage", "") + break + else: + print(Fore.RED + f"Invalid choice. Please enter a number between 0 and {len(repo_files)}" + Style.RESET_ALL) + except ValueError: + print(Fore.RED + "Invalid input. Please enter a number." + Style.RESET_ALL) + + # Read the selected file + if not selected_file.exists(): + print(Fore.RED + f"Selected file not found: {selected_file}" + Style.RESET_ALL) + return [] + + try: + with open(selected_file, 'r') as f: + file_paths = [line.strip() for line in f if line.strip()] + + print(Fore.GREEN + f"Selected: {repo_name}" + Style.RESET_ALL) + print(Fore.GREEN + f"Found {len(file_paths)} files with 100% test coverage" + Style.RESET_ALL) + return file_paths + + except Exception as e: + print(Fore.RED + f"Error reading file {selected_file}: {e}" + Style.RESET_ALL) + return [] + + + +def process_java_files_with_workflow(file_paths: list, settings, db_manager, prompt_manager, langgraph): + """Process each Java file through the agentic workflow.""" + processed_files = [] + failed_files = [] + + for i, file_path in enumerate(file_paths, 1): + print(Fore.BLUE + f"\n{'='*60}" + Style.RESET_ALL) + print(Fore.BLUE + f"Processing file {i}/{len(file_paths)}: {file_path}" + Style.RESET_ALL) + print(Fore.BLUE + f"{'='*60}" + Style.RESET_ALL) + + # Read the Java file content + java_code = read_java_file(file_path) + if java_code is None: + failed_files.append(file_path) + continue + + # Create initial state for this file + initial_state = { + "code": java_code, + "context": None, + "trove_context": None, + "antipatterns_scanner_results": None, + "refactoring_strategy_results": None, + "refactored_code": None, + "code_review_results": None, + "code_review_times": 0, + "msgs": [], + "answer": None, + "current_file_path": file_path # Track current file being processed + } + + try: + # Run the agentic workflow + print(Fore.CYAN + "Running agentic workflow..." + Style.RESET_ALL) + final_state = langgraph.invoke(initial_state) + + # Save intermediate results for analysis + save_intermediate_results(file_path, final_state, settings) + + # Check if refactoring was successful + if final_state.get('refactored_code'): + # Parse anti-pattern results + antipatterns_found, antipatterns_count = parse_antipattern_results(final_state.get('antipatterns_scanner_results')) + + # Save the refactored code back to the file + if save_refactored_code(file_path, final_state['refactored_code']): + processed_files.append({ + 'file_path': file_path, + 'status': 'success', + 'antipatterns_found': antipatterns_found, + 'antipatterns_count': antipatterns_count, + 'code_review_times': final_state.get('code_review_times', 0), + 'has_intermediate_results': True + }) + print(Fore.GREEN + f"Successfully processed: {file_path}" + Style.RESET_ALL) + else: + failed_files.append(file_path) + else: + # Parse anti-pattern results + antipatterns_found, antipatterns_count = parse_antipattern_results(final_state.get('antipatterns_scanner_results')) + + print(Fore.YELLOW + f"No refactored code generated for: {file_path}" + Style.RESET_ALL) + processed_files.append({ + 'file_path': file_path, + 'status': 'no_refactoring', + 'antipatterns_found': antipatterns_found, + 'antipatterns_count': antipatterns_count, + 'code_review_times': final_state.get('code_review_times', 0), + 'has_intermediate_results': True + }) + + except Exception as e: + print(Fore.RED + f"Error processing {file_path}: {e}" + Style.RESET_ALL) + failed_files.append(file_path) + + return processed_files, failed_files + + +def run_full_repo_workflow(settings, db_manager, prompt_manager, langgraph): + """Run the full repository workflow for files with 100% test coverage.""" + print(Fore.BLUE + "\n=== Full Repository Workflow ===" + Style.RESET_ALL) + print("Process Java files with 100% test coverage from JaCoCo results...") + + # Read JaCoCo results to get files with 100% test coverage + print(Fore.CYAN + "\nReading JaCoCo results..." + Style.RESET_ALL) + file_paths = read_jacoco_results() + + if not file_paths: + print(Fore.RED + "No files found in JaCoCo results. Please run JaCoCo analysis first." + Style.RESET_ALL) + print("Run: python jacoco_tool/jacoco_analysis.py") + return False + + # Extract repository paths from file paths + print(Fore.CYAN + "\nIdentifying repositories to backup..." + Style.RESET_ALL) + repo_paths = get_repository_paths_from_files(file_paths) + + if not repo_paths: + print(Fore.RED + "No repository paths could be identified from the file paths." + Style.RESET_ALL) + return False + + print(f"Found {len(repo_paths)} repositories to backup:") + for repo_path in sorted(repo_paths): + repo_name = Path(repo_path).name + print(f" • {repo_name} ({repo_path})") + + # Ask user for confirmation to proceed with backup and processing + print(f"\nFiles to process ({len(file_paths)} total):") + for i, path in enumerate(file_paths[:5], 1): # Show first 5 files + print(f" {i}. {path}") + if len(file_paths) > 5: + print(f" ... and {len(file_paths) - 5} more files") + + proceed = input(f"\nProceed with backing up {len(repo_paths)} repositories and processing {len(file_paths)} files? (Y/N): ").strip().lower() + if proceed != 'y': + print("Operation cancelled.") + return False + + # Create repository backups + print(Fore.BLUE + f"\n{'='*60}" + Style.RESET_ALL) + print(Fore.BLUE + "CREATING REPOSITORY BACKUPS" + Style.RESET_ALL) + print(Fore.BLUE + f"{'='*60}" + Style.RESET_ALL) + + backup_info = create_repository_backup(repo_paths) + + if backup_info['failed_backups']: + print(Fore.RED + f"\nWarning: {len(backup_info['failed_backups'])} repositories failed to backup:" + Style.RESET_ALL) + for failed in backup_info['failed_backups']: + print(Fore.RED + f" {failed['repo_path']}: {failed['error']}" + Style.RESET_ALL) + + continue_anyway = input("\nContinue processing despite backup failures? (Y/N): ").strip().lower() + if continue_anyway != 'y': + print("Operation cancelled due to backup failures.") + return False + + print(Fore.GREEN + f"\nSuccessfully backed up {len(backup_info['backed_up_repos'])} repositories" + Style.RESET_ALL) + print(Fore.GREEN + f"Backup location: {backup_info['backup_dir']}" + Style.RESET_ALL) + + # Process each file through the agentic workflow + print(Fore.BLUE + f"\n{'='*60}" + Style.RESET_ALL) + print(Fore.BLUE + "STARTING FILE PROCESSING" + Style.RESET_ALL) + print(Fore.BLUE + f"{'='*60}" + Style.RESET_ALL) + + processed_files, failed_files = process_java_files_with_workflow( + file_paths, settings, db_manager, prompt_manager, langgraph + ) + + # Create comprehensive processing summary + summary_file = create_processing_summary(processed_files, backup_info) + + # Generate summary report + print(Fore.BLUE + "\n" + "="*80 + Style.RESET_ALL) + print(Fore.BLUE + "BATCH PROCESSING SUMMARY" + Style.RESET_ALL) + print(Fore.BLUE + "="*80 + Style.RESET_ALL) + + # Backup summary + print(Fore.CYAN + "Repository Backup Summary:" + Style.RESET_ALL) + print(f" Backup timestamp: {backup_info['timestamp']}") + print(f" Backup location: {backup_info['backup_dir']}") + print(f" Repositories backed up: {len(backup_info['backed_up_repos'])}") + if backup_info['failed_backups']: + print(f" Failed backups: {len(backup_info['failed_backups'])}") + + # Processing summary + print(Fore.CYAN + "\nFile Processing Summary:" + Style.RESET_ALL) + print(f" Total files processed: {len(processed_files)}") + print(f" Failed files: {len(failed_files)}") + + # Categorize results + successful_refactoring = [f for f in processed_files if f['status'] == 'success'] + no_refactoring_needed = [f for f in processed_files if f['status'] == 'no_refactoring'] + files_with_antipatterns = [f for f in processed_files if f.get('antipatterns_found', False)] + total_antipatterns = sum(f.get('antipatterns_count', 0) for f in processed_files) + + print(Fore.GREEN + f" Successfully refactored: {len(successful_refactoring)}" + Style.RESET_ALL) + print(Fore.YELLOW + f" No refactoring needed: {len(no_refactoring_needed)}" + Style.RESET_ALL) + print(Fore.RED + f" Failed: {len(failed_files)}" + Style.RESET_ALL) + print(Fore.MAGENTA + f" Files with anti-patterns: {len(files_with_antipatterns)}" + Style.RESET_ALL) + print(Fore.MAGENTA + f" Total anti-patterns found: {total_antipatterns}" + Style.RESET_ALL) + + # Statistics + if processed_files: + refactor_rate = len(successful_refactoring) / len(processed_files) * 100 + antipattern_rate = len(files_with_antipatterns) / len(processed_files) * 100 + + + print(Fore.CYAN + "\nProcessing Statistics:" + Style.RESET_ALL) + print(f" Refactoring success rate: {refactor_rate:.1f}%") + print(f" Anti-pattern detection rate: {antipattern_rate:.1f}%") + print(f" Total anti-patterns found: {total_antipatterns}") + + # Show detailed results + if successful_refactoring: + print(Fore.GREEN + "\nSuccessfully refactored files:" + Style.RESET_ALL) + for file_info in successful_refactoring: + antipatterns_info = f" (antipatterns: {file_info.get('antipatterns_count', 0)})" if file_info.get('antipatterns_count', 0) > 0 else "" + print(f"{file_info['file_path']} (reviews: {file_info['code_review_times']}){antipatterns_info}") + + if failed_files: + print(Fore.RED + "\nFailed files:" + Style.RESET_ALL) + for file_path in failed_files: + print(f"{file_path}") + + print(Fore.GREEN + f"\nBatch processing complete!" + Style.RESET_ALL) + print(Fore.CYAN + f"Repository backups available at: {backup_info['backup_dir']}" + Style.RESET_ALL) + print(f"To restore a repository, copy from backup directory back to original location.") + + # Intermediate results information + print(Fore.MAGENTA + f"\nIntermediate Results:" + Style.RESET_ALL) + print(f" Individual file analysis results saved in: ../processing_results/") + if summary_file: + print(f" Comprehensive summary saved: {Path(summary_file).name}") diff --git a/AntiPattern_Remediator/main.py b/AntiPattern_Remediator/main.py index fc05fdd..a659983 100644 --- a/AntiPattern_Remediator/main.py +++ b/AntiPattern_Remediator/main.py @@ -7,35 +7,16 @@ from dotenv import load_dotenv load_dotenv() from colorama import Fore, Style +import os +from pathlib import Path +from full_repo_workflow import run_full_repo_workflow -def main(): - """Main function: Run antipattern analysis""" - - # Let user select provider - print("Available providers: 1) ollama 2) ibm 3) vllm") - choice = input("Select provider (1-3): ").strip() - - provider_map = {"1": "ollama", "2": "ibm", "3": "vllm"} - provider = provider_map.get(choice, "ollama") # default to ollama - - #Let us choose which DB to interact with - print("Choose your trove: 1) ChromaDB (VectorDB) 2) TinyDB (DocumentDB)") - db_choice = input("Choose 1 or 2: ").strip() - - # Initialize global settings with selected provider - settings = initialize_settings(provider) - print(Fore.GREEN + f"Using {settings.LLM_PROVIDER} with model {settings.LLM_MODEL}" + Style.RESET_ALL) - - # Temporary Lazy Imports - from src.core.graph import CreateGraph - from src.data.database import VectorDBManager, TinyDBManager - from src.core.prompt import PromptManager - from scripts import seed_database - - # Initialize PromptManager - print("Initializing PromptManager...") - prompt_manager = PromptManager() +def run_code_snippet_workflow(settings, db_manager, prompt_manager, langgraph): + """Run the original workflow with a hardcoded Java code snippet.""" + print(Fore.BLUE + "\n=== Code Snippet Analysis Workflow ===" + Style.RESET_ALL) + print("Analyzing the provided Java code snippet...") + # Example Java code legacy_code = """ public class ApplicationManager { @@ -74,6 +55,7 @@ def main(): } } """ + initial_state = { "code": legacy_code, "context": None, @@ -87,27 +69,75 @@ def main(): "answer": None } - #Setup Database + final_state = langgraph.invoke(initial_state) + + print(Fore.GREEN + f"\nAnalysis Complete!" + Style.RESET_ALL) + print(f"Final state keys: {list(final_state.keys())}") + print(f"Context retrieved: {'Yes' if final_state.get('context') else 'No'}") + print(f"Analysis completed: {'Yes' if final_state.get('antipatterns_scanner_results') else 'No'}") + print(f"Refactored code: {'Yes' if final_state.get('refactored_code') else 'No'}") + print(f"Code review results: {final_state.get('code_review_times')}") + + +def main(): + """Main function: Choose between code snippet analysis or full repository run""" + + print(Fore.BLUE + "=== AntiPattern Remediator Tool ===" + Style.RESET_ALL) + print("Choose your analysis mode:") + print("1) Code Snippet Analysis - Analyze a sample Java code snippet") + print("2) Full Repository Run - Process files with 100% test coverage from JaCoCo results") + + # Let user choose analysis mode + mode_choice = input("\nSelect mode (1-2): ").strip() + + if mode_choice not in ["1", "2"]: + print(Fore.RED + "Invalid choice. Defaulting to Code Snippet Analysis." + Style.RESET_ALL) + mode_choice = "1" + + # Let user select provider + print("\nAvailable providers: 1) ollama 2) ibm 3) vllm") + choice = input("Select provider (1-3): ").strip() + + provider_map = {"1": "ollama", "2": "ibm", "3": "vllm"} + provider = provider_map.get(choice, "ollama") # default to ollama + + # Let us choose which DB to interact with + print("Choose your trove: 1) ChromaDB (VectorDB) 2) TinyDB (DocumentDB)") + db_choice = input("Choose 1 or 2: ").strip() + + # Initialize global settings with selected provider + settings = initialize_settings(provider) + print(Fore.GREEN + f"Using {settings.LLM_PROVIDER} with model {settings.LLM_MODEL}" + Style.RESET_ALL) + + # Temporary Lazy Imports + from src.core.graph import CreateGraph + from src.data.database import VectorDBManager, TinyDBManager + from src.core.prompt import PromptManager + from scripts import seed_database + + # Initialize PromptManager + print("Initializing PromptManager...") + prompt_manager = PromptManager() + + # Setup Database if db_choice == "2": print("Seeding TinyDB with AntiPattern Dataset") seed_database.main() db_manager = TinyDBManager() - print("Using TinyDB for knowledge retreival") + print("Using TinyDB for knowledge retrieval") else: vector_db = VectorDBManager() db_manager = vector_db.get_db() - print("Using ChromaDB for knowledge retreival") + print("Using ChromaDB for knowledge retrieval") retriever = db_manager.as_retriever() langgraph = CreateGraph(db_manager, prompt_manager, retriever=retriever).workflow - final_state = langgraph.invoke(initial_state) - print(Fore.GREEN + f"\nAnalysis Complete!" + Style.RESET_ALL) - print(f"Final state keys: {list(final_state.keys())}") - print(f"Context retrieved: {'Yes' if final_state.get('context') else 'No'}") - print(f"Analysis completed: {'Yes' if final_state.get('antipatterns_scanner_results') else 'No'}") - print(f"Refactored code: {'Yes' if final_state.get('refactored_code') else 'No'}") - print(f"Code review results: {final_state.get('code_review_times')}") + # Run the selected workflow + if mode_choice == "1": + run_code_snippet_workflow(settings, db_manager, prompt_manager, langgraph) + else: + run_full_repo_workflow(settings, db_manager, prompt_manager, langgraph) if __name__ == "__main__": main() diff --git a/AntiPattern_Remediator/src/core/agents/antipattern_scanner.py b/AntiPattern_Remediator/src/core/agents/antipattern_scanner.py index 50b92d8..6873e7f 100644 --- a/AntiPattern_Remediator/src/core/agents/antipattern_scanner.py +++ b/AntiPattern_Remediator/src/core/agents/antipattern_scanner.py @@ -10,6 +10,7 @@ from colorama import Fore, Style from ..prompt import PromptManager from sonarqube_tool import SonarQubeAPI +from pathlib import Path class AntipatternScanner: @@ -27,8 +28,28 @@ def retrieve_context(self, state: AgentState): search_query = f"Java antipatterns code analysis: {state['code'][:50]}" # Use retriever_tool to get relevant context context = self.tool.invoke({"query": search_query}) + + # Get current file path from state + current_file_path = state['current_file_path'] + + # Extract project key and relative file path from the current file path + project_key = None + relative_file_path = None + + if current_file_path: + path_obj = Path(current_file_path) + + # Find the repository name (project key) by looking for 'clones' directory + for i, part in enumerate(path_obj.parts): + if part == 'clones' and i + 1 < len(path_obj.parts): + project_key = path_obj.parts[i + 1] # Repository name as project key + # Get the relative path from the repository root + relative_file_path = str(Path(*path_obj.parts[i + 2:])) + break + api = SonarQubeAPI() - issues = api.get_issues_for_file(project_key="commons-collections", file_path="src/main/java/org/apache/commons/collections4/collection/SynchronizedCollection.java") + print(Fore.CYAN + f"Using SonarQube project: {project_key}, file: {relative_file_path}" + Style.RESET_ALL) + issues = api.get_issues_for_file(project_key=project_key, file_path=relative_file_path) solutions = [] for issue in issues["issues"]: solutions.append(api.get_rules_and_fix_method(rule_key=issue['rule'])) diff --git a/AntiPattern_Remediator/src/core/agents/code_transformer.py b/AntiPattern_Remediator/src/core/agents/code_transformer.py index b4eb67e..c3a9192 100644 --- a/AntiPattern_Remediator/src/core/agents/code_transformer.py +++ b/AntiPattern_Remediator/src/core/agents/code_transformer.py @@ -4,6 +4,7 @@ from ..state import AgentState from colorama import Fore, Style from ..prompt import PromptManager +import re class CodeTransformer: @@ -12,6 +13,30 @@ class CodeTransformer: def __init__(self, model, prompt_manager: PromptManager): self.llm = model self.prompt_manager = prompt_manager + + def extract_java(s: str) -> str: + + # Strip common wrappers + fences = [ + (r"^```[a-zA-Z0-9]*\n(.*?)\n```$", re.DOTALL), + (r'^"""\s*\n?(.*?)\n?"""$', re.DOTALL), + (r"^'''\s*\n?(.*?)\n?'''$", re.DOTALL), + ] + for pat, flg in fences: + m = re.match(pat, s, flags=flg) + if m: + s = m.group(1).strip() + break + + # Also remove stray leading/trailing fence lines if any + s = re.sub(r"^```[a-zA-Z0-9]*\n?", "", s) + s = re.sub(r"\n?```$", "", s) + s = re.sub(r'^"""\n?', "", s) + s = re.sub(r'\n?"""$', "", s) + s = re.sub(r"^'''\n?", "", s) + s = re.sub(r"\n?'''$", "", s) + + return s.strip() def transform_code(self, state: AgentState) -> AgentState: print("--- TRANSFORMING CODE ---") @@ -44,6 +69,7 @@ def transform_code(self, state: AgentState) -> AgentState: state["refactored_code"] = "Error: No valid response received from LLM." raise ValueError("No valid response received from LLM.") refactored_code = response.content.strip() + refactored_code = CodeTransformer.extract_java(refactored_code) print(Fore.GREEN + "Code transformation complete." + Style.RESET_ALL) state["refactored_code"] = refactored_code diff --git a/AntiPattern_Remediator/src/core/state.py b/AntiPattern_Remediator/src/core/state.py index c85077f..fb42b13 100644 --- a/AntiPattern_Remediator/src/core/state.py +++ b/AntiPattern_Remediator/src/core/state.py @@ -17,3 +17,4 @@ class AgentState(TypedDict): code_review_times: int # Number of times code has been reviewed msgs: List[Dict[str, Any]] # Message history for conversation context answer: Optional[str] # Analysis result + current_file_path: Optional[str] # Path to the current file being processed diff --git a/AntiPattern_Remediator/static/prompt/antipattern_scanner.yaml b/AntiPattern_Remediator/static/prompt/antipattern_scanner.yaml index 33587d3..8f6a5cf 100644 --- a/AntiPattern_Remediator/static/prompt/antipattern_scanner.yaml +++ b/AntiPattern_Remediator/static/prompt/antipattern_scanner.yaml @@ -5,6 +5,7 @@ antipattern_scanner: variables: - code - context + - sonarqube_issues description: "Analyzes Java code for antipatterns and design issues, providing structured JSON output" version: "1.0" @@ -33,6 +34,7 @@ antipattern_scanner: Analysis Requirements: - Carefully analyze the code for Java antipatterns and design smells. - Base your analysis strictly on the antipattern definitions provided earlier in this conversation. Do not invent new antipatterns. + - Consider the provided SonarQube issues as potential indicators of antipatterns. - Make sure your results are clear and actionable, so others can know how to address the identified issues. - Return your analysis in JSON format with the following structure. diff --git a/AntiPattern_Remediator/static/prompt/code_transformer.yaml b/AntiPattern_Remediator/static/prompt/code_transformer.yaml index 596a309..1a0a4e4 100644 --- a/AntiPattern_Remediator/static/prompt/code_transformer.yaml +++ b/AntiPattern_Remediator/static/prompt/code_transformer.yaml @@ -12,6 +12,7 @@ code_transformer: system: | You are an expert Java programmer responsible for refactoring code based on a provided strategy. You will be given the original code and a JSON object containing a list of refactoring strategies. + Do **not** use Markdown code fences (```), triple quotes ("""), or any quoting. Output must be raw Java code. Your task is to apply all these strategies to produce a single, fully refactored version of the code. user: | @@ -30,6 +31,10 @@ code_transformer: {code} ``` + **OUTPUT CONTRACT (read carefully):** + Return ONLY raw Java source code. + Do not include Markdown code fences, triple quotes, or any prose. + **Your Task:** 1. Synthesize all the provided refactoring strategies. 2. Apply the combined strategies to the 'Original Java Code'. diff --git a/AntiPattern_Remediator/workflow/__init__.py b/AntiPattern_Remediator/workflow/__init__.py new file mode 100644 index 0000000..21cb8dc --- /dev/null +++ b/AntiPattern_Remediator/workflow/__init__.py @@ -0,0 +1,11 @@ +""" +Workflow utilities package for AntiPattern Remediator + +This package contains modular utilities for the full repository workflow: +- workflow_utils: Core utilities and path operations +- backup_manager: Repository backup operations +- results_manager: Results processing and reporting +- file_operations: File I/O operations +""" + +__version__ = "1.0.0" diff --git a/AntiPattern_Remediator/workflow/backup_manager.py b/AntiPattern_Remediator/workflow/backup_manager.py new file mode 100644 index 0000000..289f51d --- /dev/null +++ b/AntiPattern_Remediator/workflow/backup_manager.py @@ -0,0 +1,54 @@ +""" +Repository backup management for AntiPattern Remediator + +This module handles creating backups of repositories before processing. +""" + +import shutil +from pathlib import Path +from datetime import datetime +from colorama import Fore, Style + + +def create_repository_backup(repo_paths: set, backup_base_dir: str = "../backups") -> dict: + """Create backups of repositories before processing.""" + timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") + backup_dir = Path(backup_base_dir) / f"repo_backup_{timestamp}" + backup_dir.mkdir(parents=True, exist_ok=True) + + backup_info = { + 'timestamp': timestamp, + 'backup_dir': str(backup_dir), + 'backed_up_repos': [], + 'failed_backups': [] + } + + print(Fore.BLUE + f"\nCreating repository backups in: {backup_dir}" + Style.RESET_ALL) + + for repo_path in repo_paths: + try: + repo_path_obj = Path(repo_path) + repo_name = repo_path_obj.name + backup_repo_path = backup_dir / repo_name + + print(Fore.CYAN + f"Backing up repository: {repo_name}..." + Style.RESET_ALL) + + # Copy the entire repository + shutil.copytree(repo_path, backup_repo_path, dirs_exist_ok=True) + + backup_info['backed_up_repos'].append({ + 'original_path': repo_path, + 'backup_path': str(backup_repo_path), + 'repo_name': repo_name + }) + + print(Fore.GREEN + f"Successfully backed up: {repo_name}" + Style.RESET_ALL) + + except Exception as e: + print(Fore.RED + f"Failed to backup {repo_path}: {e}" + Style.RESET_ALL) + backup_info['failed_backups'].append({ + 'repo_path': repo_path, + 'error': str(e) + }) + + return backup_info diff --git a/AntiPattern_Remediator/workflow/file_operations.py b/AntiPattern_Remediator/workflow/file_operations.py new file mode 100644 index 0000000..a5af0ee --- /dev/null +++ b/AntiPattern_Remediator/workflow/file_operations.py @@ -0,0 +1,40 @@ +""" +File I/O operations for AntiPattern Remediator + +This module handles reading and writing files during the workflow process. +""" + +from colorama import Fore, Style + + +def read_java_file(file_path: str) -> str: + """Read the content of a Java file.""" + try: + with open(file_path, 'r', encoding='utf-8') as f: + return f.read() + except Exception as e: + print(Fore.RED + f"Error reading file {file_path}: {e}" + Style.RESET_ALL) + return None + + +def save_refactored_code(file_path: str, refactored_code: str, backup: bool = False) -> bool: + """Save the refactored code back to the original file.""" + try: + # Create backup if requested (disabled by default since we backup entire repos) + if backup: + backup_path = f"{file_path}.backup" + with open(file_path, 'r', encoding='utf-8') as original: + with open(backup_path, 'w', encoding='utf-8') as backup_file: + backup_file.write(original.read()) + print(Fore.YELLOW + f"Backup created: {backup_path}" + Style.RESET_ALL) + + # Write refactored code + with open(file_path, 'w', encoding='utf-8') as f: + f.write(refactored_code) + + print(Fore.GREEN + f"Refactored code saved to: {file_path}" + Style.RESET_ALL) + return True + + except Exception as e: + print(Fore.RED + f"Error saving refactored code to {file_path}: {e}" + Style.RESET_ALL) + return False diff --git a/AntiPattern_Remediator/workflow/results_manager.py b/AntiPattern_Remediator/workflow/results_manager.py new file mode 100644 index 0000000..3dbe79f --- /dev/null +++ b/AntiPattern_Remediator/workflow/results_manager.py @@ -0,0 +1,155 @@ +""" +Results processing and reporting for AntiPattern Remediator + +This module handles saving intermediate results and creating processing summaries. +""" + +import json +from pathlib import Path +from datetime import datetime +from colorama import Fore, Style + + +def save_intermediate_results(file_path: str, final_state: dict, settings, results_dir: str = "../processing_results") -> bool: + """Save intermediate results from the agentic workflow for analysis in markdown format.""" + try: + # Create results directory if it doesn't exist + results_path = Path(results_dir) + results_path.mkdir(parents=True, exist_ok=True) + + # Create a unique filename based on the original file path + file_path_obj = Path(file_path) + + # Extract the meaningful part of the path starting from the repository name + # Find the 'clones' directory and take everything after it + meaningful_path = None + for i, part in enumerate(file_path_obj.parts): + if part == 'clones' and i + 1 < len(file_path_obj.parts): + # Take from the repo name onwards + meaningful_path = Path(*file_path_obj.parts[i+1:]) + break + + if meaningful_path is None: + # Fallback: use just the filename if 'clones' not found + meaningful_path = file_path_obj.name + + # Create a safe filename by replacing path separators and other problematic characters + safe_filename = str(meaningful_path).replace('/', '_').replace('\\', '_').replace(':', '_') + + # Replace .java extension with .md and add results suffix + if safe_filename.endswith('.java'): + safe_filename = safe_filename[:-5] # Remove .java + results_filename = f"{safe_filename}_results.md" + results_file_path = results_path / results_filename + + # Generate markdown content + timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") + markdown_content = f"""# Processing Results: {file_path_obj.name} + +## File Information +- **Original File Path**: `{file_path}` +- **Processing Timestamp**: {timestamp} +- **Code Refactored**: {'Yes' if final_state.get('refactored_code') else 'No'} + +--- + +## Anti-Pattern Scanner Results + +""" + + antipatterns_results = final_state.get('antipatterns_scanner_results') + if antipatterns_results: + if isinstance(antipatterns_results, str): + markdown_content += f"\n{antipatterns_results}\n\n\n" + elif isinstance(antipatterns_results, dict): + for key, value in antipatterns_results.items(): + markdown_content += f"### {key.replace('_', ' ').title()}\n" + if isinstance(value, (list, dict)): + markdown_content += f"```json\n{json.dumps(value, indent=2)}\n```\n\n" + else: + markdown_content += f"{value}\n\n" + else: + markdown_content += f"```json\n{json.dumps(antipatterns_results, indent=2, default=str)}\n```\n\n" + else: + markdown_content += "No anti-patterns detected or scanner did not run.\n\n" + + markdown_content += "---\n\n## Refactoring Strategy Results\n\n" + + refactoring_results = final_state.get('refactoring_strategy_results') + if refactoring_results: + if isinstance(refactoring_results, str): + markdown_content += f"\n{refactoring_results}\n\n\n" + elif isinstance(refactoring_results, dict): + for key, value in refactoring_results.items(): + markdown_content += f"### {key.replace('_', ' ').title()}\n" + if isinstance(value, (list, dict)): + markdown_content += f"```json\n{json.dumps(value, indent=2)}\n```\n\n" + else: + markdown_content += f"{value}\n\n" + else: + markdown_content += f"```json\n{json.dumps(refactoring_results, indent=2, default=str)}\n```\n\n" + else: + markdown_content += "No refactoring strategy generated.\n\n" + + markdown_content += f"---\n\n*Generated by AntiPattern Remediator Tool using {settings.LLM_MODEL}*\n" + + # Save to markdown file + with open(results_file_path, 'w', encoding='utf-8') as f: + f.write(markdown_content) + + print(Fore.CYAN + f"Intermediate results saved: {results_file_path}" + Style.RESET_ALL) + return True + + except Exception as e: + print(Fore.RED + f"Error saving intermediate results for {file_path}: {e}" + Style.RESET_ALL) + return False + + +def create_processing_summary(processed_files: list, backup_info: dict, results_dir: str = "../processing_results") -> str: + """Create a comprehensive summary report of the processing session.""" + try: + results_path = Path(results_dir) + results_path.mkdir(parents=True, exist_ok=True) + + timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") + summary_file = results_path / f"processing_summary_{timestamp}.json" + + # Categorize results + successful_refactoring = [f for f in processed_files if f['status'] == 'success'] + no_refactoring_needed = [f for f in processed_files if f['status'] == 'no_refactoring'] + files_with_antipatterns = [f for f in processed_files if f.get('antipatterns_found', False)] + total_antipatterns = sum(f.get('antipatterns_count', 0) for f in processed_files) + + summary_data = { + 'processing_session': { + 'timestamp': timestamp, + 'backup_info': backup_info, + 'total_files_processed': len(processed_files), + 'successful_refactoring': len(successful_refactoring), + 'no_refactoring_needed': len(no_refactoring_needed), + 'files_with_antipatterns_detected': len(files_with_antipatterns), + 'total_antipatterns_found': total_antipatterns + }, + 'detailed_results': { + 'successful_refactoring': successful_refactoring, + 'no_refactoring_needed': no_refactoring_needed, + }, + 'statistics': { + 'refactoring_success_rate': len(successful_refactoring) / len(processed_files) * 100 if processed_files else 0, + 'antipattern_detection_rate': len(files_with_antipatterns) / len(processed_files) * 100 if processed_files else 0, + 'average_code_reviews': sum(f.get('code_review_times', 0) for f in processed_files) / len(processed_files) if processed_files else 0, + 'total_antipatterns_found': total_antipatterns, + 'average_antipatterns_per_file': total_antipatterns / len(processed_files) if processed_files else 0 + } + } + + # Save summary + with open(summary_file, 'w', encoding='utf-8') as f: + json.dump(summary_data, f, indent=2, ensure_ascii=False, default=str) + + print(Fore.CYAN + f"Processing summary saved: {summary_file}" + Style.RESET_ALL) + return str(summary_file) + + except Exception as e: + print(Fore.RED + f"Error creating processing summary: {e}" + Style.RESET_ALL) + return None diff --git a/AntiPattern_Remediator/workflow/workflow_utils.py b/AntiPattern_Remediator/workflow/workflow_utils.py new file mode 100644 index 0000000..eeb8ef3 --- /dev/null +++ b/AntiPattern_Remediator/workflow/workflow_utils.py @@ -0,0 +1,70 @@ +""" +Core workflow utilities for AntiPattern Remediator + +This module contains utility functions for parsing results and extracting repository paths. +""" + +import json +import re +from pathlib import Path +from colorama import Fore, Style + + +def parse_antipattern_results(antipatterns_scanner_results): + """Parse anti-pattern scanner results to determine if patterns were found and count them.""" + + if not antipatterns_scanner_results: + return False, 0 + + # Try to parse as JSON first + try: + if isinstance(antipatterns_scanner_results, str): + print(type(antipatterns_scanner_results)) + # Try to extract JSON from the string - improved regex pattern + # Look for JSON objects containing total_antipatterns_found + json_pattern = r'\{\s*[^}]*?"total_antipatterns_found"\s*:\s*(\d+)[^}]*?\}' + json_match = re.search(json_pattern, antipatterns_scanner_results, re.DOTALL) + + if json_match: + try: + json_data = json.loads(json_match.group()) + total_found = json_data.get('total_antipatterns_found', 0) + return total_found > 0, total_found + except json.JSONDecodeError: + # If full JSON parsing fails, extract just the number + total_found = int(json_match.group(1)) + return total_found > 0, total_found + + return False, 0 + + elif isinstance(antipatterns_scanner_results, dict): + total_found = antipatterns_scanner_results.get('total_antipatterns_found', 0) + return total_found > 0, total_found + + except (json.JSONDecodeError, Exception) as e: + print(f"Error parsing antipattern results: {e}") + pass + + # Default: if we have results but can't parse them, assume no anti-patterns + return False, 0 + + +def get_repository_paths_from_files(file_paths: list) -> set: + """Extract unique repository paths from the list of file paths.""" + repo_paths = set() + + for file_path in file_paths: + path = Path(file_path) + + # Find the clones directory in the path + for i, part in enumerate(path.parts): + if part == 'clones' and i + 1 < len(path.parts): + # The next part after 'clones' is the repository name + repo_name = path.parts[i + 1] + # Reconstruct the full repository path + clones_path = Path(*path.parts[:i+1]) # Path up to 'clones' + repo_path = clones_path / repo_name + repo_paths.add(str(repo_path)) + break + + return repo_paths From e733cbfbdde8c4737e172e3120b78057eacdcf22 Mon Sep 17 00:00:00 2001 From: Vamsi Date: Mon, 25 Aug 2025 13:52:50 +0100 Subject: [PATCH 3/3] removed type check in workflow_utils.py --- AntiPattern_Remediator/workflow/workflow_utils.py | 1 - 1 file changed, 1 deletion(-) diff --git a/AntiPattern_Remediator/workflow/workflow_utils.py b/AntiPattern_Remediator/workflow/workflow_utils.py index eeb8ef3..f3c0af8 100644 --- a/AntiPattern_Remediator/workflow/workflow_utils.py +++ b/AntiPattern_Remediator/workflow/workflow_utils.py @@ -19,7 +19,6 @@ def parse_antipattern_results(antipatterns_scanner_results): # Try to parse as JSON first try: if isinstance(antipatterns_scanner_results, str): - print(type(antipatterns_scanner_results)) # Try to extract JSON from the string - improved regex pattern # Look for JSON objects containing total_antipatterns_found json_pattern = r'\{\s*[^}]*?"total_antipatterns_found"\s*:\s*(\d+)[^}]*?\}'