Skip to content

feat: complete Refactron optimization and LLM integration - #173

Closed
shrutu0929 wants to merge 2 commits into
Refactron-ai:mainfrom
shrutu0929:feat/llm-rag-optimized
Closed

shrutu0929 wants to merge 2 commits into
Refactron-ai:mainfrom
shrutu0929:feat/llm-rag-optimized

Conversation

@shrutu0929

@shrutu0929 shrutu0929 commented Apr 16, 2026

Copy link
Copy Markdown
Contributor

solve #172

The work consolidated into this single commit represents a comprehensive overhaul of Refactron_lib, focusing on both algorithmic performance and environment-resilient AI orchestration. At the core layer, I implemented a high-performance TaintAnalyzer utilizing sink/source indexing and expression memoization to accelerate security scans, alongside a robust Symbol Table invalidation logic that enables reliable incremental analysis across large codebases. In the AI domain, I refactored the refactoring pipeline to support Batch Processing, allowing the orchestrator to aggregate multiple code smells into a single unified fix proposal, which significantly reduces API overhead and improves the cohesion of the generated code. Finally, to ensure system stability on experimental environments like Python 3.14 on Windows, I introduced a Keyword Fallback RAG system and a lazy-loading module architecture; these features bypass complex PyTorch DLL initialization failures while maintaining the semantic search capabilities essential for project-aware code suggestions.

Summary by CodeRabbit

  • New Features

    • New CLI command to analyze and apply suggested fixes to a target file.
    • Batch suggestion generation for multiple code issues.
    • One-sentence code-chunk summarization for analysis and indexing.
    • Keyword-mode retrieval fallback when vector indexing is unavailable.
  • Improvements

    • Authentication can be overridden via an API key environment variable.
    • Increased LLM token default to 4000 for longer context.
    • Improved Windows terminal key handling and refreshed startup UI art.
    • Faster incremental project analysis and cache-based rebuilds.
  • Tests

    • Added and updated tests covering incremental symbol caching, RAG behavior, and parallel processing.

@coderabbitai

coderabbitai Bot commented Apr 16, 2026

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

Adds incremental file-aware symbol caching, statement-level taint memoization, expanded LLM orchestrator with RAG lifecycle and batch suggestions, dual-mode RAG indexing/retrieval (vector or keyword fallback), new ai_fix CLI command, various CLI/UI adjustments, increased default token budgets, tests for incremental symbol table and parallel-skips, and a new example bad_code.py.

Changes

Cohort / File(s) Summary
Symbol Table & Incremental Analysis
refactron/analysis/symbol_table.py, refactron/analysis/...
Added file metadata (mtime/size/sha256), path normalization, remove_file(), incremental rebuild logic, cache format changes, and SHA-256 based change detection.
Inference / AST Cache Eviction
refactron/core/inference.py
Canonicalizes file paths, aggressively evicts astroid caches by multiple keys, reads source from resolved path, and parses via astroid with robust fallbacks.
Taint Analysis Optimization
refactron/analysis/taint.py
Per-statement indexing of sensitive nodes and per-statement memoization for _is_expression_tainted to avoid redundant subexpression walks.
LLM Orchestrator & Prompts
refactron/llm/orchestrator.py, refactron/llm/prompts.py, refactron/llm/backend_client.py, refactron/llm/client.py
LLMOrchestrator accepts workspace_path, auto-initializes retriever, supports build_vector_index, generate_chunk_summary, generate_batch_suggestion (with code-fence stripping and ast validation); prompts extended; default max_tokens increased 2000→4000.
RAG Indexer & Retriever
refactron/rag/indexer.py, refactron/rag/retriever.py
Lazy dependency loading, dual-mode operation (vector vs keyword), keyword-mode persistence (keyword_chunks.json) and term-overlap retrieval; RAGIndexer now accepts llm_integration (LLMOrchestrator).
CLI Commands & UI
refactron/cli/analysis.py, refactron/cli/main.py, refactron/cli/refactor.py, refactron/cli/rag.py, refactron/cli/ui.py, refactron/cli/utils.py
Removed local ContextRetriever in several CLI paths in favor of LLMOrchestrator(workspace_path); added ai_fix command; rag commands route through orchestrator; UI _read_key() improved for Windows/Unix; logging exception handling broadened; env-based auth override for GROQ_API_KEY.
Core Parallel Processing
refactron/core/parallel.py, refactron/core/refactron.py
process_files now returns triple (results, errors, skips); upstream callers and wrappers updated to propagate AnalysisSkipWarning instances.
Workspace Mapping
refactron/core/workspace.py
WorkspaceMapping.from_dict hardened with defensive parsing and sensible fallbacks for missing fields.
Tests: Incremental & Parallel
tests/test_symbol_table_incremental.py, tests/test_config_management.py, tests/test_performance_optimization.py, tests/test_rag_*, tests/test_groq_client.py
Added integration tests for symbol table incremental rebuilds and hash invalidation; updated tests to handle triple return from process_files; adapted RAG/indexer/retriever tests to lazy-mode and LLMOrchestrator changes; updated GroqClient expected max_tokens.
Example / Test Fixture
bad_code.py
New example module added with module-level constants, a conditional loop, and a top-level call to do_something_crazy(10, 5).

Sequence Diagram(s)

sequenceDiagram
    participant CLI as CLI (user)
    participant Orch as LLMOrchestrator
    participant Indexer as RAGIndexer
    participant Retriever as ContextRetriever
    participant LLM as Groq/BackendLLM

    CLI->>Orch: build_vector_index(workspace_path, summarize)
    Orch->>Indexer: index_repository(workspace_path, summarize)
    Indexer-->>Orch: indexing complete / persist (vector or keyword)
    Orch->>Retriever: refresh retriever (load collection or keyword_chunks)
    Retriever-->>Orch: ready

    note over CLI,Orch: Later - batch suggestion flow
    CLI->>Orch: generate_batch_suggestion(issues, original_code)
    Orch->>Retriever: retrieve_similar(context_qs)
    Retriever-->>Orch: contexts (vector or keyword results)
    Orch->>LLM: generate(prompt + contexts)
    LLM-->>Orch: response (JSON with proposed_code)
    Orch->>Orch: clean code-fences, ast.parse validation, safety check
    Orch-->>CLI: RefactoringSuggestion (PENDING/REJECTED/FAILED)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~70 minutes

Possibly related issues

Suggested labels

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

Suggested reviewers

  • omsherikar

Poem

🐇 I hopped through symbols, hashes in tow,
Memoized whispers where tainted winds blow.
I nudged the RAG when Chroma went shy,
Batches and prompts now reach for the sky.
Little rabbit cheers — code clearer, and slow bugs say "bye!"

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 68.22% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the major changes: completing Refactron optimization (performance improvements in TaintAnalyzer, Symbol Table incremental analysis, parallel processing) and LLM integration (batch processing, RAG retrieval, orchestrator workflow).

✏️ 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.

@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: 17

Note

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

🟡 Minor comments (11)
refactron/analysis/symbol_table.py-12-12 (1)

12-12: ⚠️ Potential issue | 🟡 Minor

Remove the unused Set import.

Pre-commit is already failing with F401 here, and Set isn't referenced anywhere in this file.

🤖 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, Remove the unused Set import
from the typing import line: update the import statement that currently reads
"from typing import Any, Dict, List, Optional, Set" to exclude Set so it becomes
"from typing import Any, Dict, List, Optional"; this will resolve the F401
pre-commit failure for the unused symbol.
tests/test_symbol_table_incremental.py-3-4 (1)

3-4: ⚠️ Potential issue | 🟡 Minor

Drop the unused imports before merge.

Path and SymbolType aren't used in this file, and pre-commit is already failing with F401 here.

🤖 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 3 - 4, Remove the unused
imports to satisfy F401: in the import line that currently pulls in Path and
SymbolType, drop Path and SymbolType and keep only SymbolTableBuilder from
refactron.analysis.symbol_table; ensure the file imports only SymbolTableBuilder
(and any other actually used symbols) and run pre-commit/flake8 to verify the
F401 is resolved.
refactron/analysis/taint.py-171-172 (1)

171-172: ⚠️ Potential issue | 🟡 Minor

Wrap the taint-check call to clear the current flake8 failure.

Line 172 is over the repo's 100-character limit, so pre-commit will keep failing until this is split.

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

As per coding guidelines, **/*.py: 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/analysis/taint.py` around lines 171 - 172, The call to
self._is_expression_tainted on the assignment to is_tainted exceeds the 100-char
line limit; split the call across multiple lines or extract the arguments so the
expression fits under 100 chars. Locate the assignment to is_tainted in taint.py
(the line using value = stmt.value and is_tainted =
self._is_expression_tainted(...)) and reformat the call so each argument (value,
current_taint, memo) is on its own line or move the arguments into a short local
variable, ensuring the final is_tainted = ... line stays below 100 characters.
refactron/cli/analysis.py-527-527 (1)

527-527: ⚠️ Potential issue | 🟡 Minor

Remove the stale ContextRetriever import.

Switching to LLMOrchestrator(workspace_path=...) leaves the file-level ContextRetriever import unused, and pre-commit is already failing on F401.

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

In `@refactron/cli/analysis.py` at line 527, The file still imports
ContextRetriever but after switching to LLMOrchestrator(workspace_path=...) that
import is unused and causes F401; remove the stale ContextRetriever import from
the module-level imports (the symbol named ContextRetriever) so only used
imports remain and pre-commit no longer fails, keeping the LLMOrchestrator usage
unchanged.
refactron/core/parallel.py-74-80 (1)

74-80: ⚠️ Potential issue | 🟡 Minor

Wrap the updated tuple-contract docstring.

The new return-value description exceeds the repo's 100-character limit and is already failing pre-commit.

As per coding guidelines, **/*.py: 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/parallel.py` around lines 74 - 80, The docstring for the
function that accepts process_func and progress_callback has an overly long
"Returns" line; wrap the return-value description under 100 characters per line
so it complies with the repo's line-length rules (black/flake8). Edit the
triple-quoted docstring block that lists "Returns:" (the same docstring that
documents process_func and progress_callback) to break the long tuple
description into multiple lines, keeping the tuple format "(successful results,
failed files, skip warnings)" and/or splitting explanatory text across lines to
ensure no line exceeds 100 characters.
refactron/cli/refactor.py-530-530 (1)

530-530: ⚠️ Potential issue | 🟡 Minor

Remove the stale ContextRetriever import.

After switching both document() and ai_fix() to LLMOrchestrator(workspace_path=...), the file-level ContextRetriever import is unused and currently fails pre-commit.

Also applies to: 648-648

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

In `@refactron/cli/refactor.py` at line 530, The file still imports
ContextRetriever at module scope but document() and ai_fix() now instantiate
LLMOrchestrator(workspace_path=...), so remove the unused ContextRetriever
import declarations (any top-level import named ContextRetriever) and any other
stale references to ContextRetriever in this module; ensure imports only include
LLMOrchestrator and related symbols used by document() and ai_fix() and run
pre-commit to confirm the unused-import warning is resolved.
refactron/cli/refactor.py-620-621 (1)

620-621: ⚠️ Potential issue | 🟡 Minor

Wrap the new long strings before merging.

These additions are over the 100-character limit and are already breaking pre-commit.

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

Also applies to: 660-660, 683-688

🤖 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 - 621, The new console.print
messages (e.g., the one guarded by target_path.is_file()) exceed the
100-character line-length limit; wrap or break these long string literals so
each source line is ≤100 chars (use implicit adjacent string concatenation or
split strings and join with f-strings where needed) and apply the same fix to
the other long messages around the code paths referenced (the prints at the
other occurrences noted). Ensure formatting preserves the original message text
and any color tags (e.g., "[red]...[/red]") and run pre-commit to verify
line-length/formatting checks pass.
refactron/core/refactron.py-341-341 (1)

341-341: ⚠️ Potential issue | 🟡 Minor

Split this recovery message so pre-commit passes.

Line 341 is already failing flake8 E501 in CI.

✂️ Minimal 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` at line 341, The recovery_suggestion string
literal assigned to recovery_suggestion is too long (flake8 E501); split it into
multiple shorter string literals using implicit concatenation or join a tuple
inside parentheses so the line length stays under 100 characters. Locate the
recovery_suggestion argument (the named parameter recovery_suggestion in the
call inside refactron.py) and break the message into two or more quoted pieces
on separate lines (or use a parenthesized string expression) so the concatenated
message remains identical but each source line is <=100 chars.
refactron/rag/indexer.py-13-18 (1)

13-18: ⚠️ Potential issue | 🟡 Minor

Keep the CHROMA_AVAILABLE sentinel below the import block.

Line 15 makes the later CodeChunk / CodeParser imports count as non-top-level imports, which is why pre-commit is failing with E402.

🧹 Minimal fix
 from typing import TYPE_CHECKING, Any, Dict, List, Optional, cast

 if TYPE_CHECKING:
     from refactron.llm.orchestrator import LLMOrchestrator

-# 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
+
+# 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: Optional[bool] = None
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@refactron/rag/indexer.py` around lines 13 - 18, The sentinel CHROMA_AVAILABLE
is currently placed before the top-level imports which makes the subsequent
imports (CodeChunk and CodeParser) non-top-level and triggers E402; move the
CHROMA_AVAILABLE declaration so it appears after the import block (after the
from refactron.rag.chunker import CodeChunk and from refactron.rag.parser import
CodeParser lines) so all imports remain top-level and the sentinel still exists
for later lazy-loading logic.
refactron/llm/orchestrator.py-197-200 (1)

197-200: ⚠️ Potential issue | 🟡 Minor

Extract the proposed-code cleanup into a typed helper method to eliminate duplication and fix E701 violations.

The identical cleanup logic at lines 197–203 and 315–321 can be consolidated into a single helper method. Lines 199–200 and 317–318 violate E701 (multiple statements on one line); expanding these into proper multi-line if statements within the extracted helper will also maintain consistency between the single-issue and batch paths.

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

In `@refactron/llm/orchestrator.py` around lines 197 - 200, Extract the duplicated
proposed_code trimming logic into a typed helper, e.g. def
clean_proposed_code(proposed_code: str) -> str, that: checks for a leading code
fence, splits on "\n", removes a leading "```" line if present, removes a
trailing "```" line if present, and returns the cleaned string; replace both
inline blocks that operate on the proposed_code variable with calls to this
helper and expand the current single-line if statements (e.g. if
lines[0].startswith("```"): lines.pop(0)) into proper multi-line if blocks
inside the helper to fix the E701 violations and keep behavior identical between
the single-issue and batch code paths.
refactron/rag/indexer.py-243-256 (1)

243-256: ⚠️ Potential issue | 🟡 Minor

Give metadata an explicit mutable dict type before mutating it.

CI is failing mypy here because chunk_dict["metadata"] is inferred too narrowly for indexed assignment on line 256.

🛠️ Minimal typing fix
             for chunk in chunks:
+                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],
+                }
                 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],
-                    }
+                    "metadata": metadata,
                 }

Type annotations are required in refactron/ with mypy disallow_untyped_defs = true enabled.

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

In `@refactron/rag/indexer.py` around lines 243 - 256, The issue is that
chunk_dict["metadata"] is inferred too narrowly for incremental assignment; fix
it by building an explicitly typed mutable metadata dict first (e.g. declare
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] }) then iterate over chunk.metadata to add
entries into that metadata dict, and finally set chunk_dict = { "content":
chunk.content, "metadata": metadata }; also add the needed typing imports (Dict,
Any) if not present so mypy accepts the mutation of metadata.
🧹 Nitpick comments (1)
refactron/cli/rag.py (1)

147-156: Reuse one orchestrator for reranking.

Creating LLMOrchestrator(workspace_path=local_path) inside the result loop reinitializes the client and retriever for every hit. Build it once before the loop and reuse the same client for all rerank calls.

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

