Skip to content

Feat/pipeline config - #180

Open
shrutu0929 wants to merge 1 commit into
Refactron-ai:mainfrom
shrutu0929:feat/pipeline-config
Open

shrutu0929 wants to merge 1 commit into
Refactron-ai:mainfrom
shrutu0929:feat/pipeline-config

Conversation

@shrutu0929

@shrutu0929 shrutu0929 commented Apr 19, 2026

Copy link
Copy Markdown
Contributor

solve #174
This update resolves an inconsistency where RefactronPipeline.analyze was ignoring user and project configurations by generating a fresh, blank configuration instead of loading existing settings. To fix this, the RefactronPipeline class has been implemented to automatically detect the project root and load settings from the .refactron.yaml file, ensuring that automated pipeline and session-based runs use the exact same analyzer configurations as the standard CLI refactron analyze command. It also introduces the use_incremental parameter as an explicit pipeline-only override to guarantee reproducible CI/CD runs. Finally, full testing coverage has been added to verify that configurations are parsed and mapped accurately during a pipeline flow.

Summary by CodeRabbit

  • New Features

    • New ai_fix command for LLM-powered code fixes with backup support.
    • LLM-based triage to filter potential false-positive issues.
    • Fallback keyword search for RAG when vector indexing unavailable.
    • Windows terminal support for interactive CLI features.
  • Improvements

    • Incremental file analysis with content-hash caching.
    • Enhanced LLM response parsing and code quality.
    • Automatic RAG vector index building.

@coderabbitai

coderabbitai Bot commented Apr 19, 2026

Copy link
Copy Markdown
Contributor

Warning

Rate limit exceeded

@shrutu0929 has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 51 minutes and 47 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 51 minutes and 47 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 8832bde8-2d37-4734-ac13-b7399cd0d315

📥 Commits

Reviewing files that changed from the base of the PR and between a03d5f2 and a489d54.

📒 Files selected for processing (2)
  • refactron/core/pipeline.py
  • tests/test_pipeline.py
📝 Walkthrough

Walkthrough

This PR significantly expands refactron's capabilities by introducing LLM-based triage and batch-suggestion features, implementing incremental symbol-table analysis with file-change detection, adding keyword-mode fallback for RAG indexing, launching a new RefactronPipeline orchestration class, and enabling new CLI commands for AI-powered code fixing and platform-aware interactions.

Changes

Cohort / File(s) Summary
New Pipeline & Config
refactron/core/pipeline.py, refactron/core/config.py
Introduces RefactronPipeline class for orchestrated analysis with project-root and config-path detection; adds enable_llm_triage boolean flag to RefactronConfig.
LLM Orchestration & Token Defaults
refactron/llm/orchestrator.py, refactron/llm/prompts.py, refactron/llm/client.py, refactron/llm/backend_client.py
Extends LLMOrchestrator to accept workspace_path, auto-initialize ContextRetriever, build vector indexes, and generate chunk summaries; adds batch-suggestion and batch-triage capabilities with new prompt templates; increases max token defaults from 2000 to 4000.
RAG System & Fallback Modes
refactron/rag/indexer.py, refactron/rag/retriever.py
Replaces llm_client with llm_integration (LLMOrchestrator); implements keyword-mode fallback when ChromaDB/embeddings unavailable; adds JSON-based chunk storage and keyword-overlap retrieval.
Incremental Symbol Analysis
refactron/analysis/symbol_table.py
Implements path normalization, per-file metadata tracking (mtime, size, SHA-256), file-change detection via size/hash, cache persistence, and incremental table updates that skip re-analysis for unchanged files.
Performance & Taint Optimization
refactron/analysis/taint.py, refactron/core/inference.py, refactron/core/parallel.py
Adds statement-level pre-indexing and per-statement memoization in taint analysis; enforces cache invalidation in InferenceEngine.parse_file; extends parallel processor to track analysis skip warnings (3-tuple returns).
LLM Triage Integration
refactron/core/refactron.py, refactron/core/workspace.py
Conditionally applies LLM-based triage to filter low-confidence issues; integrates skip warnings from parallel processor; tolerates incomplete workspace JSON data.
CLI Command & UI Enhancements
refactron/cli/main.py, refactron/cli/refactor.py, refactron/cli/analysis.py, refactron/cli/rag.py, refactron/cli/ui.py, refactron/cli/utils.py
Registers ai_fix CLI subcommand with file-analysis and interactive-apply workflows; enables GROQ_API_KEY auth bypass; delegates context/RAG to LLMOrchestrator; implements platform-aware key reading (Windows/Unix); broadens exception handling in logging setup.
New Module
bad_code.py
Introduces module with constants (THRESHOLD_VALUE, MIN_X_VALUE, MAX_Y_VALUE, ITERATION_LIMIT, etc.) and function do_something_crazy() with conditional iteration logic.
Tests
tests/test_*.py
Updates ParallelProcessor call sites to handle 3-tuple returns; adds RefactronPipeline and incremental symbol-table tests; reworks RAG indexer/retriever tests to support keyword-mode fallback and mock LLMOrchestrator; adjusts GroqClient max-token assertion.

Sequence Diagram(s)

sequenceDiagram
    participant User as User/CLI
    participant AIFix as ai_fix Command
    participant Refactron as Refactron Instance
    participant LLMOrch as LLMOrchestrator
    participant RAGIdx as RAGIndexer/ContextRetriever
    participant Suggester as Suggestion Generator

    User->>AIFix: Execute ai_fix with target file
    AIFix->>Refactron: analyze(target_file)
    Refactron->>Refactron: Parse file, detect issues
    Refactron-->>AIFix: Return issues for file
    AIFix->>AIFix: Filter to target file issues
    
    AIFix->>LLMOrch: Create LLMOrchestrator(workspace_path)
    LLMOrch->>RAGIdx: _ensure_retriever() - load or build index
    RAGIdx-->>LLMOrch: ContextRetriever ready
    
    AIFix->>LLMOrch: generate_batch_suggestion(issues, code)
    LLMOrch->>RAGIdx: retrieve_similar(issue context)
    RAGIdx-->>LLMOrch: Top 3 relevant chunks
    LLMOrch->>Suggester: Build batch prompt + context
    Suggester-->>LLMOrch: LLM response (JSON)
    LLMOrch->>LLMOrch: Parse & validate proposed_code
    LLMOrch-->>AIFix: RefactoringSuggestion
    
    AIFix->>AIFix: Display suggestion, confidence
    alt Interactive Mode
        User->>AIFix: Approve changes
        AIFix->>AIFix: Create backup session
        AIFix->>AIFix: Overwrite file with proposed_code
        AIFix-->>User: Completion message
    else No Apply
        AIFix-->>User: Suggestion shown (not applied)
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related issues

Possibly related PRs

Suggested labels

enhancement, performance, refactoring, testing, size: x-large

Suggested reviewers

  • omsherikar

Poem

🐰 A fuzzy fix for code so bad,
With batches sorted, now less sad,
Cache invalidation, files reborn,
RAG has keywords—no ChromaDB forlorn!
The pipeline hops and CLI soars,
AI wisdom opens refactoring doors! ✨

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 69.37% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title 'Feat/pipeline config' is vague and uses generic/non-descriptive terms that don't clearly convey the specific change being made. Use a more descriptive title like 'Refactor RefactronPipeline to auto-detect project root and load .refactron.yaml config' or 'Add config loading to RefactronPipeline.analyze()' to better communicate the core change.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@shrutu0929
shrutu0929 force-pushed the feat/pipeline-config branch from a03d5f2 to a489d54 Compare April 19, 2026 16:02

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 13

Note

Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (6)
refactron/cli/analysis.py (2)

31-31: ⚠️ Potential issue | 🔴 Critical

Remove unused ContextRetriever import (pipeline failure).

Pre-commit flake8 is failing with F401. ContextRetriever is no longer referenced in this module after the refactor to LLMOrchestrator.

🔧 Proposed fix
-from refactron.rag.retriever import ContextRetriever
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@refactron/cli/analysis.py` at line 31, Remove the unused import
ContextRetriever from refactron.cli.analysis to fix the F401 flake8 error: open
the module where ContextRetriever is imported (the top-level import line "from
refactron.rag.retriever import ContextRetriever") and delete that import; verify
only LLMOrchestrator and other needed symbols remain imported and run
pre-commit/flake8 to confirm the pipeline passes.

211-228: ⚠️ Potential issue | 🟠 Major

JSON output ignores --fail-on threshold.

In JSON mode the exit code is hard-coded to 1 if summary["critical"] > 0 else 0, but the text-mode path (Lines 262–276) correctly honors --fail-on. CI/CD users passing --format json --fail-on ERROR will silently get a 0 exit on ERROR-level issues.

Consider computing should_fail once before the JSON branch and reusing it here too.

🔧 Proposed fix
-    # JSON format — output raw JSON and exit immediately
-    if output_format == "json":
-        import json as _json
-
-        issues_data = [
-            ...
-        ]
-        payload = {**summary, "issues": issues_data}
-        click.echo(_json.dumps(payload, indent=2))
-        raise SystemExit(1 if summary["critical"] > 0 else 0)
+    # Compute fail-on threshold (shared by JSON and text paths).
+    _LEVEL_RANK = {"INFO": 0, "WARNING": 1, "ERROR": 2, "CRITICAL": 3}
+    _SUMMARY_KEY = {"INFO": "info", "WARNING": "warnings", "ERROR": "errors", "CRITICAL": "critical"}
+    effective_fail_on = (fail_on or "CRITICAL").upper()
+    threshold = _LEVEL_RANK[effective_fail_on]
+    should_fail = any(
+        summary[_SUMMARY_KEY[lvl]] > 0 for lvl, rank in _LEVEL_RANK.items() if rank >= threshold
+    )
+
+    if output_format == "json":
+        import json as _json
+        issues_data = [ ... ]
+        payload = {**summary, "issues": issues_data}
+        click.echo(_json.dumps(payload, indent=2))
+        raise SystemExit(1 if should_fail else 0)

Then drop the duplicate block at Lines 262–276.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@refactron/cli/analysis.py` around lines 211 - 228, The JSON branch currently
decides exit code using summary["critical"] but ignores the --fail-on threshold;
compute a single should_fail boolean before the JSON/text branching (derived
from the parsed --fail-on setting and the summary counts) and use that
should_fail to set the exit status in the JSON block (replace the hard-coded
summary["critical"] check), then remove the duplicate exit decision in the later
text-mode block so both formats share the same failure logic; look for
variables/functions named output_format, summary, result.all_issues and the
later text-mode exit logic to update and consolidate.
tests/test_rag_indexer.py (2)

9-9: ⚠️ Potential issue | 🔴 Critical

Remove unused pytest import (pipeline failure).

Pre-commit flake8 is failing with F401. No pytest.raises/fixture usage remains in this file after the Chroma-available refactor.

🔧 Proposed fix
-import pytest
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/test_rag_indexer.py` at line 9, Remove the unused pytest import from
the top of the test file: delete the "import pytest" statement in
tests/test_rag_indexer.py (the unused symbol is the pytest import) so flake8
F401 no longer fails; no other code changes are required since no pytest.raises
or fixtures are used.

287-310: ⚠️ Potential issue | 🟡 Minor

Inconsistent attribute: llm_client vs llm_integration.

Other tests in this file were migrated to indexer.llm_integration and mock_llm.generate_chunk_summary (matching the production rename). test_index_file_with_summarize still sets indexer.llm_client = mock_llm and mocks generate, which no longer wires into the summarization path. The assertion on Line 310 ("Summary:" in mock_chunk.content or chunks is not None) is also a tautology — chunks is always truthy — so it silently passes even if summarization never ran.

🔧 Proposed fix
-        mock_llm = MagicMock()
-        mock_llm.generate.return_value = "Says hello."
-        indexer.llm_client = mock_llm
+        mock_llm = MagicMock()
+        mock_llm.generate_chunk_summary.return_value = "Says hello."
+        indexer.llm_integration = mock_llm
         ...
-        assert "Summary:" in mock_chunk.content or chunks is not None
+        assert len(chunks) == 1
+        mock_llm.generate_chunk_summary.assert_called()
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/test_rag_indexer.py` around lines 287 - 310, The test uses the old
attribute llm_client and generate; update it to use the production names: set
indexer.llm_integration = mock_llm and
mock_llm.generate_chunk_summary.return_value = "Says hello." so
_index_file(summarize=True) actually calls the mocked summarizer (targeting
indexer._index_file and generate_chunk_summary). Replace the tautological
assertion with a real check, e.g. assert "Summary:" in chunks[0].content or
assert mock_llm.generate_chunk_summary.assert_called_with(mock_chunk) to verify
summarization ran.
refactron/cli/rag.py (1)

143-168: ⚠️ Potential issue | 🟠 Major

Hoist LLMOrchestrator out of the result loop.

When --rerank is set, a fresh LLMOrchestrator(workspace_path=local_path) is built for every result (each call re-loads the retriever/vector index). Construct it once before the loop.

🔧 Proposed fix
         console.print(f"\n[primary]Found {len(results)} results for:[/primary] {query}\n")
 
+        rerank_orchestrator = LLMOrchestrator(workspace_path=local_path) if rerank else None
+
         for i, result in enumerate(results, 1):
             relevance_score = max(0, 1 - result.distance) * 100
 
             # AI Reranking if enabled
             if rerank:
                 try:
