llm-reviewer
by asheshv
README.md
# llm-reviewer
Review code changes using LLMs — CLI + MCP server for any AI agent. Supports GitHub Copilot, local Ollama models, and any OpenAI-compatible endpoint.
## Installation
```bash
# Install globally from GitHub
npm install -g github:asheshv/llm-reviewer
# Or use without installing:
npx --yes github:asheshv/llm-reviewer --help
```
## Quick Start
```bash
# Review uncommitted local changes (uses Copilot by default)
llm-reviewer
# Review changes in a feature branch vs main
llm-reviewer branch main
# Review a pull request
llm-reviewer pr 123
# Review with a local Ollama model
llm-reviewer local --provider ollama --model qwen2.5-coder:14b
# Review with any OpenAI-compatible endpoint
LLM_REVIEWER_API_KEY="sk-..." llm-reviewer local \
--provider custom --base-url https://openrouter.ai/api/v1 \
--model google/gemini-2.5-flash
```
## CLI Usage
```
llm-reviewer [mode] [options]
Modes (default: local):
unstaged Working tree vs index
staged Index vs HEAD
local Working tree vs HEAD (default)
branch [base] Current branch vs base (default: main)
pr <number> Pull request diff
commits <n> Last N commits
range <ref1>..<ref2> Arbitrary ref range
Options:
--model <id> Model to use (default: auto)
--format <fmt> text | markdown | json (default: markdown)
--stream Force streaming output
--no-stream Force buffered output
--prompt <text> Override review prompt
--config <path> Override config file path
--provider <name> Review provider: copilot, ollama, custom, custom:<name>
--chunking <mode> auto | always | never (default: auto)
--timeout <seconds> Request timeout (default: 30 for copilot/custom, 120 for ollama)
--ollama-url <url> Ollama base URL (default: http://localhost:11434)
--base-url <url> Base URL for custom provider (OpenAI-compatible endpoint)
--verbose Enable debug logging to stderr
--help Show help
--version Show version
Subcommands:
llm-reviewer models List available models
llm-reviewer chat "<msg>" Free-form LLM chat
llm-reviewer status Show provider connectivity and configuration
```
### Examples
```bash
# Review staged changes before committing
llm-reviewer staged
# Review the last 3 commits
llm-reviewer commits 3
# Review a ref range
llm-reviewer range v1.0.0..HEAD
# Use a specific model with JSON output
llm-reviewer branch main --model gpt-4.1 --format json
# Custom review instructions
llm-reviewer --prompt "Focus on security and error handling"
# Review with Ollama (local LLM)
llm-reviewer local --provider ollama --model qwen2.5-coder:14b
# List Ollama models
llm-reviewer models --provider ollama
# Check provider status
llm-reviewer status --provider ollama
# Force chunking for large diffs
llm-reviewer branch main --chunking always
# Longer timeout for large models
llm-reviewer commits 1 --provider ollama --model qwen2.5-coder:32b --timeout 300
```
## Providers
### Copilot (default)
Uses GitHub Copilot's chat API. Requires a GitHub token (see [Authentication](#authentication)).
```bash
llm-reviewer local # auto-selects model
llm-reviewer local --model gpt-4.1 # specific model
llm-reviewer models # list available models
```
### Ollama (local)
Uses a locally running [Ollama](https://ollama.com) instance. No auth required.
```bash
# Start Ollama (if not running)
ollama serve
# List available models
llm-reviewer models --provider ollama
# Review with a specific model (model is required for Ollama)
llm-reviewer local --provider ollama --model qwen2.5-coder:14b
# Custom Ollama URL
llm-reviewer local --provider ollama --ollama-url http://remote:11434 --model codellama
# Non-streaming for cleaner output
llm-reviewer commits 1 --provider ollama --model qwen2.5-coder:14b --no-stream
```
### Custom Provider (OpenAI-compatible)
Connect to any endpoint that implements the OpenAI Chat Completions API — OpenRouter, Groq, Together AI, Fireworks, LM Studio, vLLM, etc.
> The base URL must include the full API path (e.g., `https://api.groq.com/openai/v1`, not `https://api.groq.com`). If you get 404 errors, check that your URL includes `/v1` or the provider's equivalent path prefix.
```bash
# Quick start: API key via env var, endpoint via --base-url
LLM_REVIEWER_API_KEY="sk-or-..." llm-reviewer local \
--provider custom --base-url https://openrouter.ai/api/v1 \
--model google/gemini-2.5-flash
# Named provider from config file (see config example below)
llm-reviewer local --provider custom:groq --model llama-3.3-70b-versatile
# List models from a custom endpoint
llm-reviewer models --provider custom --base-url https://api.groq.com/openai/v1
# Dynamic auth with a shell command
# Config: { "providerOptions": { "gcp": { "baseUrl": "https://...", "apiKeyCommand": "gcloud auth print-access-token" } } }
llm-reviewer local --provider custom:gcp --model gemini-2.5-flash
# Bare "custom" picks from providerOptions if --base-url is not set (see Named Configurations)
llm-reviewer local --provider custom --model google/gemini-2.5-flash
# Local endpoint (no auth required)
llm-reviewer local --provider custom --base-url http://localhost:1234/v1 --model local-model
```
**Model is always required** for custom providers — there is no auto-select. Set `--model` or add `"model"` to your config file.
#### Named Configurations
Define multiple endpoints in your config file using `providerOptions`:
```json
{
"providerOptions": {
"groq": {
"baseUrl": "https://api.groq.com/openai/v1",
"apiKeyCommand": "op read 'op://Dev/Groq/api-key'"
},
"openrouter": {
"baseUrl": "https://openrouter.ai/api/v1",
"apiKeyCommand": "cat ~/.config/openrouter/key"
}
}
}
```
Then use them with `--provider custom:<name>`:
```bash
llm-reviewer local --provider custom:groq --model llama-3.3-70b-versatile
llm-reviewer local --provider custom:openrouter --model anthropic/claude-sonnet-4
```
When using bare `--provider custom`, resolution order is:
1. `--base-url` CLI flag or `LLM_REVIEWER_BASE_URL` env var
2. `providerOptions.custom` entry
3. First non-builtin `providerOptions` entry
#### Authentication
Custom providers support three auth modes:
| Mode | Config field | Description |
|------|-------------|-------------|
| Static key | `apiKey` | Bearer token sent with every request |
| Dynamic command | `apiKeyCommand` | Shell command executed to obtain a key; cached until auth failure |
| No auth | _(omit both)_ | No `Authorization` header (for local endpoints) |
When both `apiKey` and `apiKeyCommand` are present, `apiKeyCommand` wins (dynamic over static).
On a 401 (or 403 without rate-limit header), the key is refreshed by re-running the command and the request is retried once.
#### Popular OpenAI-compatible Endpoints
| Provider | Base URL | Auth | Model examples |
|----------|----------|------|----------------|
| OpenRouter | `https://openrouter.ai/api/v1` | Bearer key | `google/gemini-2.5-flash`, `anthropic/claude-sonnet-4` |
| Groq | `https://api.groq.com/openai/v1` | Bearer key | `llama-3.3-70b-versatile`, `mixtral-8x7b-32768` |
| Together AI | `https://api.together.xyz/v1` | Bearer key | `meta-llama/Llama-3.3-70B-Instruct-Turbo` |
| Fireworks | `https://api.fireworks.ai/inference/v1` | Bearer key | `accounts/fireworks/models/llama-v3p3-70b-instruct` |
| LM Studio | `http://localhost:1234/v1` | None | Whatever model is loaded |
| vLLM | `http://localhost:8000/v1` | None | Depends on deployment |
#### Security Notes
- **`apiKeyCommand`** executes shell commands with full user permissions. Treat it like `package.json` scripts — review project config files before running in untrusted repos.
- **Do not** store static `apiKey` values in project config files that may be committed to version control. Use `apiKeyCommand` or `LLM_REVIEWER_API_KEY` env var instead.
- API keys never appear in error messages. Command strings are redacted from all error output.
#### Timeout
Custom providers use the default 30s timeout (same as Copilot). Cloud APIs are fast. For local models behind a custom endpoint, set `--timeout 120` or `"timeout": 120` in config.
### Status Command
Check provider connectivity, resolved configuration, and available models:
```bash
llm-reviewer status # default provider (copilot)
llm-reviewer status --provider ollama # check Ollama
llm-reviewer status --provider custom:groq # check custom endpoint
llm-reviewer status --json # machine-readable output
```
## Chunked Review
For large diffs that exceed the model's context window, `llm-reviewer` automatically splits the diff into chunks, reviews each independently, then aggregates findings via a reduce pass.
- **`auto`** (default) — chunks when diff exceeds 80% of model context
- **`always`** — always chunk, even small diffs
- **`never`** — fail if diff too large (useful for CI hard limits)
```bash
llm-reviewer branch main --chunking always # force chunking
llm-reviewer branch main --chunking never # disable chunking
# Kill switch via environment variable
LLM_REVIEWER_CHUNKING=never llm-reviewer local
```
Chunked reviews show progress on stderr:
```
Reviewing chunk 1/3 (src/auth.ts, src/config.ts)... done (3,200 tokens)
Reviewing chunk 2/3 (src/review.ts, src/prompt.ts)... done (2,800 tokens)
Reviewing chunk 3/3 (src/cli.ts)... done (1,950 tokens)
Aggregating findings... done (4,500 tokens)
```
## MCP Server Setup
The MCP server exposes LLM review capabilities as tools for AI agents (Claude Code, Cursor, Zed, Cline, etc.).
### For Claude Code
Add to `.mcp.json` in your project root or `~/.config/claude/mcp.json`:
```json
{
"mcpServers": {
"llm-reviewer": {
"command": "llm-reviewer",
"args": ["--mcp"]
}
}
}
```
### For Generic MCP Clients
```json
{
"llm-reviewer": {
"type": "stdio",
"command": "llm-reviewer",
"args": ["--mcp"]
}
}
```
Or if using locally (not installed globally):
```json
{
"llm-reviewer": {
"type": "stdio",
"command": "node",
"args": ["/absolute/path/to/llm-reviewer/dist/cli.js", "--mcp"]
}
}
```
### Available MCP Tools
- `llm_review` — Review code changes (all 7 modes supported)
- `llm_chat` — Free-form chat with LLM (with optional code context)
- `llm_models` — List available models
## Claude Code Skill
You can also add `llm-reviewer` as a [Claude Code skill](https://docs.anthropic.com/en/docs/claude-code/skills) so Claude can invoke it automatically when you ask for code review.
### Setup
```bash
# Copy the skill into your Claude skills directory
mkdir -p ~/.claude/skills/llm-reviewer
cp skills/SKILL.md ~/.claude/skills/llm-reviewer/SKILL.md
```
### Usage
Once installed, Claude Code will automatically use `llm-reviewer` when you:
- Ask to review code changes, diffs, or PRs
- Complete a feature and want a second opinion
- Request aggressive multi-round review
You can also invoke it explicitly:
```
/llm-reviewer
```
The skill supports all providers (Copilot, Ollama, Custom), chunking, cross-model review, and the full CLI feature set. See [`skills/SKILL.md`](skills/SKILL.md) for the complete reference.
## Configuration
Configuration is loaded from multiple layers (lowest to highest precedence):
1. **Built-in defaults** — Ships with the tool
2. **Environment variables** — `LLM_REVIEWER_*` (see below)
3. **Global config** — `~/.llm-reviewer/config.json` or `config.md`
4. **Project config** — `<git-root>/.llm-reviewer/config.json` or `config.md`
5. **CLI flags** — `--model`, `--format`, `--provider`, etc. (highest precedence)
### Environment Variables
| Variable | Description | Example |
|----------|-------------|---------|
| `LLM_REVIEWER_PROVIDER` | Override provider | `ollama`, `custom` |
| `LLM_REVIEWER_OLLAMA_URL` | Ollama base URL | `http://remote:11434` |
| `LLM_REVIEWER_BASE_URL` | Custom provider base URL | `https://api.groq.com/openai/v1` |
| `LLM_REVIEWER_API_KEY` | Custom provider API key (static) | `sk-...` |
| `LLM_REVIEWER_API_KEY_COMMAND` | Shell command to obtain API key | `op read 'op://Dev/key'` |
| `LLM_REVIEWER_CHUNKING` | Chunking mode (kill switch) | `never` |
When both `LLM_REVIEWER_API_KEY` and `LLM_REVIEWER_API_KEY_COMMAND` are set, `API_KEY` takes precedence (env vars use static-over-dynamic ordering since the user explicitly set both). Within a config file, `apiKeyCommand` wins over `apiKey` (dynamic over static). These env vars only affect the bare `--provider custom`, not named providers like `custom:groq`.
### config.json Schema
```json
{
"model": "auto",
"format": "markdown",
"stream": true,
"mode": "extend",
"prompt": "path/to/custom-prompt.md",
"defaultBase": "main",
"ignorePaths": ["*.lock", "dist/**"],
"provider": "copilot",
"providerOptions": {
"ollama": {
"baseUrl": "http://localhost:11434"
},
"groq": {
"baseUrl": "https://api.groq.com/openai/v1",
"apiKeyCommand": "op read 'op://Dev/Groq/api-key'"
},
"openrouter": {
"baseUrl": "https://openrouter.ai/api/v1",
"apiKeyCommand": "cat ~/.config/openrouter/key"
}
},
"chunking": "auto",
"timeout": 30
}
```
| Field | Type | Default | Description |
|-------|------|---------|-------------|
| `model` | string | `"auto"` | Model ID or `"auto"` for automatic selection |
| `format` | string | `"markdown"` | Output format: `text`, `markdown`, or `json` |
| `stream` | boolean | `true` | Enable streaming output |
| `mode` | string | `"extend"` | Prompt merge strategy: `extend` or `replace` |
| `prompt` | string | — | Inline text or path to `.md` file (relative to config dir) |
| `defaultBase` | string | `"main"` | Default base branch for `branch` mode |
| `ignorePaths` | string[] | `[]` | Glob patterns to exclude from diffs (merged across layers) |
| `provider` | string | `"copilot"` | Provider: `copilot`, `ollama`, `custom`, `custom:<name>` |
| `providerOptions` | object | `{}` | Provider-specific config (Ollama URL, custom endpoints) |
| `chunking` | string | `"auto"` | Chunking mode: `auto`, `always`, or `never` |
| `timeout` | number | `30` | Request timeout in seconds (auto: 120 for Ollama) |
### Prompt Customization
You can customize review instructions in two ways:
1. **Inline in config.json:**
```json
{
"mode": "extend",
"prompt": "Focus on TypeScript type safety and error handling."
}
```
2. **Separate config.md file:**
```markdown
# Project-Specific Review Guidelines
- Check React hooks dependencies
- Verify error boundaries are present
- Flag any `any` types
```
### Prompt Merge Modes
- **`"extend"`** (default) — Appends your instructions to the default prompt
- **`"replace"`** — Replaces the default prompt entirely with yours
Example multi-layer merge (all using `"extend"`):
```
[Default prompt from prompts/default-review.md]
## Additional Instructions (Global)
[Global ~/.llm-reviewer/config.md]
## Project Instructions
[Project .llm-reviewer/config.md]
```
If project config uses `"mode": "replace"`, only the project prompt is used.
CLI `--prompt` flag always replaces everything:
```bash
llm-reviewer --prompt "Only check for SQL injection"
```
## Default Review Prompt
The built-in prompt checks code changes in priority order:
1. **Security** — SQL injection, XSS, command injection, auth bypass, OWASP Top 10
2. **Correctness** — Edge cases, race conditions, error paths, invalid assumptions
3. **Performance** — N+1 queries, missing indexes, algorithmic complexity
4. **Readability** — Unclear naming, misleading comments, unnecessary complexity
5. **Simplicity** — Over-engineering, premature abstractions, YAGNI violations
Findings are categorized as HIGH, MEDIUM, or LOW severity. Security issues are always HIGH.
To customize, add a `config.md` file in `~/.llm-reviewer/` (global) or `<git-root>/.llm-reviewer/` (project).
## Output Formats
### Markdown (default)
```markdown
### HIGH SQL Injection in query builder
**File:** `src/db.ts` **Line:** 42
**Category:** Security
User input is concatenated directly into SQL query.
**Suggestion:**
Use parameterized queries instead...
```
### Text
Plain text output without markdown formatting. Suitable for terminals or tools that don't support markdown.
### JSON
Complete structured output in a single JSON object:
```json
{
"review": {
"content": "### HIGH SQL Injection...",
"model": "gpt-4.1",
"usage": { "totalTokens": 1234 }
},
"diff": {
"filesChanged": 5,
"insertions": 120,
"deletions": 45,
"files": [
{ "path": "src/db.ts", "status": "modified" }
]
},
"warnings": [],
"exitCode": 1
}
```
For chunked reviews, usage includes a breakdown:
```json
"usage": {
"totalTokens": 12450,
"chunkedBreakdown": { "mapTokens": 7950, "reduceTokens": 4500, "chunks": 3 }
}
```
### NDJSON (Streaming JSON)
Use `--stream --format json` for newline-delimited JSON stream:
```bash
llm-reviewer --stream --format json | while read line; do
echo "$line" | jq -r '.text // empty'
done
```
Each line is a valid JSON object. Enables real-time machine consumption of streaming output.
## Exit Codes
| Code | Meaning | Use Case |
|------|---------|----------|
| 0 | Success — no HIGH severity issues | Normal completion |
| 1 | Review completed with HIGH findings | CI gating: `llm-reviewer branch main \|\| exit 1` |
| 2 | Authentication failure | No GitHub token found |
| 3 | Diff error | Empty diff, not a git repo, etc. |
| 4 | API/model error | Rate limit, server error, model unavailable |
| 5 | Config error | Malformed config file |
### CI Integration Example
```bash
# Fail the build if high-severity issues are found
llm-reviewer branch main || exit 1
# Or capture the exit code
llm-reviewer branch main
if [ $? -eq 1 ]; then
echo "High-severity issues found. Please review."
exit 1
fi
```
## Authentication
GitHub token is resolved in priority order. First match wins.
1. **`$GITHUB_TOKEN` environment variable**
```bash
export GITHUB_TOKEN="ghp_xxxxxxxxxxxx"
llm-reviewer
```
2. **Copilot config files** (created by editor extensions)
- `~/.config/github-copilot/hosts.json`
- `~/.config/github-copilot/apps.json`
3. **GitHub CLI (`gh`)**
```bash
gh auth login
llm-reviewer
```
The tool automatically exchanges your OAuth token for a session token and caches it for subsequent requests.
**Note:** GitHub token authentication is only required for the Copilot provider. Ollama requires no auth. Custom providers use their own auth (see [Custom Provider](#custom-provider-openai-compatible)).
### Setting Up Authentication
**Option 1: GitHub CLI (recommended)**
```bash
gh auth login
```
**Option 2: Environment Variable**
```bash
# Get a token from https://github.com/settings/tokens
# Scopes required: read:user, copilot
export GITHUB_TOKEN="ghp_xxxxxxxxxxxx"
```
**Option 3: Use Copilot Extension**
Sign in to GitHub Copilot in VS Code, Neovim, or JetBrains. The tool will use the cached token.
## Development
```bash
# Clone the repository
git clone https://github.com/asheshv/llm-reviewer.git
cd llm-reviewer
# Install dependencies
npm install
# Run tests
npm test
# Build the project
npm run build
# Run the CLI locally
node dist/cli.js --help
# Or link globally for testing
npm link
llm-reviewer --help
```
### Project Structure
```
llm-reviewer/
├── src/
│ ├── cli.ts # CLI entry point
│ ├── mcp-server.ts # MCP server entry point
│ └── lib/
│ ├── providers/ # Provider abstraction layer
│ │ ├── types.ts # ReviewProvider interface
│ │ ├── openai-chat-provider.ts # Shared OpenAI-compatible base
│ │ ├── copilot-provider.ts # GitHub Copilot provider
│ │ ├── ollama-provider.ts # Ollama local provider
│ │ ├── custom-provider.ts # OpenAI-compatible custom provider
│ │ └── index.ts # Provider factory
│ ├── auth.ts # Token resolution + session exchange
│ ├── chunking.ts # Diff splitting + bin-packing
│ ├── config.ts # Multi-layer config loading
│ ├── diff.ts # Git diff collection (7 modes)
│ ├── formatter.ts # Output formatting (text/markdown/json)
│ ├── prompt.ts # Prompt assembly + file manifests
│ ├── review.ts # Review orchestration + map-reduce chunking
│ ├── streaming.ts # SSE parser
│ ├── truncation.ts # Severity-aware truncation for reduce pass
│ ├── types.ts # Shared type definitions
│ └── index.ts # Public API exports
├── test/ # Tests (vitest + msw)
├── prompts/
│ └── default-review.md # Built-in review prompt
├── skills/
│ └── SKILL.md # Claude Code skill definition
├── docs/
│ ├── spec/ # Design specifications
│ ├── adr/ # Architecture decision records
│ ├── plans/ # Implementation plans
│ └── reference/ # API documentation
└── .llm-reviewer/ # Project-level config (dogfooding)
├── config.json
└── config.md
```
## License
MIT License. See [LICENSE](./LICENSE) for details.
---
**Questions or issues?** Open an issue on [GitHub](https://github.com/asheshv/llm-reviewer).
This server cannot be deployed
Maintenance
ActivityInactive
ResponsivenessUnresponsive