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.
Files changed (27) hide show
  1. {code_review_ai_cli-2.0.0 → code_review_ai_cli-2.0.1}/PKG-INFO +1 -1
  2. {code_review_ai_cli-2.0.0 → code_review_ai_cli-2.0.1}/code_review_ai_cli.egg-info/PKG-INFO +1 -1
  3. {code_review_ai_cli-2.0.0 → code_review_ai_cli-2.0.1}/pyproject.toml +1 -1
  4. {code_review_ai_cli-2.0.0 → code_review_ai_cli-2.0.1}/src/ai_review.py +4 -10
  5. {code_review_ai_cli-2.0.0 → code_review_ai_cli-2.0.1}/src/config.py +3 -3
  6. {code_review_ai_cli-2.0.0 → code_review_ai_cli-2.0.1}/src/git_utils.py +15 -9
  7. {code_review_ai_cli-2.0.0 → code_review_ai_cli-2.0.1}/src/llm_client.py +22 -24
  8. {code_review_ai_cli-2.0.0 → code_review_ai_cli-2.0.1}/src/prompts/config.yaml.template +1 -1
  9. {code_review_ai_cli-2.0.0 → code_review_ai_cli-2.0.1}/src/tfs_client.py +4 -1
  10. {code_review_ai_cli-2.0.0 → code_review_ai_cli-2.0.1}/tests/test_ai_review.py +3 -3
  11. {code_review_ai_cli-2.0.0 → code_review_ai_cli-2.0.1}/tests/test_config.py +2 -2
  12. {code_review_ai_cli-2.0.0 → code_review_ai_cli-2.0.1}/tests/test_git_utils.py +10 -10
  13. {code_review_ai_cli-2.0.0 → code_review_ai_cli-2.0.1}/tests/test_llm_client.py +5 -5
  14. {code_review_ai_cli-2.0.0 → code_review_ai_cli-2.0.1}/tests/test_tfs_client.py +6 -2
  15. {code_review_ai_cli-2.0.0 → code_review_ai_cli-2.0.1}/README.md +0 -0
  16. {code_review_ai_cli-2.0.0 → code_review_ai_cli-2.0.1}/code_review_ai_cli.egg-info/SOURCES.txt +0 -0
  17. {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
  18. {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
  19. {code_review_ai_cli-2.0.0 → code_review_ai_cli-2.0.1}/code_review_ai_cli.egg-info/requires.txt +0 -0
  20. {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
  21. {code_review_ai_cli-2.0.0 → code_review_ai_cli-2.0.1}/setup.cfg +0 -0
  22. {code_review_ai_cli-2.0.0 → code_review_ai_cli-2.0.1}/src/__init__.py +0 -0
  23. {code_review_ai_cli-2.0.0 → code_review_ai_cli-2.0.1}/src/formatter.py +0 -0
  24. {code_review_ai_cli-2.0.0 → code_review_ai_cli-2.0.1}/src/prompt_utils.py +0 -0
  25. {code_review_ai_cli-2.0.0 → code_review_ai_cli-2.0.1}/src/prompts/review_prompt.md.template +0 -0
  26. {code_review_ai_cli-2.0.0 → code_review_ai_cli-2.0.1}/tests/test_formatter.py +0 -0
  27. {code_review_ai_cli-2.0.0 → code_review_ai_cli-2.0.1}/tests/test_prompt_utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: code-review-ai-cli
3
- Version: 2.0.0
3
+ Version: 2.0.1
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
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: code-review-ai-cli
3
- Version: 2.0.0
3
+ Version: 2.0.1
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
@@ -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.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}) Quick")
838
- print(f" {c.CYAN}2{c.RESET}) Detailed")
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-3, default=2]: {c.RESET}").strip()
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": "quick", "2": "detailed", "3": "security"}
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" # "quick" | "detailed" | "security"
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 ("quick", "detailed", "security"):
305
+ if self.verbosity not in ("detailed", "security"):
306
306
  issues.append(
307
307
  f"Invalid verbosity: '{self.verbosity}'. "
308
- "Use 'quick', 'detailed' or 'security'."
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 context lines and deleted lines (-) from the diff.
288
- Keeps only added lines (+) and structural headers needed for the LLM.
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 for the LLM. Every
303
- line between the sentinel markers is kept, regardless of whether it
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 context/deleted lines stripped but
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
- # Context lines, deleted lines and '\ No newline' markers are discarded.
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 ("quick", "detailed", "security") to its
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 ``"quick"``, ``"detailed"``,
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
- * Only added lines (``+``) appear in the diff section.
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 prevent the model from hallucinating
159
- about the surrounding code.
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 only added lines (+) — context and deletions were removed. "
186
- "The complete new-version file content, after the changes (between ### FULL_FILE_CONTEXT_START and ### FULL_FILE_CONTEXT_END markers) "
187
- "is provided for each file as read-only context. "
188
- "Use it to understand the surrounding code, but focus your review EXCLUSIVELY on the changed lines (marked + in the diff). "
189
- "Do NOT report issues in unchanged lines unless they directly affect the correctness of the changes. "
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 only added lines (+). "
196
- "The new-version (post-change) full file content (between ### FULL_FILE_CONTEXT_START and ### FULL_FILE_CONTEXT_END markers) "
197
- "is provided for each file as read-only context. "
198
- "Use it to understand the surrounding code, but focus your review EXCLUSIVELY on the changed lines. "
199
- "Do NOT report issues in unchanged lines unless they directly affect the correctness of the changes."
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: "quick", "detailed" or "security"
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
- result.extend(new_lines)
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
- "--quick",
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 == "quick"
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", "quick"), ("2", "detailed"), ("3", "security"), ("", "detailed"), ("99", "detailed")],
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: quick
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 == "quick"
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 stripped
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" not in filtered
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 test_filter_diff_additions_only_without_context_block_strips_context_and_deletions() -> None:
301
- """Without FULL_FILE_CONTEXT blocks the method must still strip context and deletion lines."""
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" not in filtered
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 stripped after block ends
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" not in filtered
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("quick").lower()
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("quick", language="pt") # type: ignore[call-arg]
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 test_get_system_prompt_quick_returns_non_empty_string() -> None:
119
- """quick prompt must return a non-empty string."""
120
- prompt = get_system_prompt("quick")
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 the new (source branch, post-change) file content."""
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: