pr-sentinel 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.
- pr_sentinel-0.1.0/LICENSE +21 -0
- pr_sentinel-0.1.0/PKG-INFO +266 -0
- pr_sentinel-0.1.0/README.md +234 -0
- pr_sentinel-0.1.0/pyproject.toml +64 -0
- pr_sentinel-0.1.0/setup.cfg +4 -0
- pr_sentinel-0.1.0/src/pr_sentinel/__init__.py +1 -0
- pr_sentinel-0.1.0/src/pr_sentinel/agents/__init__.py +11 -0
- pr_sentinel-0.1.0/src/pr_sentinel/agents/base.py +64 -0
- pr_sentinel-0.1.0/src/pr_sentinel/agents/performance_agent.py +7 -0
- pr_sentinel-0.1.0/src/pr_sentinel/agents/quality_agent.py +7 -0
- pr_sentinel-0.1.0/src/pr_sentinel/agents/security_agent.py +7 -0
- pr_sentinel-0.1.0/src/pr_sentinel/agents/testing_agent.py +7 -0
- pr_sentinel-0.1.0/src/pr_sentinel/cache.py +130 -0
- pr_sentinel-0.1.0/src/pr_sentinel/chunker.py +33 -0
- pr_sentinel-0.1.0/src/pr_sentinel/claude_runner.py +112 -0
- pr_sentinel-0.1.0/src/pr_sentinel/cli.py +605 -0
- pr_sentinel-0.1.0/src/pr_sentinel/diff_parser.py +105 -0
- pr_sentinel-0.1.0/src/pr_sentinel/git_diff.py +65 -0
- pr_sentinel-0.1.0/src/pr_sentinel/orchestrator.py +105 -0
- pr_sentinel-0.1.0/src/pr_sentinel/prompts/performance.md +50 -0
- pr_sentinel-0.1.0/src/pr_sentinel/prompts/quality.md +48 -0
- pr_sentinel-0.1.0/src/pr_sentinel/prompts/security.md +48 -0
- pr_sentinel-0.1.0/src/pr_sentinel/prompts/testing.md +50 -0
- pr_sentinel-0.1.0/src/pr_sentinel/report_generator.py +292 -0
- pr_sentinel-0.1.0/src/pr_sentinel.egg-info/PKG-INFO +266 -0
- pr_sentinel-0.1.0/src/pr_sentinel.egg-info/SOURCES.txt +31 -0
- pr_sentinel-0.1.0/src/pr_sentinel.egg-info/dependency_links.txt +1 -0
- pr_sentinel-0.1.0/src/pr_sentinel.egg-info/entry_points.txt +2 -0
- pr_sentinel-0.1.0/src/pr_sentinel.egg-info/requires.txt +7 -0
- pr_sentinel-0.1.0/src/pr_sentinel.egg-info/top_level.txt +1 -0
- pr_sentinel-0.1.0/tests/test_chunker.py +52 -0
- pr_sentinel-0.1.0/tests/test_diff_parser.py +114 -0
- pr_sentinel-0.1.0/tests/test_report_generator.py +110 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Yarramaddi Kishor Kumar Reddy
|
|
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.
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pr-sentinel
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Local PR review tool that orchestrates Claude Code CLI agents over git diffs.
|
|
5
|
+
Author-email: Yarramaddi Kishor Kumar Reddy <kishor04reddy@gmail.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/kishor2004reddy/Pull-Request-Sentinel-CLI
|
|
8
|
+
Project-URL: Repository, https://github.com/kishor2004reddy/Pull-Request-Sentinel-CLI
|
|
9
|
+
Project-URL: Issues, https://github.com/kishor2004reddy/Pull-Request-Sentinel-CLI/issues
|
|
10
|
+
Keywords: pr-review,code-review,claude,claude-code,cli,git-diff,static-analysis
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Environment :: Console
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Topic :: Software Development :: Quality Assurance
|
|
21
|
+
Classifier: Topic :: Software Development :: Version Control :: Git
|
|
22
|
+
Requires-Python: >=3.11
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
License-File: LICENSE
|
|
25
|
+
Requires-Dist: click>=8.1
|
|
26
|
+
Requires-Dist: rich>=13
|
|
27
|
+
Provides-Extra: dev
|
|
28
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
29
|
+
Requires-Dist: build; extra == "dev"
|
|
30
|
+
Requires-Dist: twine; extra == "dev"
|
|
31
|
+
Dynamic: license-file
|
|
32
|
+
|
|
33
|
+
# PR Sentinel
|
|
34
|
+
|
|
35
|
+
Local pull-request review tool. Reads a git diff, runs four specialized review agents over it via the Claude Code CLI, and emits a structured JSON + Markdown report so you can fix issues *before* raising the PR.
|
|
36
|
+
|
|
37
|
+
No API keys. No hosted services. PR Sentinel shells out to `claude -p` and uses your existing Claude Code authentication.
|
|
38
|
+
|
|
39
|
+
## How it works
|
|
40
|
+
|
|
41
|
+
```
|
|
42
|
+
git diff main...HEAD
|
|
43
|
+
│
|
|
44
|
+
▼
|
|
45
|
+
[ diff_parser ] ── filters noise (lock files, build dirs, minified, generated)
|
|
46
|
+
│
|
|
47
|
+
▼
|
|
48
|
+
[ chunker ] ── packs files into ≤60k-char chunks (hybrid batching)
|
|
49
|
+
│
|
|
50
|
+
▼
|
|
51
|
+
[ orchestrator ] ── runs (agent × chunk) tasks in a bounded thread pool
|
|
52
|
+
│
|
|
53
|
+
├── Security Agent (claude -p prompts/security.md)
|
|
54
|
+
├── Code Quality Agent (claude -p prompts/quality.md)
|
|
55
|
+
├── Performance Agent (claude -p prompts/performance.md)
|
|
56
|
+
└── Testing Agent (claude -p prompts/testing.md)
|
|
57
|
+
│
|
|
58
|
+
▼
|
|
59
|
+
[ cache ] ── sha256(model + prompt) → response, on disk
|
|
60
|
+
│
|
|
61
|
+
▼
|
|
62
|
+
[ report_generator ] ── merges findings, computes risk level
|
|
63
|
+
│
|
|
64
|
+
▼
|
|
65
|
+
reports/report.json + reports/review-report.md
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Requirements
|
|
69
|
+
|
|
70
|
+
- Python 3.11+
|
|
71
|
+
- [Claude Code CLI](https://docs.claude.com/en/docs/claude-code) installed and authenticated. `claude --version` must work from your shell.
|
|
72
|
+
- Git, if you want to review live branches (not required for `--diff` mode).
|
|
73
|
+
|
|
74
|
+
## Install
|
|
75
|
+
|
|
76
|
+
From PyPI:
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
pip install pr-sentinel
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
From source (for development):
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
git clone https://github.com/kishor2004reddy/Pull-Request-Sentinel-CLI.git
|
|
86
|
+
cd Pull-Request-Sentinel-CLI
|
|
87
|
+
pip install -e .[dev]
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Verify:
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
pr-sentinel --help
|
|
94
|
+
pr-sentinel agents
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## Quickstart
|
|
98
|
+
|
|
99
|
+
Review your current branch against `main`:
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
pr-sentinel review --base main
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Or review a saved diff file (works without a git repo):
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
git diff main...HEAD > my.diff
|
|
109
|
+
pr-sentinel review --diff my.diff --out reports
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
Open `reports/review-report.md`.
|
|
113
|
+
|
|
114
|
+
## Commands
|
|
115
|
+
|
|
116
|
+
### `pr-sentinel review`
|
|
117
|
+
|
|
118
|
+
| Flag | Default | Description |
|
|
119
|
+
|------|---------|-------------|
|
|
120
|
+
| `--base` | `main` | Branch to diff against. Runs `git diff <base>...<head>`. |
|
|
121
|
+
| `--head` | `HEAD` | Source branch/ref to review. Use with `--base` to diff arbitrary refs without checking them out. |
|
|
122
|
+
| `--diff PATH` | — | Review a saved diff file instead of running git. Mutually exclusive with `--staged`. |
|
|
123
|
+
| `--staged` | off | Review staged changes (`git diff --cached`). |
|
|
124
|
+
| `--repo PATH` | cwd | Path to the git repository to review. Ignored when `--diff` is used. |
|
|
125
|
+
| `--agents` | `security,quality,performance,testing` | Comma-separated agents to run. |
|
|
126
|
+
| `--out` | `./reports` | Output directory. |
|
|
127
|
+
| `--format` | `both` | `json`, `markdown`, or `both`. |
|
|
128
|
+
| `--max-file-size` | `20000` | Per-file diff size cap (chars). Larger files get truncated with a marker. |
|
|
129
|
+
| `--chunk-budget` | `60000` | Max combined diff size per Claude call before chunking kicks in. |
|
|
130
|
+
| `--model` | `haiku` | Claude model to use. Shortcuts: `sonnet`, `opus`, `haiku`. Or pass a full model ID such as `claude-opus-4-7`. |
|
|
131
|
+
| `--max-parallel` | `8` | Max concurrent `claude` calls across all (agent, chunk) pairs. |
|
|
132
|
+
| `--timeout` | `600` | Per-call timeout in seconds for each `claude` subprocess. |
|
|
133
|
+
| `--no-cache` | off | Bypass the response cache for this run. Successful responses are still written to the cache. |
|
|
134
|
+
|
|
135
|
+
### `pr-sentinel agents`
|
|
136
|
+
|
|
137
|
+
Lists available agents and their implementation status.
|
|
138
|
+
|
|
139
|
+
### `pr-sentinel cache`
|
|
140
|
+
|
|
141
|
+
Inspect and manage the on-disk response cache.
|
|
142
|
+
|
|
143
|
+
| Subcommand | Description |
|
|
144
|
+
|---|---|
|
|
145
|
+
| `cache size` | Show cache location, entry count, and disk usage. |
|
|
146
|
+
| `cache clear` | Wipe the entire cache (prompts for confirmation). |
|
|
147
|
+
| `cache prune --older-than 30d` | Delete entries older than the given age. Supports `s/m/h/d` suffixes. Add `--dry-run` to preview. |
|
|
148
|
+
|
|
149
|
+
The cache lives at `~/.pr-sentinel/cache/` by default. Override with the `PR_SENTINEL_CACHE_DIR` environment variable. Keys are `sha256(model + prompt)`, so changing the model or any prompt content invalidates the entry automatically.
|
|
150
|
+
|
|
151
|
+
## Examples
|
|
152
|
+
|
|
153
|
+
Review current branch vs `main`:
|
|
154
|
+
```bash
|
|
155
|
+
pr-sentinel review --base main
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
Run only the security agent:
|
|
159
|
+
```bash
|
|
160
|
+
pr-sentinel review --base main --agents security
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
Review what's currently staged:
|
|
164
|
+
```bash
|
|
165
|
+
pr-sentinel review --staged
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
Review a feature branch without checking it out:
|
|
169
|
+
```bash
|
|
170
|
+
pr-sentinel review --base main --head feature/new-auth
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
Use a stronger model for higher-stakes reviews:
|
|
174
|
+
```bash
|
|
175
|
+
pr-sentinel review --base main --model opus
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
Force a fresh run, ignoring cached responses:
|
|
179
|
+
```bash
|
|
180
|
+
pr-sentinel review --base main --no-cache
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
Prune cache entries older than a week:
|
|
184
|
+
```bash
|
|
185
|
+
pr-sentinel cache prune --older-than 7d
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
## Report structure
|
|
189
|
+
|
|
190
|
+
The markdown report always emits these five sections in this fixed order, regardless of findings:
|
|
191
|
+
|
|
192
|
+
1. **Summary** — risk level, source, branch, timestamp, agents, finding counts
|
|
193
|
+
2. **Merge Verdict** — deterministic verdict driven by risk level (not a separate Claude call)
|
|
194
|
+
3. **Key Findings** — top blocking issues (High + Medium)
|
|
195
|
+
4. **Key Recommendations** — deduplicated fixes
|
|
196
|
+
5. **All Findings** — per-agent summary table + full per-issue detail
|
|
197
|
+
|
|
198
|
+
The JSON report contains the same data in a single structured object suitable for piping into other tools.
|
|
199
|
+
|
|
200
|
+
### Risk levels
|
|
201
|
+
|
|
202
|
+
| Level | Trigger |
|
|
203
|
+
|-------|---------|
|
|
204
|
+
| **High** | Any High-severity finding, or 5+ Medium-severity findings |
|
|
205
|
+
| **Medium** | One or more Medium-severity findings (and fewer than 5) |
|
|
206
|
+
| **Low** | Only Low-severity findings |
|
|
207
|
+
| **None** | No findings across all executed agents |
|
|
208
|
+
| **Unknown** | All executed agents failed — risk could not be determined |
|
|
209
|
+
|
|
210
|
+
## Architecture
|
|
211
|
+
|
|
212
|
+
```
|
|
213
|
+
src/pr_sentinel/
|
|
214
|
+
├── cli.py # Click entrypoint + Rich UI
|
|
215
|
+
├── git_diff.py # git rev-parse, git diff, --staged
|
|
216
|
+
├── diff_parser.py # per-file splitting + noise filter + truncation
|
|
217
|
+
├── chunker.py # greedy packer to keep prompts under chunk-budget
|
|
218
|
+
├── claude_runner.py # subprocess(claude -p) + JSON extraction + 1 retry
|
|
219
|
+
├── orchestrator.py # parallel (agent, chunk) execution via ThreadPoolExecutor
|
|
220
|
+
├── cache.py # sha256-keyed disk cache for Claude responses
|
|
221
|
+
├── report_generator.py # build_report + JSON/Markdown writers
|
|
222
|
+
├── agents/
|
|
223
|
+
│ ├── base.py # BaseAgent: load prompt, chunk, call, validate
|
|
224
|
+
│ ├── security_agent.py
|
|
225
|
+
│ ├── quality_agent.py
|
|
226
|
+
│ ├── performance_agent.py
|
|
227
|
+
│ └── testing_agent.py
|
|
228
|
+
└── prompts/
|
|
229
|
+
├── security.md
|
|
230
|
+
├── quality.md
|
|
231
|
+
├── performance.md
|
|
232
|
+
└── testing.md
|
|
233
|
+
|
|
234
|
+
tests/
|
|
235
|
+
├── test_diff_parser.py
|
|
236
|
+
├── test_chunker.py
|
|
237
|
+
└── test_report_generator.py
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
## Running tests
|
|
241
|
+
|
|
242
|
+
```bash
|
|
243
|
+
pip install -e .[dev]
|
|
244
|
+
pytest -q
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
## Troubleshooting
|
|
248
|
+
|
|
249
|
+
**`claude CLI not found on PATH`** — install Claude Code and confirm `claude --version` works in the same shell where you run `pr-sentinel`.
|
|
250
|
+
|
|
251
|
+
**`claude returned non-JSON output after retry`** — Claude occasionally returns prose instead of JSON. The runner retries once; if it still fails, that chunk's findings are dropped and the agent is marked failed for the run. Re-running usually succeeds.
|
|
252
|
+
|
|
253
|
+
**Slow runs** — large diffs trigger chunking. Each chunk is one Claude call per agent. Reduce scope with `--agents security` if you only want one perspective, or with `--max-file-size` to truncate huge files. The cache amortizes repeat runs against the same diff.
|
|
254
|
+
|
|
255
|
+
**Lock files / minified files showing up** — they shouldn't. If they do, add the pattern to `NOISE_PATTERNS` in [diff_parser.py](src/pr_sentinel/diff_parser.py).
|
|
256
|
+
|
|
257
|
+
## Notes & limitations
|
|
258
|
+
|
|
259
|
+
- The Performance Agent only sees the diff. It can flag pattern-level issues (N+1 queries, sync-over-async) but cannot reason about runtime behavior or system load.
|
|
260
|
+
- `lineHint` is approximate — unified diffs have hunk headers, not absolute line numbers. The prompt asks Claude for a *description* of the location rather than a hallucinated number.
|
|
261
|
+
- Agents cannot read other files in the repo. Review depth is limited to what's visible in the diff itself.
|
|
262
|
+
- If any one chunk fails for an agent (timeout, exit code, unparseable JSON after retry), that agent is marked failed for the run and its partial findings are discarded. Other agents continue.
|
|
263
|
+
|
|
264
|
+
## License
|
|
265
|
+
|
|
266
|
+
MIT
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
# PR Sentinel
|
|
2
|
+
|
|
3
|
+
Local pull-request review tool. Reads a git diff, runs four specialized review agents over it via the Claude Code CLI, and emits a structured JSON + Markdown report so you can fix issues *before* raising the PR.
|
|
4
|
+
|
|
5
|
+
No API keys. No hosted services. PR Sentinel shells out to `claude -p` and uses your existing Claude Code authentication.
|
|
6
|
+
|
|
7
|
+
## How it works
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
git diff main...HEAD
|
|
11
|
+
│
|
|
12
|
+
▼
|
|
13
|
+
[ diff_parser ] ── filters noise (lock files, build dirs, minified, generated)
|
|
14
|
+
│
|
|
15
|
+
▼
|
|
16
|
+
[ chunker ] ── packs files into ≤60k-char chunks (hybrid batching)
|
|
17
|
+
│
|
|
18
|
+
▼
|
|
19
|
+
[ orchestrator ] ── runs (agent × chunk) tasks in a bounded thread pool
|
|
20
|
+
│
|
|
21
|
+
├── Security Agent (claude -p prompts/security.md)
|
|
22
|
+
├── Code Quality Agent (claude -p prompts/quality.md)
|
|
23
|
+
├── Performance Agent (claude -p prompts/performance.md)
|
|
24
|
+
└── Testing Agent (claude -p prompts/testing.md)
|
|
25
|
+
│
|
|
26
|
+
▼
|
|
27
|
+
[ cache ] ── sha256(model + prompt) → response, on disk
|
|
28
|
+
│
|
|
29
|
+
▼
|
|
30
|
+
[ report_generator ] ── merges findings, computes risk level
|
|
31
|
+
│
|
|
32
|
+
▼
|
|
33
|
+
reports/report.json + reports/review-report.md
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Requirements
|
|
37
|
+
|
|
38
|
+
- Python 3.11+
|
|
39
|
+
- [Claude Code CLI](https://docs.claude.com/en/docs/claude-code) installed and authenticated. `claude --version` must work from your shell.
|
|
40
|
+
- Git, if you want to review live branches (not required for `--diff` mode).
|
|
41
|
+
|
|
42
|
+
## Install
|
|
43
|
+
|
|
44
|
+
From PyPI:
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
pip install pr-sentinel
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
From source (for development):
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
git clone https://github.com/kishor2004reddy/Pull-Request-Sentinel-CLI.git
|
|
54
|
+
cd Pull-Request-Sentinel-CLI
|
|
55
|
+
pip install -e .[dev]
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Verify:
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
pr-sentinel --help
|
|
62
|
+
pr-sentinel agents
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Quickstart
|
|
66
|
+
|
|
67
|
+
Review your current branch against `main`:
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
pr-sentinel review --base main
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Or review a saved diff file (works without a git repo):
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
git diff main...HEAD > my.diff
|
|
77
|
+
pr-sentinel review --diff my.diff --out reports
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Open `reports/review-report.md`.
|
|
81
|
+
|
|
82
|
+
## Commands
|
|
83
|
+
|
|
84
|
+
### `pr-sentinel review`
|
|
85
|
+
|
|
86
|
+
| Flag | Default | Description |
|
|
87
|
+
|------|---------|-------------|
|
|
88
|
+
| `--base` | `main` | Branch to diff against. Runs `git diff <base>...<head>`. |
|
|
89
|
+
| `--head` | `HEAD` | Source branch/ref to review. Use with `--base` to diff arbitrary refs without checking them out. |
|
|
90
|
+
| `--diff PATH` | — | Review a saved diff file instead of running git. Mutually exclusive with `--staged`. |
|
|
91
|
+
| `--staged` | off | Review staged changes (`git diff --cached`). |
|
|
92
|
+
| `--repo PATH` | cwd | Path to the git repository to review. Ignored when `--diff` is used. |
|
|
93
|
+
| `--agents` | `security,quality,performance,testing` | Comma-separated agents to run. |
|
|
94
|
+
| `--out` | `./reports` | Output directory. |
|
|
95
|
+
| `--format` | `both` | `json`, `markdown`, or `both`. |
|
|
96
|
+
| `--max-file-size` | `20000` | Per-file diff size cap (chars). Larger files get truncated with a marker. |
|
|
97
|
+
| `--chunk-budget` | `60000` | Max combined diff size per Claude call before chunking kicks in. |
|
|
98
|
+
| `--model` | `haiku` | Claude model to use. Shortcuts: `sonnet`, `opus`, `haiku`. Or pass a full model ID such as `claude-opus-4-7`. |
|
|
99
|
+
| `--max-parallel` | `8` | Max concurrent `claude` calls across all (agent, chunk) pairs. |
|
|
100
|
+
| `--timeout` | `600` | Per-call timeout in seconds for each `claude` subprocess. |
|
|
101
|
+
| `--no-cache` | off | Bypass the response cache for this run. Successful responses are still written to the cache. |
|
|
102
|
+
|
|
103
|
+
### `pr-sentinel agents`
|
|
104
|
+
|
|
105
|
+
Lists available agents and their implementation status.
|
|
106
|
+
|
|
107
|
+
### `pr-sentinel cache`
|
|
108
|
+
|
|
109
|
+
Inspect and manage the on-disk response cache.
|
|
110
|
+
|
|
111
|
+
| Subcommand | Description |
|
|
112
|
+
|---|---|
|
|
113
|
+
| `cache size` | Show cache location, entry count, and disk usage. |
|
|
114
|
+
| `cache clear` | Wipe the entire cache (prompts for confirmation). |
|
|
115
|
+
| `cache prune --older-than 30d` | Delete entries older than the given age. Supports `s/m/h/d` suffixes. Add `--dry-run` to preview. |
|
|
116
|
+
|
|
117
|
+
The cache lives at `~/.pr-sentinel/cache/` by default. Override with the `PR_SENTINEL_CACHE_DIR` environment variable. Keys are `sha256(model + prompt)`, so changing the model or any prompt content invalidates the entry automatically.
|
|
118
|
+
|
|
119
|
+
## Examples
|
|
120
|
+
|
|
121
|
+
Review current branch vs `main`:
|
|
122
|
+
```bash
|
|
123
|
+
pr-sentinel review --base main
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Run only the security agent:
|
|
127
|
+
```bash
|
|
128
|
+
pr-sentinel review --base main --agents security
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
Review what's currently staged:
|
|
132
|
+
```bash
|
|
133
|
+
pr-sentinel review --staged
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Review a feature branch without checking it out:
|
|
137
|
+
```bash
|
|
138
|
+
pr-sentinel review --base main --head feature/new-auth
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
Use a stronger model for higher-stakes reviews:
|
|
142
|
+
```bash
|
|
143
|
+
pr-sentinel review --base main --model opus
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
Force a fresh run, ignoring cached responses:
|
|
147
|
+
```bash
|
|
148
|
+
pr-sentinel review --base main --no-cache
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
Prune cache entries older than a week:
|
|
152
|
+
```bash
|
|
153
|
+
pr-sentinel cache prune --older-than 7d
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
## Report structure
|
|
157
|
+
|
|
158
|
+
The markdown report always emits these five sections in this fixed order, regardless of findings:
|
|
159
|
+
|
|
160
|
+
1. **Summary** — risk level, source, branch, timestamp, agents, finding counts
|
|
161
|
+
2. **Merge Verdict** — deterministic verdict driven by risk level (not a separate Claude call)
|
|
162
|
+
3. **Key Findings** — top blocking issues (High + Medium)
|
|
163
|
+
4. **Key Recommendations** — deduplicated fixes
|
|
164
|
+
5. **All Findings** — per-agent summary table + full per-issue detail
|
|
165
|
+
|
|
166
|
+
The JSON report contains the same data in a single structured object suitable for piping into other tools.
|
|
167
|
+
|
|
168
|
+
### Risk levels
|
|
169
|
+
|
|
170
|
+
| Level | Trigger |
|
|
171
|
+
|-------|---------|
|
|
172
|
+
| **High** | Any High-severity finding, or 5+ Medium-severity findings |
|
|
173
|
+
| **Medium** | One or more Medium-severity findings (and fewer than 5) |
|
|
174
|
+
| **Low** | Only Low-severity findings |
|
|
175
|
+
| **None** | No findings across all executed agents |
|
|
176
|
+
| **Unknown** | All executed agents failed — risk could not be determined |
|
|
177
|
+
|
|
178
|
+
## Architecture
|
|
179
|
+
|
|
180
|
+
```
|
|
181
|
+
src/pr_sentinel/
|
|
182
|
+
├── cli.py # Click entrypoint + Rich UI
|
|
183
|
+
├── git_diff.py # git rev-parse, git diff, --staged
|
|
184
|
+
├── diff_parser.py # per-file splitting + noise filter + truncation
|
|
185
|
+
├── chunker.py # greedy packer to keep prompts under chunk-budget
|
|
186
|
+
├── claude_runner.py # subprocess(claude -p) + JSON extraction + 1 retry
|
|
187
|
+
├── orchestrator.py # parallel (agent, chunk) execution via ThreadPoolExecutor
|
|
188
|
+
├── cache.py # sha256-keyed disk cache for Claude responses
|
|
189
|
+
├── report_generator.py # build_report + JSON/Markdown writers
|
|
190
|
+
├── agents/
|
|
191
|
+
│ ├── base.py # BaseAgent: load prompt, chunk, call, validate
|
|
192
|
+
│ ├── security_agent.py
|
|
193
|
+
│ ├── quality_agent.py
|
|
194
|
+
│ ├── performance_agent.py
|
|
195
|
+
│ └── testing_agent.py
|
|
196
|
+
└── prompts/
|
|
197
|
+
├── security.md
|
|
198
|
+
├── quality.md
|
|
199
|
+
├── performance.md
|
|
200
|
+
└── testing.md
|
|
201
|
+
|
|
202
|
+
tests/
|
|
203
|
+
├── test_diff_parser.py
|
|
204
|
+
├── test_chunker.py
|
|
205
|
+
└── test_report_generator.py
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
## Running tests
|
|
209
|
+
|
|
210
|
+
```bash
|
|
211
|
+
pip install -e .[dev]
|
|
212
|
+
pytest -q
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
## Troubleshooting
|
|
216
|
+
|
|
217
|
+
**`claude CLI not found on PATH`** — install Claude Code and confirm `claude --version` works in the same shell where you run `pr-sentinel`.
|
|
218
|
+
|
|
219
|
+
**`claude returned non-JSON output after retry`** — Claude occasionally returns prose instead of JSON. The runner retries once; if it still fails, that chunk's findings are dropped and the agent is marked failed for the run. Re-running usually succeeds.
|
|
220
|
+
|
|
221
|
+
**Slow runs** — large diffs trigger chunking. Each chunk is one Claude call per agent. Reduce scope with `--agents security` if you only want one perspective, or with `--max-file-size` to truncate huge files. The cache amortizes repeat runs against the same diff.
|
|
222
|
+
|
|
223
|
+
**Lock files / minified files showing up** — they shouldn't. If they do, add the pattern to `NOISE_PATTERNS` in [diff_parser.py](src/pr_sentinel/diff_parser.py).
|
|
224
|
+
|
|
225
|
+
## Notes & limitations
|
|
226
|
+
|
|
227
|
+
- The Performance Agent only sees the diff. It can flag pattern-level issues (N+1 queries, sync-over-async) but cannot reason about runtime behavior or system load.
|
|
228
|
+
- `lineHint` is approximate — unified diffs have hunk headers, not absolute line numbers. The prompt asks Claude for a *description* of the location rather than a hallucinated number.
|
|
229
|
+
- Agents cannot read other files in the repo. Review depth is limited to what's visible in the diff itself.
|
|
230
|
+
- If any one chunk fails for an agent (timeout, exit code, unparseable JSON after retry), that agent is marked failed for the run and its partial findings are discarded. Other agents continue.
|
|
231
|
+
|
|
232
|
+
## License
|
|
233
|
+
|
|
234
|
+
MIT
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=64", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "pr-sentinel"
|
|
7
|
+
description = "Local PR review tool that orchestrates Claude Code CLI agents over git diffs."
|
|
8
|
+
readme = "README.md"
|
|
9
|
+
requires-python = ">=3.11"
|
|
10
|
+
license = { text = "MIT" }
|
|
11
|
+
authors = [
|
|
12
|
+
{ name = "Yarramaddi Kishor Kumar Reddy", email = "kishor04reddy@gmail.com" },
|
|
13
|
+
]
|
|
14
|
+
keywords = [
|
|
15
|
+
"pr-review",
|
|
16
|
+
"code-review",
|
|
17
|
+
"claude",
|
|
18
|
+
"claude-code",
|
|
19
|
+
"cli",
|
|
20
|
+
"git-diff",
|
|
21
|
+
"static-analysis",
|
|
22
|
+
]
|
|
23
|
+
classifiers = [
|
|
24
|
+
"Development Status :: 3 - Alpha",
|
|
25
|
+
"Environment :: Console",
|
|
26
|
+
"Intended Audience :: Developers",
|
|
27
|
+
"License :: OSI Approved :: MIT License",
|
|
28
|
+
"Operating System :: OS Independent",
|
|
29
|
+
"Programming Language :: Python :: 3",
|
|
30
|
+
"Programming Language :: Python :: 3.11",
|
|
31
|
+
"Programming Language :: Python :: 3.12",
|
|
32
|
+
"Programming Language :: Python :: 3.13",
|
|
33
|
+
"Topic :: Software Development :: Quality Assurance",
|
|
34
|
+
"Topic :: Software Development :: Version Control :: Git",
|
|
35
|
+
]
|
|
36
|
+
dependencies = [
|
|
37
|
+
"click>=8.1",
|
|
38
|
+
"rich>=13",
|
|
39
|
+
]
|
|
40
|
+
dynamic = ["version"]
|
|
41
|
+
|
|
42
|
+
[project.optional-dependencies]
|
|
43
|
+
dev = [
|
|
44
|
+
"pytest>=7.0",
|
|
45
|
+
"build",
|
|
46
|
+
"twine",
|
|
47
|
+
]
|
|
48
|
+
|
|
49
|
+
[project.scripts]
|
|
50
|
+
pr-sentinel = "pr_sentinel.cli:main"
|
|
51
|
+
|
|
52
|
+
[project.urls]
|
|
53
|
+
Homepage = "https://github.com/kishor2004reddy/Pull-Request-Sentinel-CLI"
|
|
54
|
+
Repository = "https://github.com/kishor2004reddy/Pull-Request-Sentinel-CLI"
|
|
55
|
+
Issues = "https://github.com/kishor2004reddy/Pull-Request-Sentinel-CLI/issues"
|
|
56
|
+
|
|
57
|
+
[tool.setuptools.dynamic]
|
|
58
|
+
version = { attr = "pr_sentinel.__version__" }
|
|
59
|
+
|
|
60
|
+
[tool.setuptools.packages.find]
|
|
61
|
+
where = ["src"]
|
|
62
|
+
|
|
63
|
+
[tool.setuptools.package-data]
|
|
64
|
+
pr_sentinel = ["prompts/*.md"]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.1.0"
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
from pr_sentinel.agents.performance_agent import PerformanceAgent
|
|
2
|
+
from pr_sentinel.agents.quality_agent import QualityAgent
|
|
3
|
+
from pr_sentinel.agents.security_agent import SecurityAgent
|
|
4
|
+
from pr_sentinel.agents.testing_agent import TestingAgent
|
|
5
|
+
|
|
6
|
+
AGENT_REGISTRY = {
|
|
7
|
+
"security": SecurityAgent,
|
|
8
|
+
"quality": QualityAgent,
|
|
9
|
+
"performance": PerformanceAgent,
|
|
10
|
+
"testing": TestingAgent,
|
|
11
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
from importlib import resources
|
|
2
|
+
|
|
3
|
+
from pr_sentinel import chunker, claude_runner
|
|
4
|
+
|
|
5
|
+
VALID_SEVERITIES = {"Low", "Medium", "High"}
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def load_prompt(filename: str) -> str:
|
|
9
|
+
return resources.files("pr_sentinel.prompts").joinpath(filename).read_text(encoding="utf-8")
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class BaseAgent:
|
|
13
|
+
name: str = ""
|
|
14
|
+
prompt_file: str = ""
|
|
15
|
+
display_name: str = ""
|
|
16
|
+
|
|
17
|
+
def __init__(self) -> None:
|
|
18
|
+
if not self.prompt_file:
|
|
19
|
+
raise ValueError(f"{type(self).__name__} missing prompt_file")
|
|
20
|
+
self._template = load_prompt(self.prompt_file)
|
|
21
|
+
|
|
22
|
+
def process_chunk(
|
|
23
|
+
self,
|
|
24
|
+
chunk: list[dict],
|
|
25
|
+
model: str | None = None,
|
|
26
|
+
timeout: int = 600,
|
|
27
|
+
use_cache: bool = True,
|
|
28
|
+
) -> list[dict]:
|
|
29
|
+
"""Process a single chunk and return validated findings.
|
|
30
|
+
|
|
31
|
+
Raises on subprocess failure or unparseable output (after retry); caller
|
|
32
|
+
decides whether to discard partial findings for the agent.
|
|
33
|
+
"""
|
|
34
|
+
diff_block = chunker.format_diff_block(chunk)
|
|
35
|
+
prompt = self._template.replace("<<<DIFF>>>", diff_block)
|
|
36
|
+
response = claude_runner.run_json(
|
|
37
|
+
prompt, timeout=timeout, model=model, use_cache=use_cache
|
|
38
|
+
)
|
|
39
|
+
return self._validate_findings(response)
|
|
40
|
+
|
|
41
|
+
def _validate_findings(self, response: dict) -> list[dict]:
|
|
42
|
+
findings = response.get("findings", [])
|
|
43
|
+
if not isinstance(findings, list):
|
|
44
|
+
return []
|
|
45
|
+
|
|
46
|
+
cleaned: list[dict] = []
|
|
47
|
+
for f in findings:
|
|
48
|
+
if not isinstance(f, dict):
|
|
49
|
+
continue
|
|
50
|
+
severity = f.get("severity", "Low")
|
|
51
|
+
if severity not in VALID_SEVERITIES:
|
|
52
|
+
severity = "Low"
|
|
53
|
+
cleaned.append(
|
|
54
|
+
{
|
|
55
|
+
"agent": self.display_name,
|
|
56
|
+
"severity": severity,
|
|
57
|
+
"file": str(f.get("file", "<unknown>")),
|
|
58
|
+
"lineHint": str(f.get("lineHint", "")),
|
|
59
|
+
"issue": str(f.get("issue", "")).strip(),
|
|
60
|
+
"reasoning": str(f.get("reasoning", "")).strip(),
|
|
61
|
+
"recommendation": str(f.get("recommendation", "")).strip(),
|
|
62
|
+
}
|
|
63
|
+
)
|
|
64
|
+
return cleaned
|