From 12a424649d54e9a99c779987187a5c5be46132d6 Mon Sep 17 00:00:00 2001 From: shrutu0929 Date: Tue, 10 Mar 2026 19:52:12 +0530 Subject: [PATCH 1/6] feat(llm): batch triage RAG context extended --- refactron/llm/orchestrator.py | 87 ++++++++++++++++++++- refactron/llm/prompts.py | 21 ++++++ tests/test_llm_batch_triage.py | 134 +++++++++++++++++++++++++++++++++ 3 files changed, 239 insertions(+), 3 deletions(-) create mode 100644 tests/test_llm_batch_triage.py diff --git a/refactron/llm/orchestrator.py b/refactron/llm/orchestrator.py index f4da49d..aad5b9d 100644 --- a/refactron/llm/orchestrator.py +++ b/refactron/llm/orchestrator.py @@ -5,13 +5,18 @@ import os import re from pathlib import Path -from typing import List, Optional, Union +from typing import Dict, List, Optional, Union from refactron.core.models import CodeIssue, IssueCategory, IssueLevel from refactron.llm.backend_client import BackendLLMClient from refactron.llm.client import GroqClient from refactron.llm.models import RefactoringSuggestion, SuggestionStatus -from refactron.llm.prompts import DOCUMENTATION_PROMPT, SUGGESTION_PROMPT, SYSTEM_PROMPT +from refactron.llm.prompts import ( + BATCH_TRIAGE_PROMPT, + DOCUMENTATION_PROMPT, + SUGGESTION_PROMPT, + SYSTEM_PROMPT, +) from refactron.llm.safety import SafetyGate from refactron.rag.retriever import ContextRetriever @@ -32,7 +37,8 @@ def __init__( if llm_client: self.client = llm_client else: - # Try to use GroqClient if API key is present, otherwise use BackendLLMClient + # Try to use GroqClient if API key is present, + # otherwise use BackendLLMClient if os.getenv("GROQ_API_KEY"): try: self.client = GroqClient() @@ -246,6 +252,81 @@ def generate_documentation( status=SuggestionStatus.FAILED, ) + def evaluate_issues_batch(self, issues: List[CodeIssue], source_code: str) -> Dict[str, float]: + """Evaluate a batch of issues for a single file to suppress false positives. + + Args: + issues: List of CodeIssues found in the file + source_code: The full source code of the file + + Returns: + Dict mapping issue IDs (using rule_id or index) to confidence scores + """ + if not issues: + return {} + + # 1. Retrieve Context + context_snippets = [] + if self.retriever: + try: + # Search for similar code or relevant context + results = self.retriever.retrieve_similar(source_code[:1000], top_k=3) + context_snippets = [r.content for r in results] + except Exception as e: + logger.warning(f"Context retrieval failed: {e}") + + rag_context = "\n\n".join(context_snippets) if context_snippets else "No context available." + + # 2. Construct JSON for issues + issues_data = {} + for i, issue in enumerate(issues): + # Determine a stable issue ID + issue_id = getattr(issue, "rule_id", None) + if not issue_id: + issue_id = f"issue_{i}" + + issues_data[issue_id] = { + "message": issue.message, + "line": issue.line_number, + "category": ( + issue.category.value + if hasattr(issue.category, "value") + else str(issue.category) + ), + "severity": ( + issue.level.value if hasattr(issue.level, "value") else str(issue.level) + ), + } + + # 3. Construct Prompt + prompt = BATCH_TRIAGE_PROMPT.format( + source_code=source_code, + rag_context=rag_context, + issues_json=json.dumps(issues_data, indent=2), + ) + + # 4. Call LLM + try: + response_text = self.client.generate( + prompt=prompt, system=SYSTEM_PROMPT, temperature=0.1 + ) + clean_text = self._clean_json_response(response_text) + data = json.loads(clean_text, strict=False) + + # Ensure we return a Dict[str, float] + result = {} + for k, v in data.items(): + try: + result[str(k)] = float(v) + except (ValueError, TypeError): + result[str(k)] = 0.5 # Fallback for parsing errors + return result + + except Exception as e: + logger.error(f"Batch triage failed: {e}") + # Fallback: return default confidence + return {str(k): 0.5 for k in issues_data.keys()} + def _clean_json_response(self, text: str) -> str: """Clean LLM response to extract JSON.""" text = text.strip() diff --git a/refactron/llm/prompts.py b/refactron/llm/prompts.py index ade8031..5ab90c8 100644 --- a/refactron/llm/prompts.py +++ b/refactron/llm/prompts.py @@ -92,3 +92,24 @@ The complete Markdown documentation content including the mermaid diagram @@@END@@@ """ + +BATCH_TRIAGE_PROMPT = """ +You are a code triage expert. Evaluate the following list of code issues found in a +single file and determine the confidence that each is a true positive (requiring +fixing) rather than a false positive. + +File Source Code: +```python +{source_code} +``` + +Relevant Context (RAG): +{rag_context} + +Issues to evaluate: +{issues_json} + +Return ONLY a JSON map where the keys are the issue IDs and the values are the +confidence scores (float between 0.0 and 1.0). +Do NOT return anything except the JSON object. +""" diff --git a/tests/test_llm_batch_triage.py b/tests/test_llm_batch_triage.py new file mode 100644 index 0000000..99f0ee9 --- /dev/null +++ b/tests/test_llm_batch_triage.py @@ -0,0 +1,134 @@ +"""Tests for the Batched Triage & RAG Context in LLMOrchestrator.""" + +from pathlib import Path +from unittest.mock import MagicMock + +import pytest + +from refactron.core.models import CodeIssue, IssueCategory, IssueLevel +from refactron.llm.orchestrator import LLMOrchestrator +from refactron.rag.retriever import ContextRetriever + + +@pytest.fixture +def mock_retriever(): + retriever = MagicMock(spec=ContextRetriever) + mock_result = MagicMock() + mock_result.content = "Some context snippet" + retriever.retrieve_similar.return_value = [mock_result] + return retriever + + +@pytest.fixture +def mock_llm_client(): + client = MagicMock() + client.model = "mock-model" + # Provide a mock JSON response for evaluate_issues_batch + client.generate.return_value = """```json +{ + "issue_0": 0.85, + "issue_1": 0.12, + "E101": 0.95 +} +```""" + return client + + +def test_evaluate_issues_batch(mock_llm_client, mock_retriever): + """Test that batch evaluation correctly parses JSON map from the LLM.""" + orchestrator = LLMOrchestrator(retriever=mock_retriever, llm_client=mock_llm_client) + + issues = [ + CodeIssue( + category=IssueCategory.COMPLEXITY, + level=IssueLevel.WARNING, + message="Too complex", + file_path=Path("test.py"), + line_number=10, + ), + CodeIssue( + category=IssueCategory.STYLE, + level=IssueLevel.INFO, + message="Line too long", + file_path=Path("test.py"), + line_number=20, + ), + CodeIssue( + category=IssueCategory.CODE_SMELL, + level=IssueLevel.WARNING, + message="Bad smell", + file_path=Path("test.py"), + line_number=30, + rule_id="E101", + ), + ] + + source_code = "def complex_function():\n pass\n" * 10 + + result = orchestrator.evaluate_issues_batch(issues, source_code) + + # Check that ContextRetriever was called for RAG Context + mock_retriever.retrieve_similar.assert_called_once() + assert "def complex_function" in mock_retriever.retrieve_similar.call_args[0][0] + + # Check JSON map parsing + assert isinstance(result, dict) + assert result.get("issue_0") == 0.85 + assert result.get("issue_1") == 0.12 + assert result.get("E101") == 0.95 + + # Ensure there's exactly 3 keys corresponding to the 3 returned mapping + assert len(result) == 3 + + +def test_evaluate_issues_batch_empty_issues(mock_llm_client, mock_retriever): + """Test batch evaluation handles empty issues correctly.""" + orchestrator = LLMOrchestrator(retriever=mock_retriever, llm_client=mock_llm_client) + + result = orchestrator.evaluate_issues_batch([], "source") + assert result == {} + mock_llm_client.generate.assert_not_called() + + +def test_evaluate_issues_batch_fallback_on_error(mock_llm_client, mock_retriever): + """Test batch evaluation handles LLM errors using a fallback mechanism.""" + mock_llm_client.generate.side_effect = Exception("LLM Error") + + orchestrator = LLMOrchestrator(retriever=mock_retriever, llm_client=mock_llm_client) + + issues = [ + CodeIssue( + category=IssueCategory.STYLE, + level=IssueLevel.INFO, + message="Line too long", + file_path=Path("test.py"), + line_number=20, + ) + ] + + result = orchestrator.evaluate_issues_batch(issues, "source") + + # It should fallback to 0.5 confidence for 'issue_0' + assert result == {"issue_0": 0.5} + + +def test_evaluate_issues_batch_fallback_on_bad_json(mock_llm_client, mock_retriever): + """Test batch evaluation handles invalid JSON appropriately.""" + mock_llm_client.generate.return_value = "not a json string at all" + orchestrator = LLMOrchestrator( + retriever=mock_retriever, llm_client=mock_llm_client + ) + + issues = [ + CodeIssue( + category=IssueCategory.STYLE, + level=IssueLevel.INFO, + message="Line too long", + file_path=Path("test.py"), + line_number=20, + ) + ] + + result = orchestrator.evaluate_issues_batch(issues, "source") + + assert result == {"issue_0": 0.5} From a3f1d1a1dbfea17b57b7cf30425c742f2224f2cb Mon Sep 17 00:00:00 2001 From: shrutu0929 Date: Tue, 10 Mar 2026 20:21:34 +0530 Subject: [PATCH 2/6] style: fix black formatting length issues across tests to pass CI --- tests/test_cli.py | 36 ++++++++-------------- tests/test_llm_batch_triage.py | 4 +-- tests/test_patterns_feedback.py | 24 +++++---------- tests/test_patterns_integration.py | 42 +++++++++----------------- tests/test_performance_optimization.py | 6 ++-- tests/test_rag_indexer.py | 12 +++----- tests/test_refactron.py | 6 ++-- 7 files changed, 43 insertions(+), 87 deletions(-) diff --git a/tests/test_cli.py b/tests/test_cli.py index ad52bf9..ed4697b 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -62,13 +62,11 @@ def test_analyze_single_file(self): runner = CliRunner() with tempfile.NamedTemporaryFile(mode="w", suffix=".py", delete=False) as f: - f.write( - """ + f.write(""" def test_function(a, b, c, d, e, f): '''Function with too many parameters.''' return a + b + c + d + e + f -""" - ) +""") temp_path = f.name try: @@ -129,16 +127,14 @@ def test_analyze_detects_issues(self): runner = CliRunner() with tempfile.NamedTemporaryFile(mode="w", suffix=".py", delete=False) as f: - f.write( - """ + f.write(""" def bad_function(a, b, c, d, e, f, g): if True: if True: if True: if True: return eval(a) -""" - ) +""") temp_path = f.name try: @@ -155,13 +151,11 @@ def test_analyze_with_config(self): with tempfile.TemporaryDirectory() as tmpdir: # Create config file config_path = Path(tmpdir) / ".refactron.yaml" - config_path.write_text( - """ + config_path.write_text(""" enabled_analyzers: - complexity max_function_complexity: 5 -""" - ) +""") # Create test file test_file = Path(tmpdir) / "test.py" @@ -186,14 +180,12 @@ def test_refactor_preview_mode(self): runner = CliRunner() with tempfile.NamedTemporaryFile(mode="w", suffix=".py", delete=False) as f: - f.write( - """ + f.write(""" def calculate_discount(price): if price > 1000: return price * 0.15 return 0 -""" - ) +""") temp_path = f.name try: @@ -408,14 +400,12 @@ def test_full_workflow(self): # 2. Create test file test_file = Path(tmpdir) / "test.py" - test_file.write_text( - """ + test_file.write_text(""" def calculate(price): if price > 1000: return price * 0.15 return 0 -""" - ) +""") # 3. Analyze result = runner.invoke(analyze, [str(test_file)]) @@ -439,8 +429,7 @@ def test_analyze_with_all_analyzers(self): runner = CliRunner() with tempfile.NamedTemporaryFile(mode="w", suffix=".py", delete=False) as f: - f.write( - """ + f.write(""" import os import json @@ -452,8 +441,7 @@ def process(data): def unused(): pass -""" - ) +""") temp_path = f.name try: diff --git a/tests/test_llm_batch_triage.py b/tests/test_llm_batch_triage.py index 99f0ee9..1588dd8 100644 --- a/tests/test_llm_batch_triage.py +++ b/tests/test_llm_batch_triage.py @@ -115,9 +115,7 @@ def test_evaluate_issues_batch_fallback_on_error(mock_llm_client, mock_retriever def test_evaluate_issues_batch_fallback_on_bad_json(mock_llm_client, mock_retriever): """Test batch evaluation handles invalid JSON appropriately.""" mock_llm_client.generate.return_value = "not a json string at all" - orchestrator = LLMOrchestrator( - retriever=mock_retriever, llm_client=mock_llm_client - ) + orchestrator = LLMOrchestrator(retriever=mock_retriever, llm_client=mock_llm_client) issues = [ CodeIssue( diff --git a/tests/test_patterns_feedback.py b/tests/test_patterns_feedback.py index 5e7f2ad..2d9d741 100644 --- a/tests/test_patterns_feedback.py +++ b/tests/test_patterns_feedback.py @@ -170,14 +170,12 @@ def test_refactor_fingerprints_code(self): pytest.skip("Pattern fingerprinter not initialized") with tempfile.NamedTemporaryFile(mode="w", suffix=".py", delete=False) as f: - f.write( - """ + f.write(""" def calculate(price): if price > 1000: return price * 0.15 return 0 -""" - ) +""") temp_path = Path(f.name) try: @@ -201,8 +199,7 @@ def test_refactor_ranks_operations_and_sets_scores(self): pytest.skip("Pattern ranker not initialized") with tempfile.NamedTemporaryFile(mode="w", suffix=".py", delete=False) as f: - f.write( - """ + f.write(""" def calculate_price(price): if price > 1000: return price * 0.15 @@ -214,8 +211,7 @@ def process_order(order_id, customer_name, order_date, total_amount, discount_ra else: final_price = total_amount return final_price -""" - ) +""") temp_path = Path(f.name) try: @@ -244,14 +240,12 @@ def test_refactor_generates_operation_ids(self): refactron = Refactron(config) with tempfile.NamedTemporaryFile(mode="w", suffix=".py", delete=False) as f: - f.write( - """ + f.write(""" def calculate(price): if price > 1000: return price * 0.15 return 0 -""" - ) +""") temp_path = Path(f.name) try: @@ -382,14 +376,12 @@ def test_refactor_auto_records_on_apply(self): runner = CliRunner() with tempfile.NamedTemporaryFile(mode="w", suffix=".py", delete=False) as f: - f.write( - """ + f.write(""" def calculate(price): if price > 1000: return price * 0.15 return 0 -""" - ) +""") temp_path = f.name try: diff --git a/tests/test_patterns_integration.py b/tests/test_patterns_integration.py index de724b4..fb4f730 100644 --- a/tests/test_patterns_integration.py +++ b/tests/test_patterns_integration.py @@ -41,15 +41,13 @@ def test_refactor_feedback_learn_rank_workflow(self, refactron_with_storage, tem # Create a test file test_file = temp_storage_dir / "test_code.py" - test_file.write_text( - """ + test_file.write_text(""" def calculate_total(items): total = 0 for item in items: total += item.price * item.quantity return total -""".strip() - ) +""".strip()) # Step 1: Refactor (should fingerprint and rank) result = refactron.refactor(test_file, preview=True) @@ -137,15 +135,13 @@ def test_multiple_feedback_improves_pattern(self, refactron_with_storage, temp_s # Use code that will generate refactoring operations test_file = temp_storage_dir / "test.py" - test_file.write_text( - """ + test_file.write_text(""" def calculate_total(items): total = 0 for item in items: total += item.price * 100 # Magic number return total -""".strip() - ) +""".strip()) # Refactor multiple times and record feedback operations_seen = [] @@ -188,24 +184,20 @@ def test_patterns_isolated_by_project(self, temp_storage_dir): project1_dir = temp_storage_dir / "project1" project1_dir.mkdir() project1_file = project1_dir / "code.py" - project1_file.write_text( - """ + project1_file.write_text(""" def func1(): x = 100 # Magic number return x * 2 -""".strip() - ) +""".strip()) project2_dir = temp_storage_dir / "project2" project2_dir.mkdir() project2_file = project2_dir / "code.py" - project2_file.write_text( - """ + project2_file.write_text(""" def func2(): y = 200 # Different magic number return y * 3 -""".strip() - ) +""".strip()) # Create separate storage for each project storage1_dir = temp_storage_dir / "storage1" @@ -277,12 +269,10 @@ class TestPatternDatabasePersistence: def test_patterns_persist_across_sessions(self, temp_storage_dir): """Test that patterns persist when Refactron is recreated.""" test_file = temp_storage_dir / "test.py" - test_file.write_text( - """ + test_file.write_text(""" def calculate(x): return x * 100 # Magic number -""".strip() - ) +""".strip()) # First session: refactor and record feedback config = RefactronConfig( @@ -393,16 +383,14 @@ def test_learning_improves_ranking_over_time(self, refactron_with_storage, temp_ refactron = refactron_with_storage test_file = temp_storage_dir / "test.py" - test_file.write_text( - """ + test_file.write_text(""" def process_data(data): result = [] for item in data: if item > 0: result.append(item * 2) return result -""".strip() - ) +""".strip()) # Initial refactor - no patterns learned yet result1 = refactron.refactor(test_file, preview=True) @@ -444,16 +432,14 @@ def test_pattern_cleanup_preserves_recent_patterns( from refactron.patterns.learning_service import LearningService test_file = temp_storage_dir / "test.py" - test_file.write_text( - """ + test_file.write_text(""" def process_data(data): result = [] for item in data: if item > 0: result.append(item * 100) # Magic number return result -""".strip() - ) +""".strip()) # Create and learn a pattern result = refactron.refactor(test_file, preview=True) diff --git a/tests/test_performance_optimization.py b/tests/test_performance_optimization.py index 0b66afc..61a59de 100644 --- a/tests/test_performance_optimization.py +++ b/tests/test_performance_optimization.py @@ -322,13 +322,11 @@ def test_refactron_analyze_with_caching(self): """Test analysis with caching components initialized.""" with tempfile.TemporaryDirectory() as tmpdir: test_file = Path(tmpdir) / "test.py" - test_file.write_text( - """ + test_file.write_text(""" def simple_function(): '''A simple function.''' return 42 -""" - ) +""") config = RefactronConfig( enable_ast_cache=True, diff --git a/tests/test_rag_indexer.py b/tests/test_rag_indexer.py index 7305fe6..02c214a 100644 --- a/tests/test_rag_indexer.py +++ b/tests/test_rag_indexer.py @@ -36,18 +36,15 @@ def temp_workspace(self): workspace_path = Path(tmpdir) # Create sample Python files - (workspace_path / "simple.py").write_text( - ''' + (workspace_path / "simple.py").write_text(''' """Simple module.""" def hello(): """Say hello.""" return "Hello" -''' - ) +''') - (workspace_path / "utils.py").write_text( - ''' + (workspace_path / "utils.py").write_text(''' """Utility functions.""" class Calculator: @@ -56,8 +53,7 @@ class Calculator: def add(self, x, y): """Add two numbers.""" return x + y -''' - ) +''') yield workspace_path diff --git a/tests/test_refactron.py b/tests/test_refactron.py index d67ee70..251b6e1 100644 --- a/tests/test_refactron.py +++ b/tests/test_refactron.py @@ -27,8 +27,7 @@ def test_analyze_simple_file() -> None: """Test analyzing a simple Python file.""" # Create a temporary file with tempfile.NamedTemporaryFile(mode="w", suffix=".py", delete=False) as f: - f.write( - """ + f.write(""" def simple_function(): '''A simple function.''' return 42 @@ -36,8 +35,7 @@ def simple_function(): def complex_function(a, b, c, d, e, f): '''A function with too many parameters.''' return a + b + c + d + e + f -""" - ) +""") temp_path = f.name try: From 0780fe87bce2158c9cfa2ca6eb80b64417215066 Mon Sep 17 00:00:00 2001 From: shrutu0929 Date: Tue, 10 Mar 2026 20:35:09 +0530 Subject: [PATCH 3/6] style: fix black formatting length issues across tests using version 24.10.0 --- tests/test_cli.py | 36 ++++++++++++++-------- tests/test_patterns_feedback.py | 24 ++++++++++----- tests/test_patterns_integration.py | 42 +++++++++++++++++--------- tests/test_performance_optimization.py | 6 ++-- tests/test_rag_indexer.py | 12 +++++--- tests/test_refactron.py | 6 ++-- 6 files changed, 84 insertions(+), 42 deletions(-) diff --git a/tests/test_cli.py b/tests/test_cli.py index ed4697b..ad52bf9 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -62,11 +62,13 @@ def test_analyze_single_file(self): runner = CliRunner() with tempfile.NamedTemporaryFile(mode="w", suffix=".py", delete=False) as f: - f.write(""" + f.write( + """ def test_function(a, b, c, d, e, f): '''Function with too many parameters.''' return a + b + c + d + e + f -""") +""" + ) temp_path = f.name try: @@ -127,14 +129,16 @@ def test_analyze_detects_issues(self): runner = CliRunner() with tempfile.NamedTemporaryFile(mode="w", suffix=".py", delete=False) as f: - f.write(""" + f.write( + """ def bad_function(a, b, c, d, e, f, g): if True: if True: if True: if True: return eval(a) -""") +""" + ) temp_path = f.name try: @@ -151,11 +155,13 @@ def test_analyze_with_config(self): with tempfile.TemporaryDirectory() as tmpdir: # Create config file config_path = Path(tmpdir) / ".refactron.yaml" - config_path.write_text(""" + config_path.write_text( + """ enabled_analyzers: - complexity max_function_complexity: 5 -""") +""" + ) # Create test file test_file = Path(tmpdir) / "test.py" @@ -180,12 +186,14 @@ def test_refactor_preview_mode(self): runner = CliRunner() with tempfile.NamedTemporaryFile(mode="w", suffix=".py", delete=False) as f: - f.write(""" + f.write( + """ def calculate_discount(price): if price > 1000: return price * 0.15 return 0 -""") +""" + ) temp_path = f.name try: @@ -400,12 +408,14 @@ def test_full_workflow(self): # 2. Create test file test_file = Path(tmpdir) / "test.py" - test_file.write_text(""" + test_file.write_text( + """ def calculate(price): if price > 1000: return price * 0.15 return 0 -""") +""" + ) # 3. Analyze result = runner.invoke(analyze, [str(test_file)]) @@ -429,7 +439,8 @@ def test_analyze_with_all_analyzers(self): runner = CliRunner() with tempfile.NamedTemporaryFile(mode="w", suffix=".py", delete=False) as f: - f.write(""" + f.write( + """ import os import json @@ -441,7 +452,8 @@ def process(data): def unused(): pass -""") +""" + ) temp_path = f.name try: diff --git a/tests/test_patterns_feedback.py b/tests/test_patterns_feedback.py index 2d9d741..5e7f2ad 100644 --- a/tests/test_patterns_feedback.py +++ b/tests/test_patterns_feedback.py @@ -170,12 +170,14 @@ def test_refactor_fingerprints_code(self): pytest.skip("Pattern fingerprinter not initialized") with tempfile.NamedTemporaryFile(mode="w", suffix=".py", delete=False) as f: - f.write(""" + f.write( + """ def calculate(price): if price > 1000: return price * 0.15 return 0 -""") +""" + ) temp_path = Path(f.name) try: @@ -199,7 +201,8 @@ def test_refactor_ranks_operations_and_sets_scores(self): pytest.skip("Pattern ranker not initialized") with tempfile.NamedTemporaryFile(mode="w", suffix=".py", delete=False) as f: - f.write(""" + f.write( + """ def calculate_price(price): if price > 1000: return price * 0.15 @@ -211,7 +214,8 @@ def process_order(order_id, customer_name, order_date, total_amount, discount_ra else: final_price = total_amount return final_price -""") +""" + ) temp_path = Path(f.name) try: @@ -240,12 +244,14 @@ def test_refactor_generates_operation_ids(self): refactron = Refactron(config) with tempfile.NamedTemporaryFile(mode="w", suffix=".py", delete=False) as f: - f.write(""" + f.write( + """ def calculate(price): if price > 1000: return price * 0.15 return 0 -""") +""" + ) temp_path = Path(f.name) try: @@ -376,12 +382,14 @@ def test_refactor_auto_records_on_apply(self): runner = CliRunner() with tempfile.NamedTemporaryFile(mode="w", suffix=".py", delete=False) as f: - f.write(""" + f.write( + """ def calculate(price): if price > 1000: return price * 0.15 return 0 -""") +""" + ) temp_path = f.name try: diff --git a/tests/test_patterns_integration.py b/tests/test_patterns_integration.py index fb4f730..de724b4 100644 --- a/tests/test_patterns_integration.py +++ b/tests/test_patterns_integration.py @@ -41,13 +41,15 @@ def test_refactor_feedback_learn_rank_workflow(self, refactron_with_storage, tem # Create a test file test_file = temp_storage_dir / "test_code.py" - test_file.write_text(""" + test_file.write_text( + """ def calculate_total(items): total = 0 for item in items: total += item.price * item.quantity return total -""".strip()) +""".strip() + ) # Step 1: Refactor (should fingerprint and rank) result = refactron.refactor(test_file, preview=True) @@ -135,13 +137,15 @@ def test_multiple_feedback_improves_pattern(self, refactron_with_storage, temp_s # Use code that will generate refactoring operations test_file = temp_storage_dir / "test.py" - test_file.write_text(""" + test_file.write_text( + """ def calculate_total(items): total = 0 for item in items: total += item.price * 100 # Magic number return total -""".strip()) +""".strip() + ) # Refactor multiple times and record feedback operations_seen = [] @@ -184,20 +188,24 @@ def test_patterns_isolated_by_project(self, temp_storage_dir): project1_dir = temp_storage_dir / "project1" project1_dir.mkdir() project1_file = project1_dir / "code.py" - project1_file.write_text(""" + project1_file.write_text( + """ def func1(): x = 100 # Magic number return x * 2 -""".strip()) +""".strip() + ) project2_dir = temp_storage_dir / "project2" project2_dir.mkdir() project2_file = project2_dir / "code.py" - project2_file.write_text(""" + project2_file.write_text( + """ def func2(): y = 200 # Different magic number return y * 3 -""".strip()) +""".strip() + ) # Create separate storage for each project storage1_dir = temp_storage_dir / "storage1" @@ -269,10 +277,12 @@ class TestPatternDatabasePersistence: def test_patterns_persist_across_sessions(self, temp_storage_dir): """Test that patterns persist when Refactron is recreated.""" test_file = temp_storage_dir / "test.py" - test_file.write_text(""" + test_file.write_text( + """ def calculate(x): return x * 100 # Magic number -""".strip()) +""".strip() + ) # First session: refactor and record feedback config = RefactronConfig( @@ -383,14 +393,16 @@ def test_learning_improves_ranking_over_time(self, refactron_with_storage, temp_ refactron = refactron_with_storage test_file = temp_storage_dir / "test.py" - test_file.write_text(""" + test_file.write_text( + """ def process_data(data): result = [] for item in data: if item > 0: result.append(item * 2) return result -""".strip()) +""".strip() + ) # Initial refactor - no patterns learned yet result1 = refactron.refactor(test_file, preview=True) @@ -432,14 +444,16 @@ def test_pattern_cleanup_preserves_recent_patterns( from refactron.patterns.learning_service import LearningService test_file = temp_storage_dir / "test.py" - test_file.write_text(""" + test_file.write_text( + """ def process_data(data): result = [] for item in data: if item > 0: result.append(item * 100) # Magic number return result -""".strip()) +""".strip() + ) # Create and learn a pattern result = refactron.refactor(test_file, preview=True) diff --git a/tests/test_performance_optimization.py b/tests/test_performance_optimization.py index 61a59de..0b66afc 100644 --- a/tests/test_performance_optimization.py +++ b/tests/test_performance_optimization.py @@ -322,11 +322,13 @@ def test_refactron_analyze_with_caching(self): """Test analysis with caching components initialized.""" with tempfile.TemporaryDirectory() as tmpdir: test_file = Path(tmpdir) / "test.py" - test_file.write_text(""" + test_file.write_text( + """ def simple_function(): '''A simple function.''' return 42 -""") +""" + ) config = RefactronConfig( enable_ast_cache=True, diff --git a/tests/test_rag_indexer.py b/tests/test_rag_indexer.py index 02c214a..7305fe6 100644 --- a/tests/test_rag_indexer.py +++ b/tests/test_rag_indexer.py @@ -36,15 +36,18 @@ def temp_workspace(self): workspace_path = Path(tmpdir) # Create sample Python files - (workspace_path / "simple.py").write_text(''' + (workspace_path / "simple.py").write_text( + ''' """Simple module.""" def hello(): """Say hello.""" return "Hello" -''') +''' + ) - (workspace_path / "utils.py").write_text(''' + (workspace_path / "utils.py").write_text( + ''' """Utility functions.""" class Calculator: @@ -53,7 +56,8 @@ class Calculator: def add(self, x, y): """Add two numbers.""" return x + y -''') +''' + ) yield workspace_path diff --git a/tests/test_refactron.py b/tests/test_refactron.py index 251b6e1..d67ee70 100644 --- a/tests/test_refactron.py +++ b/tests/test_refactron.py @@ -27,7 +27,8 @@ def test_analyze_simple_file() -> None: """Test analyzing a simple Python file.""" # Create a temporary file with tempfile.NamedTemporaryFile(mode="w", suffix=".py", delete=False) as f: - f.write(""" + f.write( + """ def simple_function(): '''A simple function.''' return 42 @@ -35,7 +36,8 @@ def simple_function(): def complex_function(a, b, c, d, e, f): '''A function with too many parameters.''' return a + b + c + d + e + f -""") +""" + ) temp_path = f.name try: From b9de523b177f13818fd4a93907c778b355c30a90 Mon Sep 17 00:00:00 2001 From: shrutu0929 Date: Tue, 10 Mar 2026 22:24:42 +0530 Subject: [PATCH 4/6] feat(llm): batch triage RAG context and fix linting/type errors --- docs/advanced/ci-cd.mdx | 12 +++---- docs/advanced/monitoring.mdx | 8 ++--- docs/advanced/performance.mdx | 10 +++--- docs/api-reference/refactron-class.mdx | 4 +-- docs/docs.json | 2 +- docs/essentials/authentication.mdx | 10 +++--- docs/essentials/configuration.mdx | 16 ++++----- docs/essentials/installation.mdx | 8 ++--- docs/guides/ai-features.mdx | 32 ++++++++--------- docs/guides/code-analysis.mdx | 28 +++++++-------- docs/guides/pattern-learning.mdx | 16 ++++----- docs/guides/refactoring.mdx | 50 +++++++++++++------------- docs/introduction.mdx | 10 +++--- docs/quickstart.mdx | 22 ++++++------ docs/resources/faq.mdx | 38 ++++++++++---------- documentation/docs/CLI_REFERENCE.md | 49 +++++++++++++------------ documentation/docs/api/analyzers.md | 1 - documentation/docs/api/autofix.md | 1 - documentation/docs/api/cicd.md | 1 - documentation/docs/api/core.md | 1 - documentation/docs/api/llm.md | 1 - documentation/docs/api/patterns.md | 1 - documentation/docs/api/rag.md | 1 - documentation/docs/api/refactorers.md | 1 - refactron/cli.py | 8 ++--- refactron/core/repositories.py | 9 ++--- refactron/core/workspace.py | 3 +- refactron/llm/backend_client.py | 9 +++-- refactron/llm/client.py | 4 +-- refactron/llm/models.py | 4 +-- refactron/llm/safety.py | 4 +-- refactron/rag/indexer.py | 24 +++++++------ refactron/rag/parser.py | 42 ++++++++++++++++++---- refactron/rag/retriever.py | 3 +- scripts/analyze_feedback_data.py | 25 ++++++------- tests/test_backend_client.py | 3 +- tests/test_llm_orchestrator.py | 3 +- tests/test_patterns_integration.py | 9 +++-- tests/test_rag_indexer.py | 3 +- 39 files changed, 253 insertions(+), 223 deletions(-) diff --git a/docs/advanced/ci-cd.mdx b/docs/advanced/ci-cd.mdx index ccb4f46..ba05805 100644 --- a/docs/advanced/ci-cd.mdx +++ b/docs/advanced/ci-cd.mdx @@ -39,15 +39,15 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - + - name: Set up Python uses: actions/setup-python@v2 with: python-version: '3.9' - + - name: Install Refactron run: pip install refactron - + - name: Analyze Code run: refactron analyze . --log-format json ``` @@ -131,7 +131,7 @@ repos: refactron analyze . --log-format json ``` - + Fail builds based on severity: ```yaml @@ -142,7 +142,7 @@ repos: max_error_issues: 10 ``` - + Speed up CI runs by caching pip packages: ```yaml @@ -153,7 +153,7 @@ repos: key: ${{ runner.os }}-pip-refactron ``` - + Create HTML reports and save as artifacts: ```bash diff --git a/docs/advanced/monitoring.mdx b/docs/advanced/monitoring.mdx index 9a0441d..0a63072 100644 --- a/docs/advanced/monitoring.mdx +++ b/docs/advanced/monitoring.mdx @@ -8,7 +8,7 @@ description: 'Production monitoring and telemetry' Refactron includes comprehensive logging and monitoring capabilities for production environments: - **Structured Logging** - JSON-formatted logs for CI/CD -- **Metrics Collection** - Track analysis time and success rates +- **Metrics Collection** - Track analysis time and success rates - **Prometheus Integration** - Expose metrics via HTTP endpoint - **Opt-in Telemetry** - Anonymous usage analytics @@ -187,15 +187,15 @@ prometheus_port: 9090 JSON format integrates easily with log aggregation systems - + Track performance and identify bottlenecks - + Visualize Refactron metrics over time - + Respect user privacy with opt-in telemetry diff --git a/docs/advanced/performance.mdx b/docs/advanced/performance.mdx index 9734477..cc28219 100644 --- a/docs/advanced/performance.mdx +++ b/docs/advanced/performance.mdx @@ -36,12 +36,12 @@ Cache parsed Abstract Syntax Trees to avoid re-parsing. - Reduces CPU usage - Especially effective for large files - + ```python from refactron import Refactron from refactron.core.config import RefactronConfig - + config = RefactronConfig( enable_ast_cache=True, max_ast_cache_size_mb=100 @@ -49,7 +49,7 @@ Cache parsed Abstract Syntax Trees to avoid re-parsing. refactron = Refactron(config) ``` - + ```python stats = refactron.get_performance_stats() @@ -156,13 +156,13 @@ refactron.clear_caches() print(f"Hit rate: {stats['ast_cache']['hit_rate']}%") ``` - + - Reduce cache size: `max_ast_cache_size_mb: 50` - Lower parallel workers: `max_parallel_workers: 2` - Clear caches periodically: `refactron.clear_caches()` - + Disable for small codebases: ```yaml diff --git a/docs/api-reference/refactron-class.mdx b/docs/api-reference/refactron-class.mdx index 40c9f7d..a93705f 100644 --- a/docs/api-reference/refactron-class.mdx +++ b/docs/api-reference/refactron-class.mdx @@ -80,7 +80,7 @@ def refactor( Specific refactoring types to apply. None = all types. - + Available types: - `extract_constant` - `add_docstring` @@ -316,7 +316,7 @@ for op in result.operations: print(f" File: {op.file_path}:{op.line_number}") print(f" Risk: {op.risk_score}") print(f" Description: {op.description}") - + # Record feedback refactron.record_feedback( operation_id=op.operation_id, diff --git a/docs/docs.json b/docs/docs.json index ae238f7..cdf306b 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -93,4 +93,4 @@ "vscode" ] } -} \ No newline at end of file +} diff --git a/docs/essentials/authentication.mdx b/docs/essentials/authentication.mdx index ddb6cd9..4f7e397 100644 --- a/docs/essentials/authentication.mdx +++ b/docs/essentials/authentication.mdx @@ -84,17 +84,17 @@ Features that require authentication: refactron repo connect my-repo ``` - + Enhanced AI-powered refactoring with cloud LLM models - + ```bash refactron metrics ``` - + Share learned patterns across your team @@ -120,7 +120,7 @@ Cloud features gracefully degrade when offline. 2. Paste it into your browser manually 3. Complete the authentication - + If you see "Token expired" errors: ```bash @@ -128,7 +128,7 @@ Cloud features gracefully degrade when offline. refactron login ``` - + Verify the API key is set correctly: ```bash diff --git a/docs/essentials/configuration.mdx b/docs/essentials/configuration.mdx index 335614a..c3d3abe 100644 --- a/docs/essentials/configuration.mdx +++ b/docs/essentials/configuration.mdx @@ -67,23 +67,23 @@ pattern_storage_dir: null # null = auto-detect Detects security vulnerabilities like SQL injection, code injection, hardcoded secrets, and SSRF - + Identifies magic numbers, long functions, excessive parameters, and deep nesting - + Measures cyclomatic complexity, maintainability index, and nested loops - + Checks for missing or incomplete type annotations - + Finds unused functions and unreachable code - + Analyzes circular imports and wildcard imports @@ -95,15 +95,15 @@ pattern_storage_dir: null # null = auto-detect Extract magic numbers into named constants - + Add missing docstrings to functions and classes - + Simplify complex conditional statements - + Reduce function parameter count using dataclasses or dictionaries diff --git a/docs/essentials/installation.mdx b/docs/essentials/installation.mdx index 0ec2f0e..7fe801e 100644 --- a/docs/essentials/installation.mdx +++ b/docs/essentials/installation.mdx @@ -6,7 +6,7 @@ description: 'How to install and set up Refactron' ## Requirements - **Python Version**: 3.8 or higher + **Python Version**: 3.8 or higher **Supported Platforms**: macOS, Linux, Windows @@ -71,7 +71,7 @@ Refactron automatically installs these dependencies: - **radon** - Complexity metrics - **astroid** - AST analysis - + - **chromadb** - Vector database for RAG - **tree-sitter** - Code parsing @@ -90,7 +90,7 @@ Refactron automatically installs these dependencies: pip install --user refactron ``` - + Ensure you're using Python 3.8+: ```bash @@ -101,7 +101,7 @@ Refactron automatically installs these dependencies: python3.10 -m pip install refactron ``` - + On macOS, you may need to install certificates: ```bash diff --git a/docs/guides/ai-features.mdx b/docs/guides/ai-features.mdx index 16466e6..dd843a2 100644 --- a/docs/guides/ai-features.mdx +++ b/docs/guides/ai-features.mdx @@ -120,19 +120,19 @@ refactron refactor myfile.py --ai --apply Refactron analyzes your code for issues - + RAG system retrieves relevant code chunks from your project - + LLM generates refactoring suggestions with project context - + Safety gate validates syntax and checks for issues - + Suggestions presented with explanations and risk scores @@ -158,10 +158,10 @@ AI-powered refactoring suggests: # AI-suggested refactoring def process_users(users): """Process and filter users based on eligibility criteria. - + Args: users: List of user objects to process - + Returns: List of eligible users (adult, active, verified) """ @@ -202,21 +202,21 @@ refactron suggest myfile.py --line 42 --apply Fast, cloud-based LLM provider with free tier - + **Models:** - `llama3-70b-8192` - Best quality - `llama3-8b-8192` - Faster, good quality - `mixtral-8x7b-32768` - Long context window - + **Setup:** ```bash export GROQ_API_KEY='your-key' ``` - + Bring your own LLM provider - + Configure in `.refactron.yaml`: ```yaml llm: @@ -301,19 +301,19 @@ refactron feedback --action rejected --reason "Breaks API contrac Re-run `refactron rag index` after significant code changes for accurate context - + Models like Llama 3 70B provide better refactoring logic than smaller models - + Use `--preview` to review AI-generated code before applying - + Record feedback to improve AI suggestions over time - + Run your test suite after applying AI refactorings @@ -329,14 +329,14 @@ refactron feedback --action rejected --reason "Breaks API contrac ``` Export it in your shell profile for persistence - + Create index first: ```bash refactron rag index ``` - + - Use smaller models (llama3-8b-8192) - Reduce `max_tokens` diff --git a/docs/guides/code-analysis.mdx b/docs/guides/code-analysis.mdx index 9a8de3a..9515e29 100644 --- a/docs/guides/code-analysis.mdx +++ b/docs/guides/code-analysis.mdx @@ -49,7 +49,7 @@ for issue in analysis.issues: - **Code Injection**: Use of `eval()` and `exec()` - **Hardcoded Secrets**: API keys, passwords in code - **SSRF Vulnerabilities**: Unsafe URL handling - + ```python # ❌ Security issue detected query = f"SELECT * FROM users WHERE id = {user_id}" # SQL injection @@ -57,14 +57,14 @@ for issue in analysis.issues: API_KEY = "hardcoded-secret-123" # Hardcoded secret ``` - + Identifies code smells and maintainability issues: - **Magic Numbers**: Unexplained numeric constants - **Long Functions**: Functions exceeding length threshold - **Excessive Parameters**: Too many function parameters - **Deep Nesting**: Complex nested control structures - + ```python # ❌ Code quality issues def process(a, b, c, d, e, f, g): # Too many parameters @@ -75,14 +75,14 @@ for issue in analysis.issues: return 42 # Magic number ``` - + Measures code complexity: - **Cyclomatic Complexity**: Control flow complexity - **Cognitive Complexity**: Human readability complexity - **Maintainability Index**: Overall maintainability score - **Nested Loops**: Performance-impacting nested iterations - + ```python # ❌ High complexity def complex_function(data): @@ -94,51 +94,51 @@ for issue in analysis.issues: process(val) ``` - + Checks type annotation coverage: - Missing function type hints - Incomplete parameter annotations - Missing return type annotations - + ```python # ❌ Missing type hints def calculate(x, y): # No type hints return x + y - + # ✅ Properly typed def calculate(x: int, y: int) -> int: return x + y ``` - + Finds unused and unreachable code: - Unused variables - Unused functions - Unreachable code blocks - + ```python # ❌ Dead code def process(): result = expensive_calculation() # Unused variable return None - + def unused_function(): # Never called pass - + def example(): return True print("Never executed") # Unreachable ``` - + Analyzes import patterns: - Circular imports - Wildcard imports - Deprecated modules - + ```python # ❌ Dependency issues from module_a import * # Wildcard import diff --git a/docs/guides/pattern-learning.mdx b/docs/guides/pattern-learning.mdx index 3022ede..b468c48 100644 --- a/docs/guides/pattern-learning.mdx +++ b/docs/guides/pattern-learning.mdx @@ -13,11 +13,11 @@ Refactron's Pattern Learning System learns from your refactoring decisions, buil When Refactron suggests a refactoring, it creates a unique "fingerprint" of the code pattern - + You provide feedback: **accepted**, **rejected**, or **ignored** - + Refactron tracks: - Acceptance rates for each pattern @@ -25,7 +25,7 @@ Refactron's Pattern Learning System learns from your refactoring decisions, buil - Code metrics improvements - Project-specific preferences - + Future suggestions ranked based on historical acceptance rates and patterns @@ -237,7 +237,7 @@ pattern_storage_dir: /custom/path/patterns - **Reject** inappropriate suggestions - **Ignore** if unsure - + For large projects, use automated tuning: ```bash @@ -245,14 +245,14 @@ pattern_storage_dir: /custom/path/patterns refactron patterns tune --auto ``` - + Periodically check pattern performance: ```bash refactron patterns analyze ``` - + In CI/CD environments, use consistent storage: ```yaml @@ -321,14 +321,14 @@ Pattern learning integrates with [AI Features](/guides/ai-features): 2. Verify storage directory is writable 3. Check logs: `refactron refactor --log-level DEBUG` - + **Solutions:** 1. Check `pattern_ranking_enabled` is `true` 2. Ensure patterns have been learned (provide feedback first) 3. Verify sufficient pattern history exists - + **Solutions:** 1. Check directory permissions diff --git a/docs/guides/refactoring.mdx b/docs/guides/refactoring.mdx index ac55e22..ac167f9 100644 --- a/docs/guides/refactoring.mdx +++ b/docs/guides/refactoring.mdx @@ -34,67 +34,67 @@ refactron refactor myfile.py Replaces magic numbers with named constants - + ```python # Before def calculate_tax(amount): return amount * 0.18 - + # After TAX_RATE = 0.18 - + def calculate_tax(amount): return amount * TAX_RATE ``` - + Adds missing docstrings to functions and classes - + ```python # Before def calculate_total(items): return sum(item.price for item in items) - + # After def calculate_total(items): """Calculate total price for list of items. - + Args: items: List of items with price attribute - + Returns: Total sum of item prices """ return sum(item.price for item in items) ``` - + Refactors complex conditional expressions - + ```python # Before if not (x < 10 or x > 20): process() - + # After if 10 <= x <= 20: process() ``` - + Reduces function parameters using dataclasses or dicts - + ```python # Before def create_user(name, email, age, city, country): pass - + # After from dataclasses import dataclass - + @dataclass class UserData: name: str @@ -102,7 +102,7 @@ refactron refactor myfile.py age: int city: str country: str - + def create_user(user: UserData): pass ``` @@ -226,29 +226,29 @@ file_ops.rollback_all() refactron analyze myproject/ ``` - + Preview refactoring suggestions ```bash refactron refactor myproject/ --preview ``` - + Review the diff output and risk scores - + Apply refactorings you want to keep ```bash refactron refactor myproject/ --type extract_constant ``` - + Run your tests to ensure nothing broke - + Rollback if something went wrong ```bash @@ -299,19 +299,19 @@ refactron feedback --action accepted Use `--preview` to see changes before applying them - + Commit your code before refactoring for easy rollback - + Run your test suite after applying refactorings - + Begin with safe refactorings, then gradually increase risk tolerance - + Refactor small portions at a time rather than entire codebase diff --git a/docs/introduction.mdx b/docs/introduction.mdx index 4d4ec7e..572b253 100644 --- a/docs/introduction.mdx +++ b/docs/introduction.mdx @@ -54,23 +54,23 @@ Refactron is a powerful Python library that analyzes your code for security vuln Detect SQL injection, code injection, hardcoded secrets, and SSRF vulnerabilities - + Identify magic numbers, long functions, excessive parameters, and deep nesting - + LLM orchestration with RAG (Retrieval-Augmented Generation) for context-aware refactoring - + Learn from your project-specific coding standards and improve over time - + AST caching, incremental analysis, and parallel processing for large codebases - + 14 automated fixers with configurable safety levels and rollback support diff --git a/docs/quickstart.mdx b/docs/quickstart.mdx index 35f585d..d653c9a 100644 --- a/docs/quickstart.mdx +++ b/docs/quickstart.mdx @@ -26,7 +26,7 @@ Follow this workflow to get the most out of Refactron: refactron login ``` - + Set up Refactron in your project: ```bash @@ -34,46 +34,46 @@ Follow this workflow to get the most out of Refactron: ``` Choose a template (base, django, fastapi, or flask) to get started quickly. - + Connect your GitHub repository for enhanced features: ```bash refactron repo connect ``` - + Run comprehensive code analysis: ```bash refactron analyze . --detailed ``` - + Generate AI-powered refactoring suggestions: ```bash refactron suggest myfile.py ``` - + Preview and apply refactoring changes: ```bash # Preview changes first refactron refactor myfile.py --preview - + # Apply when ready refactron refactor myfile.py --apply ``` - + Create a comprehensive technical debt report: ```bash refactron report . --format html -o report.html ``` - + Rollback changes if something goes wrong: ```bash @@ -155,15 +155,15 @@ When you run an analysis, you'll see: ✓ Analyzing myproject/ Files analyzed: 25 Issues found: 12 - + CRITICAL (2): - SQL injection vulnerability (line 45) - Hardcoded secret detected (line 78) - + ERROR (4): - High cyclomatic complexity (line 120) - Deep nesting detected (line 156) - + WARNING (6): - Magic number usage (line 23) - Missing type hints (line 67) diff --git a/docs/resources/faq.mdx b/docs/resources/faq.mdx index a7e83c7..8c47a6c 100644 --- a/docs/resources/faq.mdx +++ b/docs/resources/faq.mdx @@ -9,15 +9,15 @@ description: 'Frequently asked questions' Refactron is an intelligent Python code refactoring tool that analyzes your code for issues and suggests automated improvements with safety guarantees. - + Yes! Refactron is open source and free to use. Some advanced features require an API key for authentication. - + Refactron supports Python 3.8 and above. - + Absolutely! Refactron is designed for CI/CD integration. Use `refactron ci` to generate configuration templates. @@ -36,7 +36,7 @@ description: 'Frequently asked questions' refactron login ``` - + Refactron works out of the box with sensible defaults. For customization, run: ```bash @@ -44,7 +44,7 @@ description: 'Frequently asked questions' ``` This creates a `.refactron.yaml` configuration file. - + Refactron automatically installs all required dependencies via pip. For AI features, you'll need a Groq API key (free tier available). @@ -60,7 +60,7 @@ description: 'Frequently asked questions' ``` This will scan your code and report issues by severity. - + Refactron includes multiple safety features: - **Preview mode** - See changes before applying @@ -68,7 +68,7 @@ description: 'Frequently asked questions' - **Automatic backups** - All changes are backed up - **Rollback** - Easily undo changes - + Yes! Use the `--type` flag: ```bash @@ -81,7 +81,7 @@ description: 'Frequently asked questions' - add_docstring ``` - + First, set your Groq API key: ```bash @@ -104,11 +104,11 @@ description: 'Frequently asked questions' By default, Refactron uses Groq (Llama 3). You can also configure custom LLM providers. - + When using AI features with cloud LLM providers (like Groq), code snippets are sent for analysis. The RAG indexing happens locally. You can use local LLM providers for complete privacy. - + Re-index after significant code changes: ```bash @@ -127,14 +127,14 @@ description: 'Frequently asked questions' Refactron learns from your feedback on refactoring suggestions. When you accept or reject suggestions, it adapts to your project's style and preferences. - + Yes, in `.refactron.yaml`: ```yaml enable_pattern_learning: false ``` - + By default in `.refactron/patterns/` in your project root. You can customize this: ```yaml @@ -152,7 +152,7 @@ description: 'Frequently asked questions' refactron auth status ``` - + Enable performance optimizations: ```yaml @@ -161,7 +161,7 @@ description: 'Frequently asked questions' max_parallel_workers: 4 ``` - + You can: 1. Provide feedback to pattern learning @@ -175,7 +175,7 @@ description: 'Frequently asked questions' - "*/tests/*" ``` - + List available rollback sessions: ```bash @@ -186,7 +186,7 @@ description: 'Frequently asked questions' refactron rollback --session ``` - + Create the index first: ```bash @@ -202,14 +202,14 @@ description: 'Frequently asked questions' Yes! Extend the `BaseAnalyzer` class: ```python from refactron.analyzers.base_analyzer import BaseAnalyzer - + class MyAnalyzer(BaseAnalyzer): def analyze_file(self, file_path, ast_tree): # Your analysis logic return issues ``` - + Yes! Refactron integrates well with: - **Black** - Code formatting @@ -218,7 +218,7 @@ description: 'Frequently asked questions' - **pre-commit** - Git hooks - **Prometheus** - Monitoring - + Check out our [GitHub repository](https://github.com/Refactron-ai/Refactron_lib) and see the CONTRIBUTING.md guide! diff --git a/documentation/docs/CLI_REFERENCE.md b/documentation/docs/CLI_REFERENCE.md index 56d58d5..2d07ac0 100644 --- a/documentation/docs/CLI_REFERENCE.md +++ b/documentation/docs/CLI_REFERENCE.md @@ -16,32 +16,32 @@ This document contains the reference for all Refactron CLI commands. COMMAND CENTER Select a command by name or number - - ID COMMAND DESCRIPTION - ──────────────────────────────────────────────────────────────────────────────────────────────────────── - 01 ANALYZE Analyze code for issues and technical debt. - 02 AUTH Manage authentication state. - 03 AUTOFIX Automatically fix code issues (Phase 3... - 04 DOCUMENT Generate Google-style docstrings for a... - 05 FEEDBACK Provide feedback on a refactoring operation. - 06 GENERATE-CICD Generate CI/CD integration templates. - 07 INIT Initialize Refactron configuration in the... - 08 LOGIN Log in to Refactron CLI via device-code flow. - 09 LOGOUT Log out of Refactron CLI. - 10 METRICS Display collected metrics from the current... - 11 PATTERNS Pattern learning and project-specific... - 12 RAG RAG (Retrieval-Augmented Generation)... - 13 REFACTOR Refactor code with intelligent... - 14 REPO Manage GitHub repository connections. - 15 REPORT Generate a detailed technical debt report. - 16 ROLLBACK Rollback refactoring changes to restore... - 17 SERVE-METRICS Start a Prometheus metrics HTTP server. - 18 SUGGEST Generate AI-powered refactoring suggestions. - 19 TELEMETRY Manage telemetry settings. - + + ID COMMAND DESCRIPTION + ──────────────────────────────────────────────────────────────────────────────────────────────────────── + 01 ANALYZE Analyze code for issues and technical debt. + 02 AUTH Manage authentication state. + 03 AUTOFIX Automatically fix code issues (Phase 3... + 04 DOCUMENT Generate Google-style docstrings for a... + 05 FEEDBACK Provide feedback on a refactoring operation. + 06 GENERATE-CICD Generate CI/CD integration templates. + 07 INIT Initialize Refactron configuration in the... + 08 LOGIN Log in to Refactron CLI via device-code flow. + 09 LOGOUT Log out of Refactron CLI. + 10 METRICS Display collected metrics from the current... + 11 PATTERNS Pattern learning and project-specific... + 12 RAG RAG (Retrieval-Augmented Generation)... + 13 REFACTOR Refactor code with intelligent... + 14 REPO Manage GitHub repository connections. + 15 REPORT Generate a detailed technical debt report. + 16 ROLLBACK Rollback refactoring changes to restore... + 17 SERVE-METRICS Start a Prometheus metrics HTTP server. + 18 SUGGEST Generate AI-powered refactoring suggestions. + 19 TELEMETRY Manage telemetry settings. + GLOBAL OPTIONS ---version Show the version and exit. +--version Show the version and exit. --help Show this message and exit. USAGE: refactron ... @@ -452,4 +452,3 @@ Options: --help Show this message and exit. ``` - diff --git a/documentation/docs/api/analyzers.md b/documentation/docs/api/analyzers.md index cf39613..beccbb0 100644 --- a/documentation/docs/api/analyzers.md +++ b/documentation/docs/api/analyzers.md @@ -485,4 +485,3 @@ Returns: astroid.nodes.Module ## Functions - diff --git a/documentation/docs/api/autofix.md b/documentation/docs/api/autofix.md index 0cd2213..1e626ab 100644 --- a/documentation/docs/api/autofix.md +++ b/documentation/docs/api/autofix.md @@ -799,4 +799,3 @@ FixRiskLevel(*values) Risk levels for automatic fixes. ## Functions - diff --git a/documentation/docs/api/cicd.md b/documentation/docs/api/cicd.md index c8ff4ee..5cf00e8 100644 --- a/documentation/docs/api/cicd.md +++ b/documentation/docs/api/cicd.md @@ -467,4 +467,3 @@ Returns: Dictionary with issue counts ## Functions - diff --git a/documentation/docs/api/core.md b/documentation/docs/api/core.md index c71b646..3e64e90 100644 --- a/documentation/docs/api/core.md +++ b/documentation/docs/api/core.md @@ -2878,4 +2878,3 @@ WorkspaceMapping.to_dict(self) -> 'Dict[str, Any]' Convert to dictionary for JSON serialization. ## Functions - diff --git a/documentation/docs/api/llm.md b/documentation/docs/api/llm.md index a57f9b6..0c594aa 100644 --- a/documentation/docs/api/llm.md +++ b/documentation/docs/api/llm.md @@ -287,4 +287,3 @@ Returns: Safety check result ## Functions - diff --git a/documentation/docs/api/patterns.md b/documentation/docs/api/patterns.md index 3e4bccf..bba6981 100644 --- a/documentation/docs/api/patterns.md +++ b/documentation/docs/api/patterns.md @@ -911,4 +911,3 @@ Heuristics: - Adjust pattern weights based on project acceptance. ## Functions - diff --git a/documentation/docs/api/rag.md b/documentation/docs/api/rag.md index 930892e..66f6929 100644 --- a/documentation/docs/api/rag.md +++ b/documentation/docs/api/rag.md @@ -348,4 +348,3 @@ RetrievedContext.__init__(self, content: 'str', file_path: 'str', chunk_type: 's Initialize self. See help(type(self)) for accurate signature. ## Functions - diff --git a/documentation/docs/api/refactorers.md b/documentation/docs/api/refactorers.md index 696ca5d..27c08ea 100644 --- a/documentation/docs/api/refactorers.md +++ b/documentation/docs/api/refactorers.md @@ -275,4 +275,3 @@ Returns: List of simplification operations ## Functions - diff --git a/refactron/cli.py b/refactron/cli.py index 3b2c725..3bd578e 100644 --- a/refactron/cli.py +++ b/refactron/cli.py @@ -7,7 +7,7 @@ from dataclasses import dataclass from datetime import datetime, timezone from pathlib import Path -from typing import Optional +from typing import Optional, cast from urllib.parse import urlencode import click @@ -1146,7 +1146,7 @@ def _interactive_file_selector(workspace_path: Path) -> Path: console.print( f"\n[success]✓ Selected: {selected_path.relative_to(workspace_path)}[/success]\n" ) - return selected_path + return cast(Path, selected_path) except (KeyboardInterrupt, EOFError): console.print("\n[yellow]Selection cancelled.[/yellow]") @@ -3253,7 +3253,7 @@ def patterns_profile(project_path: str, config_path: Optional[str]) -> None: @click.option("--line", type=int, help="Specific line number to fix") @click.option("--interactive/--no-interactive", default=True, help="Use interactive mode") @click.option("--apply/--no-apply", default=False, help="Apply the suggested changes to the file") -def suggest(target: Optional[str], line: Optional[int], interactive: bool, apply: bool): +def suggest(target: Optional[str], line: Optional[int], interactive: bool, apply: bool) -> None: """ Generate AI-powered refactoring suggestions. @@ -3413,7 +3413,7 @@ def suggest(target: Optional[str], line: Optional[int], interactive: bool, apply "--apply/--no-apply", default=False, help="Apply the documentation changes to the file" ) @click.option("--interactive/--no-interactive", default=True, help="Use interactive mode for apply") -def document(target: str, apply: bool, interactive: bool): +def document(target: str, apply: bool, interactive: bool) -> None: """ Generate Google-style docstrings for a Python file. diff --git a/refactron/core/repositories.py b/refactron/core/repositories.py index 3cf1ac4..b5af6bc 100644 --- a/refactron/core/repositories.py +++ b/refactron/core/repositories.py @@ -76,7 +76,6 @@ def list_repositories(api_base_url: str, timeout_seconds: int = 10) -> List[Repo if creds.expires_at: try: # Parse the expiration time - from datetime import datetime if isinstance(creds.expires_at, str): # Remove timezone info for comparison @@ -122,8 +121,9 @@ def list_repositories(api_base_url: str, timeout_seconds: int = 10) -> List[Repo ) if not isinstance(repositories_data, list): raise RuntimeError( - f"Unexpected API response format. Expected list or dict with 'repositories' key. " - f"Got: {type(data)} with keys: {list(data.keys()) if isinstance(data, dict) else 'N/A'}" + f"Unexpected API response format. Expected list or dict with " + f"'repositories' key. Got: {type(data)} with keys: " + f"{list(data.keys()) if isinstance(data, dict) else 'N/A'}" ) else: raise RuntimeError(f"Unexpected API response type: {type(data)}") @@ -151,7 +151,8 @@ def list_repositories(api_base_url: str, timeout_seconds: int = 10) -> List[Repo ) elif e.code == 403: raise RuntimeError( - "GitHub access denied. Please reconnect your GitHub account on the Refactron website." + "GitHub access denied. " + "Please reconnect your GitHub account on the Refactron website." ) elif e.code == 404: raise RuntimeError( diff --git a/refactron/core/workspace.py b/refactron/core/workspace.py index 03b56d6..db5d948 100644 --- a/refactron/core/workspace.py +++ b/refactron/core/workspace.py @@ -121,7 +121,8 @@ def get_workspace(self, repo_name: str) -> Optional[WorkspaceMapping]: # Try matching by short name (repo name without user) repo_name_lower = repo_name.lower() for full_name, workspace_data in workspaces.items(): - # Extract short name from full name (e.g., "volumeofsphere" from "omsherikar/volumeofsphere") + # Extract short name from full name + # (e.g., "volumeofsphere" from "omsherikar/volumeofsphere") short_name = full_name.split("/")[-1].lower() if short_name == repo_name_lower: return WorkspaceMapping.from_dict(workspace_data) diff --git a/refactron/llm/backend_client.py b/refactron/llm/backend_client.py index 8687bdd..436d179 100644 --- a/refactron/llm/backend_client.py +++ b/refactron/llm/backend_client.py @@ -2,10 +2,9 @@ from __future__ import annotations -import os -from typing import Any, Dict, Optional +from typing import Optional, cast -import requests +import requests # type: ignore from refactron.core.credentials import load_credentials @@ -94,7 +93,7 @@ def generate( raise RuntimeError(f"Backend LLM proxy error ({response.status_code}): {error_msg}") data = response.json() - return data["content"] + return cast(str, data["content"]) except requests.exceptions.RequestException as e: raise RuntimeError(f"Failed to connect to Refactron backend: {e}") @@ -114,6 +113,6 @@ def check_health(self) -> bool: f"{self.backend_url}/api/llm/health", timeout=10, ) - return response.status_code == 200 + return bool(response.status_code == 200) except Exception: return False diff --git a/refactron/llm/client.py b/refactron/llm/client.py index bc3a576..d7c9ba6 100644 --- a/refactron/llm/client.py +++ b/refactron/llm/client.py @@ -3,7 +3,7 @@ from __future__ import annotations import os -from typing import Optional +from typing import Optional, cast try: from groq import Groq @@ -79,7 +79,7 @@ def generate( max_tokens=max_tokens or self.max_tokens, ) - return response.choices[0].message.content + return cast(str, response.choices[0].message.content) def check_health(self) -> bool: """Check if the Groq API is accessible. diff --git a/refactron/llm/models.py b/refactron/llm/models.py index 82ff83f..5b6e4fe 100644 --- a/refactron/llm/models.py +++ b/refactron/llm/models.py @@ -4,7 +4,7 @@ import uuid from dataclasses import dataclass, field from enum import Enum -from typing import Any, Dict, List, Optional +from typing import List, Optional from refactron.core.models import CodeIssue @@ -58,7 +58,7 @@ class RefactoringSuggestion: suggestion_id: str = field(default_factory=lambda: str(uuid.uuid4())) timestamp: float = field(default_factory=time.time) - def __post_init__(self): + def __post_init__(self) -> None: if self.safety_result is None: # Default empty safety result self.safety_result = SafetyCheckResult( diff --git a/refactron/llm/safety.py b/refactron/llm/safety.py index a3b1543..5aa895a 100644 --- a/refactron/llm/safety.py +++ b/refactron/llm/safety.py @@ -1,7 +1,7 @@ """Safety gate for validating LLM-generated code.""" import ast -from typing import List, Optional +from typing import List, Set from refactron.llm.models import RefactoringSuggestion, SafetyCheckResult @@ -90,7 +90,7 @@ def _check_dangerous_imports(self, proposed_code: str, original_code: str) -> Li """Check for potentially dangerous imports that are NEW.""" dangerous_modules = ["subprocess", "os", "shutil", "sys"] - def get_imports(code): + def get_imports(code: str) -> Set[str]: imports = set() try: tree = ast.parse(code) diff --git a/refactron/rag/indexer.py b/refactron/rag/indexer.py index 4d62fec..690cfe3 100644 --- a/refactron/rag/indexer.py +++ b/refactron/rag/indexer.py @@ -5,7 +5,7 @@ import json from dataclasses import dataclass from pathlib import Path -from typing import List, Optional +from typing import Any, Dict, List, Optional, cast try: import chromadb @@ -20,7 +20,7 @@ CHROMA_AVAILABLE = False from refactron.rag.chunker import CodeChunk -from refactron.rag.parser import CodeParser, ParsedFile +from refactron.rag.parser import CodeParser # Import for type hints try: @@ -60,7 +60,8 @@ def __init__( """ if not CHROMA_AVAILABLE: raise RuntimeError( - "ChromaDB is not available. Install with: pip install chromadb sentence-transformers" + "ChromaDB is not available. " + "Install with: pip install chromadb sentence-transformers" ) self.workspace_path = Path(workspace_path) @@ -68,7 +69,7 @@ def __init__( self.index_path.mkdir(exist_ok=True) # Initialize LLM client for summarization - from refactron.llm.client import GroqClient + # GroqClient is used for type hints and potential init below self.llm_client = llm_client @@ -125,7 +126,7 @@ def index_repository( ] total_chunks = 0 - chunk_type_counts = {} + chunk_type_counts: Dict[str, int] = {} # Index each file for py_file in python_files: @@ -181,7 +182,7 @@ def _index_file(self, file_path: Path, summarize: bool = False) -> List[CodeChun try: summary = self._summarize_chunk(chunk) if summary: - # Prepend summary to content for embedding (makes it searchable by plain English) + # Prepend summary for semantic searchability chunk.content = f"Summary: {summary}\n\n{chunk.content}" chunk.metadata["ai_summary"] = summary except Exception as e: @@ -207,7 +208,10 @@ def _summarize_chunk(self, chunk: CodeChunk) -> Optional[str]: try: summary = self.llm_client.generate( prompt=prompt, - system="You are a senior software architect. Provide a concise, semantic summary of code purpose.", + system=( + "You are a senior software architect. " + "Provide a concise, semantic summary of code purpose." + ), max_tokens=100, ) return summary.strip() @@ -271,17 +275,17 @@ def get_stats(self) -> IndexStats: index_path=str(self.index_path), ) - def _save_metadata(self, metadata: dict) -> None: + def _save_metadata(self, metadata: Dict[str, Any]) -> None: """Save index metadata.""" metadata_file = self.index_path / "metadata.json" with open(metadata_file, "w") as f: json.dump(metadata, f, indent=2) - def _load_metadata(self) -> dict: + def _load_metadata(self) -> Dict[str, Any]: """Load index metadata.""" metadata_file = self.index_path / "metadata.json" if not metadata_file.exists(): return {} with open(metadata_file, "r") as f: - return json.load(f) + return cast(Dict[str, Any], json.load(f)) diff --git a/refactron/rag/parser.py b/refactron/rag/parser.py index d48f49b..df982f8 100644 --- a/refactron/rag/parser.py +++ b/refactron/rag/parser.py @@ -4,7 +4,7 @@ from dataclasses import dataclass from pathlib import Path -from typing import Any, Dict, List, Optional, Tuple +from typing import List, Optional, Tuple try: import tree_sitter_python as tspython @@ -51,11 +51,12 @@ class ParsedFile: class CodeParser: """AST-aware code parser using tree-sitter.""" - def __init__(self): + def __init__(self) -> None: """Initialize the parser.""" if not TREE_SITTER_AVAILABLE: raise RuntimeError( - "tree-sitter is not available. Install with: pip install tree-sitter tree-sitter-python" + "tree-sitter is not available. " + "Install with: pip install tree-sitter tree-sitter-python" ) # Initialize Python language - handle different tree-sitter API versions @@ -73,8 +74,37 @@ def __init__(self): try: PY_LANGUAGE = Language(lang, "python") except TypeError: - # Last resort: try as keyword - PY_LANGUAGE = Language(lang, name="python") + # Try using the path to the compiled library (for very old or CI bindings) + try: + import os + import platform + + pkg_dir = os.path.dirname(tspython.__file__) + + # Find the correct shared library extension + system = platform.system() + if system == "Windows": + ext = ".dll" + elif system == "Darwin": + ext = ".dylib" + else: + ext = ".so" + + # Look for common names of the compiled language file + lib_path = None + for fname in os.listdir(pkg_dir): + if fname.endswith(ext): + lib_path = os.path.join(pkg_dir, fname) + break + + if lib_path: + PY_LANGUAGE = Language(lib_path, "python") + else: + # Last resort: try as keyword or whatever lang is + PY_LANGUAGE = Language(lang, name="python") + except Exception: + # Absolute last resort + PY_LANGUAGE = Language(lang, name="python") self.parser = Parser(PY_LANGUAGE) @@ -256,7 +286,7 @@ def _extract_class_docstring(self, node: Node, source: bytes) -> Optional[str]: def _extract_parameters(self, node: Node, source: bytes) -> List[str]: """Extract function parameters.""" - params = [] + params: List[str] = [] params_node = node.child_by_field_name("parameters") if not params_node: return params diff --git a/refactron/rag/retriever.py b/refactron/rag/retriever.py index 2a71d1b..9fcd16f 100644 --- a/refactron/rag/retriever.py +++ b/refactron/rag/retriever.py @@ -50,7 +50,8 @@ def __init__( """ if not CHROMA_AVAILABLE: raise RuntimeError( - "ChromaDB is not available. Install with: pip install chromadb sentence-transformers" + "ChromaDB is not available. " + "Install with: pip install chromadb sentence-transformers" ) self.workspace_path = Path(workspace_path) diff --git a/scripts/analyze_feedback_data.py b/scripts/analyze_feedback_data.py index 1093573..f35e30e 100644 --- a/scripts/analyze_feedback_data.py +++ b/scripts/analyze_feedback_data.py @@ -12,14 +12,15 @@ import sys from collections import Counter from pathlib import Path +from typing import Any, Dict, Optional # Add parent directory to path for imports sys.path.insert(0, str(Path(__file__).parent.parent)) -from refactron.patterns.storage import PatternStorage +from refactron.patterns.storage import PatternStorage # noqa: E402 -def analyze_feedback(): +def analyze_feedback() -> Optional[Dict[str, Any]]: """Analyze all available feedback data.""" # Find all pattern storage directories @@ -52,23 +53,23 @@ def analyze_feedback(): return None print(f"\n{'='*60}") - print(f"AGGREGATE STATISTICS") + print("AGGREGATE STATISTICS") print(f"{'='*60}\n") - print(f"📊 Total Records:") + print("📊 Total Records:") print(f" Feedback: {len(all_feedback)}") print(f" Patterns: {len(all_patterns)}") # Action distribution actions = Counter(f.action for f in all_feedback) - print(f"\n✅ Action Distribution:") + print("\n✅ Action Distribution:") for action, count in actions.most_common(): pct = count / len(all_feedback) * 100 print(f" {action:12s}: {count:4d} ({pct:5.1f}%)") # Operation types operation_types = Counter(f.operation_type for f in all_feedback) - print(f"\n🔧 Operation Types:") + print("\n🔧 Operation Types:") for op_type, count in operation_types.most_common(5): pct = count / len(all_feedback) * 100 print(f" {op_type:20s}: {count:4d} ({pct:5.1f}%)") @@ -79,24 +80,24 @@ def analyze_feedback(): ) with_reason = sum(1 for f in all_feedback if hasattr(f, "reason") and f.reason) - print(f"\n📋 Data Quality:") + print("\n📋 Data Quality:") print(f" With pattern hash: {with_patterns:4d} ({with_patterns/len(all_feedback)*100:5.1f}%)") print(f" With reason: {with_reason:4d} ({with_reason/len(all_feedback)*100:5.1f}%)") # ML readiness quality_score = with_patterns / len(all_feedback) if all_feedback else 0 - print(f"\n🎯 ML Readiness:") + print("\n🎯 ML Readiness:") print(f" Quality Score: {quality_score:.2%}") if len(all_feedback) < 50: - print(f" Status: ❌ INSUFFICIENT DATA") + print(" Status: ❌ INSUFFICIENT DATA") print(f" Need: {50 - len(all_feedback)} more feedback records") elif quality_score < 0.7: - print(f" Status: ⚠️ LOW QUALITY") - print(f" Many records missing pattern hashes") + print(" Status: ⚠️ LOW QUALITY") + print(" Many records missing pattern hashes") else: - print(f" Status: ✅ READY FOR TRAINING") + print(" Status: ✅ READY FOR TRAINING") # Save detailed report report = { diff --git a/tests/test_backend_client.py b/tests/test_backend_client.py index fe0399e..c3b6fdd 100644 --- a/tests/test_backend_client.py +++ b/tests/test_backend_client.py @@ -3,7 +3,6 @@ from unittest.mock import MagicMock, patch import pytest -import requests from refactron.llm.backend_client import BackendLLMClient @@ -56,7 +55,7 @@ def test_backend_client_error_handling(mock_post, mock_credentials): client = BackendLLMClient() with pytest.raises( - RuntimeError, match="Backend LLM proxy error \(500\): Internal Server Error" + RuntimeError, match=r"Backend LLM proxy error \(500\): Internal Server Error" ): client.generate(prompt="Refactor this") diff --git a/tests/test_llm_orchestrator.py b/tests/test_llm_orchestrator.py index 27b1e88..cb95413 100644 --- a/tests/test_llm_orchestrator.py +++ b/tests/test_llm_orchestrator.py @@ -1,13 +1,12 @@ """Tests for LLM Orchestrator.""" -import json from pathlib import Path from unittest.mock import MagicMock, Mock import pytest from refactron.core.models import CodeIssue, IssueCategory, IssueLevel -from refactron.llm.models import RefactoringSuggestion, SuggestionStatus +from refactron.llm.models import SuggestionStatus from refactron.llm.orchestrator import LLMOrchestrator from refactron.rag.retriever import RetrievedContext diff --git a/tests/test_patterns_integration.py b/tests/test_patterns_integration.py index de724b4..e4126b2 100644 --- a/tests/test_patterns_integration.py +++ b/tests/test_patterns_integration.py @@ -259,8 +259,13 @@ def func2(): # Verify actual pattern isolation: patterns from project1 should not be in project2 # and vice versa (they use different storage directories) - pattern_hashes1 = {p.pattern_hash for p in patterns1.values()} - pattern_hashes2 = {p.pattern_hash for p in patterns2.values()} + patterns_set1 = {p.pattern_hash for p in patterns1.values()} + patterns_set2 = {p.pattern_hash for p in patterns2.values()} + + # In most cases, these sets should be disjoint if patterns were learned + if patterns_set1 and patterns_set2: + # Basic check that isolation works at directory level (already checked above) + pass # Verify the key property: storage directories are different (isolation works) # Note: Anonymized fingerprinting may make structurally similar code have same hash diff --git a/tests/test_rag_indexer.py b/tests/test_rag_indexer.py index 7305fe6..d9820ff 100644 --- a/tests/test_rag_indexer.py +++ b/tests/test_rag_indexer.py @@ -1,9 +1,8 @@ """Tests for the RAG indexer module.""" -import sys import tempfile from pathlib import Path -from unittest.mock import MagicMock, Mock, create_autospec, patch +from unittest.mock import MagicMock, Mock, patch import pytest From 930bd31ee1d05ab78dd1966a0def2a1a46d9f254 Mon Sep 17 00:00:00 2001 From: Om Sherikar Date: Wed, 11 Mar 2026 19:06:44 +0530 Subject: [PATCH 5/6] Update refactron/llm/orchestrator.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- refactron/llm/orchestrator.py | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/refactron/llm/orchestrator.py b/refactron/llm/orchestrator.py index aad5b9d..593723b 100644 --- a/refactron/llm/orchestrator.py +++ b/refactron/llm/orchestrator.py @@ -280,12 +280,27 @@ def evaluate_issues_batch(self, issues: List[CodeIssue], source_code: str) -> Di # 2. Construct JSON for issues issues_data = {} for i, issue in enumerate(issues): - # Determine a stable issue ID - issue_id = getattr(issue, "rule_id", None) - if not issue_id: - issue_id = f"issue_{i}" - - issues_data[issue_id] = { + # Determine a stable, unique issue ID + base_id = getattr(issue, "rule_id", None) or "issue" + + # Prefer to include line number when available for better stability + line_number = getattr(issue, "line_number", None) + id_parts = [str(base_id)] + if line_number is not None: + id_parts.append(str(line_number)) + # Always include the index as a final disambiguator + id_parts.append(str(i)) + issue_id = ":".join(id_parts) + + # Ensure uniqueness in case of unexpected collisions + unique_id = issue_id + suffix = 1 + while unique_id in issues_data: + suffix += 1 + unique_id = f"{issue_id}_{suffix}" + + issues_data[unique_id] = { + "rule_id": getattr(issue, "rule_id", None), "message": issue.message, "line": issue.line_number, "category": ( From 21ddaa447a06137a00dbf4afb707ed80ab79d063 Mon Sep 17 00:00:00 2001 From: omsherikar Date: Wed, 11 Mar 2026 20:08:53 +0530 Subject: [PATCH 6/6] feat: implement batched llm triage & rag context --- refactron/llm/orchestrator.py | 3 ++- refactron/llm/prompts.py | 19 ++++++++++++++++++- tests/test_llm_batch_triage.py | 6 +++--- 3 files changed, 23 insertions(+), 5 deletions(-) diff --git a/refactron/llm/orchestrator.py b/refactron/llm/orchestrator.py index 593723b..36ac47d 100644 --- a/refactron/llm/orchestrator.py +++ b/refactron/llm/orchestrator.py @@ -13,6 +13,7 @@ from refactron.llm.models import RefactoringSuggestion, SuggestionStatus from refactron.llm.prompts import ( BATCH_TRIAGE_PROMPT, + BATCH_TRIAGE_SYSTEM_PROMPT, DOCUMENTATION_PROMPT, SUGGESTION_PROMPT, SYSTEM_PROMPT, @@ -323,7 +324,7 @@ def evaluate_issues_batch(self, issues: List[CodeIssue], source_code: str) -> Di # 4. Call LLM try: response_text = self.client.generate( - prompt=prompt, system=SYSTEM_PROMPT, temperature=0.1 + prompt=prompt, system=BATCH_TRIAGE_SYSTEM_PROMPT, temperature=0.1 ) clean_text = self._clean_json_response(response_text) data = json.loads(clean_text, strict=False) diff --git a/refactron/llm/prompts.py b/refactron/llm/prompts.py index 5ab90c8..20c5603 100644 --- a/refactron/llm/prompts.py +++ b/refactron/llm/prompts.py @@ -93,13 +93,30 @@ @@@END@@@ """ +BATCH_TRIAGE_SYSTEM_PROMPT = """\ +You are an expert software architect and code refactoring specialist. +Your goal is to evaluate multiple code issues in a single file and determine their validity. + +RESPONSE FORMAT: +You must output ONLY valid JSON. +- Escape all double quotes inside strings with backslash (e.g. \\"). +- Do not use trailing commas. +- Do not output markdown code blocks, just the raw JSON object. + +Output JSON structure must be a simple dictionary mapping issue IDs to confidence scores: +{ + "issue_1": 0.85, + "issue_2": 0.1 +} +""" + BATCH_TRIAGE_PROMPT = """ You are a code triage expert. Evaluate the following list of code issues found in a single file and determine the confidence that each is a true positive (requiring fixing) rather than a false positive. File Source Code: -```python +``` {source_code} ``` diff --git a/tests/test_llm_batch_triage.py b/tests/test_llm_batch_triage.py index 1588dd8..9053bc7 100644 --- a/tests/test_llm_batch_triage.py +++ b/tests/test_llm_batch_triage.py @@ -108,8 +108,8 @@ def test_evaluate_issues_batch_fallback_on_error(mock_llm_client, mock_retriever result = orchestrator.evaluate_issues_batch(issues, "source") - # It should fallback to 0.5 confidence for 'issue_0' - assert result == {"issue_0": 0.5} + # It should fallback to 0.5 confidence for the generated issue ID + assert result == {"issue:20:0": 0.5} def test_evaluate_issues_batch_fallback_on_bad_json(mock_llm_client, mock_retriever): @@ -129,4 +129,4 @@ def test_evaluate_issues_batch_fallback_on_bad_json(mock_llm_client, mock_retrie result = orchestrator.evaluate_issues_batch(issues, "source") - assert result == {"issue_0": 0.5} + assert result == {"issue:20:0": 0.5}