-                    orchestrator = LLMOrchestrator(workspace_path=local_path)
-                    prompt = (
+                    prompt = (
                         ...
                     )
-                    ai_response = orchestrator.client.generate(
+                    ai_response = rerank_orchestrator.client.generate(
                         prompt=prompt,
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@refactron/cli/rag.py` around lines 143 - 168, The code constructs a new
LLMOrchestrator inside the results loop when rerank is true, causing repeated
expensive reloads; move creation of LLMOrchestrator(workspace_path=local_path)
outside and before the for i, result in enumerate(results, 1): loop and reuse
that single instance (e.g., create orchestrator = LLMOrchestrator(...) only once
when rerank is set), keeping the existing try/except and ai_response logic
unchanged so you still fallback to the distance-based score on errors.
refactron/cli/refactor.py (1)

33-33: ⚠️ Potential issue | 🟡 Minor

Remove unused ContextRetriever import (flake8 F401).

The document() change removed the only use of ContextRetriever, and ai_fix doesn't reference it either.

🧹 Proposed fix
-from refactron.rag.retriever import ContextRetriever
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@refactron/cli/refactor.py` at line 33, Remove the unused import
ContextRetriever from refactron.cli.refactor (it's no longer used after the
document() change and ai_fix doesn't reference it); update the import line that
currently reads "from refactron.rag.retriever import ContextRetriever" by
deleting ContextRetriever (or removing the entire import statement if nothing
else is imported) so flake8 F401 is resolved.
🟡 Minor comments (14)
bad_code.py-5-6 (1)

5-6: ⚠️ Potential issue | 🟡 Minor

Inverted and unused min/max constants.

MIN_ITERATION_VALUE = 10 is greater than MAX_ITERATION_VALUE = 5 — the names and values are inconsistent. Additionally, neither constant is referenced anywhere in this module. Remove them, or fix the values and wire them into the loop bounds if they were intended to replace ITERATION_LIMIT.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@bad_code.py` around lines 5 - 6, MIN_ITERATION_VALUE and MAX_ITERATION_VALUE
are inverted and unused; either remove them or make them meaningful by fixing
their values and using them to control loop bounds. If intended to replace
ITERATION_LIMIT, set MIN_ITERATION_VALUE < MAX_ITERATION_VALUE (e.g.,
MIN_ITERATION_VALUE = 1, MAX_ITERATION_VALUE = 10), remove or update the old
ITERATION_LIMIT usage, and replace occurrences of ITERATION_LIMIT with logic
that validates and uses MIN_ITERATION_VALUE and MAX_ITERATION_VALUE (e.g., clamp
or iterate between them) inside the function(s) that perform iterations.
refactron/analysis/symbol_table.py-12-12 (1)

12-12: ⚠️ Potential issue | 🟡 Minor

Remove the unused Set import.

This is already failing pre-commit with flake8: F401 'typing.Set' imported but unused.

Proposed fix
-from typing import Any, Dict, List, Optional, Set
+from typing import Any, Dict, List, Optional
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@refactron/analysis/symbol_table.py` at line 12, The import line "from typing
import Any, Dict, List, Optional, Set" in symbol_table.py includes an unused
symbol Set which triggers flake8 F401; remove Set from the typing import so it
reads "from typing import Any, Dict, List, Optional" to satisfy the linter and
keep only used typing names.
refactron/core/pipeline.py-29-39 (1)

29-39: ⚠️ Potential issue | 🟡 Minor

flake8 E501 in the docstring.

Pre-commit flags lines 35 and 39 (107 and 109 chars). Reflow the docstring paragraphs to fit in 100 cols.

As per coding guidelines: "Flake8 linting must use max-line-length of 100".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@refactron/core/pipeline.py` around lines 29 - 39, The docstring contains
lines longer than the 100-char Flake8 limit; reflow the long paragraphs in the
function docstring so every line is <=100 chars (wrap the sentences describing
enable_incremental_analysis, use_incremental, Args, and Returns), keeping the
same wording and references to enable_incremental_analysis, use_incremental,
target, AnalysisResult, and target_path intact so the docstring remains accurate
and lint-clean.
refactron/core/pipeline.py-13-13 (1)

13-13: ⚠️ Potential issue | 🟡 Minor

flake8 E501 on the constructor signature.

Line 13 is 107 chars. Break it across lines.

🧹 Proposed fix
-    def __init__(self, project_root: Optional[Union[str, Path]] = None, config_path: Optional[Union[str, Path]] = None):
+    def __init__(
+        self,
+        project_root: Optional[Union[str, Path]] = None,
+        config_path: Optional[Union[str, Path]] = None,
+    ):

As per coding guidelines: "Use line length of 100 characters, enforced by black, isort, and flake8".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@refactron/core/pipeline.py` at line 13, The __init__ constructor signature in
class Pipeline is longer than the 100-char limit, triggering flake8 E501; split
the parameters across multiple lines (for example put each optional parameter on
its own line or group them so the line length stays under 100) in the def
__init__(...) declaration in refactron/core/pipeline.py, preserving the existing
type hints Optional[Union[str, Path]] for project_root and config_path and
keeping the same parameter order and default values so behavior of
Pipeline.__init__ is unchanged.
tests/test_symbol_table_incremental.py-1-5 (1)

1-5: ⚠️ Potential issue | 🟡 Minor

Drop unused imports flagged by flake8 F401.

Pre-commit fails on Path (line 3) and SymbolType (line 5); neither is referenced in the test bodies.

🧹 Proposed fix
 import json
 import time
-from pathlib import Path
-from refactron.analysis.symbol_table import SymbolTableBuilder, SymbolType
+from refactron.analysis.symbol_table import SymbolTableBuilder
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/test_symbol_table_incremental.py` around lines 1 - 5, Remove the unused
imports causing flake8 F401: drop Path from the "from pathlib import Path"
import and drop SymbolType from "from refactron.analysis.symbol_table import
SymbolTableBuilder, SymbolType", leaving only the used SymbolTableBuilder import
(or alternatively use the symbols in tests if they were intended). Ensure the
import line now only includes SymbolTableBuilder and remove the standalone Path
import.
refactron/core/refactron.py-349-353 (1)

349-353: ⚠️ Potential issue | 🟡 Minor

flake8 E501 at line 351.

Pre-commit reports line 351 is 102 chars. Wrap the recovery_suggestion string (or hoist it into a constant).

🧹 Proposed fix
-                            recovery_suggestion="Check the file for syntax errors or encoding issues",
+                            recovery_suggestion=(
+                                "Check the file for syntax errors or encoding issues"
+                            ),

As per coding guidelines: "Use line length of 100 characters, enforced by black, isort, and flake8".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@refactron/core/refactron.py` around lines 349 - 353, The long string assigned
to the recovery_suggestion in the error construction (the recovery_suggestion
argument passed when building the error object in refactron/core/refactron.py)
exceeds the 100-char line length and triggers flake8 E501; fix by either
wrapping the string literal across multiple concatenated strings or moving it
into a named constant (e.g., RECOVERY_SUGGESTION_SYNTAX_ERROR) and then using
that constant in the recovery_suggestion= parameter so the line length stays
under 100 chars while preserving the same message.
refactron/rag/indexer.py-262-282 (1)

262-282: ⚠️ Potential issue | 🟡 Minor

mypy: indexed assignment into chunk_dict["metadata"] is rejected.

Pre-commit mypy fails at line 276 because the nested metadata dict's value type is inferred as Collection[str] from the str-literal values in the initializer, so chunk_dict["metadata"][key] = value isn't type-safe. Build metadata as an explicitly-typed local dict first and assemble chunk_dict afterwards.

🧹 Proposed fix
             for chunk in chunks:
-                chunk_dict = {
-                    "content": chunk.content,
-                    "metadata": {
-                        "chunk_type": chunk.chunk_type,
-                        "file_path": chunk.file_path,
-                        "name": chunk.name,
-                        "line_start": chunk.line_range[0],
-                        "line_end": chunk.line_range[1],
-                    },
-                }
-                # Add extra metadata
-                for key, value in chunk.metadata.items():
-                    if value is not None:
-                        chunk_dict["metadata"][key] = value
+                metadata: Dict[str, Any] = {
+                    "chunk_type": chunk.chunk_type,
+                    "file_path": chunk.file_path,
+                    "name": chunk.name,
+                    "line_start": chunk.line_range[0],
+                    "line_end": chunk.line_range[1],
+                }
+                for key, value in chunk.metadata.items():
+                    if value is not None:
+                        metadata[key] = value
+                chunk_dict: Dict[str, Any] = {"content": chunk.content, "metadata": metadata}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@refactron/rag/indexer.py` around lines 262 - 282, The mypy error comes from
assigning into chunk_dict["metadata"] whose type was inferred too narrowly;
instead construct a local typed metadata dict (e.g., metadata: Dict[str, Any])
by copying the fixed fields (chunk_type, file_path, name, line_start, line_end)
and then merging chunk.metadata entries (skipping None), and only after that
build chunk_dict = {"content": chunk.content, "metadata": metadata}; update the
loop over chunks to use these symbols (chunk, chunk.metadata, chunk_dict) and
leave the JSON dump to self.chunk_storage as-is.
refactron/rag/retriever.py-86-96 (1)

86-96: ⚠️ Potential issue | 🟡 Minor

raise RuntimeError(...) inside except should chain (B904) and line 95 is over 100 chars.

Both issues in one block: ruff flags missing from (so the original get_collection exception is suppressed in tracebacks) and flake8 reports E501 on line 95.

🧹 Proposed fix
                 try:
                     self.collection = self.client.get_collection(name=collection_name)
-                except Exception:
+                except Exception as exc:
                     # If vector collection is missing but folder exists,
                     # we might have indexed in keyword mode before
                     if (self.index_path / "keyword_chunks.json").exists():
                         self.mode = "keyword"
                     else:
                         raise RuntimeError(
-                            f"Index not found at {self.index_path}. Run 'refactron rag index' first."
-                        )
+                            f"Index not found at {self.index_path}. "
+                            "Run 'refactron rag index' first."
+                        ) from exc

As per coding guidelines: "Flake8 linting must use max-line-length of 100".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@refactron/rag/retriever.py` around lines 86 - 96, The except block handling
self.client.get_collection should re-raise the RuntimeError while chaining the
original exception (e.g., use "from e") so the original traceback isn't lost;
also shorten the RuntimeError message to <=100 characters (or split/format) to
satisfy max-line-length; locate the block that sets self.mode to "keyword" when
(self.index_path / "keyword_chunks.json").exists() and replace the bare raise
RuntimeError(f"Index not found at {self.index_path}. Run 'refactron rag index'
first.") with a chained, shorter RuntimeError that includes context and uses
"from" to chain the caught exception.
refactron/llm/prompts.py-60-93 (1)

60-93: ⚠️ Potential issue | 🟡 Minor

Fix flake8 E501 violations flagged by pre-commit.

Lines 60, 61, 75, and 93 exceed the 100-char limit. Since these are inside triple-quoted string constants, wrap using implicit string concatenation or break the lines manually; the content is instructional text so whitespace can be adjusted freely.

🧹 Proposed fix (illustrative)
-BATCH_SUGGESTION_SYSTEM_PROMPT = """You are an expert software architect and code refactoring specialist.
-Your goal is to analyze multiple code issues in a file and provide a single, comprehensive fix that resolves all of them.
+BATCH_SUGGESTION_SYSTEM_PROMPT = """\
+You are an expert software architect and code refactoring specialist.
+Your goal is to analyze multiple code issues in a file and provide a
+single, comprehensive fix that resolves all of them.
...
-    "confidence_score": "Float between 0.0 and 1.0 representing your confidence in this combined fix"
+    "confidence_score": "Float between 0.0 and 1.0 representing confidence in this combined fix"
...
-Provide a single, comprehensive fix that resolves ALL the listed issues while maintaining consistency with the codebase.
+Provide a single, comprehensive fix that resolves ALL the listed issues while maintaining
+consistency with the codebase.

As per coding guidelines: "Use line length of 100 characters, enforced by black, isort, and flake8".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@refactron/llm/prompts.py` around lines 60 - 93, The triple-quoted prompt
constants BATCH_SUGGESTION_SYSTEM_PROMPT and BATCH_SUGGESTION_PROMPT contain
lines exceeding the 100-char flake8 E501 limit; fix by splitting long lines
inside those string literals using implicit concatenation or breaking them into
multiple short string literals joined by adjacency (preserving content,
backslashes and escaped newlines) so the effective runtime string is unchanged;
update the long instructional lines (the ones around the start of
BATCH_SUGGESTION_SYSTEM_PROMPT and the long "Output JSON structure:" block, plus
the long lines in BATCH_SUGGESTION_PROMPT where original_code and rag_context
are presented) to be under 100 characters each while keeping the wording and
required escape instructions intact.
refactron/cli/refactor.py-620-689 (1)

620-689: ⚠️ Potential issue | 🟡 Minor

Multiple flake8 E501 violations in the new ai_fix command.

Pre-commit reports lines 622 (107), 660 (107), 683 (117), and 688 (111) over the 100-char limit. Wrap the console strings.

🧹 Proposed fix
-        console.print(
-            "[red]Error: Please specify a single file. Directory analysis for ai-fix is coming soon.[/red]"
-        )
+        console.print(
+            "[red]Error: Please specify a single file. "
+            "Directory analysis for ai-fix is coming soon.[/red]"
+        )
...
-        console.print(
-            f"  {idx}. [yellow]{issue.category.value}[/yellow]: {issue.message} (Line {issue.line_number})"
-        )
+        console.print(
+            f"  {idx}. [yellow]{issue.category.value}[/yellow]: "
+            f"{issue.message} (Line {issue.line_number})"
+        )
...
-    console.print(
-        f"[dim]AI Confidence: {suggestion.llm_confidence:.2f}, Safety Score: {suggestion.confidence_score:.2f}[/dim]"
-    )
+    console.print(
+        f"[dim]AI Confidence: {suggestion.llm_confidence:.2f}, "
+        f"Safety Score: {suggestion.confidence_score:.2f}[/dim]"
+    )
...
-        console.print(
-            f"[red]Warning: Fix failed basic safety checks: {', '.join(suggestion.safety_result.issues)}[/red]"
-        )
+        console.print(
+            "[red]Warning: Fix failed basic safety checks: "
+            f"{', '.join(suggestion.safety_result.issues)}[/red]"
+        )

As per coding guidelines: "Use line length of 100 characters, enforced by black, isort, and flake8".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@refactron/cli/refactor.py` around lines 620 - 689, Several console string
literals exceed the 100-char line length (flake8 E501); shorten/wrap the long
strings passed to console.print and the Panel/Markdown calls so no line exceeds
100 chars. Edit the long calls around the ai-fix flow — specifically the
console.print that logs the "Error: Please specify a single file..." message
(target_path check), the multiple console.prints listing issues (uses
issue.category.value/issue.message), the console.status messages, and the
Panel/console.print calls that show suggestion.explanation and
suggestion.proposed_code (the Panel(... title=...) and style=... lines) — split
long f-strings or use implicit string concatenation or shorter variables so each
source line stays <=100 chars while preserving the exact output content and
references to target_path, issues_to_fix,
orchestrator.generate_batch_suggestion, SuggestionStatus,
suggestion.explanation, and suggestion.proposed_code.
refactron/rag/indexer.py-10-24 (1)

10-24: ⚠️ Potential issue | 🟡 Minor

flake8 E402: module-level imports must come before other top-level code.

Lines 23–24 (from refactron.rag.chunker import CodeChunk and from refactron.rag.parser import CodeParser) sit after the runtime try/except block (lines 14–17) and the CHROMA_AVAILABLE = None assignment (line 21), causing the pre-commit failure. Move the chunker/parser imports up with the other stdlib/local imports, and keep only the lazy-import try/except sentinel below them.

🧹 Proposed reordering
 from typing import TYPE_CHECKING, Any, Dict, List, Optional, cast

+from refactron.rag.chunker import CodeChunk
+from refactron.rag.parser import CodeParser
+
 if TYPE_CHECKING:
     from refactron.llm.orchestrator import LLMOrchestrator

 # LLMOrchestrator is imported lazily at runtime to keep it patchable in tests
 try:
     from refactron.llm.orchestrator import LLMOrchestrator as _LLMOrchestrator  # noqa: F401
 except Exception:
     _LLMOrchestrator = None  # type: ignore

 # RAG dependencies are loaded lazily in __init__ to prevent CLI crashes
 # if libraries like PyTorch fail to initialize (common on some Windows environments).
 CHROMA_AVAILABLE = None

-from refactron.rag.chunker import CodeChunk
-from refactron.rag.parser import CodeParser
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@refactron/rag/indexer.py` around lines 10 - 24, Move the module-level imports
for CodeChunk and CodeParser above any top-level runtime code so flake8 E402 is
satisfied: relocate "from refactron.rag.chunker import CodeChunk" and "from
refactron.rag.parser import CodeParser" to join the other imports at the top,
then keep the lazy-import try/except that defines _LLMOrchestrator and the
CHROMA_AVAILABLE = None sentinel below them; ensure the try/except still assigns
_LLMOrchestrator = None on exception and that CHROMA_AVAILABLE remains defined
after the imports.
refactron/llm/orchestrator.py-80-102 (1)

80-102: ⚠️ Potential issue | 🟡 Minor

build_vector_index doesn't refresh self.workspace_path, leading to stale retriever state.

If a caller invokes build_vector_index(some_other_path) on an orchestrator whose self.workspace_path points elsewhere, the local workspace_path is used to build the index and to create the retriever inside this method (line 99), but a subsequent _ensure_retriever() call would retry against the original self.workspace_path. Either update self.workspace_path = workspace_path here, or document that this method is intended to be called only with the orchestrator's own workspace.

Also note that the except Exception at line 101 silently swallows index build failures, leaving the caller (e.g. refactron/cli/rag.py) unable to distinguish "indexed successfully" from "silently failed". Consider re-raising or returning a bool/result.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@refactron/llm/orchestrator.py` around lines 80 - 102, build_vector_index
currently uses the passed workspace_path to build the index and recreate the
retriever but does not update the orchestrator state, causing later calls to
_ensure_retriever to operate against the old self.workspace_path; update
self.workspace_path = workspace_path at the start (or after successful indexing)
so the orchestrator state matches the retriever created (references:
build_vector_index, self.workspace_path, _ensure_retriever, ContextRetriever,
RAGIndexer), and change the broad except Exception that swallows failures to
either re-raise the exception or return a success boolean (e.g., return True on
success and False or raise on failure) so callers can detect index build
failures instead of silently proceeding.
refactron/llm/orchestrator.py-347-348 (1)

347-348: ⚠️ Potential issue | 🟡 Minor

Confidence parsing is inconsistent with generate_suggestion and can needlessly fail the batch.

generate_suggestion (lines 183–196) tolerates strings like "0.9", "90%", or ranges. Here a plain float(data.get("confidence_score", 0.7)) is used twice — any non-numeric response raises ValueError, which is caught by the outer except at line 351 and downgrades a fully valid LLM suggestion to FAILED. Since BATCH_SUGGESTION_SYSTEM_PROMPT asks the model for a "Float between 0.0 and 1.0 representing your confidence" as a string, this is a likely code path.

Consider extracting the existing confidence-parsing block into a helper (e.g., _parse_confidence) and reusing it in both methods. Same comment applies to clamping — generate_suggestion clamps with min(max(confidence, 0.0), 1.0) while this one does not.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@refactron/llm/orchestrator.py` around lines 347 - 348, The confidence parsing
here is brittle: replace the direct float(...) calls in the block that sets
confidence_score and llm_confidence with a reusable helper (e.g.,
_parse_confidence) that mirrors generate_suggestion's logic — accept numeric
types and strings like "0.9", "90%", and ranges, normalize to a single float,
clamp with min(max(value, 0.0), 1.0), and return a default when parsing fails;
implement _parse_confidence (or extract the existing parsing from
generate_suggestion) and call it for both confidence_score and llm_confidence
(using the same parsed value) instead of float(data.get("confidence_score",
0.7)) so non-numeric but valid string responses won't cause ValueError and
incorrect FAILED results.
refactron/core/parallel.py-74-75 (1)

74-75: ⚠️ Potential issue | 🟡 Minor

Fix flake8 E501: docstring line exceeds 100 chars.

Line 75 is 111 characters, failing the pre-commit lint check (max-line-length=100). As per coding guidelines ("Use line length of 100 characters, enforced by black, isort, and flake8"), wrap the docstring.

🛠️ Proposed fix
-            process_func: Function to process a single file. Should return
-                         (FileMetrics, None, skip_warn) on success or (None, FileAnalysisError, None) on error.
+            process_func: Function to process a single file. Should return
+                         (FileMetrics, None, skip_warn) on success or
+                         (None, FileAnalysisError, None) on error.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@refactron/core/parallel.py` around lines 74 - 75, The docstring line
describing the process_func parameter exceeds 100 characters; update the
docstring in refactron/core/parallel.py so the line wrapping keeps each line
<=100 chars (e.g., split the sentence into two lines) while preserving the
meaning and types (process_func, FileMetrics, FileAnalysisError, skip_warn).
Ensure you break the long line into shorter lines at sensible boundaries (after
commas or between clauses) so flake8 E501 is satisfied.
🧹 Nitpick comments (13)
bad_code.py (1)

14-17: Minor cleanups: collapse nested if and rename unused loop variable.

Per Ruff B007, i is unused in the loop body. The two nested ifs can also be combined for readability.

Proposed fix
-    if x > THRESHOLD_VALUE:
-        if y < MAX_Y_VALUE:
-            for i in range(ITERATION_LIMIT):
-                print("doing something", x)
+    if x > THRESHOLD_VALUE and y < MAX_Y_VALUE:
+        for _ in range(ITERATION_LIMIT):
+            print("doing something", x)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@bad_code.py` around lines 14 - 17, Collapse the two nested conditionals into
a single combined check (replace the nested if x > THRESHOLD_VALUE: if y <
MAX_Y_VALUE: with a single if x > THRESHOLD_VALUE and y < MAX_Y_VALUE:) and
rename the unused loop variable i in the for loop over range(ITERATION_LIMIT) to
an underscore (for _ in range(ITERATION_LIMIT)) to satisfy Ruff B007 and improve
readability; update the block containing x, THRESHOLD_VALUE, y, MAX_Y_VALUE, and
ITERATION_LIMIT accordingly.
refactron/analysis/taint.py (2)

125-125: Duplicate/misplaced comment.

# 2. Iterative Taint Propagation duplicates the same heading already present at Line 101 directly above this loop. Looks like a stray artifact — consider removing to avoid misleading readers into thinking a new phase starts here.

🧹 Proposed cleanup
-                # 2. Iterative Taint Propagation
                 # Process block statements
                 current_taint = incoming_taint.copy()
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@refactron/analysis/taint.py` at line 125, Remove the stray duplicate comment
"# 2. Iterative Taint Propagation" found immediately above the iterative loop in
the taint propagation routine; keep the original heading already present earlier
in the same function in refactron.analysis.taint and simply delete this
redundant line so the code and comment flow remain clear and spacing is
preserved.

82-91: Minor: indexing stores ast.Attribute nodes that are never consumed.

_check_sink only branches on isinstance(node, ast.Call) (Line 262), so every ast.Attribute pushed into _statement_meta here is dead weight — it grows the per-statement list and the outer loop iterates over entries that can never match a sink. If attributes aren't needed for future source detection in this path, restrict indexing to ast.Call only; otherwise add a short comment documenting the intended future use.

♻️ Proposed refactor
-                for child in ast.walk(stmt):
-                    if isinstance(child, (ast.Call, ast.Attribute)):
-                        sensitive.append(child)
+                for child in ast.walk(stmt):
+                    if isinstance(child, ast.Call):
+                        sensitive.append(child)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@refactron/analysis/taint.py` around lines 82 - 91, _index_sensitive_nodes
currently adds both ast.Call and ast.Attribute nodes into self._statement_meta
but _check_sink only handles ast.Call, so remove ast.Attribute from the indexed
set (only collect ast.Call) or, if attributes are intended for future use, add a
short clarifying comment in _index_sensitive_nodes explaining why ast.Attribute
entries are stored; update the tuple in the isinstance check in
_index_sensitive_nodes to only include ast.Call (or add the comment) and ensure
references to _statement_meta and _check_sink remain consistent.
refactron/cli/main.py (1)

37-38: Move import os to module top.

PEP 8 / isort prefers module-level imports. This inline import is unnecessary.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@refactron/cli/main.py` around lines 37 - 38, The inline "import os" should be
moved to the top of the module with the other imports to satisfy PEP8/isort;
locate the "import os" occurrence in refactron.cli.main (currently an inline
import) and relocate it to the module-level import block, grouping it
appropriately with standard-library imports and re-running isort/flake8 to
confirm import order and style.
refactron/cli/rag.py (1)

71-84: Redundant RAGIndexer instantiation to fetch stats.

orchestrator.build_vector_index already constructs a RAGIndexer internally and calls index_repository, which (per refactron/rag/indexer.py) returns an IndexStats. The current code discards that and then re-instantiates RAGIndexer(local_path) to call get_stats() — duplicating setup (SentenceTransformer load, Chroma client init).

Consider having LLMOrchestrator.build_vector_index return the IndexStats from index_repository, then reuse it here.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@refactron/cli/rag.py` around lines 71 - 84,
LLMOrchestrator.build_vector_index currently discards the IndexStats produced by
RAGIndexer.index_repository, causing the CLI to re-instantiate RAGIndexer just
to call get_stats(); change build_vector_index
(LLMOrchestrator.build_vector_index) to return the IndexStats returned by
RAGIndexer.index_repository (or propagate it up) and then in
refactron/cli/rag.py use the returned stats directly instead of creating a new
RAGIndexer(local_path) and calling get_stats(), so you avoid reloading
SentenceTransformer and reinitializing the Chroma client.
tests/test_rag_indexer.py (2)

73-84: Rename test — it no longer asserts a raise.

The test now verifies keyword-mode fallback behavior; its name test_raises_without_chromadb is misleading and will confuse future readers.

🔧 Proposed fix
-    def test_raises_without_chromadb(self, tmp_path):
+    def test_falls_back_to_keyword_without_chromadb(self, tmp_path):
         """When ChromaDB is unavailable the indexer falls back to keyword mode (no exception)."""
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/test_rag_indexer.py` around lines 73 - 84, Rename the misleading test
function test_raises_without_chromadb to a name that reflects its behavior
(e.g., test_falls_back_to_keyword_mode_without_chromadb), update any references
to it, and ensure the test still forces _mod.CHROMA_AVAILABLE = False and
constructs refactron.rag.indexer.RAGIndexer(workspace_path=tmp_path) to assert
indexer.mode == "keyword"; keep the finally block resetting
_mod.CHROMA_AVAILABLE = None and do not change the test logic in RAGIndexer.

14-47: Global-state mutation in make_indexer.

The helper mutates refactron.rag.indexer module globals (CHROMA_AVAILABLE, SentenceTransformer, chromadb, Settings) and resets them in finally before the indexer is used by the test body. Because the reset happens in the helper's finally (executed as make_indexer returns), subsequent method calls on the returned indexer run against reset globals — that's fine for the cached instance, but risky if any code path re-reads the globals. Consider using monkeypatch / pytest.MonkeyPatch or a context manager to scope the overrides to each test for clearer isolation.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/test_rag_indexer.py` around lines 14 - 47, The helper make_indexer
mutates module globals on refactron.rag.indexer (CHROMA_AVAILABLE,
SentenceTransformer, chromadb, Settings) and restores them in finally, which can
lead to globals being reset while the returned RAGIndexer instance might still
re-read them; change make_indexer to accept pytest's monkeypatch (or use a
context manager) and replace direct assignments with
monkeypatch.setattr/_setitem on the refactron.rag.indexer module for
CHROMA_AVAILABLE, SentenceTransformer, chromadb, and Settings so the overrides
are scoped to the test and automatically reverted by pytest, keeping RAGIndexer
instantiation and subsequent method calls safe; reference RAGIndexer,
CHROMA_AVAILABLE, SentenceTransformer, chromadb, and Settings to locate the code
to change.
refactron/cli/analysis.py (1)

166-172: Dead locals() guard.

Both branches above already assign target_path (Line 152 from the interactive selector, Line 168 from _validate_path), so "target_path" not in locals() is always False. Safe to remove.

🧹 Proposed cleanup
-    else:
-        # Path explicitly provided, validate and use it
-        target_path = _validate_path(target)
-
-    # Setup (only if not already set by interactive selector)
-    if "target_path" not in locals():
-        target_path = _validate_path(target)
-    cfg = _load_config(config, profile, environment)
+    else:
+        # Path explicitly provided, validate and use it
+        target_path = _validate_path(target)
+
+    cfg = _load_config(config, profile, environment)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@refactron/cli/analysis.py` around lines 166 - 172, The locals() guard around
target_path is dead code because both branches already set target_path (the
interactive selector on earlier branch and the else branch calling
_validate_path), so remove the redundant "if 'target_path' not in locals():
target_path = _validate_path(target)" block; ensure only a single assignment
path remains and keep the callsite using target_path unchanged (referencing
target_path and _validate_path).
tests/test_config_management.py (1)

903-919: Prefix unused unpacked values with _ (ruff RUF059).

Ruff flags several unused unpacked variables across this file (Lines 909, 915, 1229, 1247, 1253, 1268, 1278). Prefixing with _ keeps intent clear and silences the lint.

Example for Line 909:

-    _, errors, skips = p_seq.process_files(files, process_func)
+    _, errors, _skips = p_seq.process_files(files, process_func)

Apply the same pattern to the other flagged lines.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/test_config_management.py` around lines 903 - 919, Several unpacked
return values are unused; rename them to indicate unused by prefixing with an
underscore. For the examples in this diff, change the first unpack in
p_seq.process_files to use a throwaway name (e.g., _ , errors, skips or
_results, errors, skips) and likewise in p_thr.process_files rename the unused
unpacked variables (results, errors, skips) to _results, _errors, _skips or
single underscores; apply the same pattern to the other flagged occurrences
(lines referenced) where functions like process_func return tuples but some
elements are unused so prefix those local/unpacked variable names with "_" to
satisfy RUF059.
tests/test_pipeline.py (1)

38-53: Strengthen the override test.

A few gaps worth closing:

  1. No .refactron.yaml is written, so this test also exercises the "no config file" path implicitly — consider adding an explicit test for that scenario so the behavior is unambiguous.
  2. No assertion that MockRefactron / instance.analyze was actually called, so the test would still pass if the pipeline short-circuited.
  3. The PR summary highlights use_incremental as the explicit override; consider adding a symmetric case for use_incremental=False overriding a YAML that enables it, to lock in both directions.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/test_pipeline.py` around lines 38 - 53, Update the tests to make
assertions explicit and add the symmetric override case: in
test_pipeline_incremental_override, ensure no .refactron.yaml exists (or
explicitly create one with a known setting) and after calling
RefactronPipeline(project_root=...) .analyze(target_file, use_incremental=True)
assert that MockRefactron was instantiated (MockRefactron.assert_called()) and
that the created instance had analyze invoked
(MockRefactron.return_value.analyze.assert_called_with(... or at least called));
also add a new test (e.g., test_pipeline_incremental_override_false) that writes
a .refactron.yaml enabling incremental analysis, then calls
pipeline.analyze(..., use_incremental=False) and asserts the config passed to
MockRefactron (args[0]) has enable_incremental_analysis is False to lock in both
override directions; reference RefactronPipeline, analyze, MockRefactron and the
.refactron.yaml configuration keys when implementing.
tests/test_symbol_table_incremental.py (1)

99-106: os.utime restoring st_mtime is filesystem-dependent and may silently defeat this test.

Line 99 assumes "x = 1" and "y = 2" produce identical file sizes (they do). But line 102 calls os.utime and lines 105–106 assert the mtime and size remained unchanged — on filesystems with low mtime granularity (e.g., FAT32) or those that report nanosecond precision inconsistently, this pair of assertions can flake even though the intent is to exercise a hash-based change detection. If the assertions pass, the test is meaningful; if they fail, contributors may disable the test rather than realize it's environment-sensitive. Consider documenting this explicitly (or using pytest.skip when size/mtime cannot be stabilized) so future debuggers don't chase a phantom issue.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/test_symbol_table_incremental.py` around lines 99 - 106, The test
currently writes to file1, calls os.utime(file1, (original_mtime,
original_mtime)) and then asserts file1.stat().st_mtime == original_mtime and
st_size == original_size; make this robust by detecting filesystems or platforms
where utime/granularity cannot be reliably restored and skipping or adjusting
the assertions: after os.utime, re-read st_mtime and st_size (using
file1.stat()) and if the mtime or size differs from original_mtime/original_size
within an allowed tolerance (or if utime had no effect), call pytest.skip with a
clear message about filesystem mtime granularity rather than failing the test,
or else proceed to assert only file size and then rely on the content-hash path
for change detection; reference file1, os.utime, and the two assertions to
locate where to add the skip/tolerance logic.
tests/test_rag_retriever.py (1)

14-41: Optional: fold test_retriever_missing_index setup into make_retriever.

make_retriever resets globals in finally immediately after constructing the retriever, so the live retriever still works (mocks are captured on self). That's fine, but test_retriever_missing_index has to duplicate the whole mock-globals dance because it needs to customize get_collection.side_effect before instantiation. Consider letting the helper accept overrides (e.g. collection_side_effect, skip_instantiation) so the duplicated block at lines 77–95 can reuse it.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/test_rag_retriever.py` around lines 14 - 41, Update make_retriever to
accept optional parameters (e.g., collection_side_effect=None,
skip_instantiation=False) so callers can customize
mock_chroma_client.get_collection behavior before creating the ContextRetriever;
inside make_retriever set mock_chroma_client.get_collection.side_effect =
collection_side_effect if provided, and if skip_instantiation is True return the
mocks (mock_collection, mock_model, mock_chroma_client) instead of instantiating
ContextRetriever, otherwise instantiate ContextRetriever as before, attach
r._mock_collection and r._mock_model, and keep the existing finally cleanup that
resets _mod.CHROMA_AVAILABLE and pops SentenceTransformer/chromadb/Settings;
reference make_retriever, ContextRetriever, mock_chroma_client, mock_collection,
mock_model, collection_side_effect, and skip_instantiation so
test_retriever_missing_index can call make_retriever(collection_side_effect=...,
skip_instantiation=False) or skip_instantiation=True to avoid duplicating the
globals setup.
refactron/llm/orchestrator.py (1)

198-217: Deduplicate the proposed_code sanitization between single and batch flows.

Lines 200–217 and 322–337 are essentially the same logic (strip ``` fences, unwrap {…} if `ast.parse` accepts the inner text). Extract into a private helper to avoid drift and keep both code paths in sync when the heuristic evolves.

♻️ Suggested shape
def _clean_proposed_code(self, proposed_code: str) -> str:
    if not proposed_code:
        return proposed_code
    if proposed_code.startswith("```"):
        lines = proposed_code.split("\n")
        if lines[0].startswith("```"):
            lines.pop(0)
        if lines and lines[-1].startswith("```"):
            lines.pop(-1)
        proposed_code = "\n".join(lines).strip()
    if proposed_code.startswith("{") and proposed_code.endswith("}"):
        potential_code = proposed_code[1:-1].strip()
        try:
            ast.parse(potential_code)
            proposed_code = potential_code
        except SyntaxError:
            pass
    return proposed_code
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@refactron/llm/orchestrator.py` around lines 198 - 217, The proposed_code
sanitization logic is duplicated in two places; extract it into a private helper
(e.g., _clean_proposed_code(self, proposed_code: str) -> str) and replace both
copies with calls to that helper; the helper should preserve current behavior:
return early for falsy input, strip leading/trailing ``` fences by splitting on
"\n" and popping fence lines, and if the result starts with "{" and ends with
"}" attempt ast.parse on the inner text and adopt it only if parsing succeeds
(catch SyntaxError), then return the cleaned string; update both code paths that
reference proposed_code and ast.parse to call _clean_proposed_code instead.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 7cf0b2b1-4578-403d-a6f9-76a87e8f656b

📥 Commits

Reviewing files that changed from the base of the PR and between a9659f5 and a03d5f2.

📒 Files selected for processing (29)
  • bad_code.py
  • refactron/analysis/symbol_table.py
  • refactron/analysis/taint.py
  • refactron/cli/analysis.py
  • refactron/cli/main.py
  • refactron/cli/rag.py
  • refactron/cli/refactor.py
  • refactron/cli/ui.py
  • refactron/cli/utils.py
  • refactron/core/config.py
  • refactron/core/inference.py
  • refactron/core/parallel.py
  • refactron/core/pipeline.py
  • refactron/core/refactron.py
  • refactron/core/workspace.py
  • refactron/llm/backend_client.py
  • refactron/llm/client.py
  • refactron/llm/orchestrator.py
  • refactron/llm/prompts.py
  • refactron/rag/indexer.py
  • refactron/rag/retriever.py
  • tests/test_cli_patterns_rag.py
  • tests/test_config_management.py
  • tests/test_groq_client.py
  • tests/test_performance_optimization.py
  • tests/test_pipeline.py
  • tests/test_rag_indexer.py
  • tests/test_rag_retriever.py
  • tests/test_symbol_table_incremental.py

Comment thread bad_code.py Outdated
for i in range(ITERATION_LIMIT):
print("doing something", x)
return x + y
do_something_crazy(10, 5) No newline at end of file

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Top-level invocation causes side effects on import.

do_something_crazy(10, 5) runs at import time, printing 100 lines to stdout whenever anything imports this module (including test collection / analyzer discovery). Guard it behind if __name__ == "__main__": or remove it.

Proposed fix
-do_something_crazy(10, 5)
+if __name__ == "__main__":
+    do_something_crazy(10, 5)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@bad_code.py` at line 19, The top-level call do_something_crazy(10, 5) causes
side effects on import; move or guard the invocation so it only runs when
executed as a script: either remove the call entirely or wrap it in an if
__name__ == "__main__": block and keep the do_something_crazy(10, 5) invocation
inside that block so imports no longer trigger the behavior.

Comment thread refactron/analysis/symbol_table.py Outdated
Comment on lines +102 to +109
# Remove from exports
names_to_remove = [
name
for name, sym in self.exports.items()
if self._normalize_path(sym.file_path) == norm_path
]
for name in names_to_remove:
self.exports.pop(name, None)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Preserve same-name exports from other files during incremental removal.

If a.py and b.py both export foo, and b.py currently owns exports["foo"], deleting or reanalyzing b.py removes foo entirely even though a.py still has a valid global symbol. That breaks cross-file resolution after incremental updates.

Proposed fix
         for name in names_to_remove:
             self.exports.pop(name, None)
+            for scopes in self.symbols.values():
+                candidate = scopes.get("global", {}).get(name)
+                if candidate and candidate.type in (
+                    SymbolType.CLASS,
+                    SymbolType.FUNCTION,
+                    SymbolType.VARIABLE,
+                ):
+                    self.exports[name] = candidate
+                    break
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@refactron/analysis/symbol_table.py` around lines 102 - 109, When removing
exports coming from norm_path, don't blindly drop names that were overwritten by
that file; for each name in names_to_remove (computed from self.exports), check
self.globals (or the container of all symbols) for other symbols with the same
name whose _normalize_path(sym.file_path) != norm_path and that are valid global
symbols, and if found restore self.exports[name] to one of those remaining
symbols (choose deterministically, e.g., first match), otherwise pop the export;
use the existing helpers like _normalize_path and the mappings self.exports and
self.globals to locate candidates.

Comment thread refactron/analysis/taint.py Outdated
Comment on lines +82 to +91
def _index_sensitive_nodes(self):
"""Pre-index statements that contain potential sinks or sources."""
for node in self.data_flow.nodes:
for stmt in node.statements:
sensitive = []
for child in ast.walk(stmt):
if isinstance(child, (ast.Call, ast.Attribute)):
sensitive.append(child)
if sensitive:
self._statement_meta[stmt] = sensitive

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Add missing return type annotation to fix mypy failure.

Pipeline failure: mypy: Function is missing a return type annotation at Line 82. Per coding guidelines, refactron/**/*.py requires disallow_untyped_defs = true.

🔧 Proposed fix
-    def _index_sensitive_nodes(self):
+    def _index_sensitive_nodes(self) -> None:
         """Pre-index statements that contain potential sinks or sources."""

As per coding guidelines: "Type annotations are required in refactron/ with mypy disallow_untyped_defs = true enabled".

🧰 Tools
🪛 GitHub Actions: Pre-commit

[error] 82-82: mypy: Function is missing a return type annotation [no-untyped-def]. Hint: Use '-> None' if the function does not return a value.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@refactron/analysis/taint.py` around lines 82 - 91, The method
_index_sensitive_nodes is missing a return type annotation; annotate it with an
explicit return type (-> None) to satisfy mypy and the refactron coding
guidelines. Update the def _index_sensitive_nodes signature to include the
return annotation while leaving the body intact; no other behavior changes are
necessary. Ensure the change is applied to the _index_sensitive_nodes method
that iterates over self.data_flow.nodes and writes to self._statement_meta.

Comment thread refactron/analysis/taint.py Outdated
targets = [stmt.target]

value = stmt.value # type: ignore[union-attr, attr-defined]
is_tainted = self._is_expression_tainted(value, current_taint, memo) # type: ignore[arg-type]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Wrap line to fix flake8 E501.

Pipeline failure: flake8: E501 line too long (106 > 100 characters) at Line 172. Per coding guidelines, line length must be ≤ 100 characters.

🔧 Proposed fix
-            is_tainted = self._is_expression_tainted(value, current_taint, memo)  # type: ignore[arg-type]
+            is_tainted = self._is_expression_tainted(
+                value, current_taint, memo  # type: ignore[arg-type]
+            )

As per coding guidelines: "Flake8 linting must use max-line-length of 100".

🧰 Tools
🪛 GitHub Actions: Pre-commit

[error] 172-172: flake8: E501 line too long (106 > 100 characters)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@refactron/analysis/taint.py` at line 172, The line calling
self._is_expression_tainted(value, current_taint, memo) exceeds 100 chars; split
the call across multiple lines to keep length ≤100 characters (for example break
after the opening parenthesis or put each argument on its own line) so that the
invocation to _is_expression_tainted with arguments value, current_taint, and
memo is wrapped and flake8 E501 is resolved.

Comment thread refactron/cli/main.py
Comment on lines 36 to 48
# 1. Pre-check authentication status
import os

creds = load_credentials()
is_authenticated = False
if creds and creds.access_token:

# If using local GROQ, we bypass cloud authentication checks
if os.environ.get("GROQ_API_KEY"):
is_authenticated = True
elif creds and creds.access_token:
now = datetime.now(timezone.utc)
if not creds.expires_at or creds.expires_at > now:
is_authenticated = True

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

GROQ_API_KEY env var bypasses all authentication globally.

Any user who sets GROQ_API_KEY is treated as fully authenticated for every subcommand — including ones that hit the Refactron backend (repo, rag, telemetry, etc.), not just LLM-local flows. This is a security posture regression and lets a user skip token validation/expiry entirely by exporting an unrelated env var.

As per coding guidelines: "All CLI commands except login/logout/auth must check for valid access token stored via core/credentials.py before proceeding".

Consider scoping the bypass tightly — e.g. only for commands that actually invoke the local LLM path (suggest, ai-fix, refactor), or gate it behind an explicit opt-in like REFACTRON_LOCAL_MODE=1 and still require credentials for backend calls.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@refactron/cli/main.py` around lines 36 - 48, The current pre-check in main.py
treats any presence of GROQ_API_KEY as full authentication (GROQ_API_KEY,
load_credentials, is_authenticated) — tighten this: only allow the GROQ_API_KEY
bypass for local-LLM commands (e.g., "suggest", "ai-fix", "refactor") or when an
explicit opt-in env var (e.g., REFACTRON_LOCAL_MODE=1) is set; otherwise require
valid credentials from load_credentials() and validate creds.expires_at as
before. Update the is_authenticated logic to first determine the invoked
subcommand (from argv/CLI context) and only apply the GROQ_API_KEY shortcut when
subcommand ∈ localCommands OR REFACTRON_LOCAL_MODE == "1"; for all other
commands (repo, rag, telemetry, login/logout excluded) enforce token
presence/expiry check using creds.access_token and creds.expires_at.

Comment on lines +42 to +61
if self.config_path and self.config_path.exists():
config = RefactronConfig.from_file(self.config_path)
else:
if not self.project_root:
try:
self.project_root = Refactron().detect_project_root(target_path)
except Exception:
self.project_root = target_path if target_path.is_dir() else target_path.parent

yaml_path = self.project_root / ".refactron.yaml"
if yaml_path.exists():
config = RefactronConfig.from_file(yaml_path)
else:
config = RefactronConfig.default()

# 2. Allow explicit overrides
# In pipeline mode, incremental analysis is off by default for consistency
config.enable_incremental_analysis = use_incremental

refactron = Refactron(config)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Full Refactron() is constructed just to call detect_project_root, and then a second Refactron(config) is created for analysis.

Refactron.__init__ is heavy: it configures structured logging, starts Prometheus (if enabled), builds the AST cache, the incremental tracker, the parallel processor, the memory profiler, pattern storage, and — now — an LLMOrchestrator. Doing this once on line 47 purely to locate a project root and then doing it all over again on line 61 with the real config doubles the startup cost and, more importantly, applies side effects (logging reconfiguration, Prometheus server binding, telemetry events) using the default config before the user's YAML is loaded.

Recommendations (any one fixes the problem):

  • Extract detect_project_root into a module-level helper (or @staticmethod) that only walks the filesystem — it has no dependencies on instance state today.
  • Or, load the config first (possibly using target_path itself as a starting search point) and construct Refactron only once, at line 61.

Additionally, if self.config_path is passed explicitly but doesn't exist (line 42), the code silently falls through to project-root-based discovery. A user-supplied path that doesn't exist is almost certainly a mistake and should raise FileNotFoundError rather than be ignored.

🧹 Proposed direction
-        # 1. Resolve config the same way as CLI
-        if self.config_path and self.config_path.exists():
-            config = RefactronConfig.from_file(self.config_path)
-        else:
-            if not self.project_root:
-                try:
-                    self.project_root = Refactron().detect_project_root(target_path)
-                except Exception:
-                    self.project_root = target_path if target_path.is_dir() else target_path.parent
-
-            yaml_path = self.project_root / ".refactron.yaml"
-            if yaml_path.exists():
-                config = RefactronConfig.from_file(yaml_path)
-            else:
-                config = RefactronConfig.default()
+        # 1. Resolve config the same way as CLI
+        if self.config_path is not None:
+            if not self.config_path.exists():
+                raise FileNotFoundError(f"Config file not found: {self.config_path}")
+            config = RefactronConfig.from_file(self.config_path)
+        else:
+            if not self.project_root:
+                self.project_root = _detect_project_root(target_path)  # lightweight helper
+            yaml_path = self.project_root / ".refactron.yaml"
+            config = (
+                RefactronConfig.from_file(yaml_path)
+                if yaml_path.exists()
+                else RefactronConfig.default()
+            )
🧰 Tools
🪛 Ruff (0.15.10)

[warning] 48-48: Do not catch blind exception: Exception

(BLE001)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@refactron/core/pipeline.py` around lines 42 - 61, The code currently
constructs a full Refactron() just to call detect_project_root and then
constructs Refactron(config) again, causing heavy startup side-effects; refactor
by making detect_project_root a `@staticmethod` or module-level helper (move logic
from Refactron.detect_project_root into a pure function) and call that without
instantiating Refactron, or alternatively change the flow to load the
YAML/config first and instantiate Refactron only once as Refactron(config) for
analysis; also when self.config_path is provided but does not exist, raise
FileNotFoundError instead of silently falling back so a mistaken path (from
RefactronConfig.from_file/self.config_path usage) surfaces to the user.

Comment thread refactron/core/refactron.py Outdated
Comment on lines +528 to +569
# Optional LLM Triage to suppress false positives
if self.llm_orchestrator and metrics.issues:
try:
start_triage = time.time()
logger.debug(
f"Running LLM triage on {len(metrics.issues)} issues in {file_path.name}"
)
confidence_map = self.llm_orchestrator.evaluate_issues_batch(
metrics.issues, source_code
)

filtered_issues = []
for i, issue in enumerate(metrics.issues):
base_id = getattr(issue, "rule_id", None) or "issue"
line_number = getattr(issue, "line_number", None)
id_parts = [str(base_id)]
if line_number is not None:
id_parts.append(str(line_number))
id_parts.append(str(i))
issue_id = ":".join(id_parts)

matched_confidence = 1.0
for k, v in confidence_map.items():
if k == issue_id or k.startswith(f"{issue_id}_"):
matched_confidence = v
break

if matched_confidence >= 0.5:
filtered_issues.append(issue)
else:
logger.debug(
f"LLM suppressed false positive: {issue.message} (score: {matched_confidence})"
)

metrics.issues = filtered_issues
logger.debug(
f"Completed LLM triage in {time.time() - start_triage:.2f}s. Kept {len(metrics.issues)} issues."
)
except Exception as e:
logger.warning(
f"LLM Triage failed for {file_path}, falling back to static results: {e}"
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Issue-ID reconstruction is duplicated from LLMOrchestrator.evaluate_issues_batch and will silently desync.

Lines 540–547 rebuild the same base_id:line:index ID scheme that evaluate_issues_batch uses internally (see refactron/llm/orchestrator.py:493-516). If the orchestrator ever changes its ID strategy (e.g. uses a hash, adds a field, or re-orders issues), this matching loop will silently miss every entry and — because matched_confidence defaults to 1.0 — all issues will pass triage, defeating the feature without any failure signal.

Consider having evaluate_issues_batch return results keyed by something stable the caller owns (e.g. issue index, or (issue, confidence) tuples) so the filter here can't drift.

Also: lines 559 and 564 exceed 100 chars — pre-commit flake8 E501 is failing on both.

🧹 Proposed line-length fix for the flake8 failures
-                        logger.debug(
-                            f"LLM suppressed false positive: {issue.message} (score: {matched_confidence})"
-                        )
+                        logger.debug(
+                            "LLM suppressed false positive: %s (score: %.3f)",
+                            issue.message,
+                            matched_confidence,
+                        )
...
-                logger.debug(
-                    f"Completed LLM triage in {time.time() - start_triage:.2f}s. Kept {len(metrics.issues)} issues."
-                )
+                logger.debug(
+                    "Completed LLM triage in %.2fs. Kept %d issues.",
+                    time.time() - start_triage,
+                    len(metrics.issues),
+                )

As per coding guidelines: "Use line length of 100 characters, enforced by black, isort, and flake8".

🧰 Tools
🪛 GitHub Actions: Pre-commit

[error] 559-559: flake8: E501 line too long (107 > 100 characters)


[error] 564-564: flake8: E501 line too long (116 > 100 characters)

🪛 Ruff (0.15.10)

[warning] 566-566: Do not catch blind exception: Exception

(BLE001)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@refactron/core/refactron.py` around lines 528 - 569, The current LLM triage
reconstructs issue IDs locally (the id_parts/issue_id logic) and matches against
confidence_map returned from LLMOrchestrator.evaluate_issues_batch, which can
silently desync; change evaluate_issues_batch to return confidence results keyed
by a stable caller-owned key (preferably the issue index or return a list of
(index, confidence) tuples) and update this caller (the block using
self.llm_orchestrator.evaluate_issues_batch, confidence_map, and the matching
loop) to use the stable index-based key instead of rebuilding issue_id; also
split the long logger.debug and logger.warning strings into shorter concatenated
parts so the two messages comply with the 100-char line-length limit.

Comment thread refactron/core/workspace.py Outdated
Comment on lines +40 to +52
# Robust parsing to handle older configuration formats
repo_full_name = data.get("repo_full_name", "unknown/unknown")
repo_name = data.get("repo_name")

# Derive repo_name from full_name if missing
if not repo_name and "/" in repo_full_name:
repo_name = repo_full_name.split("/")[-1]

return cls(
repo_id=data["repo_id"],
repo_name=data["repo_name"],
repo_full_name=data["repo_full_name"],
local_path=data["local_path"],
connected_at=data["connected_at"],
repo_name=repo_name or repo_full_name or "unknown",
repo_full_name=repo_full_name,
local_path=data.get("local_path", ""),
connected_at=data.get("connected_at", "unknown"),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Avoid turning malformed workspace records into active workspaces.

Defaulting repo_full_name to "unknown/unknown" and local_path to "" lets corrupted/old records pass as connected workspaces. Callers such as refactron/cli/rag.py, refactron/cli/refactor.py, and refactron/cli/analysis.py then display the fake repo name and use Path(current_workspace.local_path) without validation, which can target the current directory.

Prefer skipping/repairing invalid records at load time, or require repo_full_name and a non-empty local_path before constructing WorkspaceMapping.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@refactron/core/workspace.py` around lines 40 - 52, The current from-dict
logic silently defaults malformed records (repo_full_name="unknown/unknown" and
local_path="") and returns a WorkspaceMapping (cls) for invalid data; instead
validate before constructing: in the constructor/factory that sets
repo_full_name, repo_name and local_path, require a non-empty local_path and a
repo_full_name that contains a "/" (or otherwise meets your repo_full_name
format) and only derive repo_name when valid; if validation fails, do not return
a WorkspaceMapping—either return None or raise a clear ValueError so callers
(e.g., refactron/cli/rag.py, refactron/cli/refactor.py,
refactron/cli/analysis.py) can skip/repair the record—update the method that
currently uses repo_full_name, repo_name, local_path and cls(...) to implement
these checks and propagate the None/error to callers.

Comment thread refactron/llm/orchestrator.py Outdated
Comment on lines +282 to +342
# 1. Retrieve Context
context_snippets = []
if self.retriever:
try:
# Use the first few issues for context retrieval
query = " ".join([i.message for i in issues[:3]])
results = self.retriever.retrieve_similar(query, 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. Format issues for prompt
issues_details = ""
for idx, issue in enumerate(issues, 1):
issues_details += (
f"{idx}. {issue.category.value} (Line {issue.line_number}): {issue.message}\n"
)

# 3. Construct Prompt
prompt = BATCH_SUGGESTION_PROMPT.format(
issues_details=issues_details,
original_code=original_code,
rag_context=rag_context,
)

# 4. Call LLM
response_text = "N/A"
try:
response_text = self.client.generate(
prompt=prompt, system=BATCH_SUGGESTION_SYSTEM_PROMPT, temperature=0.2
)

# Reuse cleaning and parsing logic
clean_text = self._clean_json_response(response_text)
data = json.loads(clean_text, strict=False)

proposed_code = data.get("proposed_code", "")

# Clean up hallucinations
if proposed_code.startswith("```"):
lines = proposed_code.split("\n")
if lines[0].startswith("```"):
lines.pop(0)
if lines and lines[-1].startswith("```"):
lines.pop(-1)
proposed_code = "\n".join(lines).strip()

if proposed_code.startswith("{") and proposed_code.endswith("}"):
potential_code = proposed_code[1:-1].strip()
try:
ast.parse(potential_code)
proposed_code = potential_code
except SyntaxError:
pass

suggestion = RefactoringSuggestion(
issue=issues[0], # Use first issue as primary reference
original_code=original_code,
context_files=[r.file_path for r in results] if self.retriever else [],

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

results can be unbound when retriever retrieval fails, forcing the whole batch suggestion to fail.

results is only assigned inside the inner try at line 288. If self.retriever is set but retrieve_similar(...) raises (e.g., transient RAG/Chroma error), execution continues to build the prompt and call the LLM, but then line 342 references results which is never bound. The resulting NameError is swallowed by the outer except at line 351, so a successful LLM response is discarded and the caller receives a FAILED suggestion — the exact opposite of graceful RAG degradation.

🛠️ Proposed fix
         # 1. Retrieve Context
         context_snippets = []
+        results = []
         if self.retriever:
             try:
                 # Use the first few issues for context retrieval
                 query = " ".join([i.message for i in issues[:3]])
                 results = self.retriever.retrieve_similar(query, top_k=3)
                 context_snippets = [r.content for r in results]
             except Exception as e:
                 logger.warning(f"Context retrieval failed: {e}")
@@
             suggestion = RefactoringSuggestion(
                 issue=issues[0],  # Use first issue as primary reference
                 original_code=original_code,
-                context_files=[r.file_path for r in results] if self.retriever else [],
+                context_files=[r.file_path for r in results],
                 proposed_code=proposed_code,
🧰 Tools
🪛 Ruff (0.15.10)

[warning] 290-290: Do not catch blind exception: Exception

(BLE001)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@refactron/llm/orchestrator.py` around lines 282 - 342, The variable results
may be referenced unbound when self.retriever is truthy but
retrieve_similar(...) raises; initialize results to an empty list before the
retriever block (or set results = [] in the except handler) so references later
(e.g., building RefactoringSuggestion.context_files = [r.file_path for r in
results]) are safe; modify the code around the retriever try/except (symbols:
self.retriever, retrieve_similar, results, context_snippets,
RefactoringSuggestion) to ensure results is always defined as a list even on
retrieval failure.

Comment thread tests/test_pipeline.py

import tempfile
from pathlib import Path
from unittest.mock import patch, MagicMock

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Remove unused MagicMock import (pipeline failure).

Pre-commit flake8 is failing with F401. Only patch is referenced.

🔧 Proposed fix
-from unittest.mock import patch, MagicMock
+from unittest.mock import patch
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
from unittest.mock import patch, MagicMock
from unittest.mock import patch
🧰 Tools
🪛 GitHub Actions: Pre-commit

[error] 5-5: flake8: F401 'unittest.mock.MagicMock' imported but unused

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/test_pipeline.py` at line 5, Remove the unused MagicMock import to fix
the flake8 F401 failure: in the import statement that currently reads "from
unittest.mock import patch, MagicMock" keep only "patch" (remove "MagicMock") so
only referenced symbols are imported in tests/test_pipeline.py.

@github-actions

github-actions Bot commented May 4, 2026

Copy link
Copy Markdown

This pull request has been automatically marked as stale because it has not had recent activity for 14 days.

If you believe this PR is still relevant, please:

  • Add a comment explaining why
  • Remove the stale label
  • Or simply comment to keep it active

This helps us keep the repository focused on active contributions. Thank you! 🙏

@github-actions github-actions Bot added the stale label May 4, 2026
@github-actions

github-actions Bot commented May 7, 2026

Copy link
Copy Markdown

This pull request has been automatically closed because it was marked as stale and had no recent activity for 3 days.

If you believe this PR should be reopened, please:

  • Add a comment explaining why
  • Remove the closed-by-stale-bot label
  • Or create a new PR with updated information

Thank you for your understanding! 🙏

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants