code-review-ai-cli 1.3.4__tar.gz → 1.3.5__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 (25) hide show
  1. {code_review_ai_cli-1.3.4 → code_review_ai_cli-1.3.5}/PKG-INFO +1 -2
  2. {code_review_ai_cli-1.3.4 → code_review_ai_cli-1.3.5}/README.md +0 -1
  3. {code_review_ai_cli-1.3.4 → code_review_ai_cli-1.3.5}/code_review_ai_cli.egg-info/PKG-INFO +1 -2
  4. {code_review_ai_cli-1.3.4 → code_review_ai_cli-1.3.5}/pyproject.toml +1 -1
  5. {code_review_ai_cli-1.3.4 → code_review_ai_cli-1.3.5}/src/ai_review.py +0 -1
  6. {code_review_ai_cli-1.3.4 → code_review_ai_cli-1.3.5}/src/config.py +0 -2
  7. {code_review_ai_cli-1.3.4 → code_review_ai_cli-1.3.5}/src/llm_client.py +112 -223
  8. {code_review_ai_cli-1.3.4 → code_review_ai_cli-1.3.5}/src/prompts/config.yaml.template +0 -3
  9. {code_review_ai_cli-1.3.4 → code_review_ai_cli-1.3.5}/tests/test_config.py +8 -2
  10. {code_review_ai_cli-1.3.4 → code_review_ai_cli-1.3.5}/tests/test_llm_client.py +49 -69
  11. {code_review_ai_cli-1.3.4 → code_review_ai_cli-1.3.5}/code_review_ai_cli.egg-info/SOURCES.txt +0 -0
  12. {code_review_ai_cli-1.3.4 → code_review_ai_cli-1.3.5}/code_review_ai_cli.egg-info/dependency_links.txt +0 -0
  13. {code_review_ai_cli-1.3.4 → code_review_ai_cli-1.3.5}/code_review_ai_cli.egg-info/entry_points.txt +0 -0
  14. {code_review_ai_cli-1.3.4 → code_review_ai_cli-1.3.5}/code_review_ai_cli.egg-info/requires.txt +0 -0
  15. {code_review_ai_cli-1.3.4 → code_review_ai_cli-1.3.5}/code_review_ai_cli.egg-info/top_level.txt +0 -0
  16. {code_review_ai_cli-1.3.4 → code_review_ai_cli-1.3.5}/setup.cfg +0 -0
  17. {code_review_ai_cli-1.3.4 → code_review_ai_cli-1.3.5}/src/__init__.py +0 -0
  18. {code_review_ai_cli-1.3.4 → code_review_ai_cli-1.3.5}/src/formatter.py +0 -0
  19. {code_review_ai_cli-1.3.4 → code_review_ai_cli-1.3.5}/src/git_utils.py +0 -0
  20. {code_review_ai_cli-1.3.4 → code_review_ai_cli-1.3.5}/src/prompts/review_prompt.md.template +0 -0
  21. {code_review_ai_cli-1.3.4 → code_review_ai_cli-1.3.5}/src/tfs_client.py +0 -0
  22. {code_review_ai_cli-1.3.4 → code_review_ai_cli-1.3.5}/tests/test_ai_review.py +0 -0
  23. {code_review_ai_cli-1.3.4 → code_review_ai_cli-1.3.5}/tests/test_formatter.py +0 -0
  24. {code_review_ai_cli-1.3.4 → code_review_ai_cli-1.3.5}/tests/test_git_utils.py +0 -0
  25. {code_review_ai_cli-1.3.4 → code_review_ai_cli-1.3.5}/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: 1.3.4
3
+ Version: 1.3.5
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
@@ -126,7 +126,6 @@ tfs:
126
126
  # ca_bundle: C:/certs/corporate-root-ca.pem
127
127
 
128
128
  review:
129
- language: pt
130
129
  verbosity: detailed
131
130
  scope: diff_only
132
131
  custom_prompt_file: review_prompt.md
@@ -89,7 +89,6 @@ tfs:
89
89
  # ca_bundle: C:/certs/corporate-root-ca.pem
90
90
 
91
91
  review:
92
- language: pt
93
92
  verbosity: detailed
94
93
  scope: diff_only
95
94
  custom_prompt_file: review_prompt.md
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: code-review-ai-cli
3
- Version: 1.3.4
3
+ Version: 1.3.5
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
@@ -126,7 +126,6 @@ tfs:
126
126
  # ca_bundle: C:/certs/corporate-root-ca.pem
127
127
 
128
128
  review:
129
- language: pt
130
129
  verbosity: detailed
131
130
  scope: diff_only
132
131
  custom_prompt_file: review_prompt.md
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "code-review-ai-cli"
7
- version = "1.3.4"
7
+ version = "1.3.5"
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"
@@ -858,7 +858,6 @@ def _show_config(config: ReviewConfig) -> None:
858
858
  print(f" {c.CYAN}API Key:{c.RESET} {'✅ Configured' if has_effective_key else '❌ Not configured'}")
859
859
  print(f" {c.CYAN}Temperature:{c.RESET} {config.temperature}")
860
860
  print(f" {c.CYAN}Max Tokens:{c.RESET} {config.max_tokens}")
861
- print(f" {c.CYAN}Language:{c.RESET} {config.review_language}")
862
861
  print(f" {c.CYAN}Verbosity:{c.RESET} {config.verbosity}")
863
862
  print(f" {c.CYAN}Format:{c.RESET} {config.output_format}")
864
863
  print(f"\n {c.CYAN}TFS URL:{c.RESET} {config.tfs_base_url or '(not configured)'}")
@@ -85,7 +85,6 @@ class ReviewConfig:
85
85
  tfs_repository: str = "" # Default repository (empty = all)
86
86
 
87
87
  # --- Review -------------------------------------------------------
88
- review_language: str = "pt" # Review language (pt/en)
89
88
  verbosity: str = "detailed" # "quick" | "detailed" | "security"
90
89
  review_scope: str = "diff_only" # "diff_only" | "full_code"
91
90
  max_diff_files: int = 50 # Max diff files sent to LLM
@@ -211,7 +210,6 @@ class ReviewConfig:
211
210
  "tfs_ca_bundle": ("tfs", "ca_bundle"),
212
211
  "tfs_repository": ("tfs", "repository"),
213
212
  # Review
214
- "review_language": ("review", "language"),
215
213
  "verbosity": ("review", "verbosity"),
216
214
  "review_scope": ("review", "scope"),
217
215
  "max_diff_files": ("review", "max_diff_files"),
@@ -26,209 +26,124 @@ class LLMError(Exception):
26
26
  # ---------------------------------------------------------------------------
27
27
  # System Prompts
28
28
  # ---------------------------------------------------------------------------
29
+ # Maps each verbosity level ("quick", "detailed", "security") to its
30
+ # English-only system prompt.
31
+ #
32
+ # Type:
33
+ # dict[str, str] — flat mapping of verbosity -> prompt string. There is
34
+ # no per-language nesting; each value is a plain ``str``.
35
+ #
36
+ # See Also:
37
+ # get_system_prompt: Resolves a prompt for a given verbosity, with a
38
+ # fallback to "detailed" when the key is not recognised.
29
39
  SYSTEM_PROMPTS = {
30
- "quick": {
31
- "pt": (
32
- "És um code reviewer senior experiente. Analisa o diff de código fornecido "
33
- "e dá um review CONCISO e direto. Foca-te nos problemas mais críticos:\n"
34
- "- Bugs e erros lógicos\n"
35
- "- Problemas de segurança\n"
36
- "- Problemas de performance graves\n\n"
37
- "Formato: Lista de bullet points com o ficheiro e linha quando possível. "
38
- ),
39
- "en": (
40
- "You are an experienced Senior Code Reviewer. Analyze the provided code diff "
41
- "and give a CONCISE review. Focus on critical issues:\n"
42
- "- Bugs and logic errors\n"
43
- "- Security issues\n"
44
- "- Major performance problems\n\n"
45
- "Format: Bullet points with file and line when possible. "
46
- ),
47
- },
48
- "detailed": {
49
- "pt": (
50
- "És um code reviewer senior experiente. Analisa o diff de código "
51
- "fornecido e retorna apenas comentários inline.\n\n"
52
- "Formato de output — para cada problema encontrado, escreve exatamente:\n"
53
- "- Linha <número_linha>: <descrição do problema> \n\n"
54
- "Regras:\n"
55
- "- Reporta APENAS problemas específicos associados a uma linha ou bloco concreto de código alterado.\n"
56
- "- NÃO produzas secções de sumário (ex: 'Bugs Potenciais', 'Visão Geral de Segurança').\n"
57
- "- NÃO produzas parágrafos introdutórios ou de fecho.\n"
58
- "- Se não encontrares problemas, escreve apenas: Nenhum problema encontrado.\n"
59
- "- Sê específico, objetivo e conciso. Responde em português."
60
- ),
61
- "en": (
62
- "You are an experienced Senior Code Reviewer. Analyze the provided code and return only inline comments.\n\n"
63
- "Output format — for each issue found, output exactly:\n"
64
- "- Line <line_number>: <issue description>\n\n"
65
- "Rules:\n"
66
- "- Report ONLY specific issues tied to a concrete line or block of changed code.\n"
67
- "- Do NOT produce summary sections (e.g. 'Potential Bugs', 'Security Overview').\n"
68
- "- Do NOT produce introductory or closing paragraphs.\n"
69
- "- If no issues are found, output only: No issues found.\n"
70
- "- Be specific, actionable, and concise."
71
- ),
72
- },
73
- "security": {
74
- "pt": (
75
- "És um especialista em segurança de aplicações. Analisa o diff "
76
- "de código fornecido com foco EXCLUSIVO em segurança.\n\n"
77
- "Procura por:\n"
78
- "- SQL Injection\n"
79
- "- Cross-Site Scripting (XSS)\n"
80
- "- Cross-Site Request Forgery (CSRF)\n"
81
- "- Credenciais hardcoded ou secrets expostos\n"
82
- "- Vulnerabilidades de autenticação/autorização\n"
83
- "- Insecure deserialization\n"
84
- "- Path traversal\n"
85
- "- Command injection\n"
86
- "- Dependências com vulnerabilidades conhecidas\n"
87
- "- Logging de informação sensível\n"
88
- "- Configurações inseguras\n\n"
89
- "Fornece recomendações de correção para cada problema. "
90
- "Responde em português."
91
- ),
92
- "en": (
93
- "You are an application security. Analyze the "
94
- "provided code diff with EXCLUSIVE focus on security.\n\n"
95
- "Look for:\n"
96
- "- SQL Injection\n"
97
- "- Cross-Site Scripting (XSS)\n"
98
- "- Cross-Site Request Forgery (CSRF)\n"
99
- "- Hardcoded credentials or exposed secrets\n"
100
- "- Authentication/authorization vulnerabilities\n"
101
- "- Insecure deserialization\n"
102
- "- Path traversal\n"
103
- "- Command injection\n"
104
- "- Dependencies with known vulnerabilities\n"
105
- "- Logging of sensitive information\n"
106
- "- Insecure configurations\n\n"
107
- "Provide fix recommendations for each issue."
108
- ),
109
- },
110
- }
111
-
112
- # Special prompt for PR review with a combined narrative summary + structured comments
113
- PR_COMMENT_PROMPT = {
114
- "pt": (
115
- "Analisa o diff de código de um Pull Request e retorna a tua resposta num único objeto JSON "
116
- "com dois campos:\n"
117
- '- "summary": um resumo narrativo do review, em texto (string). Se não encontrares problemas, '
118
- 'escreve algo como "Nenhum problema encontrado."\n'
119
- '- "comments": um array de objetos, um por cada problema encontrado, com:\n'
120
- ' - "file": caminho do ficheiro (ex: "src/auth.py")\n'
121
- ' - "line": número da linha no diff (inteiro, ou 0 se geral)\n'
122
- ' - "type": tipo de issue ("bug", "security", "performance", "style", "suggestion", "praise")\n'
123
- ' - "comment": descrição direta do problema em português, sem saudações e sem emojis\n'
124
- ' - "suggestion": sugestão de correção (opcional, string vazia se não aplicável)\n'
125
- ' - "reference": fonte ou referência para o problema (URL de documentação, padrão ou princípio). '
126
- 'Importante: incluir SEMPRE uma referência relevante.\n\n'
127
- "No campo 'comment', escreve de forma objetiva e curta. "
128
- "Não uses introduções como 'Olá' ou 'Como code reviewer sénior'.\n"
129
- "No campo 'reference', inclui uma fonte confiável, padrão ou link para documentação relevante.\n\n"
130
- "Responde APENAS com um JSON object válido. Exemplo:\n"
131
- '{\n'
132
- ' "summary": "Resumo narrativo curto do review...",\n'
133
- ' "comments": [\n'
134
- ' {\n'
135
- ' "file": "src/auth.py",\n'
136
- ' "line": 42,\n'
137
- ' "type": "security",\n'
138
- ' "comment": "Password armazenada em texto simples sem hashing",\n'
139
- ' "suggestion": "Usar bcrypt ou argon2 para hash de passwords",\n'
140
- ' "reference": "OWASP - Password Storage Cheat Sheet"\n'
141
- ' }\n'
142
- ' ]\n'
143
- '}\n\n'
40
+ "quick": (
41
+ "You are an experienced Senior Code Reviewer. Analyze the provided code diff "
42
+ "and give a CONCISE review. Focus on critical issues:\n"
43
+ "- Bugs and logic errors\n"
44
+ "- Security issues\n"
45
+ "- Major performance problems\n\n"
46
+ "Format: Bullet points with file and line when possible. "
47
+ ),
48
+ "detailed": (
49
+ "You are an experienced Senior Code Reviewer. Analyze the provided code and return only inline comments.\n\n"
50
+ "Output format — for each issue found, output exactly:\n"
51
+ "- Line <line_number>: <issue description>\n\n"
52
+ "Rules:\n"
53
+ "- Report ONLY specific issues tied to a concrete line or block of changed code.\n"
54
+ "- Do NOT produce summary sections (e.g. 'Potential Bugs', 'Security Overview').\n"
55
+ "- Do NOT produce introductory or closing paragraphs.\n"
56
+ "- If no issues are found, output only: No issues found.\n"
57
+ "- Be specific, actionable, and concise."
144
58
  ),
145
- "en": (
146
- "Analyze the Pull Request code diff and return your response as a single JSON object with two "
147
- "fields:\n"
148
- '- "summary": a narrative text summary of the review (string). If no issues are found, write '
149
- 'something like "No issues found."\n'
150
- '- "comments": an array of objects, one per issue found, with:\n'
151
- ' - "file": file path (e.g., "src/auth.py")\n'
152
- ' - "line": line number in diff (integer, or 0 if general)\n'
153
- ' - "type": issue type ("bug", "security", "performance", "style", "suggestion", "praise")\n'
154
- ' - "comment": direct description of the issue, with no greetings and no emojis\n'
155
- ' - "suggestion": fix suggestion (optional, empty string if not applicable)\n'
156
- ' - "reference": source or reference for the issue (e.g., "OWASP Top 10", "PEP 8", documentation '
157
- 'URL, standard or principle). Important: ALWAYS include a relevant reference.\n\n'
158
- "In 'comment', use a short and objective tone. "
159
- "Do not include intros like 'Hello' or 'As a senior reviewer'.\n"
160
- "In 'reference', include a trusted source, standard or link to relevant documentation.\n\n"
161
- "Respond ONLY with a valid JSON object. Example:\n"
162
- '{\n'
163
- ' "summary": "Short narrative summary of the review...",\n'
164
- ' "comments": [\n'
165
- ' {\n'
166
- ' "file": "src/auth.py",\n'
167
- ' "line": 42,\n'
168
- ' "type": "security",\n'
169
- ' "comment": "Password stored in plain text without hashing",\n'
170
- ' "suggestion": "Use bcrypt or argon2 to hash passwords",\n'
171
- ' "reference": "OWASP - Password Storage Cheat Sheet"\n'
172
- ' }\n'
173
- ' ]\n'
174
- '}\n\n'
59
+ "security": (
60
+ "You are an application security. Analyze the "
61
+ "provided code diff with EXCLUSIVE focus on security.\n\n"
62
+ "Look for:\n"
63
+ "- SQL Injection\n"
64
+ "- Cross-Site Scripting (XSS)\n"
65
+ "- Cross-Site Request Forgery (CSRF)\n"
66
+ "- Hardcoded credentials or exposed secrets\n"
67
+ "- Authentication/authorization vulnerabilities\n"
68
+ "- Insecure deserialization\n"
69
+ "- Path traversal\n"
70
+ "- Command injection\n"
71
+ "- Dependencies with known vulnerabilities\n"
72
+ "- Logging of sensitive information\n"
73
+ "- Insecure configurations\n\n"
74
+ "Provide fix recommendations for each issue."
175
75
  ),
176
76
  }
177
77
 
178
-
179
- def get_system_prompt(verbosity: str, language: str) -> str:
180
- """Returns the system prompt for the given verbosity level and language.
78
+ # Special prompt for PR review with a combined narrative summary + structured comments.
79
+ #
80
+ # Type:
81
+ # str — a single English-only prompt (not a per-language dict) instructing
82
+ # the LLM to respond with one JSON object containing a "summary" string
83
+ # and a "comments" array (each with file, line, type, comment,
84
+ # suggestion, and reference fields).
85
+ #
86
+ # See Also:
87
+ # LLMClient.review_pr: Combines this prompt with the verbosity system
88
+ # prompt (from get_system_prompt) and scope guidance (from
89
+ # get_scope_guidance) to build the final system prompt sent to the LLM.
90
+ PR_COMMENT_PROMPT = (
91
+ "Analyze the Pull Request code diff and return your response as a single JSON object with two "
92
+ "fields:\n"
93
+ '- "summary": a narrative text summary of the review (string). If no issues are found, write '
94
+ 'something like "No issues found."\n'
95
+ '- "comments": an array of objects, one per issue found, with:\n'
96
+ ' - "file": file path (e.g., "src/auth.py")\n'
97
+ ' - "line": line number in diff (integer, or 0 if general)\n'
98
+ ' - "type": issue type ("bug", "security", "performance", "style", "suggestion", "praise")\n'
99
+ ' - "comment": direct description of the issue, with no greetings and no emojis\n'
100
+ ' - "suggestion": fix suggestion (optional, empty string if not applicable)\n'
101
+ ' - "reference": source or reference for the issue (e.g., "OWASP Top 10", "PEP 8", documentation '
102
+ 'URL, standard or principle). Important: ALWAYS include a relevant reference.\n\n'
103
+ "In 'comment', use a short and objective tone. "
104
+ "Do not include intros like 'Hello' or 'As a senior reviewer'.\n"
105
+ "In 'reference', include a trusted source, standard or link to relevant documentation.\n\n"
106
+ "Respond ONLY with a valid JSON object. Example:\n"
107
+ '{\n'
108
+ ' "summary": "Short narrative summary of the review...",\n'
109
+ ' "comments": [\n'
110
+ ' {\n'
111
+ ' "file": "src/auth.py",\n'
112
+ ' "line": 42,\n'
113
+ ' "type": "security",\n'
114
+ ' "comment": "Password stored in plain text without hashing",\n'
115
+ ' "suggestion": "Use bcrypt or argon2 to hash passwords",\n'
116
+ ' "reference": "OWASP - Password Storage Cheat Sheet"\n'
117
+ ' }\n'
118
+ ' ]\n'
119
+ '}\n\n'
120
+ )
121
+
122
+
123
+ def get_system_prompt(verbosity: str) -> str:
124
+ """Returns the system prompt for the given verbosity level.
181
125
 
182
126
  Selects a prompt from ``SYSTEM_PROMPTS`` keyed by *verbosity*. If the
183
127
  requested verbosity is not found, the function falls back to
184
- ``"detailed"``. Within the selected prompt group, the language is
185
- resolved by *language*; if the language key is absent, ``"pt"``
186
- (Portuguese) is used as the default.
128
+ ``"detailed"``.
187
129
 
188
130
  The ``"detailed"`` prompt instructs the LLM to return **only** inline
189
131
  comments in the format ``- Line <n>: <description>``,
190
132
  prohibiting summary sections and introductory/closing paragraphs.
191
133
  When no issues are found the expected output is ``No issues found.``
192
- (English) or ``Nenhum problema encontrado.`` (Portuguese).
193
134
 
194
135
  Args:
195
136
  verbosity: Review depth key — one of ``"quick"``, ``"detailed"``,
196
137
  or ``"security"``. Any unrecognised value falls back to
197
138
  ``"detailed"``.
198
- language: Response language code — ``"en"`` for English,
199
- ``"pt"`` for Portuguese.
200
139
 
201
140
  Returns:
202
- The system prompt string for the resolved verbosity and language.
141
+ The system prompt string for the resolved verbosity.
203
142
  """
204
- prompts = SYSTEM_PROMPTS.get(verbosity, SYSTEM_PROMPTS["detailed"])
205
- return prompts.get(language, prompts["pt"])
206
-
143
+ return SYSTEM_PROMPTS.get(verbosity, SYSTEM_PROMPTS["detailed"])
207
144
 
208
- def get_pr_comment_prompt(language: str) -> str:
209
- """Returns the prompt that requests structured JSON PR comments from the LLM.
210
145
 
211
- Retrieves the prompt from ``PR_COMMENT_PROMPT`` for the given *language*.
212
- If the language key is not present, ``"pt"`` (Portuguese) is used as the
213
- default.
214
-
215
- The returned prompt instructs the LLM to produce a single valid JSON
216
- object with a ``"summary"`` field (narrative review text) and a
217
- ``"comments"`` field (array of review comment objects, each with the
218
- fields ``file``, ``line``, ``type``, ``comment``, ``suggestion``,
219
- and ``reference``).
220
-
221
- Args:
222
- language: Response language code — ``"en"`` for English,
223
- ``"pt"`` for Portuguese.
224
-
225
- Returns:
226
- The prompt string used to request structured PR comments.
227
- """
228
- return PR_COMMENT_PROMPT.get(language, PR_COMMENT_PROMPT["pt"])
229
-
230
-
231
- def get_scope_guidance(review_scope: str, language: str, structured: bool = False) -> str:
146
+ def get_scope_guidance(review_scope: str, structured: bool = False) -> str:
232
147
  """Returns LLM instructions tailored to the active review scope.
233
148
 
234
149
  For ``diff_only`` scope, the instructions inform the LLM that:
@@ -248,8 +163,6 @@ def get_scope_guidance(review_scope: str, language: str, structured: bool = Fals
248
163
 
249
164
  Args:
250
165
  review_scope: ``"diff_only"`` (default) or ``"full_code"``.
251
- language: Response language code — ``"en"`` for English,
252
- ``"pt"`` for Portuguese.
253
166
  structured: When ``True``, appends additional constraints for the
254
167
  structured JSON comment mode (every comment must carry a valid
255
168
  file path and line number > 0 for inline posting).
@@ -260,52 +173,29 @@ def get_scope_guidance(review_scope: str, language: str, structured: bool = Fals
260
173
  scope = (review_scope or "diff_only").lower()
261
174
 
262
175
  if scope == "full_code":
263
- if language == "en":
264
- return (
265
- "Review scope: full_code. The diff contains only added lines (+) for each file. "
266
- "Analyze the complete content of the changed files and identify issues in the new code. "
267
- "Do not comment on deleted or absent code."
268
- )
269
176
  return (
270
- "Ambito de review: full_code. O diff contém apenas linhas adicionadas (+) de cada ficheiro. "
271
- "Analisa o conteúdo completo dos ficheiros alterados e identifica problemas no novo código. "
272
- "Não comentes código eliminado ou ausente."
177
+ "Review scope: full_code. The diff contains only added lines (+) for each file. "
178
+ "Analyze the complete content of the changed files and identify issues in the new code. "
179
+ "Do not comment on deleted or absent code."
273
180
  )
274
181
 
275
182
  if structured:
276
- if language == "en":
277
- return (
278
- "Review scope: diff_only. The diff contains only added lines (+) — context and deletions were removed. "
279
- "The complete new-version file content, after the changes (between ### FULL_FILE_CONTEXT_START and ### FULL_FILE_CONTEXT_END markers) "
280
- "is provided for each file as read-only context. "
281
- "Use it to understand the surrounding code, but focus your review EXCLUSIVELY on the changed lines (marked + in the diff). "
282
- "Do NOT report issues in unchanged lines unless they directly affect the correctness of the changes. "
283
- "For every problem, you MUST provide a valid file and line (>0) to allow inline comments. "
284
- "Do not emit general problem comments without file/line."
285
- )
286
183
  return (
287
- "Ambito de review: diff_only. O diff contém apenas linhas adicionadas (+) — contexto e eliminações foram removidos. "
288
- "É fornecido o conteúdo completo do ficheiro na versão nova, depois das alterações (entre os marcadores ### FULL_FILE_CONTEXT_START e ### FULL_FILE_CONTEXT_END) como contexto de leitura. "
289
- "Usa-a para compreender o código envolvente, mas foca o teu review EXCLUSIVAMENTE nas linhas alteradas (marcadas com + no diff). "
290
- "NÃO reportes problemas em linhas não alteradas, exceto se afetarem diretamente a correção das alterações. "
291
- "Para cada problema, DEVE ser fornecido file e line válidos (>0) para comentário inline. "
292
- "Não emitas comentários gerais de problema sem file/line."
293
- )
294
-
295
- if language == "en":
296
- return (
297
- "Review scope: diff_only. The diff contains only added lines (+). "
298
- "The new-version (post-change) full file content (between ### FULL_FILE_CONTEXT_START and ### FULL_FILE_CONTEXT_END markers) "
184
+ "Review scope: diff_only. The diff contains only added lines (+) — context and deletions were removed. "
185
+ "The complete new-version file content, after the changes (between ### FULL_FILE_CONTEXT_START and ### FULL_FILE_CONTEXT_END markers) "
299
186
  "is provided for each file as read-only context. "
300
- "Use it to understand the surrounding code, but focus your review EXCLUSIVELY on the changed lines. "
301
- "Do NOT report issues in unchanged lines unless they directly affect the correctness of the changes."
187
+ "Use it to understand the surrounding code, but focus your review EXCLUSIVELY on the changed lines (marked + in the diff). "
188
+ "Do NOT report issues in unchanged lines unless they directly affect the correctness of the changes. "
189
+ "For every problem, you MUST provide a valid file and line (>0) to allow inline comments. "
190
+ "Do not emit general problem comments without file/line."
302
191
  )
192
+
303
193
  return (
304
- "Ambito de review: diff_only. O diff contém apenas linhas adicionadas (+). "
305
- "Uma secção com o conteúdo completo do ficheiro na versão nova, após as alterações (entre os marcadores ### FULL_FILE_CONTEXT_START e ### FULL_FILE_CONTEXT_END) "
306
- "é fornecida como contexto de leitura. "
307
- "Usa-a para compreender o código envolvente, mas foca o teu review EXCLUSIVAMENTE nas linhas alteradas. "
308
- "NÃO reportes problemas em linhas não alteradas, exceto se afetarem diretamente a correção das alterações."
194
+ "Review scope: diff_only. The diff contains only added lines (+). "
195
+ "The new-version (post-change) full file content (between ### FULL_FILE_CONTEXT_START and ### FULL_FILE_CONTEXT_END markers) "
196
+ "is provided for each file as read-only context. "
197
+ "Use it to understand the surrounding code, but focus your review EXCLUSIVELY on the changed lines. "
198
+ "Do NOT report issues in unchanged lines unless they directly affect the correctness of the changes."
309
199
  )
310
200
 
311
201
 
@@ -407,13 +297,12 @@ class LLMClient:
407
297
  is a list of dicts with keys: file, line, type, comment, suggestion,
408
298
  reference.
409
299
  """
410
- base_prompt = get_system_prompt(self.config.verbosity, self.config.review_language)
411
- json_schema_prompt = get_pr_comment_prompt(self.config.review_language)
300
+ base_prompt = get_system_prompt(self.config.verbosity)
301
+ json_schema_prompt = PR_COMMENT_PROMPT
412
302
  custom_prompt = self._load_custom_prompt_text()
413
303
 
414
304
  scope_guidance = get_scope_guidance(
415
305
  review_scope=review_scope,
416
- language=self.config.review_language,
417
306
  structured=True,
418
307
  )
419
308
 
@@ -72,9 +72,6 @@ tfs:
72
72
  # Review Preferences
73
73
  # ---------------------------------------------------------------------------
74
74
  review:
75
- # Language: "pt" or "en"
76
- language: en
77
-
78
75
  # Default verbosity: "quick", "detailed" or "security"
79
76
  verbosity: detailed
80
77
 
@@ -17,6 +17,14 @@ def test_get_effective_model_prefers_explicit_value() -> None:
17
17
  assert config.get_effective_model() == "custom-model"
18
18
 
19
19
 
20
+ def test_review_config_has_no_review_language_attribute() -> None:
21
+ """Regression: the configurable review language feature was removed."""
22
+ config = ReviewConfig()
23
+
24
+ assert hasattr(config, "review_language") is False
25
+ assert "review_language" not in ReviewConfig.__dataclass_fields__
26
+
27
+
20
28
  @pytest.mark.parametrize(
21
29
  ("provider", "field", "expected"),
22
30
  [
@@ -171,7 +179,6 @@ tfs:
171
179
  ca_bundle: ~/ca.pem
172
180
  repository: repo-a
173
181
  review:
174
- language: en
175
182
  verbosity: quick
176
183
  scope: full_code
177
184
  max_diff_files: 12
@@ -215,7 +222,6 @@ output:
215
222
  assert config.tfs_verify_ssl is False
216
223
  assert config.tfs_ca_bundle == "~/ca.pem"
217
224
  assert config.tfs_repository == "repo-a"
218
- assert config.review_language == "en"
219
225
  assert config.verbosity == "quick"
220
226
  assert config.review_scope == "full_code"
221
227
  assert config.max_diff_files == 12
@@ -10,7 +10,8 @@ from types import ModuleType, SimpleNamespace
10
10
  import pytest
11
11
 
12
12
  from src.config import ReviewConfig
13
- from src.llm_client import LLMClient, LLMError, build_user_message, get_pr_comment_prompt, get_scope_guidance, get_system_prompt
13
+ from src import llm_client
14
+ from src.llm_client import LLMClient, LLMError, build_user_message, get_scope_guidance, get_system_prompt, PR_COMMENT_PROMPT
14
15
 
15
16
 
16
17
  class FakeResponse:
@@ -79,7 +80,6 @@ def make_llm_config(**changes: object) -> ReviewConfig:
79
80
  model="gpt-4o-mini",
80
81
  max_tokens=256,
81
82
  temperature=0.2,
82
- review_language="en",
83
83
  verbosity="detailed",
84
84
  )
85
85
  for key, value in changes.items():
@@ -89,88 +89,72 @@ 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", "en").lower()
93
- assert "JSON" in get_pr_comment_prompt("pt")
94
- assert "full_code" in get_scope_guidance("full_code", "en")
95
- assert "file e line" in get_scope_guidance("diff_only", "pt", structured=True)
96
- assert "added lines" in get_scope_guidance("diff_only", "en")
92
+ assert "code reviewer" in get_system_prompt("quick").lower()
93
+ assert "JSON" in PR_COMMENT_PROMPT
94
+ assert "full_code" in get_scope_guidance("full_code")
95
+ assert "file and line" in get_scope_guidance("diff_only", structured=True).lower()
96
+ assert "added lines" in get_scope_guidance("diff_only")
97
+
98
+
99
+ def test_get_pr_comment_prompt_function_removed() -> None:
100
+ """Regression: get_pr_comment_prompt() was replaced by PR_COMMENT_PROMPT."""
101
+ assert not hasattr(llm_client, "get_pr_comment_prompt")
102
+
103
+
104
+ def test_get_system_prompt_and_get_scope_guidance_reject_language_kwarg() -> None:
105
+ """Regression: language parameter was removed from both prompt helpers."""
106
+ with pytest.raises(TypeError):
107
+ get_system_prompt("quick", language="pt") # type: ignore[call-arg]
108
+
109
+ with pytest.raises(TypeError):
110
+ get_scope_guidance("diff_only", structured=False, language="pt") # type: ignore[call-arg]
111
+
97
112
 
98
113
 
99
114
  # ---------------------------------------------------------------------------
100
115
  # SYSTEM_PROMPTS / get_system_prompt — Phase 1 change validation
101
116
  # ---------------------------------------------------------------------------
102
117
 
103
- @pytest.mark.parametrize("language", ["en", "pt"])
104
- def test_get_system_prompt_quick_returns_non_empty_string(language: str) -> None:
105
- """quick prompts must return a non-empty string for every supported language."""
106
- prompt = get_system_prompt("quick", language)
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")
107
121
  assert isinstance(prompt, str)
108
122
  assert len(prompt.strip()) > 0
109
123
 
110
124
 
111
- def test_get_system_prompt_detailed_en_enforces_inline_comment_format() -> None:
112
- """detailed/en must specify the inline comment output format after Phase 1."""
113
- prompt = get_system_prompt("detailed", "en")
125
+ def test_get_system_prompt_detailed_enforces_inline_comment_format() -> None:
126
+ """detailed must specify the inline comment output format after Phase 1."""
127
+ prompt = get_system_prompt("detailed")
114
128
  # Must instruct the LLM to return only inline comments
115
129
  assert "inline comments" in prompt.lower()
116
130
  # Must include the exact per-line format marker
117
131
  assert "Line <line_number>" in prompt
118
132
 
119
133
 
120
- def test_get_system_prompt_detailed_en_forbids_summary_sections() -> None:
121
- """detailed/en must explicitly forbid summary sections after Phase 1."""
122
- prompt = get_system_prompt("detailed", "en")
134
+ def test_get_system_prompt_detailed_forbids_summary_sections() -> None:
135
+ """detailed must explicitly forbid summary sections after Phase 1."""
136
+ prompt = get_system_prompt("detailed")
123
137
  assert "Do NOT produce summary sections" in prompt
124
138
 
125
139
 
126
- def test_get_system_prompt_detailed_en_contains_no_focus_on_or_structured_review_language() -> None:
127
- """detailed/en must NOT contain generic 'Focus on' or 'structured review' language after Phase 1."""
128
- prompt = get_system_prompt("detailed", "en")
140
+ def test_get_system_prompt_detailed_contains_no_focus_on_or_structured_review_language() -> None:
141
+ """detailed must NOT contain generic 'Focus on' or 'structured review' language after Phase 1."""
142
+ prompt = get_system_prompt("detailed")
129
143
  assert "focus on" not in prompt.lower()
130
144
  assert "structured review" not in prompt.lower()
131
145
 
132
146
 
133
- def test_get_system_prompt_detailed_pt_enforces_inline_comment_format() -> None:
134
- """detailed/pt must specify the inline comment output format in Portuguese after Phase 1."""
135
- prompt = get_system_prompt("detailed", "pt")
136
- assert "inline" in prompt.lower()
137
- # Must include the per-line format marker in Portuguese
138
- assert "Linha" in prompt
139
-
140
-
141
- def test_get_system_prompt_detailed_pt_forbids_summary_sections() -> None:
142
- """detailed/pt must explicitly forbid summary sections in Portuguese after Phase 1."""
143
- prompt = get_system_prompt("detailed", "pt")
144
- assert "NÃO produzas" in prompt
145
- # The forbidden construct must mention summary sections
146
- assert "sumário" in prompt.lower() or "secções" in prompt.lower()
147
-
148
-
149
- def test_get_system_prompt_detailed_pt_contains_no_focus_on_or_structured_review_language() -> None:
150
- """detailed/pt must NOT contain generic 'Focus on' or 'structured review' language after Phase 1."""
151
- prompt = get_system_prompt("detailed", "pt")
152
- assert "focus on" not in prompt.lower()
153
- assert "structured review" not in prompt.lower()
154
-
155
-
156
- def test_get_system_prompt_security_en_mentions_security_specific_terms() -> None:
157
- """security/en must include core vulnerability types it is expected to check for."""
158
- prompt = get_system_prompt("security", "en")
147
+ def test_get_system_prompt_security_mentions_security_specific_terms() -> None:
148
+ """security must include core vulnerability types it is expected to check for."""
149
+ prompt = get_system_prompt("security")
159
150
  for term in ("SQL Injection", "XSS", "CSRF"):
160
151
  assert term in prompt, f"Security prompt missing expected term: '{term}'"
161
152
 
162
153
 
163
154
  def test_get_system_prompt_unknown_verbosity_falls_back_to_detailed() -> None:
164
155
  """An unrecognised verbosity level must silently fall back to the 'detailed' prompt."""
165
- fallback = get_system_prompt("unknown_verbosity", "en")
166
- expected = get_system_prompt("detailed", "en")
167
- assert fallback == expected
168
-
169
-
170
- def test_get_system_prompt_unknown_language_falls_back_to_pt() -> None:
171
- """An unrecognised language code must fall back to the Portuguese ('pt') prompt."""
172
- fallback = get_system_prompt("detailed", "xx")
173
- expected = get_system_prompt("detailed", "pt")
156
+ fallback = get_system_prompt("unknown_verbosity")
157
+ expected = get_system_prompt("detailed")
174
158
  assert fallback == expected
175
159
 
176
160
 
@@ -179,39 +163,35 @@ def test_get_system_prompt_unknown_language_falls_back_to_pt() -> None:
179
163
  # ---------------------------------------------------------------------------
180
164
 
181
165
  def test_get_scope_guidance_diff_only_references_full_file_context_markers() -> None:
182
- """diff_only guidance must reference FULL_FILE_CONTEXT_START and _END for all language/mode combos."""
183
- for lang in ("en", "pt"):
184
- for structured in (False, True):
185
- guidance = get_scope_guidance("diff_only", lang, structured=structured)
186
- assert "FULL_FILE_CONTEXT_START" in guidance, f"lang={lang} structured={structured}"
187
- assert "FULL_FILE_CONTEXT_END" in guidance, f"lang={lang} structured={structured}"
166
+ """diff_only guidance must reference FULL_FILE_CONTEXT_START and _END for both modes."""
167
+ for structured in (False, True):
168
+ guidance = get_scope_guidance("diff_only", structured=structured)
169
+ assert "FULL_FILE_CONTEXT_START" in guidance, f"structured={structured}"
170
+ assert "FULL_FILE_CONTEXT_END" in guidance, f"structured={structured}"
188
171
 
189
172
 
190
- @pytest.mark.parametrize("language", ["en", "pt"])
191
- def test_get_scope_guidance_diff_only_structured_demands_valid_line_number(language: str) -> None:
173
+ def test_get_scope_guidance_diff_only_structured_demands_valid_line_number() -> None:
192
174
  """Structured diff_only guidance must instruct the LLM to supply a line number > 0."""
193
- guidance = get_scope_guidance("diff_only", language, structured=True)
175
+ guidance = get_scope_guidance("diff_only", structured=True)
194
176
  assert "(>0)" in guidance
195
177
 
196
178
 
197
179
  def test_get_scope_guidance_diff_only_non_structured_focuses_on_changed_lines() -> None:
198
180
  """Non-structured diff_only guidance must tell the LLM to focus on changed/added lines."""
199
- guidance_en = get_scope_guidance("diff_only", "en", structured=False)
200
- guidance_pt = get_scope_guidance("diff_only", "pt", structured=False)
181
+ guidance_en = get_scope_guidance("diff_only", structured=False)
201
182
 
202
183
  assert "changed lines" in guidance_en.lower() or "added lines" in guidance_en.lower()
203
- assert "alteradas" in guidance_pt.lower() or "adicionadas" in guidance_pt.lower()
204
184
 
205
185
 
206
186
  def test_get_scope_guidance_diff_only_structured_does_not_demand_line_in_non_structured() -> None:
207
187
  """Non-structured diff_only guidance must NOT mandate a minimum line number."""
208
- guidance_en = get_scope_guidance("diff_only", "en", structured=False)
188
+ guidance_en = get_scope_guidance("diff_only", structured=False)
209
189
  assert "(>0)" not in guidance_en
210
190
 
211
191
 
212
192
  def test_get_scope_guidance_unknown_scope_falls_back_to_diff_only() -> None:
213
193
  """An unrecognised review_scope must fall through and return diff_only guidance."""
214
- guidance = get_scope_guidance("unknown_scope", "en")
194
+ guidance = get_scope_guidance("unknown_scope")
215
195
 
216
196
  assert "diff_only" in guidance
217
197
  assert "FULL_FILE_CONTEXT_START" in guidance