prlens 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- prlens-0.1.0/LICENSE +21 -0
- prlens-0.1.0/PKG-INFO +152 -0
- prlens-0.1.0/README.md +121 -0
- prlens-0.1.0/prlens/__init__.py +3 -0
- prlens-0.1.0/prlens/cli.py +88 -0
- prlens-0.1.0/prlens/config.py +65 -0
- prlens-0.1.0/prlens/gh/__init__.py +0 -0
- prlens-0.1.0/prlens/gh/pull_request.py +17 -0
- prlens-0.1.0/prlens/guidelines/backend.md +37 -0
- prlens-0.1.0/prlens/guidelines/frontend.md +65 -0
- prlens-0.1.0/prlens/providers/__init__.py +0 -0
- prlens-0.1.0/prlens/providers/anthropic.py +114 -0
- prlens-0.1.0/prlens/providers/base.py +19 -0
- prlens-0.1.0/prlens/providers/openai.py +116 -0
- prlens-0.1.0/prlens/reviewer.py +357 -0
- prlens-0.1.0/prlens/utils/__init__.py +0 -0
- prlens-0.1.0/prlens/utils/code.py +11 -0
- prlens-0.1.0/prlens.egg-info/PKG-INFO +152 -0
- prlens-0.1.0/prlens.egg-info/SOURCES.txt +27 -0
- prlens-0.1.0/prlens.egg-info/dependency_links.txt +1 -0
- prlens-0.1.0/prlens.egg-info/entry_points.txt +2 -0
- prlens-0.1.0/prlens.egg-info/requires.txt +12 -0
- prlens-0.1.0/prlens.egg-info/top_level.txt +1 -0
- prlens-0.1.0/pyproject.toml +58 -0
- prlens-0.1.0/setup.cfg +4 -0
- prlens-0.1.0/tests/test_code_utils.py +27 -0
- prlens-0.1.0/tests/test_config.py +93 -0
- prlens-0.1.0/tests/test_diff_positions.py +59 -0
- prlens-0.1.0/tests/test_reviewer_helpers.py +78 -0
prlens-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 lens contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
prlens-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: prlens
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: AI-powered GitHub PR code reviewer for teams
|
|
5
|
+
License: MIT
|
|
6
|
+
Project-URL: Homepage, https://github.com/jodohq/lens
|
|
7
|
+
Project-URL: Issues, https://github.com/jodohq/lens/issues
|
|
8
|
+
Keywords: code-review,github,ai,llm,pull-request
|
|
9
|
+
Classifier: Development Status :: 4 - Beta
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Classifier: Topic :: Software Development :: Quality Assurance
|
|
16
|
+
Requires-Python: >=3.11
|
|
17
|
+
Description-Content-Type: text/markdown
|
|
18
|
+
License-File: LICENSE
|
|
19
|
+
Requires-Dist: PyGithub>=2.1
|
|
20
|
+
Requires-Dist: openai>=1.0
|
|
21
|
+
Requires-Dist: anthropic>=0.25
|
|
22
|
+
Requires-Dist: click>=8.0
|
|
23
|
+
Requires-Dist: pyyaml>=6.0
|
|
24
|
+
Requires-Dist: python-dotenv>=1.0
|
|
25
|
+
Requires-Dist: rich>=13.0
|
|
26
|
+
Provides-Extra: dev
|
|
27
|
+
Requires-Dist: pytest>=8.0; extra == "dev"
|
|
28
|
+
Requires-Dist: pytest-mock>=3.12; extra == "dev"
|
|
29
|
+
Requires-Dist: black>=24.0; extra == "dev"
|
|
30
|
+
Dynamic: license-file
|
|
31
|
+
|
|
32
|
+
# lens
|
|
33
|
+
|
|
34
|
+
AI-powered GitHub PR code reviewer for teams. Fetches a pull request, reviews each changed file against your coding guidelines using Claude or GPT-4o, and posts inline comments directly on GitHub.
|
|
35
|
+
|
|
36
|
+
## Features
|
|
37
|
+
|
|
38
|
+
- Reviews only added/modified files — skips deleted files and binary assets
|
|
39
|
+
- Supports **Anthropic Claude** and **OpenAI GPT-4o** as AI backends
|
|
40
|
+
- Bring your own guidelines via a simple YAML config file
|
|
41
|
+
- Posts inline review comments on the diff using the GitHub Review API
|
|
42
|
+
- Runs as a **CLI tool** locally or as a **GitHub Action** automatically on every PR
|
|
43
|
+
- Prevents duplicate comments across repeated runs
|
|
44
|
+
- Filters known AI false positives (hallucinated suggestions)
|
|
45
|
+
|
|
46
|
+
## Installation
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
pip install prlens
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Quick Start (CLI)
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
export GITHUB_TOKEN=ghp_...
|
|
56
|
+
export ANTHROPIC_API_KEY=sk-ant-...
|
|
57
|
+
|
|
58
|
+
prlens --repo owner/repo --pr 42 --model anthropic
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Omit `--pr` to list open PRs and pick one interactively.
|
|
62
|
+
|
|
63
|
+
## GitHub Action
|
|
64
|
+
|
|
65
|
+
Add this workflow to your repository to automatically review every pull request:
|
|
66
|
+
|
|
67
|
+
```yaml
|
|
68
|
+
# .github/workflows/code-review.yml
|
|
69
|
+
name: Code Review
|
|
70
|
+
|
|
71
|
+
on:
|
|
72
|
+
pull_request:
|
|
73
|
+
types: [opened, synchronize]
|
|
74
|
+
|
|
75
|
+
jobs:
|
|
76
|
+
review:
|
|
77
|
+
runs-on: ubuntu-latest
|
|
78
|
+
steps:
|
|
79
|
+
- uses: jodohq/prlens/.github/actions/review@main
|
|
80
|
+
with:
|
|
81
|
+
model: anthropic
|
|
82
|
+
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
83
|
+
anthropic-api-key: ${{ secrets.ANTHROPIC_API_KEY }}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## Configuration
|
|
87
|
+
|
|
88
|
+
Copy `.prlens.example.yml` to `.prlens.yml` in your repository root:
|
|
89
|
+
|
|
90
|
+
```yaml
|
|
91
|
+
# .prlens.yml
|
|
92
|
+
model: anthropic # anthropic | openai
|
|
93
|
+
max_chars_per_file: 20000
|
|
94
|
+
batch_limit: 60
|
|
95
|
+
|
|
96
|
+
# Path to your team's coding guidelines (relative to repo root)
|
|
97
|
+
# guidelines: ./docs/guidelines.md
|
|
98
|
+
|
|
99
|
+
# Files/directories to skip — fnmatch globs or directory names
|
|
100
|
+
# exclude:
|
|
101
|
+
# - migrations/
|
|
102
|
+
# - "*.min.js"
|
|
103
|
+
|
|
104
|
+
# Set to true to review draft PRs (skipped by default)
|
|
105
|
+
review_draft_prs: false
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
The `--model` and `--guidelines` CLI flags override the config file. The config file overrides built-in defaults.
|
|
109
|
+
|
|
110
|
+
## Custom Guidelines
|
|
111
|
+
|
|
112
|
+
Point `guidelines` in `.prlens.yml` to any Markdown file containing your team's coding standards:
|
|
113
|
+
|
|
114
|
+
```yaml
|
|
115
|
+
guidelines: ./docs/guidelines.md
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
When not set, built-in generic guidelines are used as a starting point. Copy them from [`prlens/guidelines/`](prlens/guidelines/) and customize.
|
|
119
|
+
|
|
120
|
+
## Environment Variables
|
|
121
|
+
|
|
122
|
+
| Variable | Required | Description |
|
|
123
|
+
|---|---|---|
|
|
124
|
+
| `GITHUB_TOKEN` | Yes | GitHub personal access token with `pull_requests: write` |
|
|
125
|
+
| `ANTHROPIC_API_KEY` | When using Claude | Anthropic API key |
|
|
126
|
+
| `OPENAI_API_KEY` | When using GPT-4o | OpenAI API key |
|
|
127
|
+
|
|
128
|
+
## CLI Reference
|
|
129
|
+
|
|
130
|
+
```
|
|
131
|
+
Usage: prlens [OPTIONS]
|
|
132
|
+
|
|
133
|
+
AI-powered GitHub PR code reviewer.
|
|
134
|
+
|
|
135
|
+
Options:
|
|
136
|
+
--repo TEXT GitHub repository in owner/name format. [required]
|
|
137
|
+
--pr INTEGER Pull request number. Omit to list open PRs interactively.
|
|
138
|
+
--model [anthropic|openai] AI model provider. Overrides config file.
|
|
139
|
+
--config TEXT Path to the configuration file. [default: .prlens.yml]
|
|
140
|
+
-y, --yes Skip confirmation prompts and post comments automatically.
|
|
141
|
+
--guidelines PATH Path to a Markdown guidelines file. Overrides config file.
|
|
142
|
+
-s, --shadow Dry-run mode: print review comments to the terminal without posting to GitHub.
|
|
143
|
+
--help Show this message and exit.
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
## Contributing
|
|
147
|
+
|
|
148
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md) for how to set up a development environment, run tests, and add new AI providers.
|
|
149
|
+
|
|
150
|
+
## License
|
|
151
|
+
|
|
152
|
+
[MIT](LICENSE)
|
prlens-0.1.0/README.md
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# lens
|
|
2
|
+
|
|
3
|
+
AI-powered GitHub PR code reviewer for teams. Fetches a pull request, reviews each changed file against your coding guidelines using Claude or GPT-4o, and posts inline comments directly on GitHub.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- Reviews only added/modified files — skips deleted files and binary assets
|
|
8
|
+
- Supports **Anthropic Claude** and **OpenAI GPT-4o** as AI backends
|
|
9
|
+
- Bring your own guidelines via a simple YAML config file
|
|
10
|
+
- Posts inline review comments on the diff using the GitHub Review API
|
|
11
|
+
- Runs as a **CLI tool** locally or as a **GitHub Action** automatically on every PR
|
|
12
|
+
- Prevents duplicate comments across repeated runs
|
|
13
|
+
- Filters known AI false positives (hallucinated suggestions)
|
|
14
|
+
|
|
15
|
+
## Installation
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
pip install prlens
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Quick Start (CLI)
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
export GITHUB_TOKEN=ghp_...
|
|
25
|
+
export ANTHROPIC_API_KEY=sk-ant-...
|
|
26
|
+
|
|
27
|
+
prlens --repo owner/repo --pr 42 --model anthropic
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Omit `--pr` to list open PRs and pick one interactively.
|
|
31
|
+
|
|
32
|
+
## GitHub Action
|
|
33
|
+
|
|
34
|
+
Add this workflow to your repository to automatically review every pull request:
|
|
35
|
+
|
|
36
|
+
```yaml
|
|
37
|
+
# .github/workflows/code-review.yml
|
|
38
|
+
name: Code Review
|
|
39
|
+
|
|
40
|
+
on:
|
|
41
|
+
pull_request:
|
|
42
|
+
types: [opened, synchronize]
|
|
43
|
+
|
|
44
|
+
jobs:
|
|
45
|
+
review:
|
|
46
|
+
runs-on: ubuntu-latest
|
|
47
|
+
steps:
|
|
48
|
+
- uses: jodohq/prlens/.github/actions/review@main
|
|
49
|
+
with:
|
|
50
|
+
model: anthropic
|
|
51
|
+
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
52
|
+
anthropic-api-key: ${{ secrets.ANTHROPIC_API_KEY }}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Configuration
|
|
56
|
+
|
|
57
|
+
Copy `.prlens.example.yml` to `.prlens.yml` in your repository root:
|
|
58
|
+
|
|
59
|
+
```yaml
|
|
60
|
+
# .prlens.yml
|
|
61
|
+
model: anthropic # anthropic | openai
|
|
62
|
+
max_chars_per_file: 20000
|
|
63
|
+
batch_limit: 60
|
|
64
|
+
|
|
65
|
+
# Path to your team's coding guidelines (relative to repo root)
|
|
66
|
+
# guidelines: ./docs/guidelines.md
|
|
67
|
+
|
|
68
|
+
# Files/directories to skip — fnmatch globs or directory names
|
|
69
|
+
# exclude:
|
|
70
|
+
# - migrations/
|
|
71
|
+
# - "*.min.js"
|
|
72
|
+
|
|
73
|
+
# Set to true to review draft PRs (skipped by default)
|
|
74
|
+
review_draft_prs: false
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
The `--model` and `--guidelines` CLI flags override the config file. The config file overrides built-in defaults.
|
|
78
|
+
|
|
79
|
+
## Custom Guidelines
|
|
80
|
+
|
|
81
|
+
Point `guidelines` in `.prlens.yml` to any Markdown file containing your team's coding standards:
|
|
82
|
+
|
|
83
|
+
```yaml
|
|
84
|
+
guidelines: ./docs/guidelines.md
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
When not set, built-in generic guidelines are used as a starting point. Copy them from [`prlens/guidelines/`](prlens/guidelines/) and customize.
|
|
88
|
+
|
|
89
|
+
## Environment Variables
|
|
90
|
+
|
|
91
|
+
| Variable | Required | Description |
|
|
92
|
+
|---|---|---|
|
|
93
|
+
| `GITHUB_TOKEN` | Yes | GitHub personal access token with `pull_requests: write` |
|
|
94
|
+
| `ANTHROPIC_API_KEY` | When using Claude | Anthropic API key |
|
|
95
|
+
| `OPENAI_API_KEY` | When using GPT-4o | OpenAI API key |
|
|
96
|
+
|
|
97
|
+
## CLI Reference
|
|
98
|
+
|
|
99
|
+
```
|
|
100
|
+
Usage: prlens [OPTIONS]
|
|
101
|
+
|
|
102
|
+
AI-powered GitHub PR code reviewer.
|
|
103
|
+
|
|
104
|
+
Options:
|
|
105
|
+
--repo TEXT GitHub repository in owner/name format. [required]
|
|
106
|
+
--pr INTEGER Pull request number. Omit to list open PRs interactively.
|
|
107
|
+
--model [anthropic|openai] AI model provider. Overrides config file.
|
|
108
|
+
--config TEXT Path to the configuration file. [default: .prlens.yml]
|
|
109
|
+
-y, --yes Skip confirmation prompts and post comments automatically.
|
|
110
|
+
--guidelines PATH Path to a Markdown guidelines file. Overrides config file.
|
|
111
|
+
-s, --shadow Dry-run mode: print review comments to the terminal without posting to GitHub.
|
|
112
|
+
--help Show this message and exit.
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## Contributing
|
|
116
|
+
|
|
117
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md) for how to set up a development environment, run tests, and add new AI providers.
|
|
118
|
+
|
|
119
|
+
## License
|
|
120
|
+
|
|
121
|
+
[MIT](LICENSE)
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
"""CLI entry point for lens."""
|
|
2
|
+
|
|
3
|
+
import click
|
|
4
|
+
from rich.console import Console
|
|
5
|
+
|
|
6
|
+
from prlens.config import load_config
|
|
7
|
+
from prlens.gh.pull_request import get_repo, get_pull_requests
|
|
8
|
+
from prlens.reviewer import run_review
|
|
9
|
+
|
|
10
|
+
console = Console()
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@click.command()
|
|
14
|
+
@click.option("--repo", required=True, help="GitHub repository in owner/name format.")
|
|
15
|
+
@click.option(
|
|
16
|
+
"--pr",
|
|
17
|
+
"pr_number",
|
|
18
|
+
type=int,
|
|
19
|
+
default=None,
|
|
20
|
+
help="Pull request number. Omit to list open PRs interactively.",
|
|
21
|
+
)
|
|
22
|
+
@click.option(
|
|
23
|
+
"--model",
|
|
24
|
+
type=click.Choice(["anthropic", "openai"]),
|
|
25
|
+
default=None,
|
|
26
|
+
help="AI model provider. Overrides config file.",
|
|
27
|
+
)
|
|
28
|
+
@click.option(
|
|
29
|
+
"--guidelines",
|
|
30
|
+
"guidelines_path",
|
|
31
|
+
default=None,
|
|
32
|
+
help="Path to a Markdown guidelines file. Overrides config file.",
|
|
33
|
+
)
|
|
34
|
+
@click.option(
|
|
35
|
+
"--config",
|
|
36
|
+
"config_path",
|
|
37
|
+
default=".prlens.yml",
|
|
38
|
+
show_default=True,
|
|
39
|
+
help="Path to the configuration file.",
|
|
40
|
+
)
|
|
41
|
+
@click.option(
|
|
42
|
+
"--yes",
|
|
43
|
+
"-y",
|
|
44
|
+
is_flag=True,
|
|
45
|
+
help="Skip confirmation prompts and post comments automatically.",
|
|
46
|
+
)
|
|
47
|
+
@click.option(
|
|
48
|
+
"--shadow",
|
|
49
|
+
"-s",
|
|
50
|
+
is_flag=True,
|
|
51
|
+
help="Dry-run mode: print review comments to the terminal without posting to GitHub.",
|
|
52
|
+
)
|
|
53
|
+
def main(repo: str, pr_number: int | None, model: str | None, guidelines_path: str | None, config_path: str, yes: bool, shadow: bool):
|
|
54
|
+
"""AI-powered GitHub PR code reviewer.
|
|
55
|
+
|
|
56
|
+
Fetches a pull request, reviews each changed file against your coding
|
|
57
|
+
guidelines using Claude or GPT-4o, and posts inline comments on GitHub.
|
|
58
|
+
|
|
59
|
+
\b
|
|
60
|
+
Required environment variables:
|
|
61
|
+
GITHUB_TOKEN GitHub personal access token
|
|
62
|
+
ANTHROPIC_API_KEY Required when using --model anthropic
|
|
63
|
+
OPENAI_API_KEY Required when using --model openai
|
|
64
|
+
"""
|
|
65
|
+
config = load_config(config_path, cli_overrides={"model": model, "guidelines": guidelines_path})
|
|
66
|
+
|
|
67
|
+
if not config.get("github_token"):
|
|
68
|
+
raise click.UsageError(
|
|
69
|
+
"GITHUB_TOKEN environment variable is not set. "
|
|
70
|
+
"Create a token at https://github.com/settings/tokens"
|
|
71
|
+
)
|
|
72
|
+
if config["model"] == "anthropic" and not config.get("anthropic_api_key"):
|
|
73
|
+
raise click.UsageError("ANTHROPIC_API_KEY environment variable is not set.")
|
|
74
|
+
if config["model"] == "openai" and not config.get("openai_api_key"):
|
|
75
|
+
raise click.UsageError("OPENAI_API_KEY environment variable is not set.")
|
|
76
|
+
|
|
77
|
+
if pr_number is None:
|
|
78
|
+
this_repo = get_repo(repo, token=config["github_token"])
|
|
79
|
+
prs = list(get_pull_requests(this_repo))
|
|
80
|
+
if not prs:
|
|
81
|
+
console.print("[yellow]No open pull requests found.[/yellow]")
|
|
82
|
+
return
|
|
83
|
+
console.print("\nOpen pull requests:")
|
|
84
|
+
for pr in prs:
|
|
85
|
+
console.print(f" [bold]#{pr.number}[/bold] {pr.title}")
|
|
86
|
+
pr_number = click.prompt("\nEnter the pull request number", type=int)
|
|
87
|
+
|
|
88
|
+
run_review(repo=repo, pr_number=pr_number, config=config, auto_confirm=yes, shadow=shadow)
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
import yaml
|
|
6
|
+
|
|
7
|
+
DEFAULT_CONFIG: dict = {
|
|
8
|
+
"model": "anthropic",
|
|
9
|
+
"max_chars_per_file": 20000,
|
|
10
|
+
"batch_limit": 60,
|
|
11
|
+
"guidelines": None, # None = use built-in default; set to a path string to override
|
|
12
|
+
"exclude": [], # fnmatch patterns or directory names to skip (e.g. "migrations/", "*.min.js")
|
|
13
|
+
"review_draft_prs": False,
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
BUILTIN_GUIDELINES_DIR = Path(__file__).parent / "guidelines"
|
|
17
|
+
_BUILTIN_DEFAULT = BUILTIN_GUIDELINES_DIR / "backend.md"
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def load_config(config_path: str = ".prlens.yml", cli_overrides: Optional[dict] = None) -> dict:
|
|
21
|
+
"""
|
|
22
|
+
Load configuration by merging (in order of precedence):
|
|
23
|
+
1. Built-in defaults
|
|
24
|
+
2. .prlens.yml in the current directory
|
|
25
|
+
3. CLI argument overrides
|
|
26
|
+
"""
|
|
27
|
+
config = {**DEFAULT_CONFIG, "exclude": list(DEFAULT_CONFIG["exclude"])}
|
|
28
|
+
|
|
29
|
+
path = Path(config_path)
|
|
30
|
+
if path.exists():
|
|
31
|
+
with open(path) as f:
|
|
32
|
+
file_config = yaml.safe_load(f) or {}
|
|
33
|
+
config.update(file_config)
|
|
34
|
+
|
|
35
|
+
if cli_overrides:
|
|
36
|
+
for key, value in cli_overrides.items():
|
|
37
|
+
if value is not None:
|
|
38
|
+
config[key] = value
|
|
39
|
+
|
|
40
|
+
# Resolve credentials from environment variables
|
|
41
|
+
config["github_token"] = os.environ.get("GITHUB_TOKEN")
|
|
42
|
+
config["anthropic_api_key"] = os.environ.get("ANTHROPIC_API_KEY")
|
|
43
|
+
config["openai_api_key"] = os.environ.get("OPENAI_API_KEY")
|
|
44
|
+
|
|
45
|
+
return config
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def load_guidelines(config: dict) -> str:
|
|
49
|
+
"""
|
|
50
|
+
Load review guidelines.
|
|
51
|
+
|
|
52
|
+
If ``guidelines`` is set in config, loads from that path (relative to cwd).
|
|
53
|
+
Otherwise falls back to the built-in default.
|
|
54
|
+
"""
|
|
55
|
+
custom_path = config.get("guidelines")
|
|
56
|
+
if custom_path:
|
|
57
|
+
p = Path(custom_path)
|
|
58
|
+
if not p.exists():
|
|
59
|
+
raise FileNotFoundError(f"Guidelines file not found: {custom_path}")
|
|
60
|
+
return p.read_text()
|
|
61
|
+
|
|
62
|
+
if _BUILTIN_DEFAULT.exists():
|
|
63
|
+
return _BUILTIN_DEFAULT.read_text()
|
|
64
|
+
|
|
65
|
+
raise FileNotFoundError("No guidelines configured and built-in default is missing.")
|
|
File without changes
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from github import Github
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def get_repo(repo_name: str, token: str):
|
|
5
|
+
return Github(token).get_repo(repo_name)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def get_pull(repo, pr_number: int):
|
|
9
|
+
return repo.get_pull(pr_number)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def get_pull_requests(repo, state: str = "open"):
|
|
13
|
+
return repo.get_pulls(state=state)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def get_diff(pr):
|
|
17
|
+
return pr.get_files()
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# Backend Code Review Guidelines
|
|
2
|
+
|
|
3
|
+
## REST & Architecture
|
|
4
|
+
|
|
5
|
+
- Use proper HTTP verbs (e.g., `POST /users` not `POST /users/create`).
|
|
6
|
+
- Keep business logic in service layers — views/controllers should only orchestrate.
|
|
7
|
+
- Avoid putting business logic in model `save()` methods or view handlers.
|
|
8
|
+
- Avoid boolean flags that alter method behavior; create explicit, separate methods instead.
|
|
9
|
+
|
|
10
|
+
## Code Structure & Reusability
|
|
11
|
+
|
|
12
|
+
- Place shared logic in a shared library if it is reused across multiple services.
|
|
13
|
+
- Wrap external integrations (e.g., Slack, email, storage) in clean service layers that do not depend on internal app logic.
|
|
14
|
+
- Avoid adding executable code in `__init__.py` files — use them only for imports and package exposure.
|
|
15
|
+
|
|
16
|
+
## Django-Specific Practices (if applicable)
|
|
17
|
+
|
|
18
|
+
- Use `select_related` / `prefetch_related` to avoid N+1 queries.
|
|
19
|
+
- Validate inputs in serializers, not in views or services.
|
|
20
|
+
- Use custom domain exceptions instead of generic ones for consistent error handling.
|
|
21
|
+
- Organize code modularly by domain (e.g., `users`, `payments`, `notifications`).
|
|
22
|
+
|
|
23
|
+
## Python Best Practices
|
|
24
|
+
|
|
25
|
+
- Use type hints in all function and method signatures.
|
|
26
|
+
- Avoid wildcard imports (`from module import *`).
|
|
27
|
+
- Use `logging` with appropriate levels instead of `print()`.
|
|
28
|
+
- Replace magic strings and numbers with named constants or enums.
|
|
29
|
+
- Prefer `pathlib.Path` over string-based file paths.
|
|
30
|
+
- Use context managers (`with` statements) for files and resources.
|
|
31
|
+
|
|
32
|
+
## Testing & Maintainability
|
|
33
|
+
|
|
34
|
+
- Write unit tests for all new service logic.
|
|
35
|
+
- Keep tests fast and deterministic; mock all external dependencies.
|
|
36
|
+
- Use environment variables for secrets and configuration — never hardcode them.
|
|
37
|
+
- Add docstrings and API documentation for new public endpoints and logic.
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# Frontend Code Review Guidelines
|
|
2
|
+
|
|
3
|
+
## API Handling
|
|
4
|
+
|
|
5
|
+
- Store API responses in global state (e.g., Redux) only if needed across multiple components.
|
|
6
|
+
- Use component-local state (`useState`/`useEffect`) for view-specific or session-specific data.
|
|
7
|
+
- Avoid flag-based conditional API logic inside components — extract it to helper functions or hooks.
|
|
8
|
+
- Optimize for performance: debounce search inputs, paginate large datasets, cache static responses.
|
|
9
|
+
|
|
10
|
+
## State Management
|
|
11
|
+
|
|
12
|
+
- Use global state slices for shared state only.
|
|
13
|
+
- Avoid duplicating state between global state and component-local state.
|
|
14
|
+
- Encapsulate side effects and data-fetching in reusable custom hooks.
|
|
15
|
+
|
|
16
|
+
## Component Architecture
|
|
17
|
+
|
|
18
|
+
- Follow a clean separation of concerns:
|
|
19
|
+
- `components/` — Dumb, reusable UI elements
|
|
20
|
+
- `containers/` — Smart components with data-fetching
|
|
21
|
+
- `hooks/` — Reusable logic for side effects
|
|
22
|
+
- `utils/`, `constants/` — Low-level modules and config
|
|
23
|
+
|
|
24
|
+
## Do's
|
|
25
|
+
|
|
26
|
+
- Use constants or enums for repeated value-label pairs (e.g., statuses, categories).
|
|
27
|
+
- Keep components small and composable.
|
|
28
|
+
- Write tests for custom hooks, logic, and critical UI behaviors.
|
|
29
|
+
|
|
30
|
+
## Don'ts
|
|
31
|
+
|
|
32
|
+
- Don't use wildcard imports (e.g., `import * as lib`) — prefer named imports.
|
|
33
|
+
- Don't hardcode magic values — define them as constants or enums.
|
|
34
|
+
- Don't embed conditional API logic directly in components.
|
|
35
|
+
- Don't bloat container components — move logic to hooks or services.
|
|
36
|
+
|
|
37
|
+
## Value-to-Label Mapping
|
|
38
|
+
|
|
39
|
+
Use a structured class with static getters for value-label constants:
|
|
40
|
+
|
|
41
|
+
```js
|
|
42
|
+
// Good
|
|
43
|
+
class DocumentType {
|
|
44
|
+
static get PASSPORT() {
|
|
45
|
+
return { code: "passport", title: "Passport" };
|
|
46
|
+
}
|
|
47
|
+
static get ALL() {
|
|
48
|
+
return [DocumentType.PASSPORT];
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Bad
|
|
53
|
+
const DocumentType = {
|
|
54
|
+
PASSPORT: { code: "passport", title: "Passport" },
|
|
55
|
+
};
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Using a class prevents unintentional mutation and supports lazy initialization.
|
|
59
|
+
|
|
60
|
+
## Syntax & Language Notes
|
|
61
|
+
|
|
62
|
+
- Use named imports: `import { Button } from "antd";`
|
|
63
|
+
- Avoid wildcard imports: `import * as antd from "antd";`
|
|
64
|
+
- Do not suggest removing fallback logic (e.g., `|| []`) unless the value is guaranteed non-null.
|
|
65
|
+
Note: JavaScript's `Map.get()` does not support default values like Python's `dict.get()`.
|
|
File without changes
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import logging
|
|
3
|
+
import time
|
|
4
|
+
|
|
5
|
+
from anthropic import Anthropic
|
|
6
|
+
from anthropic.types import TextBlock
|
|
7
|
+
|
|
8
|
+
from prlens.providers.base import BaseReviewer
|
|
9
|
+
|
|
10
|
+
logger = logging.getLogger(__name__)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class AnthropicReviewer(BaseReviewer):
|
|
14
|
+
MODEL = "claude-sonnet-4-20250514"
|
|
15
|
+
MAX_TOKENS = 4096
|
|
16
|
+
MAX_RETRIES = 3
|
|
17
|
+
|
|
18
|
+
def __init__(self, api_key: str):
|
|
19
|
+
self.client = Anthropic(api_key=api_key)
|
|
20
|
+
|
|
21
|
+
def review(
|
|
22
|
+
self,
|
|
23
|
+
description: str,
|
|
24
|
+
file_name: str,
|
|
25
|
+
diff_patch: str,
|
|
26
|
+
file_content: str,
|
|
27
|
+
guidelines: str,
|
|
28
|
+
) -> list[dict]:
|
|
29
|
+
prompt = self._build_prompt(description, file_name, diff_patch, file_content, guidelines)
|
|
30
|
+
raw = self._call_with_retry(prompt)
|
|
31
|
+
if raw is None:
|
|
32
|
+
return []
|
|
33
|
+
return self._parse(raw)
|
|
34
|
+
|
|
35
|
+
def _call_with_retry(self, prompt: str) -> str | None:
|
|
36
|
+
for attempt in range(self.MAX_RETRIES):
|
|
37
|
+
try:
|
|
38
|
+
response = self.client.messages.create(
|
|
39
|
+
model=self.MODEL,
|
|
40
|
+
messages=[{"role": "user", "content": prompt}],
|
|
41
|
+
temperature=0.3,
|
|
42
|
+
max_tokens=self.MAX_TOKENS,
|
|
43
|
+
)
|
|
44
|
+
text_blocks = [block.text for block in response.content if isinstance(block, TextBlock)]
|
|
45
|
+
return "".join(text_blocks).strip()
|
|
46
|
+
except Exception as e:
|
|
47
|
+
if attempt == self.MAX_RETRIES - 1:
|
|
48
|
+
logger.error("Anthropic API failed after %d attempts: %s", self.MAX_RETRIES, e)
|
|
49
|
+
return None
|
|
50
|
+
delay = 2**attempt
|
|
51
|
+
logger.warning(
|
|
52
|
+
"Anthropic API error (attempt %d/%d): %s. Retrying in %ds...",
|
|
53
|
+
attempt + 1,
|
|
54
|
+
self.MAX_RETRIES,
|
|
55
|
+
e,
|
|
56
|
+
delay,
|
|
57
|
+
)
|
|
58
|
+
time.sleep(delay)
|
|
59
|
+
|
|
60
|
+
def _build_prompt(
|
|
61
|
+
self, description: str, file_name: str, diff_patch: str, file_content: str, guidelines: str
|
|
62
|
+
) -> str:
|
|
63
|
+
return f"""
|
|
64
|
+
You are a strict and precise senior code reviewer.
|
|
65
|
+
Review the patch below and identify issues according to the guidelines.
|
|
66
|
+
|
|
67
|
+
{guidelines}
|
|
68
|
+
|
|
69
|
+
Rules:
|
|
70
|
+
- Focus on added lines (starting with '+') for direct violations.
|
|
71
|
+
- Also consider implications of removed lines (starting with '-') — e.g. deleted null checks, removed error handling, dropped permission guards.
|
|
72
|
+
- Do not comment on code that already follows best practices.
|
|
73
|
+
- Avoid assumptions when context is unclear. Be concise and actionable.
|
|
74
|
+
|
|
75
|
+
You are reviewing file `{file_name}`.
|
|
76
|
+
|
|
77
|
+
PR description:
|
|
78
|
+
{description}
|
|
79
|
+
|
|
80
|
+
Diff:
|
|
81
|
+
{diff_patch}
|
|
82
|
+
|
|
83
|
+
File content (for context):
|
|
84
|
+
{file_content}
|
|
85
|
+
|
|
86
|
+
### Output Format:
|
|
87
|
+
Respond with **only** a valid JSON list:
|
|
88
|
+
|
|
89
|
+
[
|
|
90
|
+
{{
|
|
91
|
+
"line": <line number in the new file (integer)>,
|
|
92
|
+
"severity": "<critical|major|minor|nitpick>",
|
|
93
|
+
"comment": "<concise, actionable comment>"
|
|
94
|
+
}},
|
|
95
|
+
...
|
|
96
|
+
]
|
|
97
|
+
|
|
98
|
+
Severity guide:
|
|
99
|
+
- critical: security vulnerability, data loss risk, crash
|
|
100
|
+
- major: logic bug, missing error handling, significant performance issue
|
|
101
|
+
- minor: code smell, unclear naming, missing type hint
|
|
102
|
+
- nitpick: style preference, minor formatting
|
|
103
|
+
|
|
104
|
+
If there are no issues, return: []
|
|
105
|
+
Do not return any text outside the JSON block.
|
|
106
|
+
"""
|
|
107
|
+
|
|
108
|
+
def _parse(self, raw: str) -> list[dict]:
|
|
109
|
+
try:
|
|
110
|
+
cleaned = raw.replace("```json", "").replace("```", "").strip()
|
|
111
|
+
return json.loads(cleaned)
|
|
112
|
+
except json.JSONDecodeError:
|
|
113
|
+
logger.warning("Failed to parse Anthropic response as JSON: %s", raw[:200])
|
|
114
|
+
return []
|