code-review-ai-cli 2.0.0__tar.gz → 2.0.1__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-2.0.0 → code_review_ai_cli-2.0.1}/PKG-INFO +1 -1
- {code_review_ai_cli-2.0.0 → code_review_ai_cli-2.0.1}/code_review_ai_cli.egg-info/PKG-INFO +1 -1
- {code_review_ai_cli-2.0.0 → code_review_ai_cli-2.0.1}/pyproject.toml +1 -1
- {code_review_ai_cli-2.0.0 → code_review_ai_cli-2.0.1}/src/ai_review.py +4 -10
- {code_review_ai_cli-2.0.0 → code_review_ai_cli-2.0.1}/src/config.py +3 -3
- {code_review_ai_cli-2.0.0 → code_review_ai_cli-2.0.1}/src/git_utils.py +15 -9
- {code_review_ai_cli-2.0.0 → code_review_ai_cli-2.0.1}/src/llm_client.py +22 -24
- {code_review_ai_cli-2.0.0 → code_review_ai_cli-2.0.1}/src/prompts/config.yaml.template +1 -1
- {code_review_ai_cli-2.0.0 → code_review_ai_cli-2.0.1}/src/tfs_client.py +4 -1
- {code_review_ai_cli-2.0.0 → code_review_ai_cli-2.0.1}/tests/test_ai_review.py +3 -3
- {code_review_ai_cli-2.0.0 → code_review_ai_cli-2.0.1}/tests/test_config.py +2 -2
- {code_review_ai_cli-2.0.0 → code_review_ai_cli-2.0.1}/tests/test_git_utils.py +10 -10
- {code_review_ai_cli-2.0.0 → code_review_ai_cli-2.0.1}/tests/test_llm_client.py +5 -5
- {code_review_ai_cli-2.0.0 → code_review_ai_cli-2.0.1}/tests/test_tfs_client.py +6 -2
- {code_review_ai_cli-2.0.0 → code_review_ai_cli-2.0.1}/README.md +0 -0
- {code_review_ai_cli-2.0.0 → code_review_ai_cli-2.0.1}/code_review_ai_cli.egg-info/SOURCES.txt +0 -0
- {code_review_ai_cli-2.0.0 → code_review_ai_cli-2.0.1}/code_review_ai_cli.egg-info/dependency_links.txt +0 -0
- {code_review_ai_cli-2.0.0 → code_review_ai_cli-2.0.1}/code_review_ai_cli.egg-info/entry_points.txt +0 -0
- {code_review_ai_cli-2.0.0 → code_review_ai_cli-2.0.1}/code_review_ai_cli.egg-info/requires.txt +0 -0
- {code_review_ai_cli-2.0.0 → code_review_ai_cli-2.0.1}/code_review_ai_cli.egg-info/top_level.txt +0 -0
- {code_review_ai_cli-2.0.0 → code_review_ai_cli-2.0.1}/setup.cfg +0 -0
- {code_review_ai_cli-2.0.0 → code_review_ai_cli-2.0.1}/src/__init__.py +0 -0
- {code_review_ai_cli-2.0.0 → code_review_ai_cli-2.0.1}/src/formatter.py +0 -0
- {code_review_ai_cli-2.0.0 → code_review_ai_cli-2.0.1}/src/prompt_utils.py +0 -0
- {code_review_ai_cli-2.0.0 → code_review_ai_cli-2.0.1}/src/prompts/review_prompt.md.template +0 -0
- {code_review_ai_cli-2.0.0 → code_review_ai_cli-2.0.1}/tests/test_formatter.py +0 -0
- {code_review_ai_cli-2.0.0 → code_review_ai_cli-2.0.1}/tests/test_prompt_utils.py +0 -0
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "code-review-ai-cli"
|
|
7
|
-
version = "2.0.
|
|
7
|
+
version = "2.0.1"
|
|
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"
|
|
@@ -13,7 +13,6 @@ Usage:
|
|
|
13
13
|
python ai_review.py list-prs # List active Pull Requests
|
|
14
14
|
|
|
15
15
|
Options:
|
|
16
|
-
--quick # Quick and concise review
|
|
17
16
|
--detailed # Detailed review (default)
|
|
18
17
|
--security # Security-focused review
|
|
19
18
|
--review-scope <diff_only|full_code> # Review scope (default: diff_only)
|
|
@@ -216,10 +215,6 @@ def _add_global_options(parser: argparse.ArgumentParser) -> None:
|
|
|
216
215
|
"""Adds global options to a subparser."""
|
|
217
216
|
|
|
218
217
|
group_review = parser.add_argument_group("Review Options")
|
|
219
|
-
group_review.add_argument(
|
|
220
|
-
"--quick", "-q", action="store_const", dest="verbosity",
|
|
221
|
-
const="quick", help="Quick and concise review"
|
|
222
|
-
)
|
|
223
218
|
group_review.add_argument(
|
|
224
219
|
"--detailed", "-d", action="store_const", dest="verbosity",
|
|
225
220
|
const="detailed", help="Detailed review (default)"
|
|
@@ -834,17 +829,16 @@ def _ask_verbosity() -> str:
|
|
|
834
829
|
"""Asks for the verbosity mode."""
|
|
835
830
|
c = Colors
|
|
836
831
|
print(f"\n{c.BOLD}Review mode:{c.RESET}")
|
|
837
|
-
print(f" {c.CYAN}1{c.RESET})
|
|
838
|
-
print(f" {c.CYAN}2{c.RESET})
|
|
839
|
-
print(f" {c.CYAN}3{c.RESET}) Security")
|
|
832
|
+
print(f" {c.CYAN}1{c.RESET}) Detailed")
|
|
833
|
+
print(f" {c.CYAN}2{c.RESET}) Security")
|
|
840
834
|
|
|
841
835
|
try:
|
|
842
|
-
mode_choice = input(f"{c.BOLD}Choose [1-
|
|
836
|
+
mode_choice = input(f"{c.BOLD}Choose [1-2, default=1]: {c.RESET}").strip()
|
|
843
837
|
except (KeyboardInterrupt, EOFError):
|
|
844
838
|
print("\n")
|
|
845
839
|
return "detailed"
|
|
846
840
|
|
|
847
|
-
verbosity_map = {"1": "
|
|
841
|
+
verbosity_map = {"1": "detailed", "2": "security"}
|
|
848
842
|
return verbosity_map.get(mode_choice, "detailed")
|
|
849
843
|
|
|
850
844
|
|
|
@@ -85,7 +85,7 @@ class ReviewConfig:
|
|
|
85
85
|
tfs_repository: str = "" # Default repository (empty = all)
|
|
86
86
|
|
|
87
87
|
# --- Review -------------------------------------------------------
|
|
88
|
-
verbosity: str = "detailed" # "
|
|
88
|
+
verbosity: str = "detailed" # "detailed" | "security"
|
|
89
89
|
review_scope: str = "diff_only" # "diff_only" | "full_code"
|
|
90
90
|
max_diff_files: int = 50 # Max diff files sent to LLM
|
|
91
91
|
max_diff_lines: int = 2000 # Max diff lines
|
|
@@ -302,10 +302,10 @@ class ReviewConfig:
|
|
|
302
302
|
" Configure bedrock.access_key_id when secret_access_key is set."
|
|
303
303
|
)
|
|
304
304
|
|
|
305
|
-
if self.verbosity not in ("
|
|
305
|
+
if self.verbosity not in ("detailed", "security"):
|
|
306
306
|
issues.append(
|
|
307
307
|
f"Invalid verbosity: '{self.verbosity}'. "
|
|
308
|
-
"Use '
|
|
308
|
+
"Use 'detailed' or 'security'."
|
|
309
309
|
)
|
|
310
310
|
|
|
311
311
|
if self.review_scope not in ("diff_only", "full_code"):
|
|
@@ -284,32 +284,37 @@ class GitUtils:
|
|
|
284
284
|
# ------------------------------------------------------------------
|
|
285
285
|
def filter_diff_additions_only(self, diff: str) -> str:
|
|
286
286
|
"""
|
|
287
|
-
Removes
|
|
288
|
-
|
|
287
|
+
Removes deleted lines (-) from the diff, keeping context lines and added lines.
|
|
288
|
+
Structural headers are always preserved.
|
|
289
289
|
|
|
290
290
|
Lines kept:
|
|
291
291
|
- ``diff --git ...``
|
|
292
292
|
- ``--- a/...``
|
|
293
293
|
- ``+++ b/...``
|
|
294
294
|
- ``@@ ... @@``
|
|
295
|
-
- ``+ <content>``
|
|
295
|
+
- ``+ <content>`` (added lines)
|
|
296
|
+
- `` <content>`` (context lines — unchanged surrounding code)
|
|
297
|
+
|
|
298
|
+
Lines removed:
|
|
299
|
+
- ``- <content>`` (deleted lines)
|
|
300
|
+
- ``\\ No newline ...`` markers
|
|
296
301
|
|
|
297
302
|
FULL_FILE_CONTEXT blocks — delimited by
|
|
298
303
|
``### FULL_FILE_CONTEXT_START: <path> ###`` and
|
|
299
304
|
``### FULL_FILE_CONTEXT_END ###`` — are preserved in their entirety.
|
|
300
305
|
These blocks are appended by
|
|
301
306
|
:py:meth:`TFSClient._build_unified_diff_part` and carry the complete
|
|
302
|
-
new-version file content as read-only background
|
|
303
|
-
line between the sentinel markers is kept,
|
|
304
|
-
starts with ``+``, ``-``, or a space.
|
|
307
|
+
new-version file content (with line numbers) as read-only background
|
|
308
|
+
for the LLM. Every line between the sentinel markers is kept,
|
|
309
|
+
regardless of whether it starts with ``+``, ``-``, or a space.
|
|
305
310
|
|
|
306
311
|
Args:
|
|
307
312
|
diff: Raw unified diff string, possibly containing
|
|
308
313
|
FULL_FILE_CONTEXT blocks.
|
|
309
314
|
|
|
310
315
|
Returns:
|
|
311
|
-
Filtered diff string with
|
|
312
|
-
FULL_FILE_CONTEXT blocks intact.
|
|
316
|
+
Filtered diff string with deleted lines stripped but context lines
|
|
317
|
+
and FULL_FILE_CONTEXT blocks intact.
|
|
313
318
|
"""
|
|
314
319
|
result = []
|
|
315
320
|
in_context_block = False
|
|
@@ -331,9 +336,10 @@ class GitUtils:
|
|
|
331
336
|
or line.startswith("+++ ")
|
|
332
337
|
or line.startswith("@@")
|
|
333
338
|
or (line.startswith("+") and not line.startswith("+++"))
|
|
339
|
+
or line.startswith(" ") # context lines — unchanged surrounding code
|
|
334
340
|
):
|
|
335
341
|
result.append(line)
|
|
336
|
-
#
|
|
342
|
+
# Deleted lines (-) and '\\ No newline' markers are discarded.
|
|
337
343
|
return "\n".join(result)
|
|
338
344
|
|
|
339
345
|
def _split_diff_sections(self, diff: str) -> tuple[list[list[str]], bool]:
|
|
@@ -27,7 +27,7 @@ class LLMError(Exception):
|
|
|
27
27
|
# ---------------------------------------------------------------------------
|
|
28
28
|
# System Prompts
|
|
29
29
|
# ---------------------------------------------------------------------------
|
|
30
|
-
# Maps each verbosity level ("
|
|
30
|
+
# Maps each verbosity level ("detailed", "security") to its
|
|
31
31
|
# English-only system prompt.
|
|
32
32
|
#
|
|
33
33
|
# Type:
|
|
@@ -38,14 +38,6 @@ class LLMError(Exception):
|
|
|
38
38
|
# get_system_prompt: Resolves a prompt for a given verbosity, with a
|
|
39
39
|
# fallback to "detailed" when the key is not recognised.
|
|
40
40
|
SYSTEM_PROMPTS = {
|
|
41
|
-
"quick": (
|
|
42
|
-
"You are an experienced Senior Code Reviewer. Analyze the provided code diff "
|
|
43
|
-
"and give a CONCISE review. Focus on critical issues:\n"
|
|
44
|
-
"- Bugs and logic errors\n"
|
|
45
|
-
"- Security issues\n"
|
|
46
|
-
"- Major performance problems\n\n"
|
|
47
|
-
"Format: Bullet points with file and line when possible. "
|
|
48
|
-
),
|
|
49
41
|
"detailed": (
|
|
50
42
|
"You are an experienced Senior Code Reviewer. Analyze the provided code and return only inline comments.\n\n"
|
|
51
43
|
"Output format — for each issue found, output exactly:\n"
|
|
@@ -134,7 +126,7 @@ def get_system_prompt(verbosity: str) -> str:
|
|
|
134
126
|
When no issues are found the expected output is ``No issues found.``
|
|
135
127
|
|
|
136
128
|
Args:
|
|
137
|
-
verbosity: Review depth key — one of ``"
|
|
129
|
+
verbosity: Review depth key — one of ``"detailed"``
|
|
138
130
|
or ``"security"``. Any unrecognised value falls back to
|
|
139
131
|
``"detailed"``.
|
|
140
132
|
|
|
@@ -149,14 +141,15 @@ def get_scope_guidance(review_scope: str, structured: bool = False) -> str:
|
|
|
149
141
|
|
|
150
142
|
For ``diff_only`` scope, the instructions inform the LLM that:
|
|
151
143
|
|
|
152
|
-
*
|
|
144
|
+
* Added lines are prefixed with ``+`` and context lines (unchanged code)
|
|
145
|
+
have no prefix. Deleted lines are not included.
|
|
153
146
|
* A ``### FULL_FILE_CONTEXT_START: <path> ###`` /
|
|
154
147
|
``### FULL_FILE_CONTEXT_END ###`` block is embedded in the payload for
|
|
155
148
|
each changed file, containing the complete new-version (after changes)
|
|
156
|
-
file content as **read-only** background.
|
|
149
|
+
file content with **1-based line numbers** as **read-only** background.
|
|
157
150
|
* The review must focus **exclusively** on the changed lines (``+``); the
|
|
158
|
-
full-file section exists only to
|
|
159
|
-
|
|
151
|
+
full-file section exists only to provide a global view and authoritative
|
|
152
|
+
line numbers for the LLM.
|
|
160
153
|
|
|
161
154
|
For ``full_code`` scope, the instructions inform the LLM that the diff
|
|
162
155
|
represents the entire new file content (every line prefixed with ``+``)
|
|
@@ -182,21 +175,26 @@ def get_scope_guidance(review_scope: str, structured: bool = False) -> str:
|
|
|
182
175
|
|
|
183
176
|
if structured:
|
|
184
177
|
return (
|
|
185
|
-
"Review scope: diff_only. The diff contains
|
|
186
|
-
"
|
|
187
|
-
"is provided for each file
|
|
188
|
-
"
|
|
189
|
-
"
|
|
178
|
+
"Review scope: diff_only. The diff contains added lines (marked +) and surrounding "
|
|
179
|
+
"context lines (unchanged code, no prefix). Deleted lines were removed. "
|
|
180
|
+
"The complete new-version file content with 1-based line numbers is provided for each file "
|
|
181
|
+
"between ### FULL_FILE_CONTEXT_START and ### FULL_FILE_CONTEXT_END markers as read-only background. "
|
|
182
|
+
"Use the line numbers in FULL_FILE_CONTEXT as the authoritative reference when reporting issues. "
|
|
183
|
+
"Focus your review EXCLUSIVELY on the changed lines (marked + in the diff). "
|
|
184
|
+
"Do NOT report issues in context or unchanged lines unless they directly affect the correctness of the changes. "
|
|
190
185
|
"For every problem, you MUST provide a valid file and line (>0) to allow inline comments. "
|
|
191
186
|
"Do not emit general problem comments without file/line."
|
|
192
187
|
)
|
|
193
188
|
|
|
194
189
|
return (
|
|
195
|
-
"Review scope: diff_only. The diff contains
|
|
196
|
-
"
|
|
197
|
-
"
|
|
198
|
-
"
|
|
199
|
-
"
|
|
190
|
+
"Review scope: diff_only. The diff contains added lines (marked +) and surrounding "
|
|
191
|
+
"context lines (unchanged code, no prefix). Deleted lines were removed. "
|
|
192
|
+
"The complete new-version file content with 1-based line numbers "
|
|
193
|
+
"(between ### FULL_FILE_CONTEXT_START and ### FULL_FILE_CONTEXT_END markers) "
|
|
194
|
+
"is provided for each file as read-only background. "
|
|
195
|
+
"Use the line numbers in FULL_FILE_CONTEXT as the authoritative reference when reporting issues. "
|
|
196
|
+
"Focus your review EXCLUSIVELY on the changed lines. "
|
|
197
|
+
"Do NOT report issues in context or unchanged lines unless they directly affect the correctness of the changes."
|
|
200
198
|
)
|
|
201
199
|
|
|
202
200
|
|
|
@@ -72,7 +72,7 @@ tfs:
|
|
|
72
72
|
# Review Preferences
|
|
73
73
|
# ---------------------------------------------------------------------------
|
|
74
74
|
review:
|
|
75
|
-
# Default verbosity: "
|
|
75
|
+
# Default verbosity: "detailed" or "security"
|
|
76
76
|
verbosity: detailed
|
|
77
77
|
|
|
78
78
|
# Review scope:
|
|
@@ -526,10 +526,13 @@ class TFSClient:
|
|
|
526
526
|
# complete resulting file so it can correctly understand line numbers
|
|
527
527
|
# and surrounding code for the added lines, avoiding confusion from
|
|
528
528
|
# showing stale pre-change content.
|
|
529
|
+
# Lines are numbered (1-based) so the LLM can use them as the
|
|
530
|
+
# authoritative reference when reporting issue positions.
|
|
529
531
|
# delete is excluded automatically because new_lines is empty for deleted files.
|
|
530
532
|
if new_lines:
|
|
531
533
|
result.append(f"### FULL_FILE_CONTEXT_START: {file_path} ###")
|
|
532
|
-
|
|
534
|
+
for i, line in enumerate(new_lines, start=1):
|
|
535
|
+
result.append(f"{i:4d}: {line}")
|
|
533
536
|
result.append("### FULL_FILE_CONTEXT_END ###")
|
|
534
537
|
|
|
535
538
|
return result
|
|
@@ -88,7 +88,7 @@ def test_build_parser_parses_pr_review_options() -> None:
|
|
|
88
88
|
"pr-review",
|
|
89
89
|
"42",
|
|
90
90
|
"--dry-run",
|
|
91
|
-
"--
|
|
91
|
+
"--security",
|
|
92
92
|
"--review-scope",
|
|
93
93
|
"full_code",
|
|
94
94
|
"--max-diff-files",
|
|
@@ -100,7 +100,7 @@ def test_build_parser_parses_pr_review_options() -> None:
|
|
|
100
100
|
assert args.command == "pr-review"
|
|
101
101
|
assert args.pr_id == 42
|
|
102
102
|
assert args.dry_run is True
|
|
103
|
-
assert args.verbosity == "
|
|
103
|
+
assert args.verbosity == "security"
|
|
104
104
|
assert args.review_scope == "full_code"
|
|
105
105
|
assert args.max_diff_files == 3
|
|
106
106
|
assert args.output == "review.md"
|
|
@@ -333,7 +333,7 @@ def test_select_comments_to_post_respects_individual_answers(mocker) -> None:
|
|
|
333
333
|
|
|
334
334
|
@pytest.mark.parametrize(
|
|
335
335
|
("choice", "expected"),
|
|
336
|
-
[("1", "
|
|
336
|
+
[("1", "detailed"), ("2", "security"), ("", "detailed"), ("99", "detailed")],
|
|
337
337
|
)
|
|
338
338
|
def test_ask_verbosity(choice: str, expected: str, mocker) -> None:
|
|
339
339
|
"""It should map menu choices to supported verbosity values."""
|
|
@@ -179,7 +179,7 @@ tfs:
|
|
|
179
179
|
ca_bundle: ~/ca.pem
|
|
180
180
|
repository: repo-a
|
|
181
181
|
review:
|
|
182
|
-
verbosity:
|
|
182
|
+
verbosity: security
|
|
183
183
|
scope: full_code
|
|
184
184
|
max_diff_files: 12
|
|
185
185
|
max_diff_lines: 456
|
|
@@ -222,7 +222,7 @@ output:
|
|
|
222
222
|
assert config.tfs_verify_ssl is False
|
|
223
223
|
assert config.tfs_ca_bundle == "~/ca.pem"
|
|
224
224
|
assert config.tfs_repository == "repo-a"
|
|
225
|
-
assert config.verbosity == "
|
|
225
|
+
assert config.verbosity == "security"
|
|
226
226
|
assert config.review_scope == "full_code"
|
|
227
227
|
assert config.max_diff_files == 12
|
|
228
228
|
assert config.max_diff_lines == 456
|
|
@@ -249,7 +249,7 @@ def test_filter_diff_additions_only_preserves_full_file_context_block() -> None:
|
|
|
249
249
|
"--- a/src/app.py",
|
|
250
250
|
"+++ b/src/app.py",
|
|
251
251
|
"@@ -1,2 +1,2 @@",
|
|
252
|
-
" this_context_line_outside", # context — must be
|
|
252
|
+
" this_context_line_outside", # context — must now be KEPT
|
|
253
253
|
"-deleted_line_outside", # deletion — must be stripped
|
|
254
254
|
"+added_line_outside", # addition — must be kept
|
|
255
255
|
"### FULL_FILE_CONTEXT_START: /src/app.py ###",
|
|
@@ -262,8 +262,8 @@ def test_filter_diff_additions_only_preserves_full_file_context_block() -> None:
|
|
|
262
262
|
|
|
263
263
|
filtered = instance.filter_diff_additions_only(diff)
|
|
264
264
|
|
|
265
|
-
assert "this_context_line_outside"
|
|
266
|
-
assert "deleted_line_outside" not in filtered
|
|
265
|
+
assert "this_context_line_outside" in filtered # context lines are now kept
|
|
266
|
+
assert "deleted_line_outside" not in filtered # deletions are still stripped
|
|
267
267
|
assert "+added_line_outside" in filtered
|
|
268
268
|
assert "### FULL_FILE_CONTEXT_START: /src/app.py ###" in filtered
|
|
269
269
|
assert "raw_context_inside" in filtered
|
|
@@ -297,8 +297,8 @@ def test_filter_diff_additions_only_handles_multiple_full_file_context_blocks()
|
|
|
297
297
|
assert "+line_b" in filtered
|
|
298
298
|
|
|
299
299
|
|
|
300
|
-
def
|
|
301
|
-
"""Without FULL_FILE_CONTEXT blocks the method must
|
|
300
|
+
def test_filter_diff_additions_only_without_context_block_keeps_context_strips_deletions() -> None:
|
|
301
|
+
"""Without FULL_FILE_CONTEXT blocks the method must keep context lines and strip deletion lines."""
|
|
302
302
|
instance = make_git_utils()
|
|
303
303
|
diff = "\n".join([
|
|
304
304
|
"diff --git a/src/app.py b/src/app.py",
|
|
@@ -310,18 +310,18 @@ def test_filter_diff_additions_only_without_context_block_strips_context_and_del
|
|
|
310
310
|
filtered = instance.filter_diff_additions_only(diff)
|
|
311
311
|
|
|
312
312
|
assert "+added_line" in filtered
|
|
313
|
-
assert "context_line"
|
|
314
|
-
assert "removed_line" not in filtered
|
|
313
|
+
assert "context_line" in filtered # context lines are now kept
|
|
314
|
+
assert "removed_line" not in filtered # deletions are still stripped
|
|
315
315
|
|
|
316
316
|
|
|
317
317
|
def test_filter_diff_additions_only_context_block_ends_before_next_diff_section() -> None:
|
|
318
|
-
"""The section after ### FULL_FILE_CONTEXT_END ### must revert to normal filtering."""
|
|
318
|
+
"""The section after ### FULL_FILE_CONTEXT_END ### must revert to normal filtering (keep context, strip deletions)."""
|
|
319
319
|
instance = make_git_utils()
|
|
320
320
|
diff = "\n".join([
|
|
321
321
|
"### FULL_FILE_CONTEXT_START: /src/a.py ###",
|
|
322
322
|
"inside_block",
|
|
323
323
|
"### FULL_FILE_CONTEXT_END ###",
|
|
324
|
-
" outside_context_line", # context — must be
|
|
324
|
+
" outside_context_line", # context — must be KEPT after block ends
|
|
325
325
|
"-outside_deleted", # deletion — must be stripped after block ends
|
|
326
326
|
"+outside_added", # addition — must be kept
|
|
327
327
|
])
|
|
@@ -329,6 +329,6 @@ def test_filter_diff_additions_only_context_block_ends_before_next_diff_section(
|
|
|
329
329
|
filtered = instance.filter_diff_additions_only(diff)
|
|
330
330
|
|
|
331
331
|
assert "inside_block" in filtered
|
|
332
|
-
assert "outside_context_line"
|
|
332
|
+
assert "outside_context_line" in filtered # context is now kept
|
|
333
333
|
assert "outside_deleted" not in filtered
|
|
334
334
|
assert "+outside_added" in filtered
|
|
@@ -89,7 +89,7 @@ def make_llm_config(**changes: object) -> ReviewConfig:
|
|
|
89
89
|
|
|
90
90
|
def test_prompt_helpers_select_expected_language_and_scope() -> None:
|
|
91
91
|
"""It should select prompts and scope guidance consistently."""
|
|
92
|
-
assert "code reviewer" in get_system_prompt("
|
|
92
|
+
assert "code reviewer" in get_system_prompt("detailed").lower()
|
|
93
93
|
assert "JSON" in PR_COMMENT_PROMPT
|
|
94
94
|
assert "full_code" in get_scope_guidance("full_code")
|
|
95
95
|
assert "file and line" in get_scope_guidance("diff_only", structured=True).lower()
|
|
@@ -104,7 +104,7 @@ def test_get_pr_comment_prompt_function_removed() -> None:
|
|
|
104
104
|
def test_get_system_prompt_and_get_scope_guidance_reject_language_kwarg() -> None:
|
|
105
105
|
"""Regression: language parameter was removed from both prompt helpers."""
|
|
106
106
|
with pytest.raises(TypeError):
|
|
107
|
-
get_system_prompt("
|
|
107
|
+
get_system_prompt("detailed", language="pt") # type: ignore[call-arg]
|
|
108
108
|
|
|
109
109
|
with pytest.raises(TypeError):
|
|
110
110
|
get_scope_guidance("diff_only", structured=False, language="pt") # type: ignore[call-arg]
|
|
@@ -115,9 +115,9 @@ def test_get_system_prompt_and_get_scope_guidance_reject_language_kwarg() -> Non
|
|
|
115
115
|
# SYSTEM_PROMPTS / get_system_prompt — Phase 1 change validation
|
|
116
116
|
# ---------------------------------------------------------------------------
|
|
117
117
|
|
|
118
|
-
def
|
|
119
|
-
"""
|
|
120
|
-
prompt = get_system_prompt("
|
|
118
|
+
def test_get_system_prompt_detailed_returns_non_empty_string() -> None:
|
|
119
|
+
"""detailed prompt must return a non-empty string."""
|
|
120
|
+
prompt = get_system_prompt("detailed")
|
|
121
121
|
assert isinstance(prompt, str)
|
|
122
122
|
assert len(prompt.strip()) > 0
|
|
123
123
|
|
|
@@ -354,7 +354,7 @@ def test_build_diff_parts_and_file_content(mocker) -> None:
|
|
|
354
354
|
# ---------------------------------------------------------------------------
|
|
355
355
|
|
|
356
356
|
def test_build_unified_diff_part_appends_full_file_context_block(mocker) -> None:
|
|
357
|
-
"""_build_unified_diff_part must append FULL_FILE_CONTEXT markers with
|
|
357
|
+
"""_build_unified_diff_part must append FULL_FILE_CONTEXT markers with numbered new-version file content."""
|
|
358
358
|
client = TFSClient(make_tfs_config())
|
|
359
359
|
mocker.patch(
|
|
360
360
|
"src.tfs_client.TFSClient._get_file_content",
|
|
@@ -373,6 +373,9 @@ def test_build_unified_diff_part_appends_full_file_context_block(mocker) -> None
|
|
|
373
373
|
assert "new line two" in context_block
|
|
374
374
|
assert "old line" not in context_block
|
|
375
375
|
assert "### FULL_FILE_CONTEXT_END ###" in joined
|
|
376
|
+
# Lines must be prefixed with 1-based line numbers
|
|
377
|
+
assert " 1: new line one" in context_block
|
|
378
|
+
assert " 2: new line two" in context_block
|
|
376
379
|
# Context block must appear after the diff headers
|
|
377
380
|
assert joined.index("### FULL_FILE_CONTEXT_START") > joined.index("diff --git")
|
|
378
381
|
|
|
@@ -416,7 +419,7 @@ def test_build_unified_diff_part_no_context_block_on_delete(mocker) -> None:
|
|
|
416
419
|
|
|
417
420
|
def test_build_unified_diff_part_context_block_uses_new_file_path(mocker) -> None:
|
|
418
421
|
"""The FULL_FILE_CONTEXT_START marker must reference the new file path (file_path), not original_path,
|
|
419
|
-
and the block content must be the new (renamed) file's content."""
|
|
422
|
+
and the block content must be the new (renamed) file's content with 1-based line numbers."""
|
|
420
423
|
client = TFSClient(make_tfs_config())
|
|
421
424
|
mocker.patch(
|
|
422
425
|
"src.tfs_client.TFSClient._get_file_content",
|
|
@@ -434,6 +437,7 @@ def test_build_unified_diff_part_context_block_uses_new_file_path(mocker) -> Non
|
|
|
434
437
|
assert "### FULL_FILE_CONTEXT_START: /src/original.py ###" not in joined
|
|
435
438
|
assert "new content" in context_block
|
|
436
439
|
assert "old content" not in context_block
|
|
440
|
+
assert " 1: new content" in context_block
|
|
437
441
|
|
|
438
442
|
|
|
439
443
|
def test_build_unified_diff_part_no_context_block_when_content_unavailable(mocker) -> None:
|
|
File without changes
|
{code_review_ai_cli-2.0.0 → code_review_ai_cli-2.0.1}/code_review_ai_cli.egg-info/SOURCES.txt
RENAMED
|
File without changes
|
|
File without changes
|
{code_review_ai_cli-2.0.0 → code_review_ai_cli-2.0.1}/code_review_ai_cli.egg-info/entry_points.txt
RENAMED
|
File without changes
|
{code_review_ai_cli-2.0.0 → code_review_ai_cli-2.0.1}/code_review_ai_cli.egg-info/requires.txt
RENAMED
|
File without changes
|
{code_review_ai_cli-2.0.0 → code_review_ai_cli-2.0.1}/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
|