preflight-ai 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.
@@ -0,0 +1,250 @@
1
+ Metadata-Version: 2.4
2
+ Name: preflight-ai
3
+ Version: 0.1.0
4
+ Summary: AI-powered code review before you open a PR
5
+ Author: Jinsung Park
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/jsliapark/preflight
8
+ Project-URL: Repository, https://github.com/jsliapark/preflight
9
+ Project-URL: Issues, https://github.com/jsliapark/preflight/issues
10
+ Keywords: code-review,ai,git,cli,anthropic,claude,pull-request
11
+ Classifier: Development Status :: 4 - Beta
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: Topic :: Software Development :: Quality Assurance
20
+ Classifier: Topic :: Software Development :: Version Control :: Git
21
+ Requires-Python: >=3.11
22
+ Description-Content-Type: text/markdown
23
+ Requires-Dist: anthropic>=0.25.0
24
+ Requires-Dist: pydantic>=2.0
25
+ Requires-Dist: click>=8.0
26
+ Requires-Dist: rich>=13.0
27
+ Requires-Dist: python-dotenv>=1.0
28
+ Requires-Dist: chromadb>=0.4.0
29
+ Requires-Dist: PyGithub>=2.0.0
30
+ Provides-Extra: dev
31
+ Requires-Dist: pytest>=7.0; extra == "dev"
32
+
33
+ # Preflight
34
+
35
+ Instant senior-level AI code review before you open a PR.
36
+
37
+ Preflight analyzes your git diff through 4 focused AI review passes—each a separate Claude API call for higher quality than a single giant prompt. Get actionable feedback on correctness, security, style, and performance issues before your code even reaches human reviewers.
38
+
39
+ ## Features
40
+
41
+ - **4 Focused Review Passes** — Correctness, Security, Style, and Performance checks run concurrently for fast, thorough analysis
42
+ - **Smart Diff Analysis** — Automatically extracts modified functions/classes and classifies intent (feature, bugfix, refactor)
43
+ - **PR Description Generation** — Creates complete PR writeups with summary, motivation, approach, and testing notes
44
+ - **Severity-Based Output** — Color-coded findings (critical, warning, suggestion) with specific line references and suggested fixes
45
+ - **Repo Standards Learning** — Optional ChromaDB integration to learn patterns from your merged PRs
46
+ - **CLI-First Design** — Simple commands that integrate into your existing workflow
47
+
48
+ ## Tech Stack
49
+
50
+ | Category | Technology |
51
+ |----------|------------|
52
+ | Language | Python 3.11+ |
53
+ | AI | Anthropic Claude API |
54
+ | CLI | Click |
55
+ | Output | Rich |
56
+ | Data Validation | Pydantic v2 |
57
+ | Vector DB | ChromaDB |
58
+ | GitHub API | PyGithub |
59
+ | Config | python-dotenv |
60
+
61
+ ## Prerequisites
62
+
63
+ - **Python 3.11 or higher**
64
+ - **Git** — Must be run from within a git repository
65
+ - **Anthropic API key** — Get one at [console.anthropic.com](https://console.anthropic.com/)
66
+ - **GitHub CLI** (optional) — Required for `preflight pr` to create PRs directly. Install from [cli.github.com](https://cli.github.com/)
67
+
68
+ ## Installation
69
+
70
+ 1. Clone the repository:
71
+
72
+ ```bash
73
+ git clone https://github.com/your-username/preflight.git
74
+ cd preflight
75
+ ```
76
+
77
+ 2. Install the package:
78
+
79
+ ```bash
80
+ pip install -e .
81
+ ```
82
+
83
+ For development (includes pytest):
84
+
85
+ ```bash
86
+ pip install -e ".[dev]"
87
+ ```
88
+
89
+ ## Configuration
90
+
91
+ Create a `.env` file in the project root (or copy from `.env.example`):
92
+
93
+ ```bash
94
+ cp .env.example .env
95
+ ```
96
+
97
+ Edit `.env` with your credentials:
98
+
99
+ ```
100
+ ANTHROPIC_API_KEY=your-anthropic-api-key-here
101
+ GITHUB_TOKEN=ghp_your-github-token-here
102
+ GITHUB_REPO=owner/repo-name
103
+ ```
104
+
105
+ | Variable | Required | Description |
106
+ |----------|----------|-------------|
107
+ | `ANTHROPIC_API_KEY` | Yes | Your Anthropic API key for Claude |
108
+ | `GITHUB_TOKEN` | No | GitHub token for Standards Indexer (repo pattern learning) |
109
+ | `GITHUB_REPO` | No | Repository in `owner/repo` format for Standards Indexer |
110
+
111
+ ## Usage
112
+
113
+ ### Code Review
114
+
115
+ Run all 4 review passes on your current branch's diff against `main`:
116
+
117
+ ```bash
118
+ preflight review
119
+ ```
120
+
121
+ Diff against a different base branch:
122
+
123
+ ```bash
124
+ preflight review --base develop
125
+ ```
126
+
127
+ Example output:
128
+
129
+ ```
130
+ Intent: feature · 3 file(s) changed
131
+
132
+ ── CORRECTNESS ──
133
+ No issues found.
134
+
135
+ ── SECURITY ──
136
+ [WARNING] src/auth.py:45
137
+ Potential SQL injection vulnerability in user query
138
+ → Use parameterized queries instead of string formatting
139
+
140
+ ── STYLE ──
141
+ [SUGGESTION] src/utils.py:12
142
+ Magic number 86400 should be a named constant
143
+ → Define SECONDS_PER_DAY = 86400
144
+
145
+ ── PERFORMANCE ──
146
+ No issues found.
147
+ ```
148
+
149
+ ### PR Description Generation
150
+
151
+ Generate a PR description and create a PR via GitHub CLI:
152
+
153
+ ```bash
154
+ preflight pr
155
+ ```
156
+
157
+ Options:
158
+
159
+ ```bash
160
+ # Diff against a different branch
161
+ preflight pr --base develop
162
+
163
+ # Override the generated title
164
+ preflight pr --title "Add user authentication"
165
+
166
+ # Copy to clipboard instead of creating PR
167
+ preflight pr --copy
168
+ ```
169
+
170
+ When you run `preflight pr`, it will:
171
+ 1. Generate a title, summary, motivation, approach, testing notes, and TODOs
172
+ 2. Show a preview panel
173
+ 3. Prompt for confirmation
174
+ 4. Push your branch and create the PR via `gh pr create`
175
+
176
+ ## Project Structure
177
+
178
+ ```
179
+ preflight/
180
+ ├── core/
181
+ │ ├── cli.py # Main CLI entry point (Click commands)
182
+ │ ├── diff_parser.py # Parses raw git diff into ChangeSet
183
+ │ └── models.py # Pydantic models (ChangeSet, ReviewResult, etc.)
184
+ ├── agents/
185
+ │ ├── diff_analyzer.py # Extracts intent and function/class changes
186
+ │ ├── review_agent.py # 4 review passes (correctness, security, style, performance)
187
+ │ ├── pr_description.py # PR description generation
188
+ │ ├── standards_agent.py # Reviews against repo-specific patterns
189
+ │ └── standards_indexer.py # Indexes merged PRs into ChromaDB
190
+ ├── prompts/
191
+ │ ├── diff_analyzer.md
192
+ │ ├── correctness_review.md
193
+ │ ├── security_review.md
194
+ │ ├── style_review.md
195
+ │ ├── performance_review.md
196
+ │ ├── pr_description.md
197
+ │ └── standards_review.md
198
+ ├── test_*.py # Test files
199
+ ├── pyproject.toml # Package configuration
200
+ ├── .env.example # Example environment variables
201
+ └── SAFETY.md # Safety considerations and limitations
202
+ ```
203
+
204
+ ## Review Passes
205
+
206
+ Each pass focuses on a specific category:
207
+
208
+ | Pass | What It Catches |
209
+ |------|-----------------|
210
+ | **Correctness** | Logic bugs, null dereferences, off-by-one errors, unhandled exceptions, race conditions |
211
+ | **Security** | SQL/command injection, hardcoded secrets, auth issues, sensitive data exposure |
212
+ | **Style** | Readability, naming conventions, magic numbers, dead code, missing docstrings |
213
+ | **Performance** | N+1 queries, unbounded memory, missing caching opportunities, sync vs async issues |
214
+
215
+ ## Running Tests
216
+
217
+ ```bash
218
+ # Run all tests
219
+ pytest
220
+
221
+ # Run specific test files
222
+ python test_diff_parser.py
223
+ python test_diff_analyzer.py
224
+ python test_agent.py
225
+ python test_evals.py
226
+ ```
227
+
228
+ ## Safety & Limitations
229
+
230
+ Preflight is **advisory only**—it does not block merges or execute any code.
231
+
232
+ - Diffs are sent to Anthropic's API; no external storage of your code
233
+ - Large diffs may be truncated to fit context limits
234
+ - The Security pass flags potential hardcoded secrets
235
+ - Standards Agent quality depends on the quality of indexed PRs
236
+
237
+ See [SAFETY.md](SAFETY.md) for full details.
238
+
239
+ ## Contributing
240
+
241
+ Contributions are welcome! To get started:
242
+
243
+ 1. Fork the repository
244
+ 2. Create a feature branch (`git checkout -b feature/your-feature`)
245
+ 3. Make your changes
246
+ 4. Run tests (`pytest`)
247
+ 5. Commit your changes (`git commit -m "Add your feature"`)
248
+ 6. Push to the branch (`git push origin feature/your-feature`)
249
+ 7. Open a Pull Request
250
+
@@ -0,0 +1,218 @@
1
+ # Preflight
2
+
3
+ Instant senior-level AI code review before you open a PR.
4
+
5
+ Preflight analyzes your git diff through 4 focused AI review passes—each a separate Claude API call for higher quality than a single giant prompt. Get actionable feedback on correctness, security, style, and performance issues before your code even reaches human reviewers.
6
+
7
+ ## Features
8
+
9
+ - **4 Focused Review Passes** — Correctness, Security, Style, and Performance checks run concurrently for fast, thorough analysis
10
+ - **Smart Diff Analysis** — Automatically extracts modified functions/classes and classifies intent (feature, bugfix, refactor)
11
+ - **PR Description Generation** — Creates complete PR writeups with summary, motivation, approach, and testing notes
12
+ - **Severity-Based Output** — Color-coded findings (critical, warning, suggestion) with specific line references and suggested fixes
13
+ - **Repo Standards Learning** — Optional ChromaDB integration to learn patterns from your merged PRs
14
+ - **CLI-First Design** — Simple commands that integrate into your existing workflow
15
+
16
+ ## Tech Stack
17
+
18
+ | Category | Technology |
19
+ |----------|------------|
20
+ | Language | Python 3.11+ |
21
+ | AI | Anthropic Claude API |
22
+ | CLI | Click |
23
+ | Output | Rich |
24
+ | Data Validation | Pydantic v2 |
25
+ | Vector DB | ChromaDB |
26
+ | GitHub API | PyGithub |
27
+ | Config | python-dotenv |
28
+
29
+ ## Prerequisites
30
+
31
+ - **Python 3.11 or higher**
32
+ - **Git** — Must be run from within a git repository
33
+ - **Anthropic API key** — Get one at [console.anthropic.com](https://console.anthropic.com/)
34
+ - **GitHub CLI** (optional) — Required for `preflight pr` to create PRs directly. Install from [cli.github.com](https://cli.github.com/)
35
+
36
+ ## Installation
37
+
38
+ 1. Clone the repository:
39
+
40
+ ```bash
41
+ git clone https://github.com/your-username/preflight.git
42
+ cd preflight
43
+ ```
44
+
45
+ 2. Install the package:
46
+
47
+ ```bash
48
+ pip install -e .
49
+ ```
50
+
51
+ For development (includes pytest):
52
+
53
+ ```bash
54
+ pip install -e ".[dev]"
55
+ ```
56
+
57
+ ## Configuration
58
+
59
+ Create a `.env` file in the project root (or copy from `.env.example`):
60
+
61
+ ```bash
62
+ cp .env.example .env
63
+ ```
64
+
65
+ Edit `.env` with your credentials:
66
+
67
+ ```
68
+ ANTHROPIC_API_KEY=your-anthropic-api-key-here
69
+ GITHUB_TOKEN=ghp_your-github-token-here
70
+ GITHUB_REPO=owner/repo-name
71
+ ```
72
+
73
+ | Variable | Required | Description |
74
+ |----------|----------|-------------|
75
+ | `ANTHROPIC_API_KEY` | Yes | Your Anthropic API key for Claude |
76
+ | `GITHUB_TOKEN` | No | GitHub token for Standards Indexer (repo pattern learning) |
77
+ | `GITHUB_REPO` | No | Repository in `owner/repo` format for Standards Indexer |
78
+
79
+ ## Usage
80
+
81
+ ### Code Review
82
+
83
+ Run all 4 review passes on your current branch's diff against `main`:
84
+
85
+ ```bash
86
+ preflight review
87
+ ```
88
+
89
+ Diff against a different base branch:
90
+
91
+ ```bash
92
+ preflight review --base develop
93
+ ```
94
+
95
+ Example output:
96
+
97
+ ```
98
+ Intent: feature · 3 file(s) changed
99
+
100
+ ── CORRECTNESS ──
101
+ No issues found.
102
+
103
+ ── SECURITY ──
104
+ [WARNING] src/auth.py:45
105
+ Potential SQL injection vulnerability in user query
106
+ → Use parameterized queries instead of string formatting
107
+
108
+ ── STYLE ──
109
+ [SUGGESTION] src/utils.py:12
110
+ Magic number 86400 should be a named constant
111
+ → Define SECONDS_PER_DAY = 86400
112
+
113
+ ── PERFORMANCE ──
114
+ No issues found.
115
+ ```
116
+
117
+ ### PR Description Generation
118
+
119
+ Generate a PR description and create a PR via GitHub CLI:
120
+
121
+ ```bash
122
+ preflight pr
123
+ ```
124
+
125
+ Options:
126
+
127
+ ```bash
128
+ # Diff against a different branch
129
+ preflight pr --base develop
130
+
131
+ # Override the generated title
132
+ preflight pr --title "Add user authentication"
133
+
134
+ # Copy to clipboard instead of creating PR
135
+ preflight pr --copy
136
+ ```
137
+
138
+ When you run `preflight pr`, it will:
139
+ 1. Generate a title, summary, motivation, approach, testing notes, and TODOs
140
+ 2. Show a preview panel
141
+ 3. Prompt for confirmation
142
+ 4. Push your branch and create the PR via `gh pr create`
143
+
144
+ ## Project Structure
145
+
146
+ ```
147
+ preflight/
148
+ ├── core/
149
+ │ ├── cli.py # Main CLI entry point (Click commands)
150
+ │ ├── diff_parser.py # Parses raw git diff into ChangeSet
151
+ │ └── models.py # Pydantic models (ChangeSet, ReviewResult, etc.)
152
+ ├── agents/
153
+ │ ├── diff_analyzer.py # Extracts intent and function/class changes
154
+ │ ├── review_agent.py # 4 review passes (correctness, security, style, performance)
155
+ │ ├── pr_description.py # PR description generation
156
+ │ ├── standards_agent.py # Reviews against repo-specific patterns
157
+ │ └── standards_indexer.py # Indexes merged PRs into ChromaDB
158
+ ├── prompts/
159
+ │ ├── diff_analyzer.md
160
+ │ ├── correctness_review.md
161
+ │ ├── security_review.md
162
+ │ ├── style_review.md
163
+ │ ├── performance_review.md
164
+ │ ├── pr_description.md
165
+ │ └── standards_review.md
166
+ ├── test_*.py # Test files
167
+ ├── pyproject.toml # Package configuration
168
+ ├── .env.example # Example environment variables
169
+ └── SAFETY.md # Safety considerations and limitations
170
+ ```
171
+
172
+ ## Review Passes
173
+
174
+ Each pass focuses on a specific category:
175
+
176
+ | Pass | What It Catches |
177
+ |------|-----------------|
178
+ | **Correctness** | Logic bugs, null dereferences, off-by-one errors, unhandled exceptions, race conditions |
179
+ | **Security** | SQL/command injection, hardcoded secrets, auth issues, sensitive data exposure |
180
+ | **Style** | Readability, naming conventions, magic numbers, dead code, missing docstrings |
181
+ | **Performance** | N+1 queries, unbounded memory, missing caching opportunities, sync vs async issues |
182
+
183
+ ## Running Tests
184
+
185
+ ```bash
186
+ # Run all tests
187
+ pytest
188
+
189
+ # Run specific test files
190
+ python test_diff_parser.py
191
+ python test_diff_analyzer.py
192
+ python test_agent.py
193
+ python test_evals.py
194
+ ```
195
+
196
+ ## Safety & Limitations
197
+
198
+ Preflight is **advisory only**—it does not block merges or execute any code.
199
+
200
+ - Diffs are sent to Anthropic's API; no external storage of your code
201
+ - Large diffs may be truncated to fit context limits
202
+ - The Security pass flags potential hardcoded secrets
203
+ - Standards Agent quality depends on the quality of indexed PRs
204
+
205
+ See [SAFETY.md](SAFETY.md) for full details.
206
+
207
+ ## Contributing
208
+
209
+ Contributions are welcome! To get started:
210
+
211
+ 1. Fork the repository
212
+ 2. Create a feature branch (`git checkout -b feature/your-feature`)
213
+ 3. Make your changes
214
+ 4. Run tests (`pytest`)
215
+ 5. Commit your changes (`git commit -m "Add your feature"`)
216
+ 6. Push to the branch (`git push origin feature/your-feature`)
217
+ 7. Open a Pull Request
218
+
File without changes
@@ -0,0 +1,68 @@
1
+ import json
2
+ from anthropic import Anthropic
3
+ from dotenv import load_dotenv
4
+ import os
5
+ from core.models import ChangeSet, ChangeIntent, FileChange
6
+
7
+ load_dotenv()
8
+ client = Anthropic()
9
+
10
+
11
+ def analyze_diff(changeset: ChangeSet) -> ChangeSet:
12
+ """
13
+ Enriches a ChangeSet with:
14
+ - intent classification (feature / bugfix / refactor / unknown)
15
+ - function and class level changes extracted by Claude
16
+ """
17
+ prompt = _load_prompt("diff_analyzer")
18
+
19
+ response = client.messages.create(
20
+ model="claude-opus-4-5",
21
+ max_tokens=1024,
22
+ system=prompt,
23
+ messages=[
24
+ {"role": "user", "content": f"Analyze this diff:\n\n{changeset.raw_diff}"}
25
+ ]
26
+ )
27
+
28
+ raw = response.content[0].text.strip()
29
+
30
+ if raw.startswith("```"):
31
+ start = raw.find("\n") + 1
32
+ end = raw.rfind("```")
33
+ raw = raw[start:end].strip()
34
+
35
+ try:
36
+ parsed = json.loads(raw)
37
+ except json.JSONDecodeError:
38
+ print("WARNING: diff analyzer could not parse Claude response, using defaults")
39
+ return changeset
40
+
41
+ # Classify intent
42
+ try:
43
+ intent = ChangeIntent(parsed.get("intent", "unknown"))
44
+ except ValueError:
45
+ intent = ChangeIntent.UNKNOWN
46
+
47
+ # Enrich each file with extracted functions and classes
48
+ updated_files = []
49
+ for f in changeset.files:
50
+ updated_files.append(f.model_copy(update={
51
+ "functions_added": parsed.get("functions_added", []),
52
+ "functions_modified": parsed.get("functions_modified", []),
53
+ "functions_deleted": parsed.get("functions_deleted", []),
54
+ "classes_added": parsed.get("classes_added", []),
55
+ "classes_modified": parsed.get("classes_modified", []),
56
+ "classes_deleted": parsed.get("classes_deleted", []),
57
+ }))
58
+
59
+ return changeset.model_copy(update={
60
+ "intent": intent,
61
+ "files": updated_files,
62
+ })
63
+
64
+
65
+ def _load_prompt(name: str) -> str:
66
+ path = os.path.join(os.path.dirname(__file__), "..", "prompts", f"{name}.md")
67
+ with open(path) as f:
68
+ return f.read()
@@ -0,0 +1,78 @@
1
+ import json
2
+ from anthropic import Anthropic
3
+ from dotenv import load_dotenv
4
+ import os
5
+ from core.models import ChangeSet, PRDescription
6
+
7
+ load_dotenv()
8
+ client = Anthropic()
9
+
10
+
11
+ def generate_pr_description(changeset: ChangeSet) -> PRDescription:
12
+ prompt = _load_prompt("pr_description")
13
+ context = _build_context(changeset)
14
+
15
+ response = client.messages.create(
16
+ model="claude-opus-4-5",
17
+ max_tokens=2048,
18
+ system=prompt,
19
+ messages=[
20
+ {"role": "user", "content": f"Generate a PR description for this change:\n\n{context}"}
21
+ ]
22
+ )
23
+
24
+ raw = response.content[0].text.strip()
25
+
26
+ if raw.startswith("```"):
27
+ start = raw.find("\n") + 1
28
+ end = raw.rfind("```")
29
+ raw = raw[start:end].strip()
30
+
31
+ parsed = json.loads(raw)
32
+
33
+ return PRDescription(
34
+ title=parsed["title"],
35
+ summary=_to_string(parsed["summary"]),
36
+ motivation=_to_string(parsed["motivation"]),
37
+ approach=_to_string(parsed["approach"]),
38
+ testing_notes=_to_string(parsed["testing_notes"]),
39
+ todos=_to_string(parsed["todos"]),
40
+ )
41
+
42
+
43
+ def _to_string(value) -> str:
44
+ """Convert a value to string, joining list items with newlines if needed."""
45
+ if isinstance(value, list):
46
+ return "\n".join(str(item) for item in value)
47
+ return str(value)
48
+
49
+
50
+ def _build_context(changeset: ChangeSet) -> str:
51
+ lines = [f"Intent: {changeset.intent.value}", "", "Files changed:"]
52
+
53
+ for f in changeset.files:
54
+ lines.append(f"- {f.path} ({f.change_type.value})")
55
+ if f.functions_added:
56
+ lines.append(f" functions_added: {', '.join(f.functions_added)}")
57
+ if f.functions_modified:
58
+ lines.append(f" functions_modified: {', '.join(f.functions_modified)}")
59
+ if f.functions_deleted:
60
+ lines.append(f" functions_deleted: {', '.join(f.functions_deleted)}")
61
+ if f.classes_added:
62
+ lines.append(f" classes_added: {', '.join(f.classes_added)}")
63
+ if f.classes_modified:
64
+ lines.append(f" classes_modified: {', '.join(f.classes_modified)}")
65
+ if f.classes_deleted:
66
+ lines.append(f" classes_deleted: {', '.join(f.classes_deleted)}")
67
+
68
+ lines.append("")
69
+ lines.append("Raw diff:")
70
+ lines.append(changeset.raw_diff)
71
+
72
+ return "\n".join(lines)
73
+
74
+
75
+ def _load_prompt(name: str) -> str:
76
+ path = os.path.join(os.path.dirname(__file__), "..", "prompts", f"{name}.md")
77
+ with open(path) as f:
78
+ return f.read()
@@ -0,0 +1,75 @@
1
+ import os
2
+ import json
3
+ from anthropic import Anthropic
4
+ from dotenv import load_dotenv
5
+ from core.models import ChangeSet, ReviewComment, ReviewResult, Severity
6
+
7
+ load_dotenv()
8
+
9
+ client = Anthropic()
10
+
11
+ def _run_pass(changeset: ChangeSet, prompt_name: str, category: str) -> ReviewResult:
12
+ prompt = _load_prompt(prompt_name)
13
+ diff_content = _format_diff_for_review(changeset)
14
+
15
+ response = client.messages.create(
16
+ model="claude-opus-4-5",
17
+ max_tokens=1024,
18
+ system=prompt,
19
+ messages=[
20
+ {"role": "user", "content": f"Please review this diff:\n\n{diff_content}"}
21
+ ]
22
+ )
23
+
24
+ raw = response.content[0].text
25
+
26
+ if raw.startswith("```"):
27
+ start = raw.find("\n") + 1
28
+ end = raw.rfind("```")
29
+ raw = raw[start:end].strip()
30
+
31
+ try:
32
+ parsed = json.loads(raw)
33
+ except json.JSONDecodeError:
34
+ print(f"ERROR: Failed to parse JSON response from Claude")
35
+ print(f"Raw response:\n{raw}")
36
+ raise
37
+
38
+ comments = [ReviewComment(
39
+ file=c["file"],
40
+ line=c.get("line"),
41
+ severity=Severity(c["severity"]),
42
+ category=category,
43
+ message=c["message"],
44
+ suggested_fix=c.get("suggested_fix")
45
+ ) for c in parsed["comments"]]
46
+
47
+ return ReviewResult(
48
+ comments=comments,
49
+ summary=parsed["summary"],
50
+ pass_name=category
51
+ )
52
+
53
+
54
+ # Public API — one line each
55
+ def run_correctness_pass(changeset: ChangeSet) -> ReviewResult:
56
+ return _run_pass(changeset, "correctness_review", "correctness")
57
+
58
+ def run_security_pass(changeset: ChangeSet) -> ReviewResult:
59
+ return _run_pass(changeset, "security_review", "security")
60
+
61
+ def run_style_pass(changeset: ChangeSet) -> ReviewResult:
62
+ return _run_pass(changeset, "style_review", "style")
63
+
64
+ def run_performance_pass(changeset: ChangeSet) -> ReviewResult:
65
+ return _run_pass(changeset, "performance_review", "performance")
66
+
67
+ # Helper functions
68
+ def _load_prompt(name: str) -> str:
69
+ path = os.path.join(os.path.dirname(__file__), "..", "prompts", f"{name}.md")
70
+ with open(path) as f:
71
+ return f.read()
72
+
73
+
74
+ def _format_diff_for_review(changeset: ChangeSet) -> str:
75
+ return changeset.raw_diff