code-review-ai-cli 1.2.0__tar.gz → 1.3.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.
- {code_review_ai_cli-1.2.0 → code_review_ai_cli-1.3.0}/PKG-INFO +91 -2
- {code_review_ai_cli-1.2.0 → code_review_ai_cli-1.3.0}/README.md +90 -1
- {code_review_ai_cli-1.2.0 → code_review_ai_cli-1.3.0}/code_review_ai_cli.egg-info/PKG-INFO +91 -2
- {code_review_ai_cli-1.2.0 → code_review_ai_cli-1.3.0}/code_review_ai_cli.egg-info/SOURCES.txt +2 -0
- {code_review_ai_cli-1.2.0 → code_review_ai_cli-1.3.0}/pyproject.toml +1 -1
- {code_review_ai_cli-1.2.0 → code_review_ai_cli-1.3.0}/src/ai_review.py +65 -25
- {code_review_ai_cli-1.2.0 → code_review_ai_cli-1.3.0}/src/config.py +11 -3
- {code_review_ai_cli-1.2.0 → code_review_ai_cli-1.3.0}/src/git_utils.py +112 -2
- {code_review_ai_cli-1.2.0 → code_review_ai_cli-1.3.0}/src/llm_client.py +0 -18
- {code_review_ai_cli-1.2.0 → code_review_ai_cli-1.3.0}/src/prompts/config.yaml.template +10 -3
- code_review_ai_cli-1.3.0/src/rag_engine.py +162 -0
- {code_review_ai_cli-1.2.0 → code_review_ai_cli-1.3.0}/src/tfs_client.py +28 -134
- {code_review_ai_cli-1.2.0 → code_review_ai_cli-1.3.0}/tests/test_ai_review.py +17 -7
- {code_review_ai_cli-1.2.0 → code_review_ai_cli-1.3.0}/tests/test_config.py +2 -2
- {code_review_ai_cli-1.2.0 → code_review_ai_cli-1.3.0}/tests/test_git_utils.py +153 -1
- {code_review_ai_cli-1.2.0 → code_review_ai_cli-1.3.0}/tests/test_llm_client.py +0 -1
- code_review_ai_cli-1.3.0/tests/test_rag_engine.py +250 -0
- {code_review_ai_cli-1.2.0 → code_review_ai_cli-1.3.0}/tests/test_tfs_client.py +66 -87
- {code_review_ai_cli-1.2.0 → code_review_ai_cli-1.3.0}/code_review_ai_cli.egg-info/dependency_links.txt +0 -0
- {code_review_ai_cli-1.2.0 → code_review_ai_cli-1.3.0}/code_review_ai_cli.egg-info/entry_points.txt +0 -0
- {code_review_ai_cli-1.2.0 → code_review_ai_cli-1.3.0}/code_review_ai_cli.egg-info/requires.txt +0 -0
- {code_review_ai_cli-1.2.0 → code_review_ai_cli-1.3.0}/code_review_ai_cli.egg-info/top_level.txt +0 -0
- {code_review_ai_cli-1.2.0 → code_review_ai_cli-1.3.0}/setup.cfg +0 -0
- {code_review_ai_cli-1.2.0 → code_review_ai_cli-1.3.0}/src/__init__.py +0 -0
- {code_review_ai_cli-1.2.0 → code_review_ai_cli-1.3.0}/src/formatter.py +0 -0
- {code_review_ai_cli-1.2.0 → code_review_ai_cli-1.3.0}/src/prompts/review_context.example.md +0 -0
- {code_review_ai_cli-1.2.0 → code_review_ai_cli-1.3.0}/src/usage_tracker.py +0 -0
- {code_review_ai_cli-1.2.0 → code_review_ai_cli-1.3.0}/tests/test_formatter.py +0 -0
- {code_review_ai_cli-1.2.0 → code_review_ai_cli-1.3.0}/tests/test_usage_tracker.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: code-review-ai-cli
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.3.0
|
|
4
4
|
Summary: Automated AI-powered code review CLI for Azure DevOps / TFS Pull Requests
|
|
5
5
|
License: MIT
|
|
6
6
|
Keywords: code-review,ai,azure-devops,tfs,pull-request,llm
|
|
@@ -48,10 +48,48 @@ Automated code review tool with Pull Request integration for Azure DevOps/TFS an
|
|
|
48
48
|
- **Multiple LLM Providers** — OpenAI, Azure OpenAI, Gemini, Claude, Ollama, GitHub Copilot, AWS Bedrock
|
|
49
49
|
- **Smart Filtering** — Filter by file extensions, limit diff size
|
|
50
50
|
- **Project-aware PR Context** — Sends repository and linked work item context while restricting findings to modified PR lines
|
|
51
|
+
- **RAG Context** — Enriches the review with related code snippets found via `git grep` in the local repository
|
|
51
52
|
- **Single Reviewer Context** — One Markdown context file with local override support
|
|
52
53
|
- **Usage Tracking** — Store per-PR token usage and optional cost estimates
|
|
53
54
|
- **Interactive CLI** — Menu-driven selection and confirmation
|
|
54
55
|
|
|
56
|
+
## Local Repository Requirement
|
|
57
|
+
|
|
58
|
+
> **The CLI must be run from inside the local clone of the repository being reviewed.**
|
|
59
|
+
|
|
60
|
+
The PR diff is fetched from Azure DevOps/TFS, but several features depend on the local git state:
|
|
61
|
+
|
|
62
|
+
| Feature | Local dependency |
|
|
63
|
+
|---|---|
|
|
64
|
+
| PR diff | `git fetch` + three-dot diff against `origin/<branch>` |
|
|
65
|
+
| RAG context | `git grep` on the working tree |
|
|
66
|
+
| Branch validation | `git branch --show-current` |
|
|
67
|
+
|
|
68
|
+
### Branch requirement when RAG is enabled
|
|
69
|
+
|
|
70
|
+
When `review.rag.enabled: true`, the local repository **must be checked out on the PR target branch** (e.g. `development`). If the local branch differs from the target, the review is blocked:
|
|
71
|
+
|
|
72
|
+
```
|
|
73
|
+
Local branch mismatch: you are on 'main' but this PR targets 'development'.
|
|
74
|
+
RAG context would be built from the wrong branch, which may corrupt the review.
|
|
75
|
+
Please run: git checkout development
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
**Example:**
|
|
79
|
+
```bash
|
|
80
|
+
# PR: merge 'feature/my-feature' → 'development'
|
|
81
|
+
cd /path/to/my-repo # must be inside the repo
|
|
82
|
+
git checkout development # must match PR target branch
|
|
83
|
+
ai-review pr-review 42
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
To skip the branch check entirely, disable RAG:
|
|
87
|
+
```yaml
|
|
88
|
+
review:
|
|
89
|
+
rag:
|
|
90
|
+
enabled: false
|
|
91
|
+
```
|
|
92
|
+
|
|
55
93
|
## Installation & Quick Start
|
|
56
94
|
|
|
57
95
|
### 1. Install Package
|
|
@@ -129,11 +167,14 @@ tfs:
|
|
|
129
167
|
review:
|
|
130
168
|
language: pt
|
|
131
169
|
verbosity: detailed # or: quick, security
|
|
132
|
-
scope: diff_with_context # diff_with_context, diff_only
|
|
170
|
+
scope: diff_with_context # diff_with_context, diff_only
|
|
133
171
|
file_extensions_filter: [".cs", ".ts", ".py"]
|
|
134
172
|
max_diff_files: 50
|
|
135
173
|
max_comments_to_post: 20
|
|
136
174
|
custom_prompt_file: review_context.local.md
|
|
175
|
+
rag:
|
|
176
|
+
enabled: true # requires local branch == PR target
|
|
177
|
+
max_chars: 40000
|
|
137
178
|
project_context:
|
|
138
179
|
enabled: true
|
|
139
180
|
mode: on_demand # on_demand, full
|
|
@@ -180,11 +221,59 @@ src/
|
|
|
180
221
|
tfs_client.py # Azure DevOps integration
|
|
181
222
|
git_utils.py # Git diff processing
|
|
182
223
|
formatter.py # Output formatting (terminal, markdown, JSON)
|
|
224
|
+
rag_engine.py # RAG context via git grep
|
|
183
225
|
|
|
184
226
|
tests/
|
|
185
227
|
test_*.py # Unit and integration tests
|
|
186
228
|
```
|
|
187
229
|
|
|
230
|
+
## RAG Context
|
|
231
|
+
|
|
232
|
+
### Current Implementation
|
|
233
|
+
|
|
234
|
+
The RAG engine enriches the LLM prompt with related code snippets found in the local repository:
|
|
235
|
+
|
|
236
|
+
1. **Extract identifiers** — function and class names are parsed from the PR diff
|
|
237
|
+
2. **Search** — `git grep` finds files containing those identifiers
|
|
238
|
+
3. **Extract snippets** — ±10 lines around each match are included as read-only context
|
|
239
|
+
|
|
240
|
+
All operations run locally with no extra dependencies. The quality of RAG context depends entirely on the local repository state, which is why the **local branch must match the PR target branch**.
|
|
241
|
+
|
|
242
|
+
### Recommended Stack for Enhanced RAG (Local & Open Source)
|
|
243
|
+
|
|
244
|
+
For teams wanting semantic similarity instead of keyword search, the recommended local stack is:
|
|
245
|
+
|
|
246
|
+
| Component | Library | Reason |
|
|
247
|
+
|---|---|---|
|
|
248
|
+
| **Vector database** | [ChromaDB](https://www.trychroma.com/) | Runs in-memory or persists to a local SQLite file — no server needed, `pip install chromadb` |
|
|
249
|
+
| **Embeddings** | [sentence-transformers](https://www.sbert.net/) | Generates vectors locally on CPU — no API calls, no cost |
|
|
250
|
+
|
|
251
|
+
Example integration pattern:
|
|
252
|
+
|
|
253
|
+
```python
|
|
254
|
+
from sentence_transformers import SentenceTransformer
|
|
255
|
+
import chromadb
|
|
256
|
+
|
|
257
|
+
model = SentenceTransformer("all-MiniLM-L6-v2") # ~80 MB, CPU-friendly
|
|
258
|
+
client = chromadb.Client() # in-memory
|
|
259
|
+
collection = client.create_collection("repo-index")
|
|
260
|
+
|
|
261
|
+
# Index
|
|
262
|
+
collection.add(
|
|
263
|
+
documents=[snippet_text],
|
|
264
|
+
embeddings=model.encode([snippet_text]).tolist(),
|
|
265
|
+
ids=["file:line"],
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
# Query
|
|
269
|
+
results = collection.query(
|
|
270
|
+
query_embeddings=model.encode([query]).tolist(),
|
|
271
|
+
n_results=5,
|
|
272
|
+
)
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
This stack keeps the CLI lightweight (`pip install`) and requires no external services or paid APIs.
|
|
276
|
+
|
|
188
277
|
### Run Tests
|
|
189
278
|
|
|
190
279
|
```bash
|
|
@@ -11,10 +11,48 @@ Automated code review tool with Pull Request integration for Azure DevOps/TFS an
|
|
|
11
11
|
- **Multiple LLM Providers** — OpenAI, Azure OpenAI, Gemini, Claude, Ollama, GitHub Copilot, AWS Bedrock
|
|
12
12
|
- **Smart Filtering** — Filter by file extensions, limit diff size
|
|
13
13
|
- **Project-aware PR Context** — Sends repository and linked work item context while restricting findings to modified PR lines
|
|
14
|
+
- **RAG Context** — Enriches the review with related code snippets found via `git grep` in the local repository
|
|
14
15
|
- **Single Reviewer Context** — One Markdown context file with local override support
|
|
15
16
|
- **Usage Tracking** — Store per-PR token usage and optional cost estimates
|
|
16
17
|
- **Interactive CLI** — Menu-driven selection and confirmation
|
|
17
18
|
|
|
19
|
+
## Local Repository Requirement
|
|
20
|
+
|
|
21
|
+
> **The CLI must be run from inside the local clone of the repository being reviewed.**
|
|
22
|
+
|
|
23
|
+
The PR diff is fetched from Azure DevOps/TFS, but several features depend on the local git state:
|
|
24
|
+
|
|
25
|
+
| Feature | Local dependency |
|
|
26
|
+
|---|---|
|
|
27
|
+
| PR diff | `git fetch` + three-dot diff against `origin/<branch>` |
|
|
28
|
+
| RAG context | `git grep` on the working tree |
|
|
29
|
+
| Branch validation | `git branch --show-current` |
|
|
30
|
+
|
|
31
|
+
### Branch requirement when RAG is enabled
|
|
32
|
+
|
|
33
|
+
When `review.rag.enabled: true`, the local repository **must be checked out on the PR target branch** (e.g. `development`). If the local branch differs from the target, the review is blocked:
|
|
34
|
+
|
|
35
|
+
```
|
|
36
|
+
Local branch mismatch: you are on 'main' but this PR targets 'development'.
|
|
37
|
+
RAG context would be built from the wrong branch, which may corrupt the review.
|
|
38
|
+
Please run: git checkout development
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
**Example:**
|
|
42
|
+
```bash
|
|
43
|
+
# PR: merge 'feature/my-feature' → 'development'
|
|
44
|
+
cd /path/to/my-repo # must be inside the repo
|
|
45
|
+
git checkout development # must match PR target branch
|
|
46
|
+
ai-review pr-review 42
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
To skip the branch check entirely, disable RAG:
|
|
50
|
+
```yaml
|
|
51
|
+
review:
|
|
52
|
+
rag:
|
|
53
|
+
enabled: false
|
|
54
|
+
```
|
|
55
|
+
|
|
18
56
|
## Installation & Quick Start
|
|
19
57
|
|
|
20
58
|
### 1. Install Package
|
|
@@ -92,11 +130,14 @@ tfs:
|
|
|
92
130
|
review:
|
|
93
131
|
language: pt
|
|
94
132
|
verbosity: detailed # or: quick, security
|
|
95
|
-
scope: diff_with_context # diff_with_context, diff_only
|
|
133
|
+
scope: diff_with_context # diff_with_context, diff_only
|
|
96
134
|
file_extensions_filter: [".cs", ".ts", ".py"]
|
|
97
135
|
max_diff_files: 50
|
|
98
136
|
max_comments_to_post: 20
|
|
99
137
|
custom_prompt_file: review_context.local.md
|
|
138
|
+
rag:
|
|
139
|
+
enabled: true # requires local branch == PR target
|
|
140
|
+
max_chars: 40000
|
|
100
141
|
project_context:
|
|
101
142
|
enabled: true
|
|
102
143
|
mode: on_demand # on_demand, full
|
|
@@ -143,11 +184,59 @@ src/
|
|
|
143
184
|
tfs_client.py # Azure DevOps integration
|
|
144
185
|
git_utils.py # Git diff processing
|
|
145
186
|
formatter.py # Output formatting (terminal, markdown, JSON)
|
|
187
|
+
rag_engine.py # RAG context via git grep
|
|
146
188
|
|
|
147
189
|
tests/
|
|
148
190
|
test_*.py # Unit and integration tests
|
|
149
191
|
```
|
|
150
192
|
|
|
193
|
+
## RAG Context
|
|
194
|
+
|
|
195
|
+
### Current Implementation
|
|
196
|
+
|
|
197
|
+
The RAG engine enriches the LLM prompt with related code snippets found in the local repository:
|
|
198
|
+
|
|
199
|
+
1. **Extract identifiers** — function and class names are parsed from the PR diff
|
|
200
|
+
2. **Search** — `git grep` finds files containing those identifiers
|
|
201
|
+
3. **Extract snippets** — ±10 lines around each match are included as read-only context
|
|
202
|
+
|
|
203
|
+
All operations run locally with no extra dependencies. The quality of RAG context depends entirely on the local repository state, which is why the **local branch must match the PR target branch**.
|
|
204
|
+
|
|
205
|
+
### Recommended Stack for Enhanced RAG (Local & Open Source)
|
|
206
|
+
|
|
207
|
+
For teams wanting semantic similarity instead of keyword search, the recommended local stack is:
|
|
208
|
+
|
|
209
|
+
| Component | Library | Reason |
|
|
210
|
+
|---|---|---|
|
|
211
|
+
| **Vector database** | [ChromaDB](https://www.trychroma.com/) | Runs in-memory or persists to a local SQLite file — no server needed, `pip install chromadb` |
|
|
212
|
+
| **Embeddings** | [sentence-transformers](https://www.sbert.net/) | Generates vectors locally on CPU — no API calls, no cost |
|
|
213
|
+
|
|
214
|
+
Example integration pattern:
|
|
215
|
+
|
|
216
|
+
```python
|
|
217
|
+
from sentence_transformers import SentenceTransformer
|
|
218
|
+
import chromadb
|
|
219
|
+
|
|
220
|
+
model = SentenceTransformer("all-MiniLM-L6-v2") # ~80 MB, CPU-friendly
|
|
221
|
+
client = chromadb.Client() # in-memory
|
|
222
|
+
collection = client.create_collection("repo-index")
|
|
223
|
+
|
|
224
|
+
# Index
|
|
225
|
+
collection.add(
|
|
226
|
+
documents=[snippet_text],
|
|
227
|
+
embeddings=model.encode([snippet_text]).tolist(),
|
|
228
|
+
ids=["file:line"],
|
|
229
|
+
)
|
|
230
|
+
|
|
231
|
+
# Query
|
|
232
|
+
results = collection.query(
|
|
233
|
+
query_embeddings=model.encode([query]).tolist(),
|
|
234
|
+
n_results=5,
|
|
235
|
+
)
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
This stack keeps the CLI lightweight (`pip install`) and requires no external services or paid APIs.
|
|
239
|
+
|
|
151
240
|
### Run Tests
|
|
152
241
|
|
|
153
242
|
```bash
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: code-review-ai-cli
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.3.0
|
|
4
4
|
Summary: Automated AI-powered code review CLI for Azure DevOps / TFS Pull Requests
|
|
5
5
|
License: MIT
|
|
6
6
|
Keywords: code-review,ai,azure-devops,tfs,pull-request,llm
|
|
@@ -48,10 +48,48 @@ Automated code review tool with Pull Request integration for Azure DevOps/TFS an
|
|
|
48
48
|
- **Multiple LLM Providers** — OpenAI, Azure OpenAI, Gemini, Claude, Ollama, GitHub Copilot, AWS Bedrock
|
|
49
49
|
- **Smart Filtering** — Filter by file extensions, limit diff size
|
|
50
50
|
- **Project-aware PR Context** — Sends repository and linked work item context while restricting findings to modified PR lines
|
|
51
|
+
- **RAG Context** — Enriches the review with related code snippets found via `git grep` in the local repository
|
|
51
52
|
- **Single Reviewer Context** — One Markdown context file with local override support
|
|
52
53
|
- **Usage Tracking** — Store per-PR token usage and optional cost estimates
|
|
53
54
|
- **Interactive CLI** — Menu-driven selection and confirmation
|
|
54
55
|
|
|
56
|
+
## Local Repository Requirement
|
|
57
|
+
|
|
58
|
+
> **The CLI must be run from inside the local clone of the repository being reviewed.**
|
|
59
|
+
|
|
60
|
+
The PR diff is fetched from Azure DevOps/TFS, but several features depend on the local git state:
|
|
61
|
+
|
|
62
|
+
| Feature | Local dependency |
|
|
63
|
+
|---|---|
|
|
64
|
+
| PR diff | `git fetch` + three-dot diff against `origin/<branch>` |
|
|
65
|
+
| RAG context | `git grep` on the working tree |
|
|
66
|
+
| Branch validation | `git branch --show-current` |
|
|
67
|
+
|
|
68
|
+
### Branch requirement when RAG is enabled
|
|
69
|
+
|
|
70
|
+
When `review.rag.enabled: true`, the local repository **must be checked out on the PR target branch** (e.g. `development`). If the local branch differs from the target, the review is blocked:
|
|
71
|
+
|
|
72
|
+
```
|
|
73
|
+
Local branch mismatch: you are on 'main' but this PR targets 'development'.
|
|
74
|
+
RAG context would be built from the wrong branch, which may corrupt the review.
|
|
75
|
+
Please run: git checkout development
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
**Example:**
|
|
79
|
+
```bash
|
|
80
|
+
# PR: merge 'feature/my-feature' → 'development'
|
|
81
|
+
cd /path/to/my-repo # must be inside the repo
|
|
82
|
+
git checkout development # must match PR target branch
|
|
83
|
+
ai-review pr-review 42
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
To skip the branch check entirely, disable RAG:
|
|
87
|
+
```yaml
|
|
88
|
+
review:
|
|
89
|
+
rag:
|
|
90
|
+
enabled: false
|
|
91
|
+
```
|
|
92
|
+
|
|
55
93
|
## Installation & Quick Start
|
|
56
94
|
|
|
57
95
|
### 1. Install Package
|
|
@@ -129,11 +167,14 @@ tfs:
|
|
|
129
167
|
review:
|
|
130
168
|
language: pt
|
|
131
169
|
verbosity: detailed # or: quick, security
|
|
132
|
-
scope: diff_with_context # diff_with_context, diff_only
|
|
170
|
+
scope: diff_with_context # diff_with_context, diff_only
|
|
133
171
|
file_extensions_filter: [".cs", ".ts", ".py"]
|
|
134
172
|
max_diff_files: 50
|
|
135
173
|
max_comments_to_post: 20
|
|
136
174
|
custom_prompt_file: review_context.local.md
|
|
175
|
+
rag:
|
|
176
|
+
enabled: true # requires local branch == PR target
|
|
177
|
+
max_chars: 40000
|
|
137
178
|
project_context:
|
|
138
179
|
enabled: true
|
|
139
180
|
mode: on_demand # on_demand, full
|
|
@@ -180,11 +221,59 @@ src/
|
|
|
180
221
|
tfs_client.py # Azure DevOps integration
|
|
181
222
|
git_utils.py # Git diff processing
|
|
182
223
|
formatter.py # Output formatting (terminal, markdown, JSON)
|
|
224
|
+
rag_engine.py # RAG context via git grep
|
|
183
225
|
|
|
184
226
|
tests/
|
|
185
227
|
test_*.py # Unit and integration tests
|
|
186
228
|
```
|
|
187
229
|
|
|
230
|
+
## RAG Context
|
|
231
|
+
|
|
232
|
+
### Current Implementation
|
|
233
|
+
|
|
234
|
+
The RAG engine enriches the LLM prompt with related code snippets found in the local repository:
|
|
235
|
+
|
|
236
|
+
1. **Extract identifiers** — function and class names are parsed from the PR diff
|
|
237
|
+
2. **Search** — `git grep` finds files containing those identifiers
|
|
238
|
+
3. **Extract snippets** — ±10 lines around each match are included as read-only context
|
|
239
|
+
|
|
240
|
+
All operations run locally with no extra dependencies. The quality of RAG context depends entirely on the local repository state, which is why the **local branch must match the PR target branch**.
|
|
241
|
+
|
|
242
|
+
### Recommended Stack for Enhanced RAG (Local & Open Source)
|
|
243
|
+
|
|
244
|
+
For teams wanting semantic similarity instead of keyword search, the recommended local stack is:
|
|
245
|
+
|
|
246
|
+
| Component | Library | Reason |
|
|
247
|
+
|---|---|---|
|
|
248
|
+
| **Vector database** | [ChromaDB](https://www.trychroma.com/) | Runs in-memory or persists to a local SQLite file — no server needed, `pip install chromadb` |
|
|
249
|
+
| **Embeddings** | [sentence-transformers](https://www.sbert.net/) | Generates vectors locally on CPU — no API calls, no cost |
|
|
250
|
+
|
|
251
|
+
Example integration pattern:
|
|
252
|
+
|
|
253
|
+
```python
|
|
254
|
+
from sentence_transformers import SentenceTransformer
|
|
255
|
+
import chromadb
|
|
256
|
+
|
|
257
|
+
model = SentenceTransformer("all-MiniLM-L6-v2") # ~80 MB, CPU-friendly
|
|
258
|
+
client = chromadb.Client() # in-memory
|
|
259
|
+
collection = client.create_collection("repo-index")
|
|
260
|
+
|
|
261
|
+
# Index
|
|
262
|
+
collection.add(
|
|
263
|
+
documents=[snippet_text],
|
|
264
|
+
embeddings=model.encode([snippet_text]).tolist(),
|
|
265
|
+
ids=["file:line"],
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
# Query
|
|
269
|
+
results = collection.query(
|
|
270
|
+
query_embeddings=model.encode([query]).tolist(),
|
|
271
|
+
n_results=5,
|
|
272
|
+
)
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
This stack keeps the CLI lightweight (`pip install`) and requires no external services or paid APIs.
|
|
276
|
+
|
|
188
277
|
### Run Tests
|
|
189
278
|
|
|
190
279
|
```bash
|
{code_review_ai_cli-1.2.0 → code_review_ai_cli-1.3.0}/code_review_ai_cli.egg-info/SOURCES.txt
RENAMED
|
@@ -12,6 +12,7 @@ src/config.py
|
|
|
12
12
|
src/formatter.py
|
|
13
13
|
src/git_utils.py
|
|
14
14
|
src/llm_client.py
|
|
15
|
+
src/rag_engine.py
|
|
15
16
|
src/tfs_client.py
|
|
16
17
|
src/usage_tracker.py
|
|
17
18
|
src/prompts/config.yaml.template
|
|
@@ -21,5 +22,6 @@ tests/test_config.py
|
|
|
21
22
|
tests/test_formatter.py
|
|
22
23
|
tests/test_git_utils.py
|
|
23
24
|
tests/test_llm_client.py
|
|
25
|
+
tests/test_rag_engine.py
|
|
24
26
|
tests/test_tfs_client.py
|
|
25
27
|
tests/test_usage_tracker.py
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "code-review-ai-cli"
|
|
7
|
-
version = "1.
|
|
7
|
+
version = "1.3.0"
|
|
8
8
|
description = "Automated AI-powered code review CLI for Azure DevOps / TFS Pull Requests"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
@@ -84,6 +84,7 @@ from src.config import ReviewConfig, VALID_PROVIDERS
|
|
|
84
84
|
from src.git_utils import GitUtils, GitError
|
|
85
85
|
from src.llm_client import LLMClient, LLMError
|
|
86
86
|
from src.formatter import ReviewFormatter, Colors, save_output
|
|
87
|
+
from src.rag_engine import obter_contexto_rag
|
|
87
88
|
from src.usage_tracker import (
|
|
88
89
|
append_usage_record,
|
|
89
90
|
build_pr_usage_record,
|
|
@@ -94,7 +95,7 @@ from src.usage_tracker import (
|
|
|
94
95
|
from src import __version__ as VERSION
|
|
95
96
|
|
|
96
97
|
|
|
97
|
-
REVIEW_SCOPE_CHOICES = ["diff_only", "diff_with_context"
|
|
98
|
+
REVIEW_SCOPE_CHOICES = ["diff_only", "diff_with_context"]
|
|
98
99
|
|
|
99
100
|
|
|
100
101
|
def _get_spinner_frames() -> list[str]:
|
|
@@ -263,7 +264,7 @@ def _add_global_options(parser: argparse.ArgumentParser) -> None:
|
|
|
263
264
|
"--review-scope", default=None,
|
|
264
265
|
choices=REVIEW_SCOPE_CHOICES,
|
|
265
266
|
help=(
|
|
266
|
-
"Review scope: diff_with_context (default)
|
|
267
|
+
"Review scope: diff_with_context (default) or diff_only"
|
|
267
268
|
)
|
|
268
269
|
)
|
|
269
270
|
group_review.add_argument(
|
|
@@ -333,11 +334,6 @@ def _review_scope_context_note(review_scope: str) -> str:
|
|
|
333
334
|
"Context will be used for understanding only; review comments remain "
|
|
334
335
|
"limited to modified PR lines."
|
|
335
336
|
)
|
|
336
|
-
if scope == "full_code":
|
|
337
|
-
return (
|
|
338
|
-
"Review is running in full_code mode; full changed files are context, "
|
|
339
|
-
"but comments remain limited to actual PR changes."
|
|
340
|
-
)
|
|
341
337
|
return "Review is running in diff_only mode."
|
|
342
338
|
|
|
343
339
|
|
|
@@ -786,7 +782,6 @@ def _comment_mentions_non_anchor_terms(comment: dict, anchor_text: str) -> bool:
|
|
|
786
782
|
comment.get("comment", ""),
|
|
787
783
|
comment.get("anchor_code", ""),
|
|
788
784
|
comment.get("problematic_code", ""),
|
|
789
|
-
comment.get("evidence", ""),
|
|
790
785
|
):
|
|
791
786
|
if term not in anchor_text:
|
|
792
787
|
return True
|
|
@@ -904,15 +899,15 @@ def _filter_comments_to_grounded_source_lines(
|
|
|
904
899
|
discarded_grounding.append(comment)
|
|
905
900
|
continue
|
|
906
901
|
|
|
907
|
-
if not
|
|
902
|
+
if not evidence:
|
|
908
903
|
discarded_grounding.append(comment)
|
|
909
904
|
continue
|
|
910
905
|
|
|
911
|
-
if not _text_contains_evidence(anchor_text,
|
|
906
|
+
if not _text_contains_evidence(anchor_text, anchor_code):
|
|
912
907
|
discarded_grounding.append(comment)
|
|
913
908
|
continue
|
|
914
909
|
|
|
915
|
-
if not _text_contains_evidence(anchor_text,
|
|
910
|
+
if not _text_contains_evidence(anchor_text, problematic_code):
|
|
916
911
|
discarded_grounding.append(comment)
|
|
917
912
|
continue
|
|
918
913
|
|
|
@@ -924,10 +919,6 @@ def _filter_comments_to_grounded_source_lines(
|
|
|
924
919
|
discarded_grounding.append(comment)
|
|
925
920
|
continue
|
|
926
921
|
|
|
927
|
-
if not _text_contains_evidence(source_text, evidence):
|
|
928
|
-
discarded_grounding.append(comment)
|
|
929
|
-
continue
|
|
930
|
-
|
|
931
922
|
if _comment_mentions_absent_source_terms(comment, source_text):
|
|
932
923
|
discarded_grounding.append(comment)
|
|
933
924
|
continue
|
|
@@ -1381,19 +1372,29 @@ def run_pr_review_workflow(args: argparse.Namespace, config: ReviewConfig,
|
|
|
1381
1372
|
# Show PR details
|
|
1382
1373
|
print(formatter.format_pr_details(pr_details))
|
|
1383
1374
|
|
|
1384
|
-
# --- Get PR diff ---
|
|
1375
|
+
# --- Get PR diff via git local ---
|
|
1385
1376
|
print(formatter.format_progress("Getting Pull Request diff"))
|
|
1386
1377
|
|
|
1378
|
+
_local_repo = (config.tfs_local_repo_path or "").strip() or os.getcwd()
|
|
1379
|
+
git_utils_pr = GitUtils(repo_path=_local_repo, pat=config.tfs_pat)
|
|
1380
|
+
|
|
1387
1381
|
try:
|
|
1388
|
-
|
|
1389
|
-
repo_name,
|
|
1390
|
-
pr_id,
|
|
1391
|
-
review_scope=config.review_scope,
|
|
1392
|
-
)
|
|
1382
|
+
target_ref, source_ref = tfs.obter_dados_pr(pr_id)
|
|
1393
1383
|
except TFSError as exc:
|
|
1394
1384
|
print(formatter.format_error(str(exc)))
|
|
1395
1385
|
return 1
|
|
1396
1386
|
|
|
1387
|
+
try:
|
|
1388
|
+
git_utils_pr.fetch_merge_commit(source_ref)
|
|
1389
|
+
except GitError as exc:
|
|
1390
|
+
print(formatter.format_warning(f"Could not fetch source branch: {exc}"))
|
|
1391
|
+
|
|
1392
|
+
try:
|
|
1393
|
+
diff = git_utils_pr.get_pr_diff(target_ref, source_ref)
|
|
1394
|
+
except GitError as exc:
|
|
1395
|
+
print(formatter.format_error(str(exc)))
|
|
1396
|
+
return 1
|
|
1397
|
+
|
|
1397
1398
|
if not diff.strip():
|
|
1398
1399
|
print(formatter.format_warning("PR contains no code changes."))
|
|
1399
1400
|
return 0
|
|
@@ -1411,8 +1412,13 @@ def run_pr_review_workflow(args: argparse.Namespace, config: ReviewConfig,
|
|
|
1411
1412
|
print(formatter.format_info("🔍 DRY-RUN mode: comments will NOT be posted"))
|
|
1412
1413
|
|
|
1413
1414
|
# Get diff files summary
|
|
1414
|
-
git_utils =
|
|
1415
|
-
|
|
1415
|
+
git_utils = git_utils_pr
|
|
1416
|
+
|
|
1417
|
+
# --- Noise filter (binary, lock files, oversized sections) ---
|
|
1418
|
+
diff = git_utils_pr.filter_diff_noise(diff, config.max_diff_lines)
|
|
1419
|
+
if not diff.strip():
|
|
1420
|
+
print(formatter.format_warning("After filtering noise (binary/lock files), the diff is empty."))
|
|
1421
|
+
return 0
|
|
1416
1422
|
|
|
1417
1423
|
# Filter extensions before limiting/truncating the diff sent to the LLM
|
|
1418
1424
|
if config.file_extensions_filter:
|
|
@@ -1460,7 +1466,41 @@ def run_pr_review_workflow(args: argparse.Namespace, config: ReviewConfig,
|
|
|
1460
1466
|
))
|
|
1461
1467
|
|
|
1462
1468
|
use_contextual_review = review_scope == "diff_with_context"
|
|
1463
|
-
load_changed_file_context = review_scope
|
|
1469
|
+
load_changed_file_context = review_scope == "diff_with_context"
|
|
1470
|
+
|
|
1471
|
+
# --- RAG context ---
|
|
1472
|
+
rag_context = ""
|
|
1473
|
+
if config.rag_enabled and diff_truncated.strip():
|
|
1474
|
+
# Verify local branch matches the PR target branch to avoid RAG context contamination.
|
|
1475
|
+
_target_branch = target_ref.removeprefix("origin/")
|
|
1476
|
+
try:
|
|
1477
|
+
_local_branch = git_utils_pr.get_current_branch()
|
|
1478
|
+
except GitError as exc:
|
|
1479
|
+
print(formatter.format_error(
|
|
1480
|
+
f"Cannot determine local branch: {exc}\n"
|
|
1481
|
+
"Ensure the local repository is accessible and try again."
|
|
1482
|
+
))
|
|
1483
|
+
return 1
|
|
1484
|
+
|
|
1485
|
+
if _local_branch != _target_branch:
|
|
1486
|
+
print(formatter.format_error(
|
|
1487
|
+
f"Local branch mismatch: you are on '{_local_branch}' "
|
|
1488
|
+
f"but this PR targets '{_target_branch}'.\n"
|
|
1489
|
+
f"RAG context would be built from the wrong branch, which may corrupt the review.\n"
|
|
1490
|
+
f"Please run: git checkout {_target_branch}"
|
|
1491
|
+
))
|
|
1492
|
+
return 1
|
|
1493
|
+
|
|
1494
|
+
print(formatter.format_progress("Loading RAG context from local repository"))
|
|
1495
|
+
rag_context = obter_contexto_rag(
|
|
1496
|
+
diff_truncated,
|
|
1497
|
+
repo_path=git_utils.repo_path,
|
|
1498
|
+
max_chars=config.rag_max_chars,
|
|
1499
|
+
)
|
|
1500
|
+
if rag_context:
|
|
1501
|
+
print(formatter.format_info(
|
|
1502
|
+
f"RAG context loaded ({len(rag_context):,} characters)."
|
|
1503
|
+
))
|
|
1464
1504
|
|
|
1465
1505
|
work_item_context = ""
|
|
1466
1506
|
linked_work_item_ids: list[int] | None = None
|
|
@@ -1604,7 +1644,7 @@ def run_pr_review_workflow(args: argparse.Namespace, config: ReviewConfig,
|
|
|
1604
1644
|
structured_comments = llm.review_pr_structured(
|
|
1605
1645
|
diff=diff_truncated,
|
|
1606
1646
|
files_summary=files_summary,
|
|
1607
|
-
context=getattr(args, "context", ""),
|
|
1647
|
+
context="\n\n".join(filter(None, [getattr(args, "context", ""), rag_context])),
|
|
1608
1648
|
review_scope=config.review_scope,
|
|
1609
1649
|
project_context=project_context,
|
|
1610
1650
|
work_item_context=work_item_context,
|
|
@@ -84,11 +84,12 @@ class ReviewConfig:
|
|
|
84
84
|
tfs_verify_ssl: bool = True # Verify TLS certificates
|
|
85
85
|
tfs_ca_bundle: str = "" # Path to corporate CA bundle (.pem)
|
|
86
86
|
tfs_repository: str = "" # Default repository (empty = all)
|
|
87
|
+
tfs_local_repo_path: str = "" # Local clone path of the reviewed repo (empty = cwd)
|
|
87
88
|
|
|
88
89
|
# --- Review -------------------------------------------------------
|
|
89
90
|
review_language: str = "pt" # Review language (pt/en)
|
|
90
91
|
verbosity: str = "detailed" # "quick" | "detailed" | "security"
|
|
91
|
-
review_scope: str = "diff_with_context" # "diff_only" | "diff_with_context"
|
|
92
|
+
review_scope: str = "diff_with_context" # "diff_only" | "diff_with_context"
|
|
92
93
|
max_diff_files: int = 50 # Max diff files sent to LLM
|
|
93
94
|
max_diff_lines: int = 2000 # Max diff lines
|
|
94
95
|
max_comments_to_post: int = 20 # Max actionable inline comments per review
|
|
@@ -141,6 +142,10 @@ class ReviewConfig:
|
|
|
141
142
|
"Microsoft.VSTS.TCM.SystemInfo",
|
|
142
143
|
])
|
|
143
144
|
|
|
145
|
+
# --- RAG Context --------------------------------------------------
|
|
146
|
+
rag_enabled: bool = True # Enable RAG context retrieval
|
|
147
|
+
rag_max_chars: int = 40000 # Max characters for RAG context
|
|
148
|
+
|
|
144
149
|
# --- PR Review ----------------------------------------------------
|
|
145
150
|
auto_post_comments: bool = False # Post comments automatically
|
|
146
151
|
dry_run: bool = False # Review without posting
|
|
@@ -260,6 +265,7 @@ class ReviewConfig:
|
|
|
260
265
|
"tfs_verify_ssl": ("tfs", "verify_ssl"),
|
|
261
266
|
"tfs_ca_bundle": ("tfs", "ca_bundle"),
|
|
262
267
|
"tfs_repository": ("tfs", "repository"),
|
|
268
|
+
"tfs_local_repo_path": ("tfs", "local_repo_path"),
|
|
263
269
|
# Review
|
|
264
270
|
"review_language": ("review", "language"),
|
|
265
271
|
"verbosity": ("review", "verbosity"),
|
|
@@ -284,6 +290,8 @@ class ReviewConfig:
|
|
|
284
290
|
"work_item_context_max_items": ("review", "work_item_context", "max_items"),
|
|
285
291
|
"work_item_context_max_chars": ("review", "work_item_context", "max_chars"),
|
|
286
292
|
"work_item_context_fields": ("review", "work_item_context", "fields"),
|
|
293
|
+
"rag_enabled": ("review", "rag", "enabled"),
|
|
294
|
+
"rag_max_chars": ("review", "rag", "max_chars"),
|
|
287
295
|
# PR
|
|
288
296
|
"auto_post_comments": ("pr", "auto_post_comments"),
|
|
289
297
|
"dry_run": ("pr", "dry_run"),
|
|
@@ -377,10 +385,10 @@ class ReviewConfig:
|
|
|
377
385
|
"Use 'quick', 'detailed' or 'security'."
|
|
378
386
|
)
|
|
379
387
|
|
|
380
|
-
if self.review_scope not in ("diff_only", "diff_with_context"
|
|
388
|
+
if self.review_scope not in ("diff_only", "diff_with_context"):
|
|
381
389
|
issues.append(
|
|
382
390
|
f"Invalid review scope: '{self.review_scope}'. "
|
|
383
|
-
"Use 'diff_only'
|
|
391
|
+
"Use 'diff_only' or 'diff_with_context'."
|
|
384
392
|
)
|
|
385
393
|
|
|
386
394
|
if self.max_diff_files <= 0:
|