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.
- {code_review_ai_cli-1.3.4 → code_review_ai_cli-1.3.5}/PKG-INFO +1 -2
- {code_review_ai_cli-1.3.4 → code_review_ai_cli-1.3.5}/README.md +0 -1
- {code_review_ai_cli-1.3.4 → code_review_ai_cli-1.3.5}/code_review_ai_cli.egg-info/PKG-INFO +1 -2
- {code_review_ai_cli-1.3.4 → code_review_ai_cli-1.3.5}/pyproject.toml +1 -1
- {code_review_ai_cli-1.3.4 → code_review_ai_cli-1.3.5}/src/ai_review.py +0 -1
- {code_review_ai_cli-1.3.4 → code_review_ai_cli-1.3.5}/src/config.py +0 -2
- {code_review_ai_cli-1.3.4 → code_review_ai_cli-1.3.5}/src/llm_client.py +112 -223
- {code_review_ai_cli-1.3.4 → code_review_ai_cli-1.3.5}/src/prompts/config.yaml.template +0 -3
- {code_review_ai_cli-1.3.4 → code_review_ai_cli-1.3.5}/tests/test_config.py +8 -2
- {code_review_ai_cli-1.3.4 → code_review_ai_cli-1.3.5}/tests/test_llm_client.py +49 -69
- {code_review_ai_cli-1.3.4 → code_review_ai_cli-1.3.5}/code_review_ai_cli.egg-info/SOURCES.txt +0 -0
- {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
- {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
- {code_review_ai_cli-1.3.4 → code_review_ai_cli-1.3.5}/code_review_ai_cli.egg-info/requires.txt +0 -0
- {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
- {code_review_ai_cli-1.3.4 → code_review_ai_cli-1.3.5}/setup.cfg +0 -0
- {code_review_ai_cli-1.3.4 → code_review_ai_cli-1.3.5}/src/__init__.py +0 -0
- {code_review_ai_cli-1.3.4 → code_review_ai_cli-1.3.5}/src/formatter.py +0 -0
- {code_review_ai_cli-1.3.4 → code_review_ai_cli-1.3.5}/src/git_utils.py +0 -0
- {code_review_ai_cli-1.3.4 → code_review_ai_cli-1.3.5}/src/prompts/review_prompt.md.template +0 -0
- {code_review_ai_cli-1.3.4 → code_review_ai_cli-1.3.5}/src/tfs_client.py +0 -0
- {code_review_ai_cli-1.3.4 → code_review_ai_cli-1.3.5}/tests/test_ai_review.py +0 -0
- {code_review_ai_cli-1.3.4 → code_review_ai_cli-1.3.5}/tests/test_formatter.py +0 -0
- {code_review_ai_cli-1.3.4 → code_review_ai_cli-1.3.5}/tests/test_git_utils.py +0 -0
- {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.
|
|
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
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: code-review-ai-cli
|
|
3
|
-
Version: 1.3.
|
|
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.
|
|
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
|
-
"
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
"
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
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
|
-
"
|
|
146
|
-
"
|
|
147
|
-
"
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
"
|
|
159
|
-
"
|
|
160
|
-
"
|
|
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
|
-
|
|
180
|
-
|
|
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"``.
|
|
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
|
|
141
|
+
The system prompt string for the resolved verbosity.
|
|
203
142
|
"""
|
|
204
|
-
|
|
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
|
-
|
|
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
|
-
"
|
|
271
|
-
"
|
|
272
|
-
"
|
|
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
|
-
"
|
|
288
|
-
"
|
|
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
|
-
"
|
|
305
|
-
"
|
|
306
|
-
"
|
|
307
|
-
"
|
|
308
|
-
"
|
|
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
|
|
411
|
-
json_schema_prompt =
|
|
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
|
|
|
@@ -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
|
|
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"
|
|
93
|
-
assert "JSON" in
|
|
94
|
-
assert "full_code" in get_scope_guidance("full_code"
|
|
95
|
-
assert "file
|
|
96
|
-
assert "added lines" in get_scope_guidance("diff_only"
|
|
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
|
-
|
|
104
|
-
|
|
105
|
-
|
|
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
|
|
112
|
-
"""detailed
|
|
113
|
-
prompt = get_system_prompt("detailed"
|
|
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
|
|
121
|
-
"""detailed
|
|
122
|
-
prompt = get_system_prompt("detailed"
|
|
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
|
|
127
|
-
"""detailed
|
|
128
|
-
prompt = get_system_prompt("detailed"
|
|
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
|
|
134
|
-
"""
|
|
135
|
-
prompt = get_system_prompt("
|
|
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"
|
|
166
|
-
expected = get_system_prompt("detailed"
|
|
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
|
|
183
|
-
for
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
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
|
-
|
|
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",
|
|
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",
|
|
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",
|
|
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"
|
|
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
|
{code_review_ai_cli-1.3.4 → code_review_ai_cli-1.3.5}/code_review_ai_cli.egg-info/SOURCES.txt
RENAMED
|
File without changes
|
|
File without changes
|
{code_review_ai_cli-1.3.4 → code_review_ai_cli-1.3.5}/code_review_ai_cli.egg-info/entry_points.txt
RENAMED
|
File without changes
|
{code_review_ai_cli-1.3.4 → code_review_ai_cli-1.3.5}/code_review_ai_cli.egg-info/requires.txt
RENAMED
|
File without changes
|
{code_review_ai_cli-1.3.4 → code_review_ai_cli-1.3.5}/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
|