code-review-ai-cli 1.3.5__tar.gz → 2.0.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.3.5 → code_review_ai_cli-2.0.0}/PKG-INFO +8 -26
- {code_review_ai_cli-1.3.5 → code_review_ai_cli-2.0.0}/README.md +7 -25
- {code_review_ai_cli-1.3.5 → code_review_ai_cli-2.0.0}/code_review_ai_cli.egg-info/PKG-INFO +8 -26
- {code_review_ai_cli-1.3.5 → code_review_ai_cli-2.0.0}/code_review_ai_cli.egg-info/SOURCES.txt +2 -0
- {code_review_ai_cli-1.3.5 → code_review_ai_cli-2.0.0}/pyproject.toml +1 -1
- {code_review_ai_cli-1.3.5 → code_review_ai_cli-2.0.0}/src/git_utils.py +0 -1
- {code_review_ai_cli-1.3.5 → code_review_ai_cli-2.0.0}/src/llm_client.py +7 -0
- code_review_ai_cli-2.0.0/src/prompt_utils.py +56 -0
- code_review_ai_cli-2.0.0/src/prompts/review_prompt.md.template +91 -0
- {code_review_ai_cli-1.3.5 → code_review_ai_cli-2.0.0}/tests/test_llm_client.py +114 -0
- code_review_ai_cli-2.0.0/tests/test_prompt_utils.py +129 -0
- code_review_ai_cli-1.3.5/src/prompts/review_prompt.md.template +0 -2
- {code_review_ai_cli-1.3.5 → code_review_ai_cli-2.0.0}/code_review_ai_cli.egg-info/dependency_links.txt +0 -0
- {code_review_ai_cli-1.3.5 → code_review_ai_cli-2.0.0}/code_review_ai_cli.egg-info/entry_points.txt +0 -0
- {code_review_ai_cli-1.3.5 → code_review_ai_cli-2.0.0}/code_review_ai_cli.egg-info/requires.txt +0 -0
- {code_review_ai_cli-1.3.5 → code_review_ai_cli-2.0.0}/code_review_ai_cli.egg-info/top_level.txt +0 -0
- {code_review_ai_cli-1.3.5 → code_review_ai_cli-2.0.0}/setup.cfg +0 -0
- {code_review_ai_cli-1.3.5 → code_review_ai_cli-2.0.0}/src/__init__.py +0 -0
- {code_review_ai_cli-1.3.5 → code_review_ai_cli-2.0.0}/src/ai_review.py +0 -0
- {code_review_ai_cli-1.3.5 → code_review_ai_cli-2.0.0}/src/config.py +0 -0
- {code_review_ai_cli-1.3.5 → code_review_ai_cli-2.0.0}/src/formatter.py +0 -0
- {code_review_ai_cli-1.3.5 → code_review_ai_cli-2.0.0}/src/prompts/config.yaml.template +0 -0
- {code_review_ai_cli-1.3.5 → code_review_ai_cli-2.0.0}/src/tfs_client.py +0 -0
- {code_review_ai_cli-1.3.5 → code_review_ai_cli-2.0.0}/tests/test_ai_review.py +0 -0
- {code_review_ai_cli-1.3.5 → code_review_ai_cli-2.0.0}/tests/test_config.py +0 -0
- {code_review_ai_cli-1.3.5 → code_review_ai_cli-2.0.0}/tests/test_formatter.py +0 -0
- {code_review_ai_cli-1.3.5 → code_review_ai_cli-2.0.0}/tests/test_git_utils.py +0 -0
- {code_review_ai_cli-1.3.5 → code_review_ai_cli-2.0.0}/tests/test_tfs_client.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: code-review-ai-cli
|
|
3
|
-
Version:
|
|
3
|
+
Version: 2.0.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
|
|
@@ -89,19 +89,6 @@ This copies two bundled templates:
|
|
|
89
89
|
- **`config.yaml`** — all available options with inline documentation
|
|
90
90
|
- **`review_prompt.md`** — default review style rules, injected into every LLM prompt
|
|
91
91
|
|
|
92
|
-
```text
|
|
93
|
-
✅ config.yaml created at: /home/user/my-project/config.yaml
|
|
94
|
-
✅ review_prompt.md created at: /home/user/my-project/review_prompt.md
|
|
95
|
-
Edit them to add your credentials, preferences and review rules.
|
|
96
|
-
```
|
|
97
|
-
|
|
98
|
-
If either file already exists you will be prompted individually before it is overwritten:
|
|
99
|
-
|
|
100
|
-
```text
|
|
101
|
-
config.yaml already exists in the current directory.
|
|
102
|
-
Overwrite? [y/N]
|
|
103
|
-
```
|
|
104
|
-
|
|
105
92
|
The tool looks for `config.yaml` in the **current working directory** at runtime. You can also pass a different path with `--config`:
|
|
106
93
|
|
|
107
94
|
```bash
|
|
@@ -156,7 +143,7 @@ output:
|
|
|
156
143
|
| `diff_only` | Default. Unified diff (changed lines only) + full file content as read-only context. |
|
|
157
144
|
| `full_code` | All lines of the new file version, every line prefixed with `+`. No baseline. |
|
|
158
145
|
|
|
159
|
-
#### `diff_only` — diff with
|
|
146
|
+
#### `diff_only` — diff with context (default)
|
|
160
147
|
|
|
161
148
|
```yaml
|
|
162
149
|
review:
|
|
@@ -164,16 +151,6 @@ review:
|
|
|
164
151
|
scope: full_code
|
|
165
152
|
```
|
|
166
153
|
|
|
167
|
-
When `diff_only` is active, the tool:
|
|
168
|
-
|
|
169
|
-
1. Generates a standard unified diff (added/removed lines with 3 lines of surrounding context) for each changed file.
|
|
170
|
-
2. Appends the **complete new-version file content** as a clearly-marked, read-only context block immediately after the diff for that file:
|
|
171
|
-
3. Strips all context lines and deleted lines (`-`) before sending to the LLM, so only added lines (`+`) and structural headers remain in the diff section.
|
|
172
|
-
4. Instructs the LLM (via the system prompt) to **use the full-file block as read-only background** and to focus the review exclusively on the `+` lines.
|
|
173
|
-
|
|
174
|
-
#### `full_code` — entire new file as added lines
|
|
175
|
-
Every line of the new file version is prefixed with `+` and sent without a `-` baseline. The LLM receives the complete content and is asked to review only the new code. This is more expensive and slower but may catch issues in unchanged lines that are affected by the changes.
|
|
176
|
-
|
|
177
154
|
### Filter by File Extension
|
|
178
155
|
|
|
179
156
|
`file_extensions_filter` works as an **allowlist**: only files with listed extensions are sent to the LLM for review. Remaining files are excluded from the diff before any processing.
|
|
@@ -204,7 +181,12 @@ Edit it to tailor the review to your team:
|
|
|
204
181
|
- Include business/architecture context
|
|
205
182
|
- Add examples of good/bad comments
|
|
206
183
|
|
|
207
|
-
|
|
184
|
+
**Rules can be scoped** to specific file types using language tags. During a review, the AI detects the files changed in the diff, identifies their extensions, and only loads:
|
|
185
|
+
|
|
186
|
+
- Rules marked with `<!-- lang: all -->` for all files.
|
|
187
|
+
- Rules matching the extensions of the files being reviewed. Example `<!-- lang: cs,ts -->` applied when .cs or .ts files are present.
|
|
188
|
+
|
|
189
|
+
The path for the **markdown-customizable prompt** is configurable in `config.yaml` (default: `review_prompt.md` in the current directory):
|
|
208
190
|
|
|
209
191
|
```yaml
|
|
210
192
|
review:
|
|
@@ -52,19 +52,6 @@ This copies two bundled templates:
|
|
|
52
52
|
- **`config.yaml`** — all available options with inline documentation
|
|
53
53
|
- **`review_prompt.md`** — default review style rules, injected into every LLM prompt
|
|
54
54
|
|
|
55
|
-
```text
|
|
56
|
-
✅ config.yaml created at: /home/user/my-project/config.yaml
|
|
57
|
-
✅ review_prompt.md created at: /home/user/my-project/review_prompt.md
|
|
58
|
-
Edit them to add your credentials, preferences and review rules.
|
|
59
|
-
```
|
|
60
|
-
|
|
61
|
-
If either file already exists you will be prompted individually before it is overwritten:
|
|
62
|
-
|
|
63
|
-
```text
|
|
64
|
-
config.yaml already exists in the current directory.
|
|
65
|
-
Overwrite? [y/N]
|
|
66
|
-
```
|
|
67
|
-
|
|
68
55
|
The tool looks for `config.yaml` in the **current working directory** at runtime. You can also pass a different path with `--config`:
|
|
69
56
|
|
|
70
57
|
```bash
|
|
@@ -119,7 +106,7 @@ output:
|
|
|
119
106
|
| `diff_only` | Default. Unified diff (changed lines only) + full file content as read-only context. |
|
|
120
107
|
| `full_code` | All lines of the new file version, every line prefixed with `+`. No baseline. |
|
|
121
108
|
|
|
122
|
-
#### `diff_only` — diff with
|
|
109
|
+
#### `diff_only` — diff with context (default)
|
|
123
110
|
|
|
124
111
|
```yaml
|
|
125
112
|
review:
|
|
@@ -127,16 +114,6 @@ review:
|
|
|
127
114
|
scope: full_code
|
|
128
115
|
```
|
|
129
116
|
|
|
130
|
-
When `diff_only` is active, the tool:
|
|
131
|
-
|
|
132
|
-
1. Generates a standard unified diff (added/removed lines with 3 lines of surrounding context) for each changed file.
|
|
133
|
-
2. Appends the **complete new-version file content** as a clearly-marked, read-only context block immediately after the diff for that file:
|
|
134
|
-
3. Strips all context lines and deleted lines (`-`) before sending to the LLM, so only added lines (`+`) and structural headers remain in the diff section.
|
|
135
|
-
4. Instructs the LLM (via the system prompt) to **use the full-file block as read-only background** and to focus the review exclusively on the `+` lines.
|
|
136
|
-
|
|
137
|
-
#### `full_code` — entire new file as added lines
|
|
138
|
-
Every line of the new file version is prefixed with `+` and sent without a `-` baseline. The LLM receives the complete content and is asked to review only the new code. This is more expensive and slower but may catch issues in unchanged lines that are affected by the changes.
|
|
139
|
-
|
|
140
117
|
### Filter by File Extension
|
|
141
118
|
|
|
142
119
|
`file_extensions_filter` works as an **allowlist**: only files with listed extensions are sent to the LLM for review. Remaining files are excluded from the diff before any processing.
|
|
@@ -167,7 +144,12 @@ Edit it to tailor the review to your team:
|
|
|
167
144
|
- Include business/architecture context
|
|
168
145
|
- Add examples of good/bad comments
|
|
169
146
|
|
|
170
|
-
|
|
147
|
+
**Rules can be scoped** to specific file types using language tags. During a review, the AI detects the files changed in the diff, identifies their extensions, and only loads:
|
|
148
|
+
|
|
149
|
+
- Rules marked with `<!-- lang: all -->` for all files.
|
|
150
|
+
- Rules matching the extensions of the files being reviewed. Example `<!-- lang: cs,ts -->` applied when .cs or .ts files are present.
|
|
151
|
+
|
|
152
|
+
The path for the **markdown-customizable prompt** is configurable in `config.yaml` (default: `review_prompt.md` in the current directory):
|
|
171
153
|
|
|
172
154
|
```yaml
|
|
173
155
|
review:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: code-review-ai-cli
|
|
3
|
-
Version:
|
|
3
|
+
Version: 2.0.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
|
|
@@ -89,19 +89,6 @@ This copies two bundled templates:
|
|
|
89
89
|
- **`config.yaml`** — all available options with inline documentation
|
|
90
90
|
- **`review_prompt.md`** — default review style rules, injected into every LLM prompt
|
|
91
91
|
|
|
92
|
-
```text
|
|
93
|
-
✅ config.yaml created at: /home/user/my-project/config.yaml
|
|
94
|
-
✅ review_prompt.md created at: /home/user/my-project/review_prompt.md
|
|
95
|
-
Edit them to add your credentials, preferences and review rules.
|
|
96
|
-
```
|
|
97
|
-
|
|
98
|
-
If either file already exists you will be prompted individually before it is overwritten:
|
|
99
|
-
|
|
100
|
-
```text
|
|
101
|
-
config.yaml already exists in the current directory.
|
|
102
|
-
Overwrite? [y/N]
|
|
103
|
-
```
|
|
104
|
-
|
|
105
92
|
The tool looks for `config.yaml` in the **current working directory** at runtime. You can also pass a different path with `--config`:
|
|
106
93
|
|
|
107
94
|
```bash
|
|
@@ -156,7 +143,7 @@ output:
|
|
|
156
143
|
| `diff_only` | Default. Unified diff (changed lines only) + full file content as read-only context. |
|
|
157
144
|
| `full_code` | All lines of the new file version, every line prefixed with `+`. No baseline. |
|
|
158
145
|
|
|
159
|
-
#### `diff_only` — diff with
|
|
146
|
+
#### `diff_only` — diff with context (default)
|
|
160
147
|
|
|
161
148
|
```yaml
|
|
162
149
|
review:
|
|
@@ -164,16 +151,6 @@ review:
|
|
|
164
151
|
scope: full_code
|
|
165
152
|
```
|
|
166
153
|
|
|
167
|
-
When `diff_only` is active, the tool:
|
|
168
|
-
|
|
169
|
-
1. Generates a standard unified diff (added/removed lines with 3 lines of surrounding context) for each changed file.
|
|
170
|
-
2. Appends the **complete new-version file content** as a clearly-marked, read-only context block immediately after the diff for that file:
|
|
171
|
-
3. Strips all context lines and deleted lines (`-`) before sending to the LLM, so only added lines (`+`) and structural headers remain in the diff section.
|
|
172
|
-
4. Instructs the LLM (via the system prompt) to **use the full-file block as read-only background** and to focus the review exclusively on the `+` lines.
|
|
173
|
-
|
|
174
|
-
#### `full_code` — entire new file as added lines
|
|
175
|
-
Every line of the new file version is prefixed with `+` and sent without a `-` baseline. The LLM receives the complete content and is asked to review only the new code. This is more expensive and slower but may catch issues in unchanged lines that are affected by the changes.
|
|
176
|
-
|
|
177
154
|
### Filter by File Extension
|
|
178
155
|
|
|
179
156
|
`file_extensions_filter` works as an **allowlist**: only files with listed extensions are sent to the LLM for review. Remaining files are excluded from the diff before any processing.
|
|
@@ -204,7 +181,12 @@ Edit it to tailor the review to your team:
|
|
|
204
181
|
- Include business/architecture context
|
|
205
182
|
- Add examples of good/bad comments
|
|
206
183
|
|
|
207
|
-
|
|
184
|
+
**Rules can be scoped** to specific file types using language tags. During a review, the AI detects the files changed in the diff, identifies their extensions, and only loads:
|
|
185
|
+
|
|
186
|
+
- Rules marked with `<!-- lang: all -->` for all files.
|
|
187
|
+
- Rules matching the extensions of the files being reviewed. Example `<!-- lang: cs,ts -->` applied when .cs or .ts files are present.
|
|
188
|
+
|
|
189
|
+
The path for the **markdown-customizable prompt** is configurable in `config.yaml` (default: `review_prompt.md` in the current directory):
|
|
208
190
|
|
|
209
191
|
```yaml
|
|
210
192
|
review:
|
{code_review_ai_cli-1.3.5 → code_review_ai_cli-2.0.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/prompt_utils.py
|
|
15
16
|
src/tfs_client.py
|
|
16
17
|
src/prompts/config.yaml.template
|
|
17
18
|
src/prompts/review_prompt.md.template
|
|
@@ -20,4 +21,5 @@ tests/test_config.py
|
|
|
20
21
|
tests/test_formatter.py
|
|
21
22
|
tests/test_git_utils.py
|
|
22
23
|
tests/test_llm_client.py
|
|
24
|
+
tests/test_prompt_utils.py
|
|
23
25
|
tests/test_tfs_client.py
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "code-review-ai-cli"
|
|
7
|
-
version = "
|
|
7
|
+
version = "2.0.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"
|
|
@@ -17,6 +17,7 @@ import json
|
|
|
17
17
|
import os
|
|
18
18
|
|
|
19
19
|
from .config import ReviewConfig
|
|
20
|
+
from .prompt_utils import detect_langs, filter_prompt_by_langs
|
|
20
21
|
|
|
21
22
|
|
|
22
23
|
class LLMError(Exception):
|
|
@@ -301,6 +302,12 @@ class LLMClient:
|
|
|
301
302
|
json_schema_prompt = PR_COMMENT_PROMPT
|
|
302
303
|
custom_prompt = self._load_custom_prompt_text()
|
|
303
304
|
|
|
305
|
+
# filter custom_prompt sections by the languages/extensions actually changed
|
|
306
|
+
if custom_prompt:
|
|
307
|
+
file_paths = [f.get("file") for f in files_summary]
|
|
308
|
+
active_langs = detect_langs(file_paths)
|
|
309
|
+
custom_prompt = filter_prompt_by_langs(custom_prompt, active_langs) # can return an empty list
|
|
310
|
+
|
|
304
311
|
scope_guidance = get_scope_guidance(
|
|
305
312
|
review_scope=review_scope,
|
|
306
313
|
structured=True,
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Prompt Utilities Module - AI Code Review
|
|
3
|
+
========================================
|
|
4
|
+
Responsible for tailoring the custom review prompt (review_prompt.md) to the
|
|
5
|
+
files actually changed in a diff:
|
|
6
|
+
- Detects which "languages" (file extensions) are present in the changeset
|
|
7
|
+
- Filters the custom prompt content, keeping only the sections tagged for
|
|
8
|
+
those extensions (plus sections tagged "all", which always apply)
|
|
9
|
+
|
|
10
|
+
No manual extension-to-language mapping is required: the file extension
|
|
11
|
+
itself (without the dot, lowercase) is used as the tag. This means the
|
|
12
|
+
prompt file can be extended with new languages (e.g. Java, Go) without any
|
|
13
|
+
code changes - just add a new <!-- lang: ... --> section to review_prompt.md.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
import os
|
|
17
|
+
import re
|
|
18
|
+
|
|
19
|
+
LANG_TAG_RE = re.compile(r"<!--\s*lang:\s*(.*?)\s*-->")
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def detect_langs(file_paths: list[str]) -> set[str]:
|
|
23
|
+
"""
|
|
24
|
+
Detects active tags directly from the extensions of changed files.
|
|
25
|
+
'all' is always included since general sections always apply.
|
|
26
|
+
No manual mapping: the extension (no dot, lowercase) IS the tag.
|
|
27
|
+
"""
|
|
28
|
+
langs = {"all"}
|
|
29
|
+
for path in file_paths:
|
|
30
|
+
if not path:
|
|
31
|
+
continue
|
|
32
|
+
ext = os.path.splitext(path)[1].lstrip(".").lower()
|
|
33
|
+
if ext:
|
|
34
|
+
langs.add(ext)
|
|
35
|
+
return langs
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def filter_prompt_by_langs(md_content: str, active_langs: set[str]) -> str:
|
|
39
|
+
"""
|
|
40
|
+
Parses review_prompt.md and returns only the sections whose
|
|
41
|
+
<!-- lang: ... --> tag intersects with active_langs.
|
|
42
|
+
"""
|
|
43
|
+
active_langs = {l.strip().lower() for l in active_langs}
|
|
44
|
+
blocks = re.split(r"(<!--\s*lang:.*?-->)", md_content)
|
|
45
|
+
|
|
46
|
+
output: list[str] = []
|
|
47
|
+
current_langs = {"all"}
|
|
48
|
+
for chunk in blocks:
|
|
49
|
+
match = LANG_TAG_RE.match(chunk.strip())
|
|
50
|
+
if match:
|
|
51
|
+
current_langs = {l.strip().lower() for l in match.group(1).split(",")}
|
|
52
|
+
continue
|
|
53
|
+
if current_langs & active_langs:
|
|
54
|
+
output.append(chunk)
|
|
55
|
+
|
|
56
|
+
return "\n".join(output).strip()
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
<!-- lang: all -->
|
|
2
|
+
## General Rules (all languages)
|
|
3
|
+
- Security: SQL injection, XSS, hardcoded secrets/credentials, missing input validation, sensitive data exposed in logs.
|
|
4
|
+
- Error handling: swallowed exceptions (empty catch), missing logging at critical points, generic error messages to end users.
|
|
5
|
+
- Readability: methods too long or with too many responsibilities (SRP), poor naming, dead/commented-out code.
|
|
6
|
+
- Duplication: repeated logic that should be extracted/reused.
|
|
7
|
+
- Testability: hard-to-mock dependencies, business logic mixed with I/O.
|
|
8
|
+
- Unused code: unused local variables, method parameters, private fields/methods, unreachable code, and unused imports/usings.
|
|
9
|
+
- Unnecessary allocations in hot paths (loops, frequently-called requests).
|
|
10
|
+
|
|
11
|
+
<!-- lang: cs,ts -->
|
|
12
|
+
## Performance
|
|
13
|
+
- Use of `StringBuilder` vs string concatenation in loops.
|
|
14
|
+
- Blocking synchronous operations where async should be used (I/O, network calls).
|
|
15
|
+
|
|
16
|
+
<!-- lang: cs,ts,sql -->
|
|
17
|
+
## Concurrency / Thread-safety
|
|
18
|
+
- Race conditions in shared code (static fields, singletons).
|
|
19
|
+
- Correct use of locks/semaphores, avoid deadlocks.
|
|
20
|
+
- Thread-safe collections where needed (`ConcurrentDictionary`, etc).
|
|
21
|
+
|
|
22
|
+
<!-- lang: all -->
|
|
23
|
+
## Configuration & Secrets
|
|
24
|
+
- Hardcoded values that should be in configuration (connection strings, URLs, timeouts).
|
|
25
|
+
- Secrets/API keys accidentally committed.
|
|
26
|
+
|
|
27
|
+
<!-- lang: cs,ts -->
|
|
28
|
+
## Dependencies & Compatibility
|
|
29
|
+
- New dependencies added without clear justification - unnecessary or outdated packages.
|
|
30
|
+
- Breaking changes when updating package versions.
|
|
31
|
+
|
|
32
|
+
<!-- lang: cs -->
|
|
33
|
+
## C#
|
|
34
|
+
- Naming Conventions & Casing: follow Microsoft's official C# naming guidelines (https://learn.microsoft.com/en-us/dotnet/csharp/fundamentals/coding-style/identifier-names).
|
|
35
|
+
- XML docs: follow Microsoft's official documentation guidance (https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/xmldoc/).
|
|
36
|
+
- Async: avoid `async void` (except handlers), use `ConfigureAwait(false)` in libraries, don't block with `.Result`/`.Wait()`.
|
|
37
|
+
- Nullable refs: correct `?` usage, avoid unjustified `!`.
|
|
38
|
+
- LINQ: avoid redundant iterations, N+1 queries with EF Core.
|
|
39
|
+
- Disposal: proper use of `IDisposable`/`using` for resources (streams, connections, etc).
|
|
40
|
+
- Immutability: prefer `readonly`, records, immutable types.
|
|
41
|
+
|
|
42
|
+
<!-- lang: cs,sql -->
|
|
43
|
+
## Logging & Observability
|
|
44
|
+
- Appropriate log levels (Debug vs Info vs Warning vs Error).
|
|
45
|
+
- Don't log sensitive data (PII, passwords, tokens).
|
|
46
|
+
|
|
47
|
+
<!-- lang: sql -->
|
|
48
|
+
## Migrations / Database (given your SQL/EF stack)
|
|
49
|
+
- Destructive changes to columns/tables with existing data (data loss risk).
|
|
50
|
+
- Migrations that lock large tables in production.
|
|
51
|
+
|
|
52
|
+
<!-- lang: sql -->
|
|
53
|
+
## SQL
|
|
54
|
+
- Avoid `SELECT *`; specify columns explicitly.
|
|
55
|
+
- Check for parameterized queries (avoid string concatenation → SQL injection).
|
|
56
|
+
- Indexes: flag queries likely to cause table scans on large tables.
|
|
57
|
+
- Transactions: ensure multi-step critical operations are wrapped in a transaction.
|
|
58
|
+
- Naming conventions for tables/columns consistent with the rest of the schema.
|
|
59
|
+
|
|
60
|
+
<!-- lang: xml -->
|
|
61
|
+
## XML
|
|
62
|
+
- Validation against schema (XSD) where applicable.
|
|
63
|
+
- Consistent formatting/indentation.
|
|
64
|
+
- Avoid duplicated configuration that could be centralized.
|
|
65
|
+
|
|
66
|
+
<!-- lang: html -->
|
|
67
|
+
## HTML
|
|
68
|
+
- A11y: `alt`, `aria-*`, labels on inputs, semantic tags (`<button>` vs `<div onclick>`).
|
|
69
|
+
- Semantic tags where they apply (`<button>`, `<label>`, `<ul>`/`<li>`) over generic `<div>`/`<span>`. Landmarks (`<header>`/`<nav>`/`<main>`) apply to shell/layout, not feature components.
|
|
70
|
+
- No inline `style=""` for static styling - use the component's `.less` file. `[style.x]`/`[ngStyle]` ok for dynamic values.
|
|
71
|
+
|
|
72
|
+
<!-- lang: ts,html -->
|
|
73
|
+
## TypeScript / Angular
|
|
74
|
+
- Follow the Angular style guide (https://angular.dev/style-guide).
|
|
75
|
+
- Avoid `any`, prefer explicit types.
|
|
76
|
+
- RxJS: unsubscribe via async pipe, `takeUntil`, or `takeUntilDestroyed`.
|
|
77
|
+
- Use `OnPush` where it makes sense.
|
|
78
|
+
- Separate presentation (component) from business logic (services); avoid heavy template logic.
|
|
79
|
+
- Files: kebab-case + type suffix (`select-maintenance-plan-step.component.ts/.html/.less`).
|
|
80
|
+
- Classes: PascalCase matching file name, no type suffix (`SelectMaintenancePlanStep`, not `...Component`).
|
|
81
|
+
- Selectors: kebab-case with project prefix (`app-select-maintenance-plan-step`).
|
|
82
|
+
- `.less` files named like their component; use `:host`, avoid global style leaks.
|
|
83
|
+
|
|
84
|
+
<!-- lang: all -->
|
|
85
|
+
## Review Process Notes (meta)
|
|
86
|
+
- Distinguish blocking comments (must-fix) from suggestions (nice-to-have) - this matters a lot for an automated reviewer, so it doesn't hold up merges over trivial issues.
|
|
87
|
+
- If the change is purely formatting/whitespace, do not comment on it.
|
|
88
|
+
- Ignore auto-generated files (generated migrations, `*.designer.cs`, `node_modules`, etc.) unless they contain manually-added code.
|
|
89
|
+
- Flag only issues relevant to the diff/change being reviewed, not the entire file, unless the change requires it.
|
|
90
|
+
- Before flagging something as "missing", verify it isn't present in the surrounding unchanged context (lines without `+`/`-`). Only flag genuinely absent elements, not elements outside the shown diff hunk.
|
|
91
|
+
- Priority order: Security > Functional bugs > Maintainability > Style.
|
|
@@ -247,6 +247,120 @@ def test_review_pr_dispatches_and_merges_custom_prompt(mocker, tmp_path: Path) -
|
|
|
247
247
|
assert "Custom user instructions" in system_prompt
|
|
248
248
|
assert "Custom context loaded from" in user_message
|
|
249
249
|
|
|
250
|
+
def test_review_omits_custom_instructions_header_when_filter_empties_prompt(mocker, tmp_path: Path) -> None:
|
|
251
|
+
"""If filtering removes all sections, the 'Custom user instructions' header should not appear."""
|
|
252
|
+
prompt_file = tmp_path / "prompt.md"
|
|
253
|
+
prompt_file.write_text(
|
|
254
|
+
"<!-- lang: java -->\n"
|
|
255
|
+
"## Java Rules\n"
|
|
256
|
+
"- Use streams.\n",
|
|
257
|
+
encoding="utf-8",
|
|
258
|
+
)
|
|
259
|
+
config = make_llm_config(custom_prompt_file=str(prompt_file))
|
|
260
|
+
client = LLMClient(config)
|
|
261
|
+
openai = mocker.patch("src.llm_client.LLMClient._call_openai", return_value='{"summary": "review text", "comments": []}')
|
|
262
|
+
|
|
263
|
+
client.review_pr("+code", [{"file": "component.ts", "additions": 1, "deletions": 0}])
|
|
264
|
+
|
|
265
|
+
system_prompt, user_message = openai.call_args.args[:2]
|
|
266
|
+
assert "Custom user instructions" not in system_prompt
|
|
267
|
+
assert "Custom context loaded from" not in user_message
|
|
268
|
+
|
|
269
|
+
def test_review_filters_custom_prompt_sections_by_changed_extensions(mocker, tmp_path: Path) -> None:
|
|
270
|
+
"""Only sections matching the extensions of changed files (plus 'all') should be kept."""
|
|
271
|
+
prompt_file = tmp_path / "prompt.md"
|
|
272
|
+
prompt_file.write_text(
|
|
273
|
+
"<!-- lang: all -->\n"
|
|
274
|
+
"## General\n"
|
|
275
|
+
"- Always check for secrets.\n"
|
|
276
|
+
"\n"
|
|
277
|
+
"<!-- lang: cs -->\n"
|
|
278
|
+
"## CSharp Rules\n"
|
|
279
|
+
"- Avoid magic strings.\n"
|
|
280
|
+
"\n"
|
|
281
|
+
"<!-- lang: py -->\n"
|
|
282
|
+
"## Python Rules\n"
|
|
283
|
+
"- Use type hints.\n",
|
|
284
|
+
encoding="utf-8",
|
|
285
|
+
)
|
|
286
|
+
config = make_llm_config(custom_prompt_file=str(prompt_file))
|
|
287
|
+
client = LLMClient(config)
|
|
288
|
+
openai = mocker.patch("src.llm_client.LLMClient._call_openai", return_value='{"summary": "review text", "comments": []}')
|
|
289
|
+
|
|
290
|
+
client.review_pr("+code", [{"file": "app.py", "additions": 1, "deletions": 0}])
|
|
291
|
+
|
|
292
|
+
system_prompt, _ = openai.call_args.args[:2]
|
|
293
|
+
assert "General" in system_prompt
|
|
294
|
+
assert "Python Rules" in system_prompt
|
|
295
|
+
assert "CSharp Rules" not in system_prompt
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
def test_review_keeps_only_all_sections_when_no_matching_extension(mocker, tmp_path: Path) -> None:
|
|
299
|
+
"""If no changed file matches a tagged section, only 'all' sections remain."""
|
|
300
|
+
prompt_file = tmp_path / "prompt.md"
|
|
301
|
+
prompt_file.write_text(
|
|
302
|
+
"<!-- lang: all -->\n"
|
|
303
|
+
"## General\n"
|
|
304
|
+
"- Always check for secrets.\n"
|
|
305
|
+
"\n"
|
|
306
|
+
"<!-- lang: java -->\n"
|
|
307
|
+
"## Java Rules\n"
|
|
308
|
+
"- Use streams.\n",
|
|
309
|
+
encoding="utf-8",
|
|
310
|
+
)
|
|
311
|
+
config = make_llm_config(custom_prompt_file=str(prompt_file))
|
|
312
|
+
client = LLMClient(config)
|
|
313
|
+
openai = mocker.patch("src.llm_client.LLMClient._call_openai", return_value='{"summary": "review text", "comments": []}')
|
|
314
|
+
|
|
315
|
+
client.review_pr("+code", [{"file": "component.ts", "additions": 1, "deletions": 0}])
|
|
316
|
+
|
|
317
|
+
system_prompt, _ = openai.call_args.args[:2]
|
|
318
|
+
assert "General" in system_prompt
|
|
319
|
+
assert "Java Rules" not in system_prompt
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
def test_review_includes_multiple_extension_sections(mocker, tmp_path: Path) -> None:
|
|
323
|
+
"""Changed files with different extensions should each pull in their matching section."""
|
|
324
|
+
prompt_file = tmp_path / "prompt.md"
|
|
325
|
+
prompt_file.write_text(
|
|
326
|
+
"<!-- lang: ts -->\n"
|
|
327
|
+
"## TypeScript Rules\n"
|
|
328
|
+
"- No any types.\n"
|
|
329
|
+
"\n"
|
|
330
|
+
"<!-- lang: html -->\n"
|
|
331
|
+
"## Html Rules\n"
|
|
332
|
+
"- No inline styles.\n",
|
|
333
|
+
encoding="utf-8",
|
|
334
|
+
)
|
|
335
|
+
config = make_llm_config(custom_prompt_file=str(prompt_file))
|
|
336
|
+
client = LLMClient(config)
|
|
337
|
+
openai = mocker.patch("src.llm_client.LLMClient._call_openai", return_value='{"summary": "review text", "comments": []}')
|
|
338
|
+
|
|
339
|
+
client.review_pr(
|
|
340
|
+
"+code",
|
|
341
|
+
[
|
|
342
|
+
{"file": "foo.component.ts", "additions": 1, "deletions": 0},
|
|
343
|
+
{"file": "foo.component.html", "additions": 1, "deletions": 0},
|
|
344
|
+
],
|
|
345
|
+
)
|
|
346
|
+
|
|
347
|
+
system_prompt, _ = openai.call_args.args[:2]
|
|
348
|
+
assert "TypeScript Rules" in system_prompt
|
|
349
|
+
assert "Html Rules" in system_prompt
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
def test_review_without_lang_tags_keeps_full_custom_prompt(mocker, tmp_path: Path) -> None:
|
|
353
|
+
"""Custom prompt with no <!-- lang: --> tags at all should pass through unchanged."""
|
|
354
|
+
prompt_file = tmp_path / "prompt.md"
|
|
355
|
+
prompt_file.write_text("Always mention tests", encoding="utf-8")
|
|
356
|
+
config = make_llm_config(custom_prompt_file=str(prompt_file))
|
|
357
|
+
client = LLMClient(config)
|
|
358
|
+
openai = mocker.patch("src.llm_client.LLMClient._call_openai", return_value='{"summary": "review text", "comments": []}')
|
|
359
|
+
|
|
360
|
+
client.review_pr("+code", [{"file": "a.py", "additions": 1, "deletions": 0}])
|
|
361
|
+
|
|
362
|
+
system_prompt, _ = openai.call_args.args[:2]
|
|
363
|
+
assert "Always mention tests" in system_prompt
|
|
250
364
|
|
|
251
365
|
def test_review_pr_raises_for_unsupported_provider() -> None:
|
|
252
366
|
"""It should reject unsupported providers before any HTTP call."""
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Tests for prompt_utils.py - language/extension based filtering of the custom review prompt.
|
|
3
|
+
"""
|
|
4
|
+
from src.prompt_utils import detect_langs, filter_prompt_by_langs
|
|
5
|
+
|
|
6
|
+
class TestDetectLangs:
|
|
7
|
+
|
|
8
|
+
def test_always_includes_all(self) -> None:
|
|
9
|
+
"""'all' must always be present, even with no files."""
|
|
10
|
+
assert detect_langs([]) == {"all"}
|
|
11
|
+
|
|
12
|
+
def test_detects_single_extension(self) -> None:
|
|
13
|
+
result = detect_langs(["src/foo.py"])
|
|
14
|
+
assert result == {"all", "py"}
|
|
15
|
+
|
|
16
|
+
def test_detects_multiple_distinct_extensions(self) -> None:
|
|
17
|
+
result = detect_langs(["a.ts", "b.html", "c.cs"])
|
|
18
|
+
assert result == {"all", "ts", "html", "cs"}
|
|
19
|
+
|
|
20
|
+
def test_deduplicates_same_extension(self) -> None:
|
|
21
|
+
result = detect_langs(["a.py", "b.py", "c.py"])
|
|
22
|
+
assert result == {"all", "py"}
|
|
23
|
+
|
|
24
|
+
def test_extension_lowercased(self) -> None:
|
|
25
|
+
result = detect_langs(["Component.TS"])
|
|
26
|
+
assert result == {"all", "ts"}
|
|
27
|
+
|
|
28
|
+
def test_ignores_none_or_empty_paths(self) -> None:
|
|
29
|
+
result = detect_langs([None, "", "a.py"])
|
|
30
|
+
assert result == {"all", "py"}
|
|
31
|
+
|
|
32
|
+
def test_file_without_extension_is_ignored(self) -> None:
|
|
33
|
+
result = detect_langs(["Dockerfile", "Makefile"])
|
|
34
|
+
assert result == {"all"}
|
|
35
|
+
|
|
36
|
+
def test_nested_path_extracts_extension_correctly(self) -> None:
|
|
37
|
+
result = detect_langs(["src/app/components/foo.component.ts"])
|
|
38
|
+
assert result == {"all", "ts"}
|
|
39
|
+
|
|
40
|
+
def test_dotfile_with_no_further_extension_is_ignored(self) -> None:
|
|
41
|
+
# os.path.splitext(".gitignore") -> ('.gitignore', '') -> no extension detected
|
|
42
|
+
result = detect_langs([".gitignore"])
|
|
43
|
+
assert result == {"all"}
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class TestFilterPromptByLangs:
|
|
47
|
+
|
|
48
|
+
def test_content_without_any_tag_is_kept_entirely(self) -> None:
|
|
49
|
+
content = "Always mention tests"
|
|
50
|
+
result = filter_prompt_by_langs(content, {"all"})
|
|
51
|
+
assert result == "Always mention tests"
|
|
52
|
+
|
|
53
|
+
def test_all_section_is_always_included(self) -> None:
|
|
54
|
+
content = (
|
|
55
|
+
"<!-- lang: all -->\n"
|
|
56
|
+
"## General\n"
|
|
57
|
+
"- Rule A\n"
|
|
58
|
+
)
|
|
59
|
+
result = filter_prompt_by_langs(content, {"all", "cs"})
|
|
60
|
+
assert "## General" in result
|
|
61
|
+
assert "Rule A" in result
|
|
62
|
+
|
|
63
|
+
def test_matching_lang_section_is_included(self) -> None:
|
|
64
|
+
content = (
|
|
65
|
+
"<!-- lang: cs,ts -->\n"
|
|
66
|
+
"## Dependencies\n"
|
|
67
|
+
"- Rule B\n"
|
|
68
|
+
)
|
|
69
|
+
result = filter_prompt_by_langs(content, {"all", "ts"})
|
|
70
|
+
assert "## Dependencies" in result
|
|
71
|
+
assert "Rule B" in result
|
|
72
|
+
|
|
73
|
+
def test_non_matching_lang_section_is_excluded(self) -> None:
|
|
74
|
+
content = (
|
|
75
|
+
"<!-- lang: java -->\n"
|
|
76
|
+
"## Java Rules\n"
|
|
77
|
+
"- Rule C\n"
|
|
78
|
+
)
|
|
79
|
+
result = filter_prompt_by_langs(content, {"all", "ts"})
|
|
80
|
+
assert "Java Rules" not in result
|
|
81
|
+
assert "Rule C" not in result
|
|
82
|
+
|
|
83
|
+
def test_mixed_sections_filters_correctly(self) -> None:
|
|
84
|
+
content = (
|
|
85
|
+
"<!-- lang: all -->\n"
|
|
86
|
+
"## General\n"
|
|
87
|
+
"- General rule\n"
|
|
88
|
+
"\n"
|
|
89
|
+
"<!-- lang: cs -->\n"
|
|
90
|
+
"## CSharp\n"
|
|
91
|
+
"- CSharp rule\n"
|
|
92
|
+
"\n"
|
|
93
|
+
"<!-- lang: html -->\n"
|
|
94
|
+
"## Html\n"
|
|
95
|
+
"- Html rule\n"
|
|
96
|
+
)
|
|
97
|
+
result = filter_prompt_by_langs(content, {"all", "html"})
|
|
98
|
+
|
|
99
|
+
assert "General rule" in result
|
|
100
|
+
assert "Html rule" in result
|
|
101
|
+
assert "CSharp rule" not in result
|
|
102
|
+
assert "CSharp" not in result
|
|
103
|
+
|
|
104
|
+
def test_tags_are_case_insensitive(self) -> None:
|
|
105
|
+
content = (
|
|
106
|
+
"<!-- lang: CS,TS -->\n"
|
|
107
|
+
"## Dependencies\n"
|
|
108
|
+
"- Rule\n"
|
|
109
|
+
)
|
|
110
|
+
result = filter_prompt_by_langs(content, {"all", "cs"})
|
|
111
|
+
assert "Rule" in result
|
|
112
|
+
|
|
113
|
+
def test_no_matching_section_returns_empty(self) -> None:
|
|
114
|
+
content = (
|
|
115
|
+
"<!-- lang: java -->\n"
|
|
116
|
+
"## Java Rules\n"
|
|
117
|
+
"- Rule\n"
|
|
118
|
+
)
|
|
119
|
+
result = filter_prompt_by_langs(content, {"all", "ts"})
|
|
120
|
+
assert result == ""
|
|
121
|
+
|
|
122
|
+
def test_tag_with_extra_whitespace_is_parsed(self) -> None:
|
|
123
|
+
content = (
|
|
124
|
+
"<!-- lang: cs , ts -->\n"
|
|
125
|
+
"## Dependencies\n"
|
|
126
|
+
"- Rule\n"
|
|
127
|
+
)
|
|
128
|
+
result = filter_prompt_by_langs(content, {"all", "ts"})
|
|
129
|
+
assert "Rule" in result
|
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
- Naming Conventions & Casing, follow Microsoft's official C# naming guidelines (https://learn.microsoft.com/en-us/dotnet/csharp/fundamentals/coding-style/identifier-names).
|
|
2
|
-
- XML Documentation Comments, follow Microsoft's official code documentation (https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/xmldoc/).
|
|
File without changes
|
{code_review_ai_cli-1.3.5 → code_review_ai_cli-2.0.0}/code_review_ai_cli.egg-info/entry_points.txt
RENAMED
|
File without changes
|
{code_review_ai_cli-1.3.5 → code_review_ai_cli-2.0.0}/code_review_ai_cli.egg-info/requires.txt
RENAMED
|
File without changes
|
{code_review_ai_cli-1.3.5 → code_review_ai_cli-2.0.0}/code_review_ai_cli.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|