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.
- {airev-1.2.0 → airev-1.3.0}/PKG-INFO +1 -1
- {airev-1.2.0 → airev-1.3.0}/src/airev.egg-info/PKG-INFO +1 -1
- {airev-1.2.0 → airev-1.3.0}/src/airev.egg-info/SOURCES.txt +1 -0
- {airev-1.2.0 → airev-1.3.0}/src/code_reviewer/__init__.py +1 -1
- {airev-1.2.0 → airev-1.3.0}/src/code_reviewer/cli.py +13 -1
- {airev-1.2.0 → airev-1.3.0}/src/code_reviewer/formatters/terminal.py +25 -2
- {airev-1.2.0 → airev-1.3.0}/src/code_reviewer/locales/en.yaml +7 -0
- {airev-1.2.0 → airev-1.3.0}/src/code_reviewer/locales/pt-br.yaml +7 -0
- {airev-1.2.0 → airev-1.3.0}/src/code_reviewer/models.py +2 -1
- {airev-1.2.0 → airev-1.3.0}/src/code_reviewer/prompt_builder.py +48 -0
- {airev-1.2.0 → airev-1.3.0}/src/code_reviewer/prompts/review_system.md +1 -1
- {airev-1.2.0 → airev-1.3.0}/src/code_reviewer/response_parser.py +5 -0
- airev-1.3.0/tests/test_cli.py +29 -0
- {airev-1.2.0 → airev-1.3.0}/tests/test_prompt_builder.py +67 -0
- {airev-1.2.0 → airev-1.3.0}/tests/test_response_parser.py +7 -0
- {airev-1.2.0 → airev-1.3.0}/README.md +0 -0
- {airev-1.2.0 → airev-1.3.0}/pyproject.toml +0 -0
- {airev-1.2.0 → airev-1.3.0}/setup.cfg +0 -0
- {airev-1.2.0 → airev-1.3.0}/src/airev.egg-info/dependency_links.txt +0 -0
- {airev-1.2.0 → airev-1.3.0}/src/airev.egg-info/entry_points.txt +0 -0
- {airev-1.2.0 → airev-1.3.0}/src/airev.egg-info/requires.txt +0 -0
- {airev-1.2.0 → airev-1.3.0}/src/airev.egg-info/top_level.txt +0 -0
- {airev-1.2.0 → airev-1.3.0}/src/code_reviewer/context_builder.py +0 -0
- {airev-1.2.0 → airev-1.3.0}/src/code_reviewer/diff_parser.py +0 -0
- {airev-1.2.0 → airev-1.3.0}/src/code_reviewer/formatters/__init__.py +0 -0
- {airev-1.2.0 → airev-1.3.0}/src/code_reviewer/formatters/progress.py +0 -0
- {airev-1.2.0 → airev-1.3.0}/src/code_reviewer/i18n/__init__.py +0 -0
- {airev-1.2.0 → airev-1.3.0}/src/code_reviewer/runners/__init__.py +0 -0
- {airev-1.2.0 → airev-1.3.0}/src/code_reviewer/runners/base.py +0 -0
- {airev-1.2.0 → airev-1.3.0}/src/code_reviewer/runners/copilot.py +0 -0
- {airev-1.2.0 → airev-1.3.0}/src/code_reviewer/runners/gemini.py +0 -0
- {airev-1.2.0 → airev-1.3.0}/src/code_reviewer/updater/__init__.py +0 -0
- {airev-1.2.0 → airev-1.3.0}/src/code_reviewer/updater/http_client.py +0 -0
- {airev-1.2.0 → airev-1.3.0}/src/code_reviewer/updater/notifier.py +0 -0
- {airev-1.2.0 → airev-1.3.0}/src/code_reviewer/updater/upgrade.py +0 -0
- {airev-1.2.0 → airev-1.3.0}/src/code_reviewer/updater/version_check.py +0 -0
- {airev-1.2.0 → airev-1.3.0}/tests/test_context_builder.py +0 -0
- {airev-1.2.0 → airev-1.3.0}/tests/test_copilot_runner.py +0 -0
- {airev-1.2.0 → airev-1.3.0}/tests/test_diff_parser.py +0 -0
- {airev-1.2.0 → airev-1.3.0}/tests/test_i18n.py +0 -0
- {airev-1.2.0 → airev-1.3.0}/tests/test_progress.py +0 -0
- {airev-1.2.0 → airev-1.3.0}/tests/test_terminal_formatter.py +0 -0
- {airev-1.2.0 → airev-1.3.0}/tests/test_updater.py +0 -0
|
@@ -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
|
|
@@ -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(
|
|
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
|
|
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
|
|
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
|