Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 13 additions & 3 deletions review.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,11 @@ def setup_logging(verbose: bool = False):
class ReviewCLI:
"""Command-line interface for code review."""

def __init__(self):
def __init__(self, trace: bool = False):
self.config = ReviewConfig()
self.parser = DiffParser(self.config)
self.reviewer = LLMReviewer(self.config)
self.reviewer = LLMReviewer(self.config, trace=trace)
self.trace = trace

def run(self, args=None):
"""Run CLI with provided arguments."""
Expand All @@ -41,6 +42,10 @@ def run(self, args=None):
# Setup logging based on verbosity
setup_logging(getattr(parsed_args, "verbose", False))

# Update trace mode if specified
self.trace = getattr(parsed_args, "trace", False)
self.reviewer.trace = self.trace

# Handle test-connection before validation
if parsed_args.test_connection:
if not self._validate_config():
Expand Down Expand Up @@ -136,6 +141,11 @@ def _create_parser(self) -> argparse.ArgumentParser:
action="store_true",
help="Verbose output with additional context",
)
parser.add_argument(
"--trace",
action="store_true",
help="Trace output showing LLM queries and tool usage",
)

# Utility commands
parser.add_argument(
Expand Down Expand Up @@ -280,7 +290,7 @@ def _get_exit_code(self, result: ReviewResult, args=None) -> int:

def main():
"""Main entry point."""
cli = ReviewCLI()
cli = ReviewCLI(trace=False)
exit_code = cli.run()
sys.exit(exit_code)

Expand Down
30 changes: 28 additions & 2 deletions review_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,9 +70,10 @@ class LLMReviewer:

Focus on security vulnerabilities first, then code quality."""

def __init__(self, config):
def __init__(self, config, trace: bool = False):
self.config = config
self.client = None
self.trace = trace
self._setup_logging()

def _build_prompt(self, diff_content: str) -> str:
Expand Down Expand Up @@ -290,6 +291,7 @@ def _review_chunks(
total_chunks = len(chunks)

self.logger.info(f"Splitting diff into {total_chunks} chunks for review")
self._trace_print(f"Chunking: splitting diff into {total_chunks} chunks")

all_critical = []
all_warnings = []
Expand All @@ -299,6 +301,7 @@ def _review_chunks(

for i, chunk in enumerate(chunks):
self.logger.info(f"Reviewing chunk {i + 1}/{total_chunks}")
self._trace_print(f"Chunking: reviewing chunk {i + 1}/{total_chunks}")
try:
result = self._call_llm(chunk)
all_critical.extend(result.critical_issues)
Expand Down Expand Up @@ -404,12 +407,24 @@ def review_diff(self, diff_content: str) -> ReviewResult:
suggestions=[],
)

def _trace_print(self, message: str):
"""Print trace message to stderr if trace mode is enabled."""
if self.trace:
import sys

print(f"[TRACE] {message}", file=sys.stderr)

def _call_llm(self, diff_content: str) -> ReviewResult:
"""Make LLM API call."""
client = self._get_client()
model = self.config.get_model()
base_url = self.config.get_base_url()

prompt = self._build_prompt(diff_content)
prompt_tokens = self._estimate_tokens(prompt)

self._trace_print(f"LLM Query: model={model}, base_url={base_url}")
self._trace_print(f"LLM Query: estimated_tokens={prompt_tokens}")

try:
response = client.chat.completions.create(
Expand All @@ -418,9 +433,13 @@ def _call_llm(self, diff_content: str) -> ReviewResult:
temperature=0.1, # Low temperature for consistent analysis
)
except OpenAIError as e:
self._trace_print(f"LLM Query failed: {e}")
raise e

raw_response = response.choices[0].message.content or ""
response_tokens = self._estimate_tokens(raw_response)
self._trace_print(f"LLM Response: received ~{response_tokens} tokens")

return self._parse_llm_response(raw_response)

def _parse_llm_response(self, response: str) -> ReviewResult:
Expand Down Expand Up @@ -499,6 +518,7 @@ def _handle_model_unavailable(
if fallback_model:
try:
self.logger.info(f"Trying fallback model: {fallback_model}")
self._trace_print(f"Fallback: trying fallback model {fallback_model}")
original_model = self.config.config["llm"]["model"]
self.config.config["llm"]["model"] = fallback_model

Expand All @@ -522,6 +542,7 @@ def _handle_model_unavailable(
# Use static analysis as final fallback
if self.config.get("fallback.enable_static_analysis", True):
self.logger.info("Using static analysis as fallback")
self._trace_print("Fallback: using static analysis")
try:
# Direct import for testing
import sys
Expand All @@ -545,6 +566,9 @@ def test_connection(self) -> bool:
try:
client = self._get_client()
model = self.config.get_model()
base_url = self.config.get_base_url()

self._trace_print(f"Test connection: model={model}, base_url={base_url}")

# Simple test message
response = client.chat.completions.create(
Expand All @@ -555,10 +579,12 @@ def test_connection(self) -> bool:
max_tokens=10,
)

return (
success = (
response.choices[0].message.content
and "OK" in response.choices[0].message.content
)
self._trace_print(f"Test connection: success={success}")
return success
except Exception as e:
self.logger.error(f"Connection test failed: {e}")
return False