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
9 changes: 9 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ LLM-powered code review system with three integration modes:

Uses OpenAI-compatible LLM API (configurable model and endpoint).

### Supported Languages
Python, JavaScript, TypeScript, JSX, TSX, Java, C, C++, Go

## Common Commands

```bash
Expand All @@ -36,6 +39,11 @@ python review.py --test-connection # Test LLM API connectivity
# Install git hooks
python install_hooks.py

# Code formatting and linting
ruff format . # Format all Python files
ruff check . # Lint all Python files
ruff check --fix . # Auto-fix linting issues

# Monitoring
python monitor.py health # System health check
python monitor.py report --days 7 # Usage report
Expand Down Expand Up @@ -88,3 +96,4 @@ static_analyzer.py Fallback security analysis when LLM unavailable
- **All code comments must be in English**
- Communication with users can be in Ukrainian
- Uses fish shell for environment configuration
- Code formatted with `ruff format` and linted with `ruff check`
62 changes: 62 additions & 0 deletions config.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ class ReviewConfig:
"api_key_env": "LLM_API_KEY",
"timeout": 30,
"max_retries": 3,
"max_tokens_per_request": 4096,
"token_limit_strategy": "chunk", # "truncate", "chunk", or "skip"
"chars_per_token": 4, # Character-to-token ratio for estimation
},
"prompt": {
"custom_prompt": None,
Expand Down Expand Up @@ -161,6 +164,65 @@ def get_model(self) -> str:
logger.debug("Using model from config: %s", config_model)
return config_model

def get_max_tokens(self) -> int:
"""Get max tokens per request. Environment variable takes precedence."""
env_value = os.getenv("LLM_MAX_TOKENS_PER_REQUEST")
if env_value:
try:
value = int(env_value)
logger.debug(
"Using max tokens from LLM_MAX_TOKENS_PER_REQUEST: %d", value
)
return value
except ValueError:
logger.warning(
"Invalid LLM_MAX_TOKENS_PER_REQUEST value: %s. Using config.",
env_value,
)

config_value = self.get("llm.max_tokens_per_request", 4096)
logger.debug("Using max tokens from config: %d", config_value)
return config_value

def get_token_limit_strategy(self) -> str:
"""Get token limit strategy. Environment variable takes precedence."""
env_value = os.getenv("LLM_TOKEN_LIMIT_STRATEGY")
valid_strategies = ("truncate", "chunk", "skip")

if env_value:
if env_value in valid_strategies:
logger.debug(
"Using token limit strategy from LLM_TOKEN_LIMIT_STRATEGY: %s",
env_value,
)
return env_value
else:
logger.warning(
"Invalid LLM_TOKEN_LIMIT_STRATEGY value: %s. Must be one of %s. Using config.",
env_value,
valid_strategies,
)

config_value = self.get("llm.token_limit_strategy", "chunk")
if config_value not in valid_strategies:
logger.warning(
"Invalid token_limit_strategy in config: %s. Using 'chunk'.",
config_value,
)
return "chunk"

logger.debug("Using token limit strategy from config: %s", config_value)
return config_value

def get_chars_per_token(self) -> int:
"""Get character-to-token ratio for estimation."""
config_value = self.get("llm.chars_per_token", 4)
if config_value < 1:
logger.warning("chars_per_token must be >= 1. Using default 4.")
return 4
logger.debug("Using chars_per_token: %d", config_value)
return config_value

def is_file_supported(self, file_path: str) -> bool:
"""Check if file extension is supported for review."""
path = Path(file_path)
Expand Down
58 changes: 58 additions & 0 deletions custom_prompt_example.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
Custom Prompt Template Example
==============================

When using "custom_prompt" in your review_config.json, you can use
the following placeholders:

{diff_content} - The git diff to review
{custom_critical_rules} - Formatted list of custom critical rules
{custom_warnings} - Formatted list of custom warnings
{custom_suggestions} - Formatted list of custom suggestions
{additional_instructions} - Additional instructions text

All placeholders are optional - use only the ones you need.

Example Configuration
---------------------
{
"prompt": {
"custom_prompt": "You are a code reviewer.\n\nCRITICAL:\n{custom_critical_rules}\n\nWARNINGS:\n{custom_warnings}\n\nSUGGESTIONS:\n{custom_suggestions}\n\n{additional_instructions}\n\nReview:\n{diff_content}\n\nRespond with CRITICAL:, WARNING:, or SUGGESTION: prefixes.",
"custom_critical_rules": ["memory leaks", "race conditions"],
"custom_warnings": ["deprecated API usage"],
"custom_suggestions": ["consider async patterns"],
"additional_instructions": "Focus on thread safety"
}
}

Minimal Example (diff only)
---------------------------
{
"prompt": {
"custom_prompt": "Review this code for security issues:\n\n{diff_content}\n\nRespond with CRITICAL:, WARNING:, or SUGGESTION: prefixes."
}
}

Full Custom Prompt Template
---------------------------
You are a security-focused code reviewer. Analyze the following changes.

CRITICAL ISSUES (block commit):
{custom_critical_rules}

WARNINGS (allow but flag):
{custom_warnings}

SUGGESTIONS (improvements):
{custom_suggestions}

{additional_instructions}

Format your response as:
CRITICAL: [issue description]
WARNING: [issue description]
SUGGESTION: [suggestion]

If no issues found for a category, respond "NONE".

Changes to review:
{diff_content}
12 changes: 10 additions & 2 deletions review.py
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,9 @@ def _format_text_output(self, result: ReviewResult, verbose: bool = False) -> st
lines = []

# Status indicator
if result.status == "model_unavailable":
if result.status == "skipped":
lines.append("⏭️ Review Skipped (token limit exceeded)")
elif result.status == "model_unavailable":
lines.append("🔴 LLM Model Unavailable")
elif result.critical_issues:
lines.append("❌ Critical Issues Found")
Expand Down Expand Up @@ -252,13 +254,19 @@ def _format_text_output(self, result: ReviewResult, verbose: bool = False) -> st
lines.append(f" • Review Status: {result.status}")
if result.fallback_used:
lines.append(" • Fallback Analysis: Yes")
if result.total_chunks > 0:
lines.append(
f" • Chunks Reviewed: {result.chunks_reviewed}/{result.total_chunks}"
)
lines.append("")

return "\n".join(lines)

def _get_exit_code(self, result: ReviewResult, args=None) -> int:
"""Get appropriate exit code based on results."""
if result.status == "model_unavailable":
if result.status == "skipped":
return 5 # Review skipped due to token limit
elif result.status == "model_unavailable":
return 3 # Model unavailable, allow commit with warning
elif result.critical_issues:
return 1 # Critical issues, block commit
Expand Down
5 changes: 4 additions & 1 deletion review_config.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@
"api_key_env": "LLM_API_KEY",
"timeout": 30,
"max_retries": 3,
"fallback_model": "anthropic/claude-haiku"
"fallback_model": "anthropic/claude-haiku",
"max_tokens_per_request": 4096,
"token_limit_strategy": "chunk",
"chars_per_token": 4
},
"prompt": {
"custom_prompt": null,
Expand Down
7 changes: 7 additions & 0 deletions review_config_example.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,13 @@
"*.spec.js"
]
},
"prompt": {
"custom_prompt": null,
"custom_critical_rules": ["memory leaks", "race conditions"],
"custom_warnings": ["deprecated API usage"],
"custom_suggestions": ["consider async patterns"],
"additional_instructions": "Focus on thread safety"
},
"output": {
"format": "text",
"show_context": true,
Expand Down
Loading