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.
- preflight_ai-0.1.0/PKG-INFO +250 -0
- preflight_ai-0.1.0/README.md +218 -0
- preflight_ai-0.1.0/agents/__init__.py +0 -0
- preflight_ai-0.1.0/agents/diff_analyzer.py +68 -0
- preflight_ai-0.1.0/agents/pr_description.py +78 -0
- preflight_ai-0.1.0/agents/review_agent.py +75 -0
- preflight_ai-0.1.0/agents/standards_agent.py +104 -0
- preflight_ai-0.1.0/agents/standards_indexer.py +112 -0
- preflight_ai-0.1.0/core/__init__.py +0 -0
- preflight_ai-0.1.0/core/cli.py +249 -0
- preflight_ai-0.1.0/core/diff_parser.py +84 -0
- preflight_ai-0.1.0/core/models.py +75 -0
- preflight_ai-0.1.0/preflight_ai.egg-info/PKG-INFO +250 -0
- preflight_ai-0.1.0/preflight_ai.egg-info/SOURCES.txt +42 -0
- preflight_ai-0.1.0/preflight_ai.egg-info/dependency_links.txt +1 -0
- preflight_ai-0.1.0/preflight_ai.egg-info/entry_points.txt +2 -0
- preflight_ai-0.1.0/preflight_ai.egg-info/requires.txt +10 -0
- preflight_ai-0.1.0/preflight_ai.egg-info/top_level.txt +3 -0
- preflight_ai-0.1.0/prompts/correctness_review.md +40 -0
- preflight_ai-0.1.0/prompts/diff_analyzer.md +28 -0
- preflight_ai-0.1.0/prompts/performance_review.md +56 -0
- preflight_ai-0.1.0/prompts/pr_description.md +27 -0
- preflight_ai-0.1.0/prompts/security_review.md +54 -0
- preflight_ai-0.1.0/prompts/standards_review.md +43 -0
- preflight_ai-0.1.0/prompts/style_review.md +71 -0
- preflight_ai-0.1.0/pyproject.toml +59 -0
- preflight_ai-0.1.0/setup.cfg +4 -0
|
@@ -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
|