In `@refactron/cli/rag.py` around lines 147 - 156, The code currently constructs a
new LLMOrchestrator inside the per-result loop (when rerank is true), which
reinitializes the client and retriever for every hit; move the instantiation of
LLMOrchestrator(workspace_path=local_path) out of the loop so a single
orchestrator is created once before iterating results and then reuse
orchestrator.client.generate (and any prompt construction) for each result,
ensuring you only create one retriever/client per rerank pass.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@bad_code.py`:
- Line 19: The file currently calls do_something_crazy(10, 5) at import time
which causes unwanted side effects; move that invocation behind a module
entrypoint guard (if __name__ == "__main__":) or remove it entirely so importing
the module won't execute do_something_crazy, and keep the call only when the
module is run as a script.

In `@refactron/analysis/symbol_table.py`:
- Around line 95-112: remove_file currently deletes export entries whose
Symbol.file_path points to the removed file but doesn't restore a different
global symbol with the same name, causing resolve_reference to lose valid
cross-file exports; update remove_file to, for each export name removed, scan
remaining self.symbols (use self._normalize_path and the Symbol.name /
Symbol.file_path attributes) to find an alternative symbol with the same name
and set self.exports[name] to that symbol (or pop only if none found), ensuring
file_metadata and symbols cleanup remains unchanged.
- Around line 183-200: The metadata dict returned from
symbol_table.file_metadata is untyped which causes mypy failures in
_has_file_changed; change the model to a typed shape (e.g., a TypedDict or
dataclass for the per-file metadata) or cast/pull fields into explicitly typed
locals before comparisons in _has_file_changed; update the type of
symbol_table.file_metadata to use that TypedDict/dataclass and in
_has_file_changed extract typed locals like size, sha256, and mtime (with proper
Optional[type] annotations) and then perform the size/hash/mtime comparisons
(still using _calculate_hash when sha256 exists) so mypy no-any-return errors
are resolved.

In `@refactron/analysis/taint.py`:
- Around line 82-91: The method _index_sensitive_nodes in
refactron/analysis/taint.py lacks a return type annotation causing mypy
no-untyped-def errors; update its signature to declare a None return (i.e.,
change `def _index_sensitive_nodes(self):` to include `-> None`) so the helper
satisfies the repo typing contract while leaving the body unchanged and keeping
references to self, data_flow, and _statement_meta intact.

In `@refactron/cli/main.py`:
- Around line 37-44: The global auth gate currently sets is_authenticated = True
when os.environ.get("GROQ_API_KEY") is present, letting all CLI commands bypass
the Refactron access-token check; remove or revert that branch in the startup
auth logic around load_credentials()/is_authenticated so the main gate only
trusts creds.access_token, and instead add explicit per-command checks where
local LLM access is allowed (e.g., in the specific command handlers that need
GROQ_API_KEY) to conditionally permit GROQ_API_KEY-based auth; update references
to load_credentials, is_authenticated, creds.access_token and
os.environ.get("GROQ_API_KEY") accordingly so all refactron/cli/*.py commands
(except login/logout/auth) validate the core/credentials.py stored access token
before proceeding.

In `@refactron/cli/rag.py`:
- Around line 72-79: The orchestrator is being constructed with workspace_path
which causes LLMOrchestrator.__init__ to call _ensure_retriever() and possibly
build the index before build_vector_index() runs, resulting in double-indexing;
fix this by changing the instantiation here to avoid auto-initialization (e.g.,
call LLMOrchestrator() without the workspace_path or add a constructor flag like
init_retriever=False that prevents _ensure_retriever() from running), or modify
LLMOrchestrator.__init__ to accept an opt-out parameter and skip calling
_ensure_retriever() when that flag is set; the call site should then call
LLMOrchestrator(..., init_retriever=False) (or omit workspace_path) and let
build_vector_index(local_path, summarize=...) perform the initial indexing.

In `@refactron/cli/refactor.py`:
- Around line 677-699: The final status message is unconditional and reports
success even when apply was skipped or failed; make the final footer conditional
on the actual outcome by tracking whether the unified fix was applied
successfully (e.g., introduce a boolean like applied = False before the do_apply
block), set applied = True only after
BackupRollbackSystem.prepare_for_refactoring and
target_path.write_text(suggestion.proposed_code) complete without exception, and
move or change the console.print("\n[bold]AI Auto-Fix Complete.[/bold] Verified
and applied unified refactoring.") to only run when applied is True (otherwise
print a skipped/failed message). Reference do_apply, apply/interactive,
BackupRollbackSystem.prepare_for_refactoring, target_path.write_text,
suggestion.proposed_code and the existing console.print calls when making the
change.
- Around line 637-639: The current filter uses a substring match when building
issues = [i for i in result.all_issues if str(target_path) in str(i.file_path)],
which can yield false positives/negatives; instead normalize both sides (e.g.
pathlib.Path(target_path).resolve() and pathlib.Path(i.file_path).resolve() or
compare their as_posix() forms) and test for equality when building issues
(referencing target_path, i.file_path, result.all_issues, issues, issues_to_fix)
so only issues belonging exactly to the target file are selected before batching
fixes.

In `@refactron/cli/ui.py`:
- Around line 353-388: The _read_key function needs explicit type annotations
and safe access to msvcrt.getch so mypy won't infer Any; add a return type
(e.g., -> str) to _read_key, annotate Windows-local variables as ch: bytes, ch2:
bytes, ch3: bytes and non-Windows as ch: str, ch2: str, ch3: str, and replace
direct msvcrt.getch() calls with a typed callable via getattr + typing.cast
(e.g., getch = cast(Callable[[], bytes], getattr(msvcrt, "getch"))) before
invoking it; update uses of KEY_ENTER/KEY_UP/KEY_DOWN to remain returning str
and ensure all intermediate returns match the declared return type.

In `@refactron/core/inference.py`:
- Around line 35-90: parse_file() mutates astroid.MANAGER caches
(manager.astroid_cache, file_to_module_cache/_mod_file_cache) concurrently and
must be serialized; add a module-level threading.Lock (e.g.,
ASTROID_MANAGER_LOCK) and wrap the entire cache eviction and parse/fallback
sequence (the blocks that pop from manager.astroid_cache, inspect
file_to_module_cache/_mod_file_cache, the exhaustive astroid_cache iteration,
the cache clearing loop, and the subsequent astroid.parse(...) /
manager.ast_from_file(...) calls) inside a with ASTROID_MANAGER_LOCK: to prevent
concurrent cache mutation; ensure the lock is used wherever parse_file() is
called concurrently so manager state is not corrupted.

In `@refactron/core/workspace.py`:
- Around line 41-52: The fallback logic for repo_full_name doesn't handle null
values and will raise when evaluating "/" in repo_full_name; update the
normalization in the Workspace constructor/path where repo_full_name is read
(the variable repo_full_name used before returning cls(...)) to use a safe
default like data.get("repo_full_name") or "unknown/unknown" (and similarly
normalize repo_name using that normalized repo_full_name) so the "/" check and
split on repo_full_name never see None and the legacy fallback is robust.

In `@refactron/llm/client.py`:
- Line 24: The default max_tokens for the LLM client was bumped to 4000 (see the
max_tokens parameter in refactron.llm.client), so update the test expectation in
tests/test_groq_client.py (the assertion checking client.max_tokens == 2000) to
match the new default (4000) or adjust the test to construct the client with an
explicit max_tokens value; locate the assertion referencing client.max_tokens in
that test and change the expected value to 4000 (or instantiate the client with
max_tokens=2000 if the test intends to assert a non-default).

In `@refactron/llm/orchestrator.py`:
- Around line 277-283: The retrieval block in orchestrator.py can leave results
undefined if retrieve_similar() throws; initialize a safe default before the try
(e.g., results = [] and/or context_snippets = []) so later code like
context_files=[r.file_path for r in results] or usage in the surrounding method
won’t crash; apply the same fix to the similar block around the 329-332 section
(ensure results/context_snippets are pre-initialized and the except path logs
the error but leaves an empty list for downstream processing).
- Around line 15-17: The orchestrator imports BATCH_SUGGESTION_PROMPT and
BATCH_SUGGESTION_SYSTEM_PROMPT but they are missing from the prompts module; add
these two constants to the prompts module (matching the naming/style of existing
prompt constants), define them as the intended system and user prompt templates
used by the batch suggestion logic, and export them so the imports in
orchestrator.py resolve; ensure their content matches what the batch flow
expects (same placeholders/formatting as other prompts) and run tests to verify
usage in the batch suggestion paths (references: BATCH_SUGGESTION_PROMPT,
BATCH_SUGGESTION_SYSTEM_PROMPT).

In `@refactron/rag/indexer.py`:
- Around line 232-263: The keyword-mode branch in add_chunks (triggered by
self.mode == "keyword") appends to and rewrites self.chunk_storage every time
add_chunks is called by index_repository(), causing duplicates; change the logic
to either (a) clear/rebuild the keyword storage at the start of an indexing run
(callable from index_repository before iterating files) so add_chunks writes a
fresh list, or (b) implement an upsert by stable chunk id inside add_chunks
(derive an id from file_path+line_range+name or use chunk.id) and replace
existing entries instead of blindly appending; update the code that reads/writes
self.chunk_storage (keyword_chunks.json) accordingly and ensure index_repository
triggers the rebuild path when beginning a full workspace indexing.

In `@refactron/rag/retriever.py`:
- Around line 125-171: retrieve_by_file currently always uses self.collection
and will raise AttributeError in keyword mode; add a branch at the start of
retrieve_by_file that checks if self.mode == "keyword" and, similar to
retrieve_similar, iterates self.keyword_chunks (guarding for empty
keyword_chunks), filters chunks by requested file_path and optional chunk_type,
computes or reuse the existing relevance/distance logic, sorts and slices by
top_k, and returns a list of RetrievedContext objects (populating content,
file_path, chunk_type, name, line_range, distance, metadata) instead of
accessing self.collection; reference retrieve_by_file, retrieve_similar,
self.mode, self.collection, and self.keyword_chunks when making the change.
- Around line 96-110: When ContextRetriever is constructed in keyword mode and
the keyword index file ("keyword_chunks.json") is missing, raise an error
instead of silently leaving self.keyword_chunks empty so callers (e.g.,
LLMOrchestrator._ensure_retriever) can detect and auto-build the index; in the
constructor (the ContextRetriever/__init__ or relevant initializer where
self.mode, self.keyword_chunks, and chunk_file are handled), change the else
branch for if chunk_file.exists() to raise a clear exception (FileNotFoundError
or custom) with a message mentioning the missing keyword_chunks.json and that
the index must be built, while keeping the existing JSON load try/except for
malformed files.

---

Minor comments:
In `@refactron/analysis/symbol_table.py`:
- Line 12: Remove the unused Set import from the typing import line: update the
import statement that currently reads "from typing import Any, Dict, List,
Optional, Set" to exclude Set so it becomes "from typing import Any, Dict, List,
Optional"; this will resolve the F401 pre-commit failure for the unused symbol.

In `@refactron/analysis/taint.py`:
- Around line 171-172: The call to self._is_expression_tainted on the assignment
to is_tainted exceeds the 100-char line limit; split the call across multiple
lines or extract the arguments so the expression fits under 100 chars. Locate
the assignment to is_tainted in taint.py (the line using value = stmt.value and
is_tainted = self._is_expression_tainted(...)) and reformat the call so each
argument (value, current_taint, memo) is on its own line or move the arguments
into a short local variable, ensuring the final is_tainted = ... line stays
below 100 characters.

In `@refactron/cli/analysis.py`:
- Line 527: The file still imports ContextRetriever but after switching to
LLMOrchestrator(workspace_path=...) that import is unused and causes F401;
remove the stale ContextRetriever import from the module-level imports (the
symbol named ContextRetriever) so only used imports remain and pre-commit no
longer fails, keeping the LLMOrchestrator usage unchanged.

In `@refactron/cli/refactor.py`:
- Line 530: The file still imports ContextRetriever at module scope but
document() and ai_fix() now instantiate LLMOrchestrator(workspace_path=...), so
remove the unused ContextRetriever import declarations (any top-level import
named ContextRetriever) and any other stale references to ContextRetriever in
this module; ensure imports only include LLMOrchestrator and related symbols
used by document() and ai_fix() and run pre-commit to confirm the unused-import
warning is resolved.
- Around line 620-621: The new console.print messages (e.g., the one guarded by
target_path.is_file()) exceed the 100-character line-length limit; wrap or break
these long string literals so each source line is ≤100 chars (use implicit
adjacent string concatenation or split strings and join with f-strings where
needed) and apply the same fix to the other long messages around the code paths
referenced (the prints at the other occurrences noted). Ensure formatting
preserves the original message text and any color tags (e.g., "[red]...[/red]")
and run pre-commit to verify line-length/formatting checks pass.

In `@refactron/core/parallel.py`:
- Around line 74-80: The docstring for the function that accepts process_func
and progress_callback has an overly long "Returns" line; wrap the return-value
description under 100 characters per line so it complies with the repo's
line-length rules (black/flake8). Edit the triple-quoted docstring block that
lists "Returns:" (the same docstring that documents process_func and
progress_callback) to break the long tuple description into multiple lines,
keeping the tuple format "(successful results, failed files, skip warnings)"
and/or splitting explanatory text across lines to ensure no line exceeds 100
characters.

In `@refactron/core/refactron.py`:
- Line 341: The recovery_suggestion string literal assigned to
recovery_suggestion is too long (flake8 E501); split it into multiple shorter
string literals using implicit concatenation or join a tuple inside parentheses
so the line length stays under 100 characters. Locate the recovery_suggestion
argument (the named parameter recovery_suggestion in the call inside
refactron.py) and break the message into two or more quoted pieces on separate
lines (or use a parenthesized string expression) so the concatenated message
remains identical but each source line is <=100 chars.

In `@refactron/llm/orchestrator.py`:
- Around line 197-200: Extract the duplicated proposed_code trimming logic into
a typed helper, e.g. def clean_proposed_code(proposed_code: str) -> str, that:
checks for a leading code fence, splits on "\n", removes a leading "```" line if
present, removes a trailing "```" line if present, and returns the cleaned
string; replace both inline blocks that operate on the proposed_code variable
with calls to this helper and expand the current single-line if statements (e.g.
if lines[0].startswith("```"): lines.pop(0)) into proper multi-line if blocks
inside the helper to fix the E701 violations and keep behavior identical between
the single-issue and batch code paths.

In `@refactron/rag/indexer.py`:
- Around line 13-18: The sentinel CHROMA_AVAILABLE is currently placed before
the top-level imports which makes the subsequent imports (CodeChunk and
CodeParser) non-top-level and triggers E402; move the CHROMA_AVAILABLE
declaration so it appears after the import block (after the from
refactron.rag.chunker import CodeChunk and from refactron.rag.parser import
CodeParser lines) so all imports remain top-level and the sentinel still exists
for later lazy-loading logic.
- Around line 243-256: The issue is that chunk_dict["metadata"] is inferred too
narrowly for incremental assignment; fix it by building an explicitly typed
mutable metadata dict first (e.g. declare 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]
}) then iterate over chunk.metadata to add entries into that metadata dict, and
finally set chunk_dict = { "content": chunk.content, "metadata": metadata };
also add the needed typing imports (Dict, Any) if not present so mypy accepts
the mutation of metadata.

In `@tests/test_symbol_table_incremental.py`:
- Around line 3-4: Remove the unused imports to satisfy F401: in the import line
that currently pulls in Path and SymbolType, drop Path and SymbolType and keep
only SymbolTableBuilder from refactron.analysis.symbol_table; ensure the file
imports only SymbolTableBuilder (and any other actually used symbols) and run
pre-commit/flake8 to verify the F401 is resolved.

---

Nitpick comments:
In `@refactron/cli/rag.py`:
- Around line 147-156: The code currently constructs a new LLMOrchestrator
inside the per-result loop (when rerank is true), which reinitializes the client
and retriever for every hit; move the instantiation of
LLMOrchestrator(workspace_path=local_path) out of the loop so a single
orchestrator is created once before iterating results and then reuse
orchestrator.client.generate (and any prompt construction) for each result,
ensuring you only create one retriever/client per rerank pass.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 94081d52-283f-4d52-a615-61e60d46b44d

📥 Commits

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

📒 Files selected for processing (20)
  • 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/core/inference.py
  • refactron/core/parallel.py
  • refactron/core/refactron.py
  • refactron/core/workspace.py
  • refactron/llm/backend_client.py
  • refactron/llm/client.py
  • refactron/llm/orchestrator.py
  • refactron/rag/indexer.py
  • refactron/rag/retriever.py
  • tests/test_config_management.py
  • tests/test_performance_optimization.py
  • tests/test_symbol_table_incremental.py

Comment thread bad_code.py
Comment on lines +95 to +112
def remove_file(self, file_path: str) -> None:
"""Remove all symbols and metadata associated with a file."""
norm_path = self._normalize_path(file_path)

if norm_path in self.symbols:
del self.symbols[norm_path]

# 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)

if norm_path in self.file_metadata:
del self.file_metadata[norm_path]

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

Rebuild exports for shadowed names when removing a file.

remove_file() deletes the winning export entry for each removed symbol name, but it never restores another global symbol with the same name from the remaining files. After deleting or reanalyzing one module, resolve_reference() can incorrectly lose a still-existing cross-file export.

🤖 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 95 - 112, remove_file
currently deletes export entries whose Symbol.file_path points to the removed
file but doesn't restore a different global symbol with the same name, causing
resolve_reference to lose valid cross-file exports; update remove_file to, for
each export name removed, scan remaining self.symbols (use self._normalize_path
and the Symbol.name / Symbol.file_path attributes) to find an alternative symbol
with the same name and set self.exports[name] to that symbol (or pop only if
none found), ensuring file_metadata and symbols cleanup remains unchanged.

Comment on lines +183 to +200
def _has_file_changed(self, file_path: Path, file_path_str: str) -> bool:
"""Check if file has changed since last analysis."""
if file_path_str not in self.symbol_table.file_metadata:
return True

metadata = self.symbol_table.file_metadata[file_path_str]
try:
stat = file_path.stat()
if stat.st_size != metadata.get("size"):
return True

# Authoritative check: compare SHA-256 hashes
stored_hash = metadata.get("sha256")
if stored_hash:
current_hash = self._calculate_hash(file_path)
return current_hash != stored_hash

return stat.st_mtime != metadata.get("mtime")

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

Give file_metadata a typed shape before returning these comparisons.

The current Dict[str, Any] metadata is what causes the return stat.st_mtime != metadata.get("mtime") path to fail mypy with no-any-return. Pull the fields into typed locals or model file_metadata with a TypedDict/dataclass before comparing.

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

🧰 Tools
🪛 GitHub Actions: Pre-commit

[error] 198-200: mypy: Returning Any from function declared to return 'bool' [no-any-return]

🤖 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 183 - 200, The metadata dict
returned from symbol_table.file_metadata is untyped which causes mypy failures
in _has_file_changed; change the model to a typed shape (e.g., a TypedDict or
dataclass for the per-file metadata) or cast/pull fields into explicitly typed
locals before comparisons in _has_file_changed; update the type of
symbol_table.file_metadata to use that TypedDict/dataclass and in
_has_file_changed extract typed locals like size, sha256, and mtime (with proper
Optional[type] annotations) and then perform the size/hash/mtime comparisons
(still using _calculate_hash when sha256 exists) so mypy no-any-return errors
are resolved.

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 | 🟠 Major

Add the missing return annotation on _index_sensitive_nodes.

Line 82 is already failing mypy with no-untyped-def. This helper should be declared -> None to satisfy the repo's typing contract.

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, refactron/**/*.py: 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] (use '-> None' if it returns nothing)

🤖 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 in refactron/analysis/taint.py lacks a return type
annotation causing mypy no-untyped-def errors; update its signature to declare a
None return (i.e., change `def _index_sensitive_nodes(self):` to include `->
None`) so the helper satisfies the repo typing contract while leaving the body
unchanged and keeping references to self, data_flow, and _statement_meta intact.

Comment thread refactron/cli/main.py
Comment on lines +37 to +44
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:

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

Keep the global auth gate tied to Refactron credentials.

Treating GROQ_API_KEY as authenticated lets every non-auth CLI command bypass the access-token check, including commands that still depend on the Refactron backend. If local-LLM mode needs exceptions, scope them per command instead of disabling the main gate.

Based on learnings, Applies to refactron/cli/*.py : All CLI commands except login/logout/auth must check for valid access token stored via core/credentials.py before proceeding

🤖 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 - 44, The global auth gate currently
sets is_authenticated = True when os.environ.get("GROQ_API_KEY") is present,
letting all CLI commands bypass the Refactron access-token check; remove or
revert that branch in the startup auth logic around
load_credentials()/is_authenticated so the main gate only trusts
creds.access_token, and instead add explicit per-command checks where local LLM
access is allowed (e.g., in the specific command handlers that need
GROQ_API_KEY) to conditionally permit GROQ_API_KEY-based auth; update references
to load_credentials, is_authenticated, creds.access_token and
os.environ.get("GROQ_API_KEY") accordingly so all refactron/cli/*.py commands
(except login/logout/auth) validate the core/credentials.py stored access token
before proceeding.

Comment thread refactron/llm/orchestrator.py
Comment on lines +277 to +283
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}")

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

Initialize results before the retrieval fallback path.

If retrieve_similar() raises, results is never assigned, and the later context_files=[r.file_path for r in results] turns a recoverable no-context case into a failed batch suggestion.

💡 Minimal fix
-        context_snippets = []
+        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)

Also applies to: 329-332

🧰 Tools
🪛 Ruff (0.15.10)

[warning] 282-282: 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 277 - 283, The retrieval block in
orchestrator.py can leave results undefined if retrieve_similar() throws;
initialize a safe default before the try (e.g., results = [] and/or
context_snippets = []) so later code like context_files=[r.file_path for r in
results] or usage in the surrounding method won’t crash; apply the same fix to
the similar block around the 329-332 section (ensure results/context_snippets
are pre-initialized and the except path logs the error but leaves an empty list
for downstream processing).

Comment thread refactron/rag/indexer.py
Comment on lines +232 to +263
if self.mode == "keyword":
# Keyword mode: append to JSON storage
current_data = []
if self.chunk_storage.exists():
try:
with open(self.chunk_storage, "r", encoding="utf-8") as f:
current_data = json.load(f)
except Exception:
current_data = []

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

current_data.append(chunk_dict)

# Deduplicate or just overwrite for now (simpler)
with open(self.chunk_storage, "w", encoding="utf-8") as f:
json.dump(current_data, f, indent=2)
return

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

Keyword-mode indexing currently duplicates chunks and rewrites the whole index for every file.

index_repository() calls add_chunks() once per file, and this branch reloads keyword_chunks.json, appends new entries, and rewrites the full file each time. Re-indexing the same workspace keeps old chunks around, so fallback retrieval accumulates duplicates and indexing cost grows quadratically. Please clear or rebuild keyword storage once per indexing run, or upsert by a stable chunk id instead.

🧰 Tools
🪛 Ruff (0.15.10)

[warning] 239-239: 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/rag/indexer.py` around lines 232 - 263, The keyword-mode branch in
add_chunks (triggered by self.mode == "keyword") appends to and rewrites
self.chunk_storage every time add_chunks is called by index_repository(),
causing duplicates; change the logic to either (a) clear/rebuild the keyword
storage at the start of an indexing run (callable from index_repository before
iterating files) so add_chunks writes a fresh list, or (b) implement an upsert
by stable chunk id inside add_chunks (derive an id from
file_path+line_range+name or use chunk.id) and replace existing entries instead
of blindly appending; update the code that reads/writes self.chunk_storage
(keyword_chunks.json) accordingly and ensure index_repository triggers the
rebuild path when beginning a full workspace indexing.

Comment on lines +96 to +110
# In keyword mode, we load chunks from the JSON file
if self.mode == "keyword":
self.keyword_chunks = []
chunk_file = self.index_path / "keyword_chunks.json"
if chunk_file.exists():
import json
try:
with open(chunk_file, "r", encoding="utf-8") as f:
self.keyword_chunks = json.load(f)
except Exception as e:
import logging
logging.getLogger(__name__).error(f"Failed to load keyword index: {e}")
else:
# Only raise if we are actually trying to search
pass

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

Raise when no keyword index exists.

In fallback mode this constructor currently succeeds with an empty keyword_chunks list when .rag/keyword_chunks.json is missing. LLMOrchestrator._ensure_retriever() only auto-builds the index when ContextRetriever(...) raises, so keyword-only environments now skip index creation entirely and lose project context until the user runs rag index manually.

🧰 Tools
🪛 Ruff (0.15.10)

[warning] 105-105: 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/rag/retriever.py` around lines 96 - 110, When ContextRetriever is
constructed in keyword mode and the keyword index file ("keyword_chunks.json")
is missing, raise an error instead of silently leaving self.keyword_chunks empty
so callers (e.g., LLMOrchestrator._ensure_retriever) can detect and auto-build
the index; in the constructor (the ContextRetriever/__init__ or relevant
initializer where self.mode, self.keyword_chunks, and chunk_file are handled),
change the else branch for if chunk_file.exists() to raise a clear exception
(FileNotFoundError or custom) with a message mentioning the missing
keyword_chunks.json and that the index must be built, while keeping the existing
JSON load try/except for malformed files.

Comment on lines +125 to +171
if self.mode == "keyword":
if not self.keyword_chunks:
import logging
logging.getLogger(__name__).warning("No keyword chunks found for retrieval.")
return []

# Implementation of simple keyword search
query_terms = set(query.lower().split())
results = []

for chunk in self.keyword_chunks:
# Filter by chunk type if requested
if chunk_type and chunk["metadata"].get("chunk_type") != chunk_type:
continue

content = chunk["content"]
# Simple score: count query terms that appear in content
content_lower = content.lower()
score = 0
for term in query_terms:
if term in content_lower:
score += 1

# Normalize score
if score > 0:
# Invert score to look like 'distance' (lower is better)
results.append((chunk, 1.0 - (score / len(query_terms))))

# Sort by distance (relevance)
results.sort(key=lambda x: x[1])

# Convert to RetrievedContext
retrieved = []
for chunk_data, dist in results[:top_k]:
meta = chunk_data["metadata"]
retrieved.append(
RetrievedContext(
content=chunk_data["content"],
file_path=meta.get("file_path", "unknown"),
chunk_type=meta.get("chunk_type", "unknown"),
name=meta.get("name", "unknown"),
line_range=(meta.get("line_start", 0), meta.get("line_end", 0)),
distance=dist,
metadata=meta,
)
)
return retrieved

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

Complete keyword-mode support for file-scoped retrieval.

retrieve_similar() now handles self.mode == "keyword", but retrieve_by_file() below still unconditionally uses self.collection. Any keyword-mode caller that asks for file-specific context will hit AttributeError. Add the same JSON-backed branch there before exposing keyword fallback more broadly.

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

In `@refactron/rag/retriever.py` around lines 125 - 171, retrieve_by_file
currently always uses self.collection and will raise AttributeError in keyword
mode; add a branch at the start of retrieve_by_file that checks if self.mode ==
"keyword" and, similar to retrieve_similar, iterates self.keyword_chunks
(guarding for empty keyword_chunks), filters chunks by requested file_path and
optional chunk_type, computes or reuse the existing relevance/distance logic,
sorts and slices by top_k, and returns a list of RetrievedContext objects
(populating content, file_path, chunk_type, name, line_range, distance,
metadata) instead of accessing self.collection; reference retrieve_by_file,
retrieve_similar, self.mode, self.collection, and self.keyword_chunks when
making the change.

- Implemented high-performance TaintAnalyzer with sink/source indexing and memoization
- Resolved symbol table cache invalidation and incremental analysis bugs
- Integrated optimized AI refactoring with batching and RAG keyword fallback
- Stabilized CLI for Windows and experimental Python 3.14 environments
@shrutu0929
shrutu0929 force-pushed the feat/llm-rag-optimized branch from 0d7f352 to 08acc44 Compare April 16, 2026 16:08

@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: 8

Caution

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

⚠️ Outside diff range comments (3)
refactron/cli/analysis.py (1)

31-31: ⚠️ Potential issue | 🟡 Minor

Remove unused import.

ContextRetriever is no longer used after delegating RAG initialization to LLMOrchestrator. The pipeline failure confirms this.

🧹 Proposed fix
 from refactron.llm.orchestrator import LLMOrchestrator
-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 now-unused import
ContextRetriever from refactron/cli/analysis.py: delete the line importing
ContextRetriever from refactron.rag.retriever (the symbol ContextRetriever) so
the file no longer references an unused name, then run linters/tests to confirm
no other references remain and commit the updated file.
tests/test_rag_indexer.py (1)

9-9: ⚠️ Potential issue | 🟡 Minor

Remove unused pytest import from line 9.

The pytest module is not explicitly used in this file. While tmp_path fixtures are used throughout the tests, pytest automatically injects fixtures without requiring an explicit import pytest statement.

🤖 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 top-level import
"import pytest" (the unused symbol is the pytest import statement) from the test
module; tests relying on fixtures like tmp_path will still work because pytest
injects fixtures automatically, so simply delete the import line to eliminate
the unused dependency.
refactron/core/refactron.py (1)

277-315: ⚠️ Potential issue | 🔴 Critical

Don't pass a nested wrapper into process mode.

process_file_wrapper is a local nested function defined inside the if block of the analyze method (line 278), but ParallelProcessor._process_parallel_processes() requires a picklable top-level callable. On Windows with spawn mode, this breaks parallel analysis, directly conflicting with the PR's Windows support goal.

Contract and current structure

From refactron/core/parallel.py:200:

process_func must be picklable (top-level function or callable class).

Current structure at refactron/core/refactron.py:278:

if self.parallel_processor.enabled and len(files) > 1:
    def process_file_wrapper(
        file_path: Path,
    ) -> Tuple[
        Optional[FileMetrics], Optional[FileAnalysisError], Optional[AnalysisSkipWarning]
    ]:
        # closure capturing self and other enclosing scope
        ...
    
    file_metrics_list, error_list, skip_warnings = self.parallel_processor.process_files(
        files,
        process_file_wrapper,  # ← nested unpicklable function passed here
    )

Move process_file_wrapper to module-level or refactor to use a callable class to satisfy the picklability requirement.

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

In `@refactron/core/refactron.py` around lines 277 - 315, The nested function
process_file_wrapper inside Refactron.analyze is not picklable and breaks
ParallelProcessor.process_files on Windows (spawn); refactor by extracting
process_file_wrapper into a top-level function or converting it into a picklable
callable class (e.g., FileProcessCallable) that accepts necessary state (refs to
self.incremental_tracker, self._analyze_file, logger) via its initializer, and
then pass that top-level callable into ParallelProcessor.process_files instead
of the nested function so the worker can be pickled across processes.
♻️ Duplicate comments (10)
bad_code.py (1)

19-19: ⚠️ Potential issue | 🟠 Major

Guard top-level execution to avoid import-time side effects.

Line 19 runs immediately on import and can cause unwanted stdout/log noise for any consumer importing this module.

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 file currently invokes do_something_crazy(10, 5)
at import time; wrap this top-level call in a guarded entrypoint so importing
the module has no side effects. Move the call into a small main() function (or
reuse an existing one) and call it only inside if __name__ == "__main__": so
do_something_crazy is executed only when the module is run as a script.
refactron/analysis/symbol_table.py (2)

65-66: ⚠️ Potential issue | 🟠 Major

Give file_metadata a typed shape.

The nested Dict[str, Any] is still what causes the mypy failure on Line 200 (no-any-return). Model the metadata with a TypedDict or dataclass, then compare typed mtime/size/sha256 fields in _has_file_changed().

Suggested fix
-from typing import Any, Dict, List, Optional
+from typing import Any, Dict, List, Optional, TypedDict
...
+class FileMetadata(TypedDict):
+    mtime: float
+    size: int
+    sha256: str
+
...
-    file_metadata: Dict[str, Dict[str, Any]] = field(default_factory=dict)
+    file_metadata: Dict[str, FileMetadata] = field(default_factory=dict)
...
         metadata = self.symbol_table.file_metadata[file_path_str]
         try:
             stat = file_path.stat()
-            if stat.st_size != metadata.get("size"):
+            stored_size: int = metadata["size"]
+            if stat.st_size != stored_size:
                 return True
 ...
-            stored_hash = metadata.get("sha256")
+            stored_hash: str = metadata["sha256"]
             if stored_hash:
                 current_hash = self._calculate_hash(file_path)
                 return current_hash != stored_hash
 ...
-            return stat.st_mtime != metadata.get("mtime")
+            stored_mtime: float = metadata["mtime"]
+            return stat.st_mtime != stored_mtime
As per coding guidelines, `refactron/**/*.py`: Type annotations are required in refactron/ with mypy disallow_untyped_defs = true enabled.

Also applies to: 183-200

🤖 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 65 - 66, Define a concrete
type for the file metadata (e.g., a TypedDict or small dataclass with fields
mtime: float, size: int, sha256: str), replace the current file_metadata:
Dict[str, Dict[str, Any]] annotation with Dict[str, FileMetadata] (or the
dataclass type), and update _has_file_changed() to access and compare the typed
fields (metadata['mtime'], metadata['size'], metadata['sha256']) instead of
using Any; also update other usages in the same module (around the 183–200
logic) to use the new type so mypy no-any-return errors are resolved.

95-112: ⚠️ Potential issue | 🟠 Major

Restore shadowed exports when removing a file.

remove_file() deletes the current export entry for names owned by the removed file, but it never reinstates another global symbol with the same name from the remaining files. After a delete or re-analysis, resolve_reference() can lose a still-valid cross-file export.

Suggested fix
     def remove_file(self, file_path: str) -> None:
         """Remove all symbols and metadata associated with a file."""
         norm_path = self._normalize_path(file_path)
 ...
         for name in names_to_remove:
-            self.exports.pop(name, None)
+            self.exports.pop(name, None)
+            replacement = None
+            for other_path, scopes in self.symbols.items():
+                if other_path == norm_path:
+                    continue
+                replacement = scopes.get("global", {}).get(name)
+                if replacement is not None:
+                    break
+            if replacement is not None:
+                self.exports[name] = replacement
🤖 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 95 - 112, remove_file
currently deletes export entries owned by the removed file but doesn't restore
any other symbol with the same name from remaining files, causing
resolve_reference to lose valid cross-file exports; update remove_file to, after
collecting names_to_remove and removing the entries, scan the remaining
self.symbols (iterate over all symbol collections in self.symbols) for each
removed name and if another symbol with the same symbol.name exists set
self.exports[name] to that remaining symbol (prefer any remaining global/export
candidate you find, e.g., first match) so exports are reinstated; reference
functions/fields: remove_file, self.symbols, self.exports, resolve_reference.
refactron/core/inference.py (1)

35-90: ⚠️ Potential issue | 🔴 Critical

Serialize astroid.MANAGER cache mutation in parse_file().

parse_file() clears and rebuilds global astroid caches on the singleton manager without any synchronization. With the batch/parallel analysis work in this PR, concurrent callers can evict or repopulate the same manager state mid-parse and return corrupt or nondeterministic ASTs.

Suggested fix
+import threading
...
+ASTROID_MANAGER_LOCK = threading.Lock()
...
     def parse_file(file_path: str) -> nodes.Module:
         """Parse a file into an astroid node tree."""
-        # Use canonical path (resolved and posix-style for consistency)
-        abs_path = Path(file_path).resolve().as_posix()
-        manager = astroid.MANAGER
-        ...
-        try:
-            with open(abs_path, "r", encoding="utf-8") as f:
-                code = f.read()
-            ...
-            return astroid.parse(code, module_name=modname, path=abs_path)
-        except (OSError, UnicodeDecodeError):
-            ...
+        with ASTROID_MANAGER_LOCK:
+            # existing cache eviction + parse/fallback sequence
+            ...
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@refactron/core/inference.py` around lines 35 - 90, parse_file() mutates the
global astroid.MANAGER caches concurrently which can cause race conditions;
serialize those operations by introducing a module-level lock (e.g.,
threading.RLock or Lock named something like _ASTROID_MANAGER_LOCK) and acquire
it around every block that reads or mutates manager.astroid_cache,
manager.file_to_module_cache/_mod_file_cache, and the fallback call
manager.ast_from_file, as well as the manual read/astroid.parse sequence so the
cache clears and rebuild are atomic. Ensure the lock is held for the entire
sequence of cache pops, module-name resolution, manual parse fallback and any
manager.ast_from_file calls and is always released (use context manager or
try/finally) to prevent deadlocks; update parse_file to reference the new lock
before touching astroid.MANAGER.
refactron/llm/orchestrator.py (1)

145-155: ⚠️ Potential issue | 🟠 Major

Initialize results before the retrieval fallback path.

Both methods log and continue when retrieve_similar() fails, but later build context_files from results. If retrieval raised, results is unbound and the suggestion path fails instead of degrading cleanly to “no context”. Seed results = [] before each try block.

Also applies to: 282-291

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

In `@refactron/llm/orchestrator.py` around lines 145 - 155, The try/except around
calls to self.retriever.retrieve_similar leaves the local variable results
potentially uninitialized when an exception occurs; seed results = []
immediately before each try that calls retrieve_similar (e.g., the block
populating context_snippets around the code using results and the similar block
later at lines ~282-291) so downstream code that builds context_files can safely
degrade to an empty list when retrieval fails; update both occurrences in
orchestrator.py to set results = [] just before the retrieve_similar(...) call.
refactron/rag/indexer.py (1)

252-282: ⚠️ Potential issue | 🟠 Major

Rebuilding keyword storage per file still duplicates chunks.

index_repository() calls add_chunks() once per file, and this branch still reloads keyword_chunks.json, appends new entries, and rewrites the whole file each time. Re-indexing the same workspace will accumulate duplicates and make keyword-mode indexing increasingly expensive.

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

In `@refactron/rag/indexer.py` around lines 252 - 282, index_repository() calls
add_chunks() per file causing keyword-mode to append duplicates to
self.chunk_storage; change the "keyword" branch in add_chunks() to de-duplicate
before writing: after loading current_data, build a set of existing chunk keys
(e.g. tuple of (file_path, line_start, line_end) or a hash of
content+file_path+line_range) from current_data, then for each chunk in chunks
compute the same key and only append chunk_dict if the key is not already in the
set (add the key to the set when appended). Ensure you still write the final
deduplicated current_data back to self.chunk_storage with json.dump.
refactron/rag/retriever.py (2)

100-116: ⚠️ Potential issue | 🟠 Major

Raise when keyword fallback has no index to load.

In keyword mode this constructor still succeeds with an empty keyword_chunks list when .rag/keyword_chunks.json is missing. That prevents callers like LLMOrchestrator._ensure_retriever() from detecting the missing index and auto-building it, so project context stays disabled until the user runs indexing manually.

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

In `@refactron/rag/retriever.py` around lines 100 - 116, When self.mode ==
"keyword" the constructor currently leaves self.keyword_chunks empty if
index_path/"keyword_chunks.json" is missing; change Retriever's constructor (the
__init__ that references self.mode, self.keyword_chunks and self.index_path) to
raise a clear exception (e.g., FileNotFoundError or a custom MissingIndexError)
when chunk_file does not exist so callers like
LLMOrchestrator._ensure_retriever() can detect the missing index and trigger
auto-building; likewise, when json.load fails, re-raise or wrap the error
instead of only logging it so failures to parse the index are surfaced to
callers.

216-226: ⚠️ Potential issue | 🟠 Major

Handle retrieve_by_file() in keyword mode.

retrieve_similar() now has a keyword-path, but retrieve_by_file() still unconditionally calls self.collection.get(...). Any keyword-mode caller that asks for file-scoped context will hit AttributeError here.

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

In `@refactron/rag/retriever.py` around lines 216 - 226, retrieve_by_file
currently always calls self.collection.get(...) which breaks in keyword mode;
update retrieve_by_file to mirror the branching logic used in retrieve_similar:
detect keyword mode the same way retrieve_similar does (e.g., self.keyword_mode
or the same attribute/flag) and, when not in keyword mode, keep using
self.collection.get(where={"file_path": file_path}), but when in keyword mode
call the keyword-path collection API used by retrieve_similar (the method
invoked in retrieve_similar for keyword-mode queries—e.g., collection.query or
the keyword-specific get method) to filter by file_path metadata, then convert
the results to List[RetrievedContext] as retrieve_by_file currently does.
refactron/cli/refactor.py (2)

691-712: ⚠️ Potential issue | 🟠 Major

Don't print “verified and applied” after a skip or failed apply.

The footer is still unconditional, so the command reports success even when the user declines the change or write_text() fails. Make the final status message depend on the actual apply outcome.

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

In `@refactron/cli/refactor.py` around lines 691 - 712, The final status message
is printed unconditionally even when the change was skipped or applying failed;
update the logic around do_apply/try/except so you track whether the apply
actually succeeded (e.g., set a boolean like apply_succeeded = False before the
try, set True after target_path.write_text(...) and successful console prints,
and leave False on exception or when interactive skip), then only print the
"[bold]AI Auto-Fix Complete.[/bold] Verified and applied unified refactoring."
footer when apply_succeeded is True; otherwise print a clear skipped/failed
footer inside the else/except paths using the existing console.print calls and
preserve existing backup/session_id behavior (references: do_apply,
BackupRollbackSystem/backup_sys, session_id, target_path.write_text,
console.print).

639-641: ⚠️ Potential issue | 🟠 Major

Match issues to the target file by normalized equality.

str(target_path) in str(i.file_path) is still a substring check, so it can pull in issues from the wrong file or miss the right one when path forms differ. Normalize both paths and compare for equality before batching fixes.

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

In `@refactron/cli/refactor.py` around lines 639 - 641, The current substring
check using str(target_path) in str(i.file_path) can match wrong files; instead
convert both sides to pathlib.Path and compare normalized resolved paths for
equality: create Path objects (e.g., Path(target_path).resolve() and
Path(i.file_path).resolve()) and use == to filter result.all_issues into issues
(then assign to issues_to_fix); ensure you handle potential exceptions from
resolve() on non-existent paths by falling back to .absolute() or
.normpath-equivalent before comparison.
🧹 Nitpick comments (5)
bad_code.py (1)

16-16: Rename unused loop variable to _ to satisfy Ruff B007.

Line 16 declares i but never uses it in the loop body.

Proposed fix
-            for i in range(ITERATION_LIMIT):
+            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` at line 16, The loop declares an unused variable i in the header
"for i in range(ITERATION_LIMIT):" which triggers Ruff B007; fix by renaming the
loop variable to the throwaway name underscore (_) in that for-loop header so
the variable is explicitly unused (or use a distinct underscore name like _i if
needed for clarity), leaving the loop body unchanged and keeping ITERATION_LIMIT
as the iterator.
refactron/core/config.py (1)

249-249: Unnecessary getattr for declared dataclass field.

enable_llm_triage is a declared field with a default value, so it always exists on the instance. Use direct attribute access like the other fields for consistency.

♻️ Proposed fix
-            "enable_llm_triage": getattr(self, "enable_llm_triage", False),
+            "enable_llm_triage": self.enable_llm_triage,
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@refactron/core/config.py` at line 249, The line using getattr(self,
"enable_llm_triage", False) should be changed to direct attribute access to
match the other dataclass fields; replace getattr usage for enable_llm_triage
with self.enable_llm_triage (or just enable_llm_triage in the same dict
construction) so the declared dataclass field is accessed consistently (refer to
the enable_llm_triage attribute in refactron/core/config.py and the surrounding
dict construction where other fields are accessed directly).
refactron/cli/utils.py (1)

117-118: Overly broad exception catch masks potential bugs.

Widening from ImportError to bare Exception will now silently swallow configuration errors, AttributeError, or other unexpected failures in the transformers logging setup. Consider catching a more specific set of exceptions (e.g., ImportError, AttributeError) or at minimum log a debug-level message.

♻️ Proposed fix
         try:
             from transformers import logging as tf_logging
 
             tf_logging.set_verbosity_error()
-        except Exception:
-            pass
+        except (ImportError, AttributeError):
+            pass  # transformers not installed or API changed
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@refactron/cli/utils.py` around lines 117 - 118, The bare "except Exception:
pass" in the transformers logging setup should be narrowed and made observable:
replace the broad except with specific exceptions (at minimum ImportError and
AttributeError) when importing/configuring transformers logging and emit a
debug-level log message with the exception details instead of silently passing;
locate the try/except around the transformers import/configuration (the block
containing "except Exception:" and any "transformers" references) and change it
to catch ImportError, AttributeError (and optionally ModuleNotFoundError) and
call the module logger's debug() with the exception message so failures are not
silently swallowed.
tests/test_config_management.py (1)

906-918: Consider prefixing unused variables with underscore.

Static analysis flags several unpacked variables as unused. While the tests are correct, prefixing with _ signals intent and silences warnings.

♻️ Example fix for one case
-    results, errors, skips = p_thr.process_files(files, lambda p: (None, None, None))
+    results, errors, _skips = p_thr.process_files(files, lambda p: (None, None, None))

Apply similar pattern to other instances where variables are unpacked but not used.

🤖 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 906 - 918, In the test cases,
some unpacked return variables from ParallelProcessor.process_files are
intentionally unused but flagged by static analysis; update those variable names
to use the underscore prefix (e.g., rename unused results/errors/skips to
_results, _errors, _skips) wherever you call p_seq.process_files or
p_thr.process_files (and in the lambda return tuples if applicable) to signal
intentional unused variables and silence warnings while keeping assertions that
rely on the used variables (e.g., keep errors or results named if they are
asserted).
refactron/cli/rag.py (1)

149-160: Instantiating orchestrator inside loop is inefficient.

A new LLMOrchestrator is created for each search result when reranking is enabled. Move the instantiation outside the loop to reuse the same client.

♻️ Proposed fix
+        # Initialize orchestrator once for reranking if needed
+        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:
+            if rerank and orchestrator:
                 try:
-                    orchestrator = LLMOrchestrator(workspace_path=local_path)
                     prompt = (  # noqa: E501
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@refactron/cli/rag.py` around lines 149 - 160, The code creates a new
LLMOrchestrator for every search result (inside the loop) which is inefficient;
move the instantiation of LLMOrchestrator(workspace_path=local_path) outside the
loop so the same orchestrator and its client are reused for all calls to
orchestrator.client.generate, and ensure any variables it depends on (e.g.,
local_path) are available before the loop; update references to
orchestrator.client.generate within the loop to use the single shared
orchestrator instance.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@bad_code.py`:
- Around line 1-19: Run the project pre-commit hooks (or run black with
target-version py38,py39,py310,py311 and end-of-file-fixer) on this file to fix
formatting and ensure a single trailing newline at EOF; specifically format the
module-level constants (THRESHOLD_VALUE, MIN_X_VALUE, MAX_Y_VALUE,
ITERATION_LIMIT, MIN_ITERATION_VALUE, MAX_ITERATION_VALUE) and the
do_something_crazy function to match Black's style, and ensure no lines exceed
100 characters for flake8 compliance before committing.

In `@refactron/analysis/symbol_table.py`:
- Around line 172-176: The file metadata is updated even when _analyze_file()
fails (it swallows exceptions), causing files to be marked up-to-date with
missing symbols; change the logic so _update_file_metadata(path) runs only after
a successful analysis: either make _analyze_file(abs_path) return a success
boolean (or re-raise errors) and call _update_file_metadata(abs_path, path_str)
only when that returns True, or let _analyze_file propagate exceptions and wrap
the caller to update metadata in the try block after a successful call; apply
the same fix to the other occurrence that wraps _analyze_file() (the block
covering lines ~223-230), keeping the symbol_table.remove_file(path_str)
behavior unchanged.
- Line 12: The import list in symbol_table.py includes an unused symbol "Set"
causing an F401 pre-commit failure; remove "Set" from the from typing import
line (the import that currently reads "from typing import Any, Dict, List,
Optional, Set") so only the actually used types (Any, Dict, List, Optional)
remain, then run the pre-commit checks to verify the F401 is resolved.

In `@refactron/cli/refactor.py`:
- Around line 602-617: ai_fix currently proceeds without verifying stored
credentials; import and call the token validation helper from core.credentials
(e.g., validate_access_token or equivalent function) at the start of ai_fix
before any work (before _load_config/_setup_logging). If the validation fails,
print a clear error to console and abort (return or sys.exit(1)) so
unauthenticated users cannot reach the LLM/refactoring flow; ensure the import
and call reference core/credentials.py and the ai_fix function name to locate
the change.
- Around line 698-705: The backup step currently ignores
prepare_for_refactoring()'s failed_files and proceeds to write the suggestion;
change the flow to abort applying changes when any backups failed: call
BackupRollbackSystem.prepare_for_refactoring(...) (as you already do) and
inspect the returned failed_files (the second return value), and if failed_files
is non-empty or contains the target_path, do not call target_path.write_text;
instead log/print an error including session_id and failed_files and
return/raise to halt the refactor command so the rollback guarantee remains
intact. Ensure references are to BackupRollbackSystem, prepare_for_refactoring,
session_id/failed_files and target_path.write_text when implementing the check.

In `@refactron/llm/prompts.py`:
- Around line 79-94: The long prompt string BATCH_SUGGESTION_PROMPT contains a
line exceeding 100 characters (the "Provide a single, comprehensive fix..."
sentence); shorten or wrap that sentence inside the triple-quoted string so no
line is >100 chars—e.g., split into two shorter lines or rephrase to a more
concise sentence while preserving the meaning and keeping
BATCH_SUGGESTION_PROMPT intact.
- Around line 60-77: The BATCH_SUGGESTION_SYSTEM_PROMPT multi-line string
exceeds the 100-character line limit; reflow its content so no physical source
line is longer than 100 chars while preserving exact text, escapes (e.g., \\\"
and \\n) and semantics. Edit the BATCH_SUGGESTION_SYSTEM_PROMPT constant in
refactron/llm/prompts.py to break long sentences into shorter concatenated or
implicitly adjacent string literals (or insert explicit \\n sequences) so each
source line is ≤100 chars, keeping the JSON output requirements and phraseology
identical.

In `@tests/test_symbol_table_incremental.py`:
- Around line 1-4: The import block has unused names and wrong grouping causing
pre-commit failures: remove unused imports Path and SymbolType from the
from-import (keep SymbolTableBuilder), and ensure stdlib imports (e.g., import
os if os.utime is used) are in the top-level stdlib group so isort passes; if
os.utime is not used, drop the os import entirely. Update the imports in
tests/test_symbol_table_incremental.py accordingly.

---

Outside diff comments:
In `@refactron/cli/analysis.py`:
- Line 31: Remove the now-unused import ContextRetriever from
refactron/cli/analysis.py: delete the line importing ContextRetriever from
refactron.rag.retriever (the symbol ContextRetriever) so the file no longer
references an unused name, then run linters/tests to confirm no other references
remain and commit the updated file.

In `@refactron/core/refactron.py`:
- Around line 277-315: The nested function process_file_wrapper inside
Refactron.analyze is not picklable and breaks ParallelProcessor.process_files on
Windows (spawn); refactor by extracting process_file_wrapper into a top-level
function or converting it into a picklable callable class (e.g.,
FileProcessCallable) that accepts necessary state (refs to
self.incremental_tracker, self._analyze_file, logger) via its initializer, and
then pass that top-level callable into ParallelProcessor.process_files instead
of the nested function so the worker can be pickled across processes.

In `@tests/test_rag_indexer.py`:
- Line 9: Remove the unused top-level import "import pytest" (the unused symbol
is the pytest import statement) from the test module; tests relying on fixtures
like tmp_path will still work because pytest injects fixtures automatically, so
simply delete the import line to eliminate the unused dependency.

---

Duplicate comments:
In `@bad_code.py`:
- Line 19: The file currently invokes do_something_crazy(10, 5) at import time;
wrap this top-level call in a guarded entrypoint so importing the module has no
side effects. Move the call into a small main() function (or reuse an existing
one) and call it only inside if __name__ == "__main__": so do_something_crazy is
executed only when the module is run as a script.

In `@refactron/analysis/symbol_table.py`:
- Around line 65-66: Define a concrete type for the file metadata (e.g., a
TypedDict or small dataclass with fields mtime: float, size: int, sha256: str),
replace the current file_metadata: Dict[str, Dict[str, Any]] annotation with
Dict[str, FileMetadata] (or the dataclass type), and update _has_file_changed()
to access and compare the typed fields (metadata['mtime'], metadata['size'],
metadata['sha256']) instead of using Any; also update other usages in the same
module (around the 183–200 logic) to use the new type so mypy no-any-return
errors are resolved.
- Around line 95-112: remove_file currently deletes export entries owned by the
removed file but doesn't restore any other symbol with the same name from
remaining files, causing resolve_reference to lose valid cross-file exports;
update remove_file to, after collecting names_to_remove and removing the
entries, scan the remaining self.symbols (iterate over all symbol collections in
self.symbols) for each removed name and if another symbol with the same
symbol.name exists set self.exports[name] to that remaining symbol (prefer any
remaining global/export candidate you find, e.g., first match) so exports are
reinstated; reference functions/fields: remove_file, self.symbols, self.exports,
resolve_reference.

In `@refactron/cli/refactor.py`:
- Around line 691-712: The final status message is printed unconditionally even
when the change was skipped or applying failed; update the logic around
do_apply/try/except so you track whether the apply actually succeeded (e.g., set
a boolean like apply_succeeded = False before the try, set True after
target_path.write_text(...) and successful console prints, and leave False on
exception or when interactive skip), then only print the "[bold]AI Auto-Fix
Complete.[/bold] Verified and applied unified refactoring." footer when
apply_succeeded is True; otherwise print a clear skipped/failed footer inside
the else/except paths using the existing console.print calls and preserve
existing backup/session_id behavior (references: do_apply,
BackupRollbackSystem/backup_sys, session_id, target_path.write_text,
console.print).
- Around line 639-641: The current substring check using str(target_path) in
str(i.file_path) can match wrong files; instead convert both sides to
pathlib.Path and compare normalized resolved paths for equality: create Path
objects (e.g., Path(target_path).resolve() and Path(i.file_path).resolve()) and
use == to filter result.all_issues into issues (then assign to issues_to_fix);
ensure you handle potential exceptions from resolve() on non-existent paths by
falling back to .absolute() or .normpath-equivalent before comparison.

In `@refactron/core/inference.py`:
- Around line 35-90: parse_file() mutates the global astroid.MANAGER caches
concurrently which can cause race conditions; serialize those operations by
introducing a module-level lock (e.g., threading.RLock or Lock named something
like _ASTROID_MANAGER_LOCK) and acquire it around every block that reads or
mutates manager.astroid_cache, manager.file_to_module_cache/_mod_file_cache, and
the fallback call manager.ast_from_file, as well as the manual
read/astroid.parse sequence so the cache clears and rebuild are atomic. Ensure
the lock is held for the entire sequence of cache pops, module-name resolution,
manual parse fallback and any manager.ast_from_file calls and is always released
(use context manager or try/finally) to prevent deadlocks; update parse_file to
reference the new lock before touching astroid.MANAGER.

In `@refactron/llm/orchestrator.py`:
- Around line 145-155: The try/except around calls to
self.retriever.retrieve_similar leaves the local variable results potentially
uninitialized when an exception occurs; seed results = [] immediately before
each try that calls retrieve_similar (e.g., the block populating
context_snippets around the code using results and the similar block later at
lines ~282-291) so downstream code that builds context_files can safely degrade
to an empty list when retrieval fails; update both occurrences in
orchestrator.py to set results = [] just before the retrieve_similar(...) call.

In `@refactron/rag/indexer.py`:
- Around line 252-282: index_repository() calls add_chunks() per file causing
keyword-mode to append duplicates to self.chunk_storage; change the "keyword"
branch in add_chunks() to de-duplicate before writing: after loading
current_data, build a set of existing chunk keys (e.g. tuple of (file_path,
line_start, line_end) or a hash of content+file_path+line_range) from
current_data, then for each chunk in chunks compute the same key and only append
chunk_dict if the key is not already in the set (add the key to the set when
appended). Ensure you still write the final deduplicated current_data back to
self.chunk_storage with json.dump.

In `@refactron/rag/retriever.py`:
- Around line 100-116: When self.mode == "keyword" the constructor currently
leaves self.keyword_chunks empty if index_path/"keyword_chunks.json" is missing;
change Retriever's constructor (the __init__ that references self.mode,
self.keyword_chunks and self.index_path) to raise a clear exception (e.g.,
FileNotFoundError or a custom MissingIndexError) when chunk_file does not exist
so callers like LLMOrchestrator._ensure_retriever() can detect the missing index
and trigger auto-building; likewise, when json.load fails, re-raise or wrap the
error instead of only logging it so failures to parse the index are surfaced to
callers.
- Around line 216-226: retrieve_by_file currently always calls
self.collection.get(...) which breaks in keyword mode; update retrieve_by_file
to mirror the branching logic used in retrieve_similar: detect keyword mode the
same way retrieve_similar does (e.g., self.keyword_mode or the same
attribute/flag) and, when not in keyword mode, keep using
self.collection.get(where={"file_path": file_path}), but when in keyword mode
call the keyword-path collection API used by retrieve_similar (the method
invoked in retrieve_similar for keyword-mode queries—e.g., collection.query or
the keyword-specific get method) to filter by file_path metadata, then convert
the results to List[RetrievedContext] as retrieve_by_file currently does.

---

Nitpick comments:
In `@bad_code.py`:
- Line 16: The loop declares an unused variable i in the header "for i in
range(ITERATION_LIMIT):" which triggers Ruff B007; fix by renaming the loop
variable to the throwaway name underscore (_) in that for-loop header so the
variable is explicitly unused (or use a distinct underscore name like _i if
needed for clarity), leaving the loop body unchanged and keeping ITERATION_LIMIT
as the iterator.

In `@refactron/cli/rag.py`:
- Around line 149-160: The code creates a new LLMOrchestrator for every search
result (inside the loop) which is inefficient; move the instantiation of
LLMOrchestrator(workspace_path=local_path) outside the loop so the same
orchestrator and its client are reused for all calls to
orchestrator.client.generate, and ensure any variables it depends on (e.g.,
local_path) are available before the loop; update references to
orchestrator.client.generate within the loop to use the single shared
orchestrator instance.

In `@refactron/cli/utils.py`:
- Around line 117-118: The bare "except Exception: pass" in the transformers
logging setup should be narrowed and made observable: replace the broad except
with specific exceptions (at minimum ImportError and AttributeError) when
importing/configuring transformers logging and emit a debug-level log message
with the exception details instead of silently passing; locate the try/except
around the transformers import/configuration (the block containing "except
Exception:" and any "transformers" references) and change it to catch
ImportError, AttributeError (and optionally ModuleNotFoundError) and call the
module logger's debug() with the exception message so failures are not silently
swallowed.

In `@refactron/core/config.py`:
- Line 249: The line using getattr(self, "enable_llm_triage", False) should be
changed to direct attribute access to match the other dataclass fields; replace
getattr usage for enable_llm_triage with self.enable_llm_triage (or just
enable_llm_triage in the same dict construction) so the declared dataclass field
is accessed consistently (refer to the enable_llm_triage attribute in
refactron/core/config.py and the surrounding dict construction where other
fields are accessed directly).

In `@tests/test_config_management.py`:
- Around line 906-918: In the test cases, some unpacked return variables from
ParallelProcessor.process_files are intentionally unused but flagged by static
analysis; update those variable names to use the underscore prefix (e.g., rename
unused results/errors/skips to _results, _errors, _skips) wherever you call
p_seq.process_files or p_thr.process_files (and in the lambda return tuples if
applicable) to signal intentional unused variables and silence warnings while
keeping assertions that rely on the used variables (e.g., keep errors or results
named if they are asserted).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: def95fad-aa8d-4f27-b1b4-00481170166b

📥 Commits

Reviewing files that changed from the base of the PR and between 0d7f352 and 9897c90.

📒 Files selected for processing (27)
  • 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/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_rag_indexer.py
  • tests/test_rag_retriever.py
  • tests/test_symbol_table_incremental.py
✅ Files skipped from review due to trivial changes (2)
  • refactron/llm/backend_client.py
  • tests/test_groq_client.py
🚧 Files skipped from review as they are similar to previous changes (6)
  • refactron/llm/client.py
  • tests/test_performance_optimization.py
  • refactron/cli/ui.py
  • refactron/core/workspace.py
  • refactron/analysis/taint.py
  • refactron/cli/main.py

Comment thread bad_code.py
Comment on lines +1 to +19
THRESHOLD_VALUE = 5
MIN_X_VALUE = 5
MAX_Y_VALUE = 10
ITERATION_LIMIT = 100
MIN_ITERATION_VALUE = 10
MAX_ITERATION_VALUE = 5
def do_something_crazy(x: int, y: int) -> int:
"""
This function performs a series of operations based on the input values x and y.
It checks if x is greater than the threshold value and y is less than the max y value.
If the conditions are met, it iterates over a range of numbers and prints a message.
Finally, it returns the sum of x and y.
"""
if x > THRESHOLD_VALUE:
if y < MAX_Y_VALUE:
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 | 🟡 Minor

Fix formatting/newline so pre-commit passes.

This file is currently failing black and end-of-file-fixer in CI; please run pre-commit formatting on the file before merge.
As per coding guidelines: Use black formatter with target-version set to py38, py39, py310, py311; Flake8 linting must use max-line-length of 100.

🧰 Tools
🪛 GitHub Actions: Pre-commit

[error] 1-1: pre-commit hook 'end-of-file-fixer' failed (exit code 1): files were modified by this hook (Fixing bad_code.py).


[error] 1-1: pre-commit hook 'black' failed: reformatted bad_code.py.

🪛 Ruff (0.15.10)

[warning] 16-16: Loop control variable i not used within loop body

Rename unused i to _i

(B007)

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

In `@bad_code.py` around lines 1 - 19, Run the project pre-commit hooks (or run
black with target-version py38,py39,py310,py311 and end-of-file-fixer) on this
file to fix formatting and ensure a single trailing newline at EOF; specifically
format the module-level constants (THRESHOLD_VALUE, MIN_X_VALUE, MAX_Y_VALUE,
ITERATION_LIMIT, MIN_ITERATION_VALUE, MAX_ITERATION_VALUE) and the
do_something_crazy function to match Black's style, and ensure no lines exceed
100 characters for flake8 compliance before committing.

from enum import Enum
from pathlib import Path
from typing import Any, Dict, List, Optional
from typing import Any, Dict, List, Optional, Set

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 | 🟡 Minor

Remove the unused Set import.

Pre-commit is already failing on Line 12 with F401.

Suggested fix
-from typing import Any, Dict, List, Optional, Set
+from typing import Any, Dict, List, Optional
📝 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 typing import Any, Dict, List, Optional, Set
from typing import Any, Dict, List, Optional
🧰 Tools
🪛 GitHub Actions: Pre-commit

[error] 12-12: flake8: F401 'typing.Set' imported but unused

🤖 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 list in
symbol_table.py includes an unused symbol "Set" causing an F401 pre-commit
failure; remove "Set" from the from typing import line (the import that
currently reads "from typing import Any, Dict, List, Optional, Set") so only the
actually used types (Any, Dict, List, Optional) remain, then run the pre-commit
checks to verify the F401 is resolved.

Comment on lines +172 to +176
if self._has_file_changed(abs_path, path_str):
logger.debug(f"Analyzing changed file: {path_str}")
self.symbol_table.remove_file(path_str)
self._analyze_file(abs_path)
self._update_file_metadata(abs_path, path_str)

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

Only refresh file_metadata after a successful analysis.

Line 176 runs even when _analyze_file() failed, because _analyze_file() swallows the exception at Lines 226-230. That marks the file as up to date, so later incremental builds skip it and its symbols stay missing until the file changes again.

Suggested fix
         for file_path in python_files:
             abs_path = file_path.resolve()
             path_str = abs_path.as_posix()
             if self._has_file_changed(abs_path, path_str):
                 logger.debug(f"Analyzing changed file: {path_str}")
                 self.symbol_table.remove_file(path_str)
-                self._analyze_file(abs_path)
-                self._update_file_metadata(abs_path, path_str)
+                if self._analyze_file(abs_path):
+                    self._update_file_metadata(abs_path, path_str)
...
-    def _analyze_file(self, file_path: Path) -> None:
+    def _analyze_file(self, file_path: Path) -> bool:
         """Analyze a single file and populate symbols."""
         path_str = file_path.resolve().as_posix()
         try:
             tree = self.inference_engine.parse_file(path_str)
             self._visit_node(tree, path_str, "global")
+            return True
         except Exception as e:
             logger.warning(f"Failed to build symbol table for {path_str}: {e}")
+            return False

Also applies to: 223-230

🤖 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 172 - 176, The file metadata
is updated even when _analyze_file() fails (it swallows exceptions), causing
files to be marked up-to-date with missing symbols; change the logic so
_update_file_metadata(path) runs only after a successful analysis: either make
_analyze_file(abs_path) return a success boolean (or re-raise errors) and call
_update_file_metadata(abs_path, path_str) only when that returns True, or let
_analyze_file propagate exceptions and wrap the caller to update metadata in the
try block after a successful call; apply the same fix to the other occurrence
that wraps _analyze_file() (the block covering lines ~223-230), keeping the
symbol_table.remove_file(path_str) behavior unchanged.

Comment thread refactron/cli/refactor.py
Comment on lines +602 to +617
def ai_fix(target: str, config: Optional[str], apply: bool, interactive: bool) -> None:
"""
Orchestrate automated AI fixes for code issues.

Analyzes the specified TARGET file, finds critical code issues,
and runs the LLM Orchestrator to suggest and apply fixes.
"""
console.print()
_auth_banner("AI Auto-Fix Orchestrator")
console.print()

# 1. Setup
cfg = _load_config(config)
cfg.enable_incremental_analysis = False
_setup_logging()

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

Gate ai_fix on the stored access token.

This command starts loading config and running analysis without the required credential check, so unauthenticated users can still reach the LLM/refactoring flow. Add the core/credentials.py token validation before any work begins.

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

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

In `@refactron/cli/refactor.py` around lines 602 - 617, ai_fix currently proceeds
without verifying stored credentials; import and call the token validation
helper from core.credentials (e.g., validate_access_token or equivalent
function) at the start of ai_fix before any work (before
_load_config/_setup_logging). If the validation fails, print a clear error to
console and abort (return or sys.exit(1)) so unauthenticated users cannot reach
the LLM/refactoring flow; ensure the import and call reference
core/credentials.py and the ai_fix function name to locate the change.

Comment thread refactron/cli/refactor.py
Comment on lines +698 to +705
backup_sys = BackupRollbackSystem(workspace_path)
session_id, _ = backup_sys.prepare_for_refactoring(
[target_path], description=f"AI Batch Fix for {len(issues_to_fix)} issues"
)
console.print(f"[dim]Backup created: {session_id}[/dim]")

# Apply changes
target_path.write_text(suggestion.proposed_code, encoding="utf-8")

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

Abort the write if the backup step reported failures.

prepare_for_refactoring() returns failed_files, but this branch ignores them and writes the new code anyway. If the target file failed to back up, the command no longer satisfies the rollback guarantee it advertises.

Based on learnings, "All refactoring must go through safety-first pipeline: preview → backup → apply → optional rollback".

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

In `@refactron/cli/refactor.py` around lines 698 - 705, The backup step currently
ignores prepare_for_refactoring()'s failed_files and proceeds to write the
suggestion; change the flow to abort applying changes when any backups failed:
call BackupRollbackSystem.prepare_for_refactoring(...) (as you already do) and
inspect the returned failed_files (the second return value), and if failed_files
is non-empty or contains the target_path, do not call target_path.write_text;
instead log/print an error including session_id and failed_files and
return/raise to halt the refactor command so the rollback guarantee remains
intact. Ensure references are to BackupRollbackSystem, prepare_for_refactoring,
session_id/failed_files and target_path.write_text when implementing the check.

Comment thread refactron/llm/prompts.py
Comment on lines +60 to +77
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.

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.
- Ensure newlines in strings are escaped as \\n.

Output JSON structure:
{
"explanation": "Summary of all fixes applied",
"proposed_code": "The complete fixed code block for the entire file/context",
"reasoning": "Briefly explain how you addressed the issues",
"confidence_score": "Float between 0.0 and 1.0 representing your confidence in this combined fix"
}
"""

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 | 🟡 Minor

Fix line length violations.

Lines 60-61, 75 exceed the 100-character limit per coding guidelines. Reformat the multi-line string to comply.

🧹 Proposed fix
-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.
 
 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.
 - Ensure newlines in strings are escaped as \\n.
 
 Output JSON structure:
 {
     "explanation": "Summary of all fixes applied",
     "proposed_code": "The complete fixed code block for the entire file/context",
     "reasoning": "Briefly explain how you addressed the issues",
-    "confidence_score": "Float between 0.0 and 1.0 representing your confidence in this combined fix"
+    "confidence_score": "Float 0.0-1.0 representing confidence in this combined fix"
 }
 """
🧰 Tools
🪛 GitHub Actions: Pre-commit

[error] 60-60: flake8: E501 line too long (105 > 100 characters)


[error] 61-61: flake8: E501 line too long (121 > 100 characters)


[error] 75-75: flake8: E501 line too long (101 > 100 characters)

🤖 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 - 77, The
BATCH_SUGGESTION_SYSTEM_PROMPT multi-line string exceeds the 100-character line
limit; reflow its content so no physical source line is longer than 100 chars
while preserving exact text, escapes (e.g., \\\" and \\n) and semantics. Edit
the BATCH_SUGGESTION_SYSTEM_PROMPT constant in refactron/llm/prompts.py to break
long sentences into shorter concatenated or implicitly adjacent string literals
(or insert explicit \\n sequences) so each source line is ≤100 chars, keeping
the JSON output requirements and phraseology identical.

Comment thread refactron/llm/prompts.py
Comment on lines +79 to +94
BATCH_SUGGESTION_PROMPT = """
Fix the following code issues in the file:

Issues:
{issues_details}

Original Code:
```python
{original_code}
```

Relevant Context (RAG):
{rag_context}

Provide a single, comprehensive fix that resolves ALL the listed issues while maintaining consistency with the codebase.
"""

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 | 🟡 Minor

Fix line length violation on line 93.

Line 93 exceeds 100 characters.

🧹 Proposed 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.
 """
🧰 Tools
🪛 GitHub Actions: Pre-commit

[error] 93-93: flake8: E501 line too long (120 > 100 characters)

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

In `@refactron/llm/prompts.py` around lines 79 - 94, The long prompt string
BATCH_SUGGESTION_PROMPT contains a line exceeding 100 characters (the "Provide a
single, comprehensive fix..." sentence); shorten or wrap that sentence inside
the triple-quoted string so no line is >100 chars—e.g., split into two shorter
lines or rephrase to a more concise sentence while preserving the meaning and
keeping BATCH_SUGGESTION_PROMPT intact.

Comment on lines +1 to +4
import json
import time
from pathlib import Path
from refactron.analysis.symbol_table import SymbolTableBuilder, SymbolType

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 | 🟡 Minor

Fix the import block so CI passes.

Pre-commit is already failing here: Path and SymbolType are unused, and isort will also want the stdlib/local imports regrouped. If os.utime() stays, move os into the top-level import block and drop the unused names.

Suggested fix
 import json
+import os
 import time
-from pathlib import Path
-from refactron.analysis.symbol_table import SymbolTableBuilder, SymbolType
+
+from refactron.analysis.symbol_table import SymbolTableBuilder
...
-    import os
-
     os.utime(file1, (original_mtime, original_mtime))

Also applies to: 100-102

🧰 Tools
🪛 GitHub Actions: Pre-commit

[error] 1-1: pre-commit hook 'isort' failed: file was modified by this hook.

🤖 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 - 4, The import block
has unused names and wrong grouping causing pre-commit failures: remove unused
imports Path and SymbolType from the from-import (keep SymbolTableBuilder), and
ensure stdlib imports (e.g., import os if os.utime is used) are in the top-level
stdlib group so isort passes; if os.utime is not used, drop the os import
entirely. Update the imports in tests/test_symbol_table_incremental.py
accordingly.

@github-actions

github-actions Bot commented May 1, 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 1, 2026
@github-actions

github-actions Bot commented May 5, 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.

1 participant