airev 1.2.0__tar.gz → 1.3.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. {airev-1.2.0 → airev-1.3.0}/PKG-INFO +1 -1
  2. {airev-1.2.0 → airev-1.3.0}/src/airev.egg-info/PKG-INFO +1 -1
  3. {airev-1.2.0 → airev-1.3.0}/src/airev.egg-info/SOURCES.txt +1 -0
  4. {airev-1.2.0 → airev-1.3.0}/src/code_reviewer/__init__.py +1 -1
  5. {airev-1.2.0 → airev-1.3.0}/src/code_reviewer/cli.py +13 -1
  6. {airev-1.2.0 → airev-1.3.0}/src/code_reviewer/formatters/terminal.py +25 -2
  7. {airev-1.2.0 → airev-1.3.0}/src/code_reviewer/locales/en.yaml +7 -0
  8. {airev-1.2.0 → airev-1.3.0}/src/code_reviewer/locales/pt-br.yaml +7 -0
  9. {airev-1.2.0 → airev-1.3.0}/src/code_reviewer/models.py +2 -1
  10. {airev-1.2.0 → airev-1.3.0}/src/code_reviewer/prompt_builder.py +48 -0
  11. {airev-1.2.0 → airev-1.3.0}/src/code_reviewer/prompts/review_system.md +1 -1
  12. {airev-1.2.0 → airev-1.3.0}/src/code_reviewer/response_parser.py +5 -0
  13. airev-1.3.0/tests/test_cli.py +29 -0
  14. {airev-1.2.0 → airev-1.3.0}/tests/test_prompt_builder.py +67 -0
  15. {airev-1.2.0 → airev-1.3.0}/tests/test_response_parser.py +7 -0
  16. {airev-1.2.0 → airev-1.3.0}/README.md +0 -0
  17. {airev-1.2.0 → airev-1.3.0}/pyproject.toml +0 -0
  18. {airev-1.2.0 → airev-1.3.0}/setup.cfg +0 -0
  19. {airev-1.2.0 → airev-1.3.0}/src/airev.egg-info/dependency_links.txt +0 -0
  20. {airev-1.2.0 → airev-1.3.0}/src/airev.egg-info/entry_points.txt +0 -0
  21. {airev-1.2.0 → airev-1.3.0}/src/airev.egg-info/requires.txt +0 -0
  22. {airev-1.2.0 → airev-1.3.0}/src/airev.egg-info/top_level.txt +0 -0
  23. {airev-1.2.0 → airev-1.3.0}/src/code_reviewer/context_builder.py +0 -0
  24. {airev-1.2.0 → airev-1.3.0}/src/code_reviewer/diff_parser.py +0 -0
  25. {airev-1.2.0 → airev-1.3.0}/src/code_reviewer/formatters/__init__.py +0 -0
  26. {airev-1.2.0 → airev-1.3.0}/src/code_reviewer/formatters/progress.py +0 -0
  27. {airev-1.2.0 → airev-1.3.0}/src/code_reviewer/i18n/__init__.py +0 -0
  28. {airev-1.2.0 → airev-1.3.0}/src/code_reviewer/runners/__init__.py +0 -0
  29. {airev-1.2.0 → airev-1.3.0}/src/code_reviewer/runners/base.py +0 -0
  30. {airev-1.2.0 → airev-1.3.0}/src/code_reviewer/runners/copilot.py +0 -0
  31. {airev-1.2.0 → airev-1.3.0}/src/code_reviewer/runners/gemini.py +0 -0
  32. {airev-1.2.0 → airev-1.3.0}/src/code_reviewer/updater/__init__.py +0 -0
  33. {airev-1.2.0 → airev-1.3.0}/src/code_reviewer/updater/http_client.py +0 -0
  34. {airev-1.2.0 → airev-1.3.0}/src/code_reviewer/updater/notifier.py +0 -0
  35. {airev-1.2.0 → airev-1.3.0}/src/code_reviewer/updater/upgrade.py +0 -0
  36. {airev-1.2.0 → airev-1.3.0}/src/code_reviewer/updater/version_check.py +0 -0
  37. {airev-1.2.0 → airev-1.3.0}/tests/test_context_builder.py +0 -0
  38. {airev-1.2.0 → airev-1.3.0}/tests/test_copilot_runner.py +0 -0
  39. {airev-1.2.0 → airev-1.3.0}/tests/test_diff_parser.py +0 -0
  40. {airev-1.2.0 → airev-1.3.0}/tests/test_i18n.py +0 -0
  41. {airev-1.2.0 → airev-1.3.0}/tests/test_progress.py +0 -0
  42. {airev-1.2.0 → airev-1.3.0}/tests/test_terminal_formatter.py +0 -0
  43. {airev-1.2.0 → airev-1.3.0}/tests/test_updater.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: airev
3
- Version: 1.2.0
3
+ Version: 1.3.0
4
4
  Summary: CLI tool for AI-powered code review with context backtracking
5
5
  Author-email: Tarcisio <tarcisiojunior@gmail.com>
6
6
  License: MIT
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: airev
3
- Version: 1.2.0
3
+ Version: 1.3.0
4
4
  Summary: CLI tool for AI-powered code review with context backtracking
5
5
  Author-email: Tarcisio <tarcisiojunior@gmail.com>
6
6
  License: MIT
@@ -29,6 +29,7 @@ src/code_reviewer/updater/http_client.py
29
29
  src/code_reviewer/updater/notifier.py
30
30
  src/code_reviewer/updater/upgrade.py
31
31
  src/code_reviewer/updater/version_check.py
32
+ tests/test_cli.py
32
33
  tests/test_context_builder.py
33
34
  tests/test_copilot_runner.py
34
35
  tests/test_diff_parser.py
@@ -1,3 +1,3 @@
1
1
  """Code Reviewer - CLI para revisão de código com IA."""
2
2
 
3
- __version__ = "1.2.0"
3
+ __version__ = "1.3.0"
@@ -75,6 +75,13 @@ def main():
75
75
  type=click.Choice(get_available_languages()),
76
76
  help="Idioma das mensagens (default: pt-br)",
77
77
  )
78
+ @click.option(
79
+ "--text-quality",
80
+ "-t",
81
+ is_flag=True,
82
+ default=False,
83
+ help="Ativa verificação de ortografia e clareza em mensagens de usuário",
84
+ )
78
85
  def review(
79
86
  base: str,
80
87
  runner: str,
@@ -83,6 +90,7 @@ def review(
83
90
  no_progress: bool,
84
91
  progress: bool,
85
92
  lang: str,
93
+ text_quality: bool,
86
94
  ):
87
95
  """Analisa o diff da branch atual contra a branch base.
88
96
 
@@ -95,6 +103,8 @@ def review(
95
103
  airev review --base main --json-output
96
104
 
97
105
  airev review --base main --no-progress
106
+
107
+ airev review --base main --text-quality
98
108
  """
99
109
  workdir = workdir or Path.cwd()
100
110
  start_time = time.perf_counter()
@@ -173,7 +183,9 @@ def review(
173
183
 
174
184
  # Monta o prompt
175
185
  with reporter.status(t("cli.building_prompt")):
176
- prompt = build_prompt(diff_files, context_graphs, current_branch, base)
186
+ prompt = build_prompt(
187
+ diff_files, context_graphs, current_branch, base, text_quality=text_quality
188
+ )
177
189
 
178
190
  # Obtém o runner
179
191
  try:
@@ -5,7 +5,7 @@ from typing import TextIO
5
5
  import sys
6
6
 
7
7
  from ..i18n import t
8
- from ..models import Finding, ReviewResult, Severity
8
+ from ..models import Category, Finding, ReviewResult, Severity
9
9
 
10
10
 
11
11
  # Códigos ANSI para cores
@@ -71,6 +71,28 @@ def format_severity(severity: Severity) -> str:
71
71
  return _colorize(f"[{severity.value}]", Colors.BOLD, Colors.BLUE)
72
72
 
73
73
 
74
+ def format_category_badge(category: Category) -> str:
75
+ """Formata o badge da categoria.
76
+
77
+ Args:
78
+ category: Categoria do finding
79
+
80
+ Returns:
81
+ String formatada com ícone e cor para a categoria
82
+ """
83
+ # Ícones e cores por categoria
84
+ category_styles = {
85
+ Category.SECURITY: ("🔒", Colors.RED),
86
+ Category.PERFORMANCE: ("⚡", Colors.YELLOW),
87
+ Category.BUG: ("🐛", Colors.MAGENTA),
88
+ Category.RESOURCE_LEAK: ("💧", Colors.CYAN),
89
+ Category.TEXT_QUALITY: ("✏️", Colors.CYAN),
90
+ }
91
+
92
+ icon, color = category_styles.get(category, ("•", Colors.WHITE))
93
+ return f"{icon} {_colorize(category.value, color)}"
94
+
95
+
74
96
  def format_finding(finding: Finding) -> str:
75
97
  """Formata um finding para exibição.
76
98
 
@@ -84,10 +106,11 @@ def format_finding(finding: Finding) -> str:
84
106
 
85
107
  # Header: [SEVERITY] arquivo:linha - Título
86
108
  severity_str = format_severity(finding.severity)
109
+ category_badge = format_category_badge(finding.category)
87
110
  location = _colorize(f"{finding.file}:{finding.line}", Colors.CYAN)
88
111
  title = _colorize(finding.title, Colors.BOLD)
89
112
 
90
- lines.append(f" {severity_str} {location} - {title}")
113
+ lines.append(f" {severity_str} {category_badge} {location} - {title}")
91
114
 
92
115
  # Descrição
93
116
  if finding.description:
@@ -47,6 +47,13 @@ terminal:
47
47
  # Findings
48
48
  suggestion: "Suggestion:"
49
49
 
50
+ # Categories
51
+ category_security: "security"
52
+ category_performance: "performance"
53
+ category_bug: "bug"
54
+ category_resource_leak: "resource leak"
55
+ category_text_quality: "text quality"
56
+
50
57
  # Summary
51
58
  summary: "SUMMARY:"
52
59
  findings_count: "{count} finding(s)"
@@ -47,6 +47,13 @@ terminal:
47
47
  # Findings
48
48
  suggestion: "Sugestão:"
49
49
 
50
+ # Categorias
51
+ category_security: "segurança"
52
+ category_performance: "performance"
53
+ category_bug: "bug"
54
+ category_resource_leak: "vazamento de recurso"
55
+ category_text_quality: "qualidade de texto"
56
+
50
57
  # Resumo
51
58
  summary: "RESUMO:"
52
59
  findings_count: "{count} finding(s)"
@@ -21,6 +21,7 @@ class Category(str, Enum):
21
21
  PERFORMANCE = "performance"
22
22
  BUG = "bug"
23
23
  RESOURCE_LEAK = "resource-leak"
24
+ TEXT_QUALITY = "text-quality"
24
25
 
25
26
 
26
27
  class DiffLine(BaseModel):
@@ -90,7 +91,7 @@ class Finding(BaseModel):
90
91
  line: int = Field(description="Número da linha")
91
92
  severity: Severity = Field(description="Severidade: CRITICAL, WARNING, INFO")
92
93
  category: Category = Field(
93
- description="Categoria: security, performance, bug, resource-leak"
94
+ description="Categoria: security, performance, bug, resource-leak, text-quality"
94
95
  )
95
96
  title: str = Field(description="Título curto do problema")
96
97
  description: str = Field(description="Descrição detalhada do problema")
@@ -152,11 +152,54 @@ def format_references_for_prompt(context_graphs: list[ContextGraph]) -> str:
152
152
  return "\n".join(parts)
153
153
 
154
154
 
155
+ def get_text_quality_section(language_name: str) -> str:
156
+ """Retorna a seção de instruções para verificação de qualidade de texto.
157
+
158
+ Args:
159
+ language_name: Nome do idioma para verificação
160
+
161
+ Returns:
162
+ String com instruções de verificação de texto
163
+ """
164
+ return f"""
165
+ ## QUALIDADE DE TEXTO
166
+
167
+ Verifique ortografia e clareza semântica em mensagens voltadas ao usuário, no idioma **{language_name}**.
168
+
169
+ ### O que verificar:
170
+
171
+ **Padrões de código:**
172
+ - `raise *Error("...")` e `raise *Exception("...")`
173
+ - `print("...")` e `console.log("...")`
174
+ - Parâmetros nomeados: `message=`, `label=`, `title=`, `description=`, `text=`
175
+ - Funções de UI: `flash("...")`, `toast("...")`, `alert("...")`
176
+
177
+ **Arquivos de i18n:**
178
+ - Arquivos em `locales/**/*`
179
+ - Arquivos em `i18n/**/*`
180
+ - Arquivos `messages.*` e `strings.*`
181
+
182
+ ### O que ignorar:
183
+
184
+ - Identificadores: snake_case, camelCase, PascalCase
185
+ - Termos técnicos: HTTP, JSON, API, SQL, URL, etc.
186
+ - Nomes próprios e termos de domínio específico
187
+ - Chaves de configuração e variáveis de ambiente
188
+
189
+ ### Formato dos findings:
190
+
191
+ - Categoria: `text-quality`
192
+ - Severidade: sempre `INFO`
193
+ - Inclua a correção sugerida no campo `suggestion`
194
+ """
195
+
196
+
155
197
  def build_prompt(
156
198
  diff_files: list[DiffFile],
157
199
  context_graphs: list[ContextGraph],
158
200
  branch: str,
159
201
  base: str,
202
+ text_quality: bool = False,
160
203
  ) -> str:
161
204
  """Monta o prompt completo para a IA.
162
205
 
@@ -165,6 +208,7 @@ def build_prompt(
165
208
  context_graphs: Grafos de contexto com backtracking
166
209
  branch: Nome da branch sendo analisada
167
210
  base: Nome da branch base
211
+ text_quality: Se True, inclui verificação de ortografia e clareza
168
212
 
169
213
  Returns:
170
214
  Prompt completo pronto para enviar à IA
@@ -183,11 +227,15 @@ def build_prompt(
183
227
  lang_code = get_language()
184
228
  language_name = LANGUAGE_NAMES.get(lang_code, lang_code)
185
229
 
230
+ # Seção de text-quality (condicional)
231
+ text_quality_section = get_text_quality_section(language_name) if text_quality else ""
232
+
186
233
  # Substitui placeholders
187
234
  prompt = template.replace("{diff}", diff_section)
188
235
  prompt = prompt.replace("{context}", context_section)
189
236
  prompt = prompt.replace("{references}", references_section)
190
237
  prompt = prompt.replace("{json_schema}", json_schema)
191
238
  prompt = prompt.replace("{language}", language_name)
239
+ prompt = prompt.replace("{text_quality_section}", text_quality_section)
192
240
 
193
241
  return prompt
@@ -25,7 +25,7 @@ O DIFF abaixo mostra APENAS as linhas alteradas, não o arquivo completo. Códig
25
25
  - **Performance**: N+1 queries, loops desnecessários, operações O(n²), falta de índices, carregamento excessivo
26
26
  - **Bugs potenciais**: null pointer, race conditions, off-by-one, divisão por zero, exceções não tratadas
27
27
  - **Recursos não fechados**: conexões de banco, arquivos, sockets, locks não liberados
28
-
28
+ {text_quality_section}
29
29
  ## FORMATO DE SAÍDA
30
30
 
31
31
  Retorne APENAS um JSON válido no formato abaixo. Não inclua explicações fora do JSON.
@@ -121,6 +121,11 @@ def normalize_category(value: str) -> Category:
121
121
  "resource_leak": Category.RESOURCE_LEAK,
122
122
  "leak": Category.RESOURCE_LEAK,
123
123
  "memory": Category.RESOURCE_LEAK,
124
+ "text-quality": Category.TEXT_QUALITY,
125
+ "text_quality": Category.TEXT_QUALITY,
126
+ "spelling": Category.TEXT_QUALITY,
127
+ "grammar": Category.TEXT_QUALITY,
128
+ "typo": Category.TEXT_QUALITY,
124
129
  }
125
130
 
126
131
  return mapping.get(value_lower, Category.BUG)
@@ -0,0 +1,29 @@
1
+ """Testes para o CLI."""
2
+
3
+ from click.testing import CliRunner
4
+
5
+ from code_reviewer.cli import review
6
+
7
+
8
+ class TestReviewCommand:
9
+ """Testes para o comando review."""
10
+
11
+ def test_flag_text_quality_reconhecida(self):
12
+ """Verifica que a flag --text-quality é aceita pelo CLI."""
13
+ runner = CliRunner()
14
+
15
+ # Invoca com --help para verificar que a flag existe
16
+ result = runner.invoke(review, ["--help"])
17
+
18
+ assert result.exit_code == 0
19
+ assert "--text-quality" in result.output
20
+ assert "ortografia" in result.output or "spelling" in result.output.lower()
21
+
22
+ def test_flag_text_quality_short_form(self):
23
+ """Verifica que a forma curta -t funciona."""
24
+ runner = CliRunner()
25
+
26
+ result = runner.invoke(review, ["--help"])
27
+
28
+ assert result.exit_code == 0
29
+ assert "-t" in result.output
@@ -188,3 +188,70 @@ class TestBuildPrompt:
188
188
  assert "REGRAS" in prompt
189
189
  assert "test.py" in prompt
190
190
  assert "review" in prompt # Schema JSON
191
+
192
+ def test_prompt_sem_text_quality_por_padrao(self):
193
+ """Verifica que seção text-quality não aparece quando flag desativada."""
194
+ diff_files = [
195
+ DiffFile(
196
+ path="test.py",
197
+ hunks=[
198
+ DiffHunk(
199
+ function_name="test",
200
+ start_line_old=1,
201
+ start_line_new=1,
202
+ added_lines=[],
203
+ removed_lines=[],
204
+ )
205
+ ],
206
+ )
207
+ ]
208
+ context_graphs = []
209
+
210
+ prompt = build_prompt(diff_files, context_graphs, "feature/test", "main")
211
+
212
+ # Não deve conter seção de text-quality
213
+ assert "QUALIDADE DE TEXTO" not in prompt
214
+ assert "text-quality" not in prompt
215
+
216
+ def test_prompt_com_text_quality_ativo(self):
217
+ """Verifica que seção text-quality aparece quando flag ativada."""
218
+ diff_files = [
219
+ DiffFile(
220
+ path="test.py",
221
+ hunks=[
222
+ DiffHunk(
223
+ function_name="test",
224
+ start_line_old=1,
225
+ start_line_new=1,
226
+ added_lines=[],
227
+ removed_lines=[],
228
+ )
229
+ ],
230
+ )
231
+ ]
232
+ context_graphs = []
233
+
234
+ prompt = build_prompt(
235
+ diff_files, context_graphs, "feature/test", "main", text_quality=True
236
+ )
237
+
238
+ # Deve conter seção de text-quality
239
+ assert "QUALIDADE DE TEXTO" in prompt
240
+ assert "text-quality" in prompt
241
+ assert "ortografia" in prompt
242
+ assert "raise *Error" in prompt
243
+ assert "locales/" in prompt
244
+
245
+ def test_prompt_text_quality_menciona_ignorar_identificadores(self):
246
+ """Verifica que instruções de exclusão estão presentes."""
247
+ diff_files = []
248
+ context_graphs = []
249
+
250
+ prompt = build_prompt(
251
+ diff_files, context_graphs, "feature/test", "main", text_quality=True
252
+ )
253
+
254
+ # Deve instruir a ignorar identificadores
255
+ assert "snake_case" in prompt
256
+ assert "camelCase" in prompt
257
+ assert "termos técnicos" in prompt or "Termos técnicos" in prompt
@@ -117,6 +117,13 @@ class TestNormalizeCategory:
117
117
  assert normalize_category("resource-leak") == Category.RESOURCE_LEAK
118
118
  assert normalize_category("memory") == Category.RESOURCE_LEAK
119
119
 
120
+ def test_text_quality(self):
121
+ assert normalize_category("text-quality") == Category.TEXT_QUALITY
122
+ assert normalize_category("text_quality") == Category.TEXT_QUALITY
123
+ assert normalize_category("spelling") == Category.TEXT_QUALITY
124
+ assert normalize_category("grammar") == Category.TEXT_QUALITY
125
+ assert normalize_category("typo") == Category.TEXT_QUALITY
126
+
120
127
  def test_desconhecido_para_bug(self):
121
128
  assert normalize_category("unknown") == Category.BUG
122
129
 
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes