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
101 changes: 101 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -204,8 +204,101 @@ codespy config --config path/to/config.yaml

# Show version
codespy --version

# Review local git changes (no GitHub/GitLab needed)
codespy review-local # Review current dir vs main
codespy review-local /path/to/repo # Review specific repo
codespy review-local --base develop # Compare against develop
codespy review-local --base origin/main # Compare against origin/main
codespy review-local --base HEAD~5 # Compare against 5 commits back

# Review uncommitted changes (staged + unstaged)
codespy review-uncommitted # Review current dir
codespy review-uncommitted /path/to/repo
codespy review-uncommitted --output json
```

### IDE Integration (MCP Server)

CodeSpy can run as an MCP (Model Context Protocol) server for integration with AI coding assistants like Cline, enabling code reviews directly from your editor without leaving your workflow.

```bash
# Start the MCP server
codespy serve

# Use a custom config file
codespy serve --config path/to/config.yaml
```

**Environment Variables for Local & MCP Reviews:**

For local CLI commands (`review-local`, `review-uncommitted`) and MCP server, set these environment variables:

```bash
# Required: LLM Provider (choose one)

# Option 1: Anthropic
export DEFAULT_MODEL=claude-opus-4-6
export ANTHROPIC_API_KEY=sk-ant-xxxxxxxxxxxxxxxxxxxx

# Option 2: AWS Bedrock
export DEFAULT_MODEL=bedrock/us.anthropic.claude-sonnet-4-5-20250929-v1:0
export AWS_REGION=us-east-1
export AWS_ACCESS_KEY_ID=xxxxxxxxxxxxxxxxxxxx
export AWS_SECRET_ACCESS_KEY=xxxxxxxxxxxxxxxxxxxx

# Option 3: OpenAI
export DEFAULT_MODEL=gpt-4
export OPENAI_API_KEY=sk-xxxxxxxxxxxxxxxxxxxx

# Optional: Customize review behavior
export DEFAULT_MAX_ITERS=20
export CODE_AND_DOC_REVIEW_MODEL=claude-sonnet-4-5-20250929
```

**Configure your IDE** (example for Cline in VS Code):

Add to `cline_mcp_settings.json`:
```json
{
"mcpServers": {
"codespy-reviewer": {
"command": "codespy",
"args": ["serve"],
"env": {
"DEFAULT_MODEL": "claude-opus-4-6",
"ANTHROPIC_API_KEY": "your-key-here"
}
}
}
}
```

Or for AWS Bedrock:
```json
{
"mcpServers": {
"codespy-reviewer": {
"command": "codespy",
"args": ["serve"],
"env": {
"DEFAULT_MODEL": "bedrock/eu.anthropic.claude-sonnet-4-5-20250929-v1:0",
"AWS_REGION": "eu-west-1",
"AWS_ACCESS_KEY_ID": "your-access-key",
"AWS_SECRET_ACCESS_KEY": "your-secret-key"
}
}
}
}
```

**Available MCP Tools:**
- `review_local_changes(repo_path, base_ref)` — Review branch changes vs base (e.g., vs `main`)
- `review_uncommitted(repo_path)` — Review staged + unstaged working tree changes
- `review_pr(mr_url)` — Review a GitHub PR or GitLab MR by URL

Then ask your AI assistant: *"Review my local changes"* or *"Review uncommitted work in /path/to/repo"*

### Using Docker

```bash
Expand Down Expand Up @@ -543,6 +636,14 @@ output_git: true

## Architecture

CodeSpy supports **three review approaches**:

1. **Remote PR/MR Review** (`review` command) — Fetches PR/MR from GitHub/GitLab, clones repo, runs review
2. **Local CLI Review** (`review-local`, `review-uncommitted` commands) — Reviews local git changes directly, no platform needed
3. **MCP Server** (`serve` command) — Exposes review tools to IDE assistants via Model Context Protocol

### Remote PR/MR Review

```
┌─────────────────────────────────────────────────────────────────────┐
│ codespy CLI │
Expand Down
6 changes: 6 additions & 0 deletions src/codespy/agents/reviewer/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,19 @@
Issue,
IssueCategory,
IssueSeverity,
LocalReviewConfig,
RemoteReviewConfig,
ReviewConfig,
ReviewResult,
)
from codespy.agents.reviewer.reviewer import ReviewPipeline

__all__ = [
"ReviewPipeline",
"ReviewResult",
"ReviewConfig",
"RemoteReviewConfig",
"LocalReviewConfig",
"Issue",
"IssueCategory",
"IssueSeverity",
Expand Down
21 changes: 20 additions & 1 deletion src/codespy/agents/reviewer/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from datetime import datetime
from enum import Enum
from pathlib import Path

from pydantic import BaseModel, Field, field_validator

Expand Down Expand Up @@ -311,4 +312,22 @@ def to_markdown(self) -> str:

def to_json_dict(self) -> dict:
"""Convert to a JSON-serializable dictionary."""
return self.model_dump(mode="json")
return self.model_dump(mode="json")


class RemoteReviewConfig(BaseModel):
"""Configuration for reviewing a remote PR/MR from GitHub or GitLab."""

url: str = Field(description="URL of the GitHub PR or GitLab MR to review")


class LocalReviewConfig(BaseModel):
"""Configuration for reviewing local git changes without a remote platform."""

repo_path: Path = Field(description="Path to the git repository")
base_ref: str = Field(default="main", description="Base git ref to compare against (e.g., 'main', 'develop', 'HEAD~5')")
uncommitted: bool = Field(default=False, description="If True, review uncommitted changes (working tree vs HEAD)")


# Union type for review configuration
ReviewConfig = RemoteReviewConfig | LocalReviewConfig
60 changes: 52 additions & 8 deletions src/codespy/agents/reviewer/reviewer.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,15 @@
from codespy.agents import SignatureContext, configure_dspy, get_cost_tracker, verify_model_access
from codespy.config import Settings, get_settings
from codespy.tools.git import GitClient, get_client, ChangedFile, MergeRequest
from codespy.agents.reviewer.models import Issue, SignatureStatsResult, ReviewResult
from codespy.tools.git.local_diff import build_mr_from_diff
from codespy.agents.reviewer.models import (
Issue,
SignatureStatsResult,
ReviewResult,
ReviewConfig,
RemoteReviewConfig,
LocalReviewConfig,
)
from codespy.agents.reviewer.modules import (
CodeAndDocReviewer,
IssueDeduplicator,
Expand Down Expand Up @@ -135,14 +143,50 @@ async def _run_review_modules(
all_issues.extend(result)
return all_issues

def forward(self, mr_url: str, verify_model: bool = True) -> ReviewResult:
"""Run the complete review pipeline on a merge request."""
def _build_local_mr(self, config: LocalReviewConfig) -> MergeRequest:
"""Build a MergeRequest from local git changes.

Args:
config: Local review configuration

Returns:
MergeRequest object built from local git changes
"""
logger.info(f"Building MR from local changes in {config.repo_path}...")
return build_mr_from_diff(
repo_path=config.repo_path,
base_ref=config.base_ref,
include_uncommitted=config.uncommitted
)

def forward(self, config: ReviewConfig) -> ReviewResult:
"""Run the complete review pipeline.

Args:
config: Review configuration (RemoteReviewConfig or LocalReviewConfig)

Returns:
ReviewResult with issues, summary, costs, etc.
"""
self.cost_tracker.reset()
logger.info(f"Starting review of {mr_url}")
if verify_model:
self._verify_model_access()
mr = self._fetch_mr(mr_url)
repo_path = self._get_repo_path(mr)

# Always verify model access
self._verify_model_access()

# Determine mode and fetch/build MR accordingly
if isinstance(config, RemoteReviewConfig):
# Remote mode: fetch from GitHub/GitLab
logger.info(f"Starting review of {config.url}")
mr = self._fetch_mr(config.url)
repo_path = self._get_repo_path(mr)
elif isinstance(config, LocalReviewConfig):
# Local mode: build MR from local git changes
mode = "uncommitted changes" if config.uncommitted else f"changes vs {config.base_ref}"
logger.info(f"Starting local review: {mode} in {config.repo_path}")
mr = self._build_local_mr(config)
repo_path = config.repo_path.resolve()
else:
raise ValueError(f"Invalid config type: {type(config)}")

# Identify scopes (the module internally checks if signature is enabled)
logger.info("Identifying code scopes...")
Expand Down
Loading