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
70 changes: 0 additions & 70 deletions .github/copilot-instructions.md

This file was deleted.

19 changes: 17 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,20 @@

## [Unreleased]

### Added
- `max_tokens` input in GitHub Action to override max tokens per LLM request per workflow run.
- `timeout` input in GitHub Action to override LLM request timeout per workflow run.
- `LLM_MAX_RESPONSE_TOKENS` environment variable to control max tokens in LLM response (default: 16384).
- `llm.max_response_tokens` config key (default: 16384).

### Changed
- Default `llm.timeout` increased from 180 to 600 seconds to accommodate slow providers and large diffs with chunking.
- Default `max_tokens_per_request` increased to 32768 (see v0.1.3 notes).
- LLM requests now use `system` + `user` message roles instead of a single `user` message, giving reviewer instructions higher priority in the model's context window.
- `max_tokens` is now explicitly passed in every LLM API call, preventing unbounded response generation.

## [0.1.3] - 2026-03-27

### Added
- `--context N` CLI flag to override the number of diff context lines per run (e.g. `--context 20`).
- `context` input in GitHub Action to override context lines per workflow run.
Expand All @@ -27,8 +41,9 @@
### Changed
- Default `output.max_context_lines` increased from 3 to 10 to reduce false positives caused by
partially visible try/except blocks and multiline function calls.
- Default `max_tokens_per_request` increased from 4096 to 8192 to handle larger diffs that result
from wider context windows.
- Default `max_tokens_per_request` increased from 4096 to 32768 to handle large PRs without
chunking on modern LLMs with wide context windows (e.g. Claude Sonnet 4 supports 200K tokens).
Override with `LLM_MAX_TOKENS_PER_REQUEST` environment variable if a lower limit is needed.
- Prompt now explicitly tells the LLM how many context lines are included and instructs it not to
flag constructs (try/except, multiline calls, class definitions) that extend beyond the visible area.
- Tightened LLM prompt to reduce noise: requires file:line references, prohibits generic advice,
Expand Down
21 changes: 15 additions & 6 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ Python, JavaScript, TypeScript, JSX, TSX, Java, C, C++, Go

```bash
# Activate virtual environment
source .python-venv/bin/activate.fish
source .venv/bin/activate.fish

# Load API key (fish shell)
source .env.fish
Expand All @@ -35,14 +35,21 @@ python review.py --format json # JSON output for CI/CD
python review.py --strict # Block on warnings too
python review.py --verbose # Verbose output
python review.py --test-connection # Test LLM API connectivity
python review.py --mode staged --offline # Static analysis only (no LLM)
python review.py --mode staged --context 20 # Override context lines
python review.py --mode staged --trace-llm # Debug: print full LLM request/response

# 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
.venv/bin/ruff format . # Format all Python files
.venv/bin/ruff check . # Lint all Python files
.venv/bin/ruff check --fix . # Auto-fix linting issues

# Tests
pytest test_docstrings.py -v # Test docstring detection
pytest test_code_suggestions.py -v # Test code suggestion parsing

# Monitoring
python monitor.py health # System health check
Expand Down Expand Up @@ -86,9 +93,11 @@ static_analyzer.py Fallback security analysis when LLM unavailable
- `LLM_API_KEY` - API key for LLM service
- `LLM_BASE_URL` - Override base URL (optional)
- `LLM_MODEL` - Override model name (optional)
- `LLM_TIMEOUT` - Request timeout in seconds (default: 180)
- `LLM_MAX_TOKENS_PER_REQUEST` - Max tokens per review chunk (default: 4096)
- `LLM_TIMEOUT` - Request timeout in seconds (default: 600)
- `LLM_MAX_TOKENS_PER_REQUEST` - Max input tokens per review chunk (default: 32768)
- `LLM_MAX_RESPONSE_TOKENS` - Max tokens in LLM response (default: 16384)
- `LLM_TOKEN_LIMIT_STRATEGY` - Strategy when exceeding tokens: `chunk`, `truncate`, or `skip` (default: `chunk`)
- `LLM_CODE_SUGGESTIONS` - Enable inline code suggestions: `true` or `false` (default: `false`)

### Current Setup
- Model: `anthropic/claude-sonnet-4`
Expand Down
13 changes: 9 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -124,8 +124,9 @@ Edit `review_config.json` to customize rules, or use environment variables:
| `LLM_API_KEY` | API key for LLM service | Yes |
| `LLM_BASE_URL` | API endpoint URL | No (uses config) |
| `LLM_MODEL` | Model name | No (uses config) |
| `LLM_TIMEOUT` | Request timeout in seconds (default: 180) | No |
| `LLM_MAX_TOKENS_PER_REQUEST` | Max tokens per review chunk (default: 8192) | No |
| `LLM_TIMEOUT` | Request timeout in seconds (default: 600) | No |
| `LLM_MAX_TOKENS_PER_REQUEST` | Max input tokens per review chunk (default: 32768) | No |
| `LLM_MAX_RESPONSE_TOKENS` | Max tokens in LLM response (default: 16384) | No |
| `LLM_TOKEN_LIMIT_STRATEGY` | Strategy when exceeding tokens: `chunk`, `truncate`, or `skip` (default: `chunk`) | No |
| `LLM_CODE_SUGGESTIONS` | Enable inline code change suggestions: `true` or `false` (default: `false`) | No |

Expand Down Expand Up @@ -191,15 +192,15 @@ For large diffs that exceed LLM token limits, the system automatically splits th
```json
{
"llm": {
"max_tokens_per_request": 8192,
"max_tokens_per_request": 32768,
"token_limit_strategy": "chunk",
"chars_per_token": 4
}
}
```

**Options:**
- `max_tokens_per_request` - Maximum tokens per LLM request (default: 8192)
- `max_tokens_per_request` - Maximum tokens per LLM request (default: 32768)
- `token_limit_strategy` - Strategy for large diffs: `"chunk"` (split and review parts) or `"truncate"` (review only the beginning)
- `chars_per_token` - Character to token ratio for estimation (default: 4)

Expand Down Expand Up @@ -329,6 +330,8 @@ Add optional inputs to customize behavior:
fail_on_critical: 'true' # Fail action on critical issues (default: true)
inline_comments: 'true' # Post inline comments on code lines (default: true)
code_suggestions: 'true' # Enable code change suggestions (default: true)
# max_tokens: '32768' # Max tokens per LLM request (default: 32768)
# timeout: '600' # LLM request timeout in seconds (default: 600)

- name: Check results
if: always()
Expand All @@ -351,6 +354,8 @@ Add optional inputs to customize behavior:
| `fail_on_critical` | Fail on critical issues | No | `true` |
| `inline_comments` | Post inline review comments on code lines | No | `true` |
| `code_suggestions` | Enable inline code change suggestions | No | `true` |
| `max_tokens` | Max tokens per LLM request | No | `32768` |
| `timeout` | LLM request timeout in seconds | No | `600` |
| `context` | Number of context lines around each change | No | `10` |

### Action Outputs
Expand Down
2 changes: 2 additions & 0 deletions VERSION
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
0.1.3

8 changes: 8 additions & 0 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,12 @@ inputs:
description: 'Enable inline code change suggestions (default: true)'
required: false
default: 'true'
max_tokens:
description: 'Max tokens per LLM request (default: 32768). Reduce if your provider has a lower limit.'
required: false
timeout:
description: 'LLM request timeout in seconds (default: 600). Increase for slow providers or large diffs.'
required: false
context:
description: 'Number of context lines around each change (default: 10)'
required: false
Expand Down Expand Up @@ -110,6 +116,8 @@ runs:
LLM_BASE_URL: ${{ inputs.base_url }}
LLM_MODEL: ${{ inputs.model }}
LLM_CODE_SUGGESTIONS: ${{ inputs.code_suggestions }}
LLM_MAX_TOKENS_PER_REQUEST: ${{ inputs.max_tokens }}
LLM_TIMEOUT: ${{ inputs.timeout }}
run: |
RESULT_FILE="${{ runner.temp }}/llm-review-result.json"

Expand Down
56 changes: 26 additions & 30 deletions config.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,10 @@ class ReviewConfig:
"model": "gpt-oss:20b",
"base_url": None,
"api_key_env": "LLM_API_KEY",
"timeout": 180,
"timeout": 600,
"max_retries": 3,
"max_tokens_per_request": 8192,
"max_tokens_per_request": 32768,
"max_response_tokens": 16384,
"token_limit_strategy": "chunk", # "truncate", "chunk", or "skip"
"chars_per_token": 4, # Character-to-token ratio for estimation
},
Expand Down Expand Up @@ -173,26 +174,34 @@ 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")
def _get_int_config(self, env_var: str, config_key: str) -> int:
"""Get integer value from environment variable, falling back to config."""
env_value = os.getenv(env_var)
if env_value:
try:
value = int(env_value)
logger.debug(
"Using max tokens from LLM_MAX_TOKENS_PER_REQUEST: %d", value
)
logger.debug("Using %s from %s: %d", config_key, env_var, value)
return value
except ValueError:
logger.warning(
"Invalid LLM_MAX_TOKENS_PER_REQUEST value: %s. Using config.",
env_value,
"Invalid %s value: %s. Using config.", env_var, env_value
)

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

def get_max_tokens(self) -> int:
"""Get max input tokens per request. Environment variable takes precedence."""
return self._get_int_config(
"LLM_MAX_TOKENS_PER_REQUEST", "llm.max_tokens_per_request"
)

def get_max_response_tokens(self) -> int:
"""Get max response tokens for LLM output. Environment variable takes precedence."""
return self._get_int_config(
"LLM_MAX_RESPONSE_TOKENS", "llm.max_response_tokens"
)

def get_token_limit_strategy(self) -> str:
"""Get token limit strategy. Environment variable takes precedence."""
env_value = os.getenv("LLM_TOKEN_LIMIT_STRATEGY")
Expand All @@ -212,7 +221,7 @@ def get_token_limit_strategy(self) -> str:
valid_strategies,
)

config_value = self.get("llm.token_limit_strategy", "chunk")
config_value = self.get("llm.token_limit_strategy")
if config_value not in valid_strategies:
logger.warning(
"Invalid token_limit_strategy in config: %s. Using 'chunk'.",
Expand All @@ -228,11 +237,11 @@ def get_code_suggestions_enabled(self) -> bool:
env_value = os.getenv("LLM_CODE_SUGGESTIONS")
if env_value is not None:
return env_value.lower() in ("true", "1", "yes")
return bool(self.get("review.enable_code_suggestions", False))
return bool(self.get("review.enable_code_suggestions"))

def get_chars_per_token(self) -> int:
"""Get character-to-token ratio for estimation."""
config_value = self.get("llm.chars_per_token", 4)
config_value = self.get("llm.chars_per_token")
if config_value < 1:
logger.warning("chars_per_token must be >= 1. Using default 4.")
return 4
Expand All @@ -241,20 +250,7 @@ def get_chars_per_token(self) -> int:

def get_timeout(self) -> int:
"""Get LLM request timeout in seconds. Environment variable takes precedence."""
env_value = os.getenv("LLM_TIMEOUT")
if env_value:
try:
value = int(env_value)
logger.debug("Using timeout from LLM_TIMEOUT: %d", value)
return value
except ValueError:
logger.warning(
"Invalid LLM_TIMEOUT value: %s. Using config.", env_value
)

config_value = self.get("llm.timeout", 180)
logger.debug("Using timeout from config: %d", config_value)
return config_value
return self._get_int_config("LLM_TIMEOUT", "llm.timeout")

def is_file_supported(self, file_path: str) -> bool:
"""Check if file extension is supported for review."""
Expand Down
8 changes: 8 additions & 0 deletions examples/workflow-advanced.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,14 @@ jobs:
# Enable inline code change suggestions (default: true)
code_suggestions: 'true'

# Max tokens per LLM request (default: 32768 — covers most PRs in a single request)
# Reduce this value if your LLM provider has a lower token limit per request
# max_tokens: '32768'

# LLM request timeout in seconds (default: 600)
# Increase for slow providers or when reviewing large diffs with chunking
# timeout: '300'

- name: Review Summary
if: always()
run: |
Expand Down
4 changes: 4 additions & 0 deletions examples/workflow-basic.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,7 @@ jobs:
api_key: ${{ secrets.LLM_API_KEY }}
base_url: ${{ secrets.LLM_BASE_URL }}
model: ${{ secrets.LLM_MODEL }}
# Reduce if your LLM provider has a lower token limit (default: 32768)
# max_tokens: '16384'
# Increase if your LLM provider is slow (default: 180s)
# timeout: '300'
3 changes: 3 additions & 0 deletions examples/workflow-example.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,6 @@ jobs:

# Optional: disable PR comments
# post_comment: 'false'

# Optional: reduce token limit if your LLM provider requires it (default: 32768)
# max_tokens: '16384'
4 changes: 2 additions & 2 deletions review_config.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@
"model": "anthropic/claude-sonnet-4",
"base_url": "https://api.opencode.ai/v1",
"api_key_env": "LLM_API_KEY",
"timeout": 180,
"timeout": 600,
"max_retries": 3,
"fallback_model": "anthropic/claude-haiku",
"max_tokens_per_request": 8192,
"max_tokens_per_request": 32768,
"token_limit_strategy": "chunk",
"chars_per_token": 4
},
Expand Down
4 changes: 2 additions & 2 deletions review_config_example.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@
"model": "gpt-4",
"base_url": "https://api.openai.com/v1",
"api_key_env": "OPENAI_API_KEY",
"timeout": 180,
"timeout": 600,
"max_retries": 3,
"fallback_model": "gpt-3.5-turbo",
"max_tokens_per_request": 8192
"max_tokens_per_request": 32768
},
"review": {
"critical_rules": [
Expand Down
2 changes: 1 addition & 1 deletion review_config_rust_example.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"model": "anthropic/claude-sonnet-4",
"base_url": "https://api.opencode.ai/v1",
"api_key_env": "LLM_API_KEY",
"timeout": 180,
"timeout": 600,
"max_retries": 3
},
"prompt": {
Expand Down
Loading
Loading