gitpr-cli 0.0.13__tar.gz → 0.0.15__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 (24) hide show
  1. {gitpr_cli-0.0.13 → gitpr_cli-0.0.15}/PKG-INFO +12 -2
  2. {gitpr_cli-0.0.13 → gitpr_cli-0.0.15}/README.md +11 -1
  3. {gitpr_cli-0.0.13 → gitpr_cli-0.0.15}/gitpr_cli.egg-info/PKG-INFO +12 -2
  4. {gitpr_cli-0.0.13 → gitpr_cli-0.0.15}/gitpr_cli.egg-info/SOURCES.txt +3 -0
  5. {gitpr_cli-0.0.13 → gitpr_cli-0.0.15}/pyproject.toml +1 -1
  6. gitpr_cli-0.0.15/src/blame_engine.py +216 -0
  7. {gitpr_cli-0.0.13 → gitpr_cli-0.0.15}/src/config.py +13 -2
  8. {gitpr_cli-0.0.13 → gitpr_cli-0.0.15}/src/core.py +4 -0
  9. gitpr_cli-0.0.15/src/issue_engine.py +76 -0
  10. {gitpr_cli-0.0.13 → gitpr_cli-0.0.15}/src/main.py +83 -2
  11. gitpr_cli-0.0.15/src/tui_issue.py +34 -0
  12. {gitpr_cli-0.0.13 → gitpr_cli-0.0.15}/src/updater.py +1 -1
  13. {gitpr_cli-0.0.13 → gitpr_cli-0.0.15}/LICENSE +0 -0
  14. {gitpr_cli-0.0.13 → gitpr_cli-0.0.15}/gitpr_cli.egg-info/dependency_links.txt +0 -0
  15. {gitpr_cli-0.0.13 → gitpr_cli-0.0.15}/gitpr_cli.egg-info/entry_points.txt +0 -0
  16. {gitpr_cli-0.0.13 → gitpr_cli-0.0.15}/gitpr_cli.egg-info/requires.txt +0 -0
  17. {gitpr_cli-0.0.13 → gitpr_cli-0.0.15}/gitpr_cli.egg-info/top_level.txt +0 -0
  18. {gitpr_cli-0.0.13 → gitpr_cli-0.0.15}/setup.cfg +0 -0
  19. {gitpr_cli-0.0.13 → gitpr_cli-0.0.15}/src/__init__.py +0 -0
  20. {gitpr_cli-0.0.13 → gitpr_cli-0.0.15}/src/ai_providers.py +0 -0
  21. {gitpr_cli-0.0.13 → gitpr_cli-0.0.15}/src/cache.py +0 -0
  22. {gitpr_cli-0.0.13 → gitpr_cli-0.0.15}/src/linter_engine.py +0 -0
  23. {gitpr_cli-0.0.13 → gitpr_cli-0.0.15}/src/security.py +0 -0
  24. {gitpr_cli-0.0.13 → gitpr_cli-0.0.15}/tests/test_core.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: gitpr-cli
3
- Version: 0.0.13
3
+ Version: 0.0.15
4
4
  Summary: Automação de PRs, Commits e Code Review com IA (Gemini e DeepSeek)
5
5
  Author-email: Natan Fiuza <contato@natanfiuza.dev.br>
6
6
  Requires-Python: >=3.10
@@ -29,6 +29,8 @@ Este projeto foi desenvolvido em Python e utiliza as seguintes bibliotecas princ
29
29
  * [**Pytest**](https://docs.pytest.org/): Para execução de testes unitários de forma simples, colorida e legível no console.
30
30
  * [**Cryptography**](https://cryptography.io/): Para garantir que sua `GEMINI_API_KEY` seja armazenada de forma encriptada e segura no disco.
31
31
  * [**PyYAML**](https://pyyaml.org/): Utilizado para ler e processar as regras customizadas de análise estática do arquivo `.gitpr.linter.yml`.
32
+ * [**Textual**](https://textual.textualize.io/): Biblioteca poderosa para a criação de Interfaces Gráficas de Terminal (TUI), utilizada no painel interativo de geração e edição de Issues.
33
+ * [**Requests**](https://pypi.org/project/requests/): Biblioteca elegante e robusta para requisições HTTP, utilizada para a comunicação com a API REST do GitHub.
32
34
 
33
35
  ----
34
36
 
@@ -124,6 +126,7 @@ Você pode passar as seguintes *flags* para ações específicas:
124
126
  * `-l` ou `--linter`: Roda **apenas o linter estático local** (sem chamadas de IA). Ideal para usar em pipelines de CI/CD para bloquear código fora do padrão.
125
127
  * `-ih` ou `--installhooks`: Instala automaticamente os **Git Hooks locais** (`pre-commit` e `prepare-commit-msg`) no seu repositório.
126
128
  * `-s` ou `--skill`: Cria os arquivos de template de contexto da IA (`.gitpr.commit.md`, `.gitpr.pr.md`, `.gitpr.review.md`, `.gitpr.filereview.md`) e do Linter (`.gitpr.linter.yml`) na raiz do projeto.
129
+ * `-is` ou `--issue`: Gera automaticamente o rascunho de uma **Issue padronizada** a partir do diff e abre uma interface interativa (TUI) para edição, salvamento ou envio direto para o repositório no GitHub via API REST.
127
130
  * `-u` ou `--update`: Verifica e instala a versão mais recente do GitPR (Auto-Updater).
128
131
  * `-h` ou `--help`: Exibe o menu de ajuda.
129
132
 
@@ -175,7 +178,8 @@ Se você deseja implementar o GitPR como uma barreira de qualidade automatizada
175
178
 
176
179
  * [**Guia de Git Hooks Locais (Shift-Left)**](docs/local-git-hooks.md): Como usar o `gitpr --installhooks` para criar travas na máquina do desenvolvedor (bloqueio de *console.log*, *localhost*, etc.) e usar a IA para escrever mensagens de commit automaticamente no editor.
177
180
  * [**Integração com CI/CD (GitHub Actions)**](docs/github-ci-linter.md): Como rodar o GitPR no seu pipeline na nuvem para realizar a validação estática e travar o botão de "Merge" de Pull Requests que violem as regras do projeto.
178
-
181
+ * [**Geração de Issues e Interface TUI**](docs/issue-tui-help.md): Como utilizar a interface gráfica de terminal (TUI) para revisar e gerenciar Issues estruturadas antes do envio.
182
+ * [**Integração e Segurança do Token GitHub (PAT)**](docs/github-pat-integration.md): Entenda como o GitPR gera issues diretamente no seu repositório de forma autenticada, mantendo suas credenciais criptografadas localmente.
179
183
 
180
184
  ## ⚡ Sistema de Cache Local (Economia de Quota)
181
185
 
@@ -190,6 +194,12 @@ Nunca mais se preocupe em baixar novas versões manualmente. O GitPR possui um G
190
194
  * Você pode forçar a busca e instalação rodando `gitpr --update` ou `gitpr -u`.
191
195
  * A ferramenta utiliza a técnica de *Hot-Swap*, baixando o novo `.exe` e substituindo a versão antiga de forma transparente.
192
196
 
197
+ ## Publicar no PyPi
198
+
199
+ ```bash
200
+ pipenv run python -m build
201
+ pipenv run twine upload dist/*
202
+ ```
193
203
  ## **🤝 Como Contribuir**
194
204
 
195
205
  Contribuições são muito bem-vindas! Para contribuir:
@@ -13,6 +13,8 @@ Este projeto foi desenvolvido em Python e utiliza as seguintes bibliotecas princ
13
13
  * [**Pytest**](https://docs.pytest.org/): Para execução de testes unitários de forma simples, colorida e legível no console.
14
14
  * [**Cryptography**](https://cryptography.io/): Para garantir que sua `GEMINI_API_KEY` seja armazenada de forma encriptada e segura no disco.
15
15
  * [**PyYAML**](https://pyyaml.org/): Utilizado para ler e processar as regras customizadas de análise estática do arquivo `.gitpr.linter.yml`.
16
+ * [**Textual**](https://textual.textualize.io/): Biblioteca poderosa para a criação de Interfaces Gráficas de Terminal (TUI), utilizada no painel interativo de geração e edição de Issues.
17
+ * [**Requests**](https://pypi.org/project/requests/): Biblioteca elegante e robusta para requisições HTTP, utilizada para a comunicação com a API REST do GitHub.
16
18
 
17
19
  ----
18
20
 
@@ -108,6 +110,7 @@ Você pode passar as seguintes *flags* para ações específicas:
108
110
  * `-l` ou `--linter`: Roda **apenas o linter estático local** (sem chamadas de IA). Ideal para usar em pipelines de CI/CD para bloquear código fora do padrão.
109
111
  * `-ih` ou `--installhooks`: Instala automaticamente os **Git Hooks locais** (`pre-commit` e `prepare-commit-msg`) no seu repositório.
110
112
  * `-s` ou `--skill`: Cria os arquivos de template de contexto da IA (`.gitpr.commit.md`, `.gitpr.pr.md`, `.gitpr.review.md`, `.gitpr.filereview.md`) e do Linter (`.gitpr.linter.yml`) na raiz do projeto.
113
+ * `-is` ou `--issue`: Gera automaticamente o rascunho de uma **Issue padronizada** a partir do diff e abre uma interface interativa (TUI) para edição, salvamento ou envio direto para o repositório no GitHub via API REST.
111
114
  * `-u` ou `--update`: Verifica e instala a versão mais recente do GitPR (Auto-Updater).
112
115
  * `-h` ou `--help`: Exibe o menu de ajuda.
113
116
 
@@ -159,7 +162,8 @@ Se você deseja implementar o GitPR como uma barreira de qualidade automatizada
159
162
 
160
163
  * [**Guia de Git Hooks Locais (Shift-Left)**](docs/local-git-hooks.md): Como usar o `gitpr --installhooks` para criar travas na máquina do desenvolvedor (bloqueio de *console.log*, *localhost*, etc.) e usar a IA para escrever mensagens de commit automaticamente no editor.
161
164
  * [**Integração com CI/CD (GitHub Actions)**](docs/github-ci-linter.md): Como rodar o GitPR no seu pipeline na nuvem para realizar a validação estática e travar o botão de "Merge" de Pull Requests que violem as regras do projeto.
162
-
165
+ * [**Geração de Issues e Interface TUI**](docs/issue-tui-help.md): Como utilizar a interface gráfica de terminal (TUI) para revisar e gerenciar Issues estruturadas antes do envio.
166
+ * [**Integração e Segurança do Token GitHub (PAT)**](docs/github-pat-integration.md): Entenda como o GitPR gera issues diretamente no seu repositório de forma autenticada, mantendo suas credenciais criptografadas localmente.
163
167
 
164
168
  ## ⚡ Sistema de Cache Local (Economia de Quota)
165
169
 
@@ -174,6 +178,12 @@ Nunca mais se preocupe em baixar novas versões manualmente. O GitPR possui um G
174
178
  * Você pode forçar a busca e instalação rodando `gitpr --update` ou `gitpr -u`.
175
179
  * A ferramenta utiliza a técnica de *Hot-Swap*, baixando o novo `.exe` e substituindo a versão antiga de forma transparente.
176
180
 
181
+ ## Publicar no PyPi
182
+
183
+ ```bash
184
+ pipenv run python -m build
185
+ pipenv run twine upload dist/*
186
+ ```
177
187
  ## **🤝 Como Contribuir**
178
188
 
179
189
  Contribuições são muito bem-vindas! Para contribuir:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: gitpr-cli
3
- Version: 0.0.13
3
+ Version: 0.0.15
4
4
  Summary: Automação de PRs, Commits e Code Review com IA (Gemini e DeepSeek)
5
5
  Author-email: Natan Fiuza <contato@natanfiuza.dev.br>
6
6
  Requires-Python: >=3.10
@@ -29,6 +29,8 @@ Este projeto foi desenvolvido em Python e utiliza as seguintes bibliotecas princ
29
29
  * [**Pytest**](https://docs.pytest.org/): Para execução de testes unitários de forma simples, colorida e legível no console.
30
30
  * [**Cryptography**](https://cryptography.io/): Para garantir que sua `GEMINI_API_KEY` seja armazenada de forma encriptada e segura no disco.
31
31
  * [**PyYAML**](https://pyyaml.org/): Utilizado para ler e processar as regras customizadas de análise estática do arquivo `.gitpr.linter.yml`.
32
+ * [**Textual**](https://textual.textualize.io/): Biblioteca poderosa para a criação de Interfaces Gráficas de Terminal (TUI), utilizada no painel interativo de geração e edição de Issues.
33
+ * [**Requests**](https://pypi.org/project/requests/): Biblioteca elegante e robusta para requisições HTTP, utilizada para a comunicação com a API REST do GitHub.
32
34
 
33
35
  ----
34
36
 
@@ -124,6 +126,7 @@ Você pode passar as seguintes *flags* para ações específicas:
124
126
  * `-l` ou `--linter`: Roda **apenas o linter estático local** (sem chamadas de IA). Ideal para usar em pipelines de CI/CD para bloquear código fora do padrão.
125
127
  * `-ih` ou `--installhooks`: Instala automaticamente os **Git Hooks locais** (`pre-commit` e `prepare-commit-msg`) no seu repositório.
126
128
  * `-s` ou `--skill`: Cria os arquivos de template de contexto da IA (`.gitpr.commit.md`, `.gitpr.pr.md`, `.gitpr.review.md`, `.gitpr.filereview.md`) e do Linter (`.gitpr.linter.yml`) na raiz do projeto.
129
+ * `-is` ou `--issue`: Gera automaticamente o rascunho de uma **Issue padronizada** a partir do diff e abre uma interface interativa (TUI) para edição, salvamento ou envio direto para o repositório no GitHub via API REST.
127
130
  * `-u` ou `--update`: Verifica e instala a versão mais recente do GitPR (Auto-Updater).
128
131
  * `-h` ou `--help`: Exibe o menu de ajuda.
129
132
 
@@ -175,7 +178,8 @@ Se você deseja implementar o GitPR como uma barreira de qualidade automatizada
175
178
 
176
179
  * [**Guia de Git Hooks Locais (Shift-Left)**](docs/local-git-hooks.md): Como usar o `gitpr --installhooks` para criar travas na máquina do desenvolvedor (bloqueio de *console.log*, *localhost*, etc.) e usar a IA para escrever mensagens de commit automaticamente no editor.
177
180
  * [**Integração com CI/CD (GitHub Actions)**](docs/github-ci-linter.md): Como rodar o GitPR no seu pipeline na nuvem para realizar a validação estática e travar o botão de "Merge" de Pull Requests que violem as regras do projeto.
178
-
181
+ * [**Geração de Issues e Interface TUI**](docs/issue-tui-help.md): Como utilizar a interface gráfica de terminal (TUI) para revisar e gerenciar Issues estruturadas antes do envio.
182
+ * [**Integração e Segurança do Token GitHub (PAT)**](docs/github-pat-integration.md): Entenda como o GitPR gera issues diretamente no seu repositório de forma autenticada, mantendo suas credenciais criptografadas localmente.
179
183
 
180
184
  ## ⚡ Sistema de Cache Local (Economia de Quota)
181
185
 
@@ -190,6 +194,12 @@ Nunca mais se preocupe em baixar novas versões manualmente. O GitPR possui um G
190
194
  * Você pode forçar a busca e instalação rodando `gitpr --update` ou `gitpr -u`.
191
195
  * A ferramenta utiliza a técnica de *Hot-Swap*, baixando o novo `.exe` e substituindo a versão antiga de forma transparente.
192
196
 
197
+ ## Publicar no PyPi
198
+
199
+ ```bash
200
+ pipenv run python -m build
201
+ pipenv run twine upload dist/*
202
+ ```
193
203
  ## **🤝 Como Contribuir**
194
204
 
195
205
  Contribuições são muito bem-vindas! Para contribuir:
@@ -9,11 +9,14 @@ gitpr_cli.egg-info/requires.txt
9
9
  gitpr_cli.egg-info/top_level.txt
10
10
  src/__init__.py
11
11
  src/ai_providers.py
12
+ src/blame_engine.py
12
13
  src/cache.py
13
14
  src/config.py
14
15
  src/core.py
16
+ src/issue_engine.py
15
17
  src/linter_engine.py
16
18
  src/main.py
17
19
  src/security.py
20
+ src/tui_issue.py
18
21
  src/updater.py
19
22
  tests/test_core.py
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "gitpr-cli"
7
- version = "0.0.13"
7
+ version = "0.0.15"
8
8
  description = "Automação de PRs, Commits e Code Review com IA (Gemini e DeepSeek)"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -0,0 +1,216 @@
1
+ import subprocess
2
+ import click
3
+ import re
4
+ import os
5
+ from datetime import datetime
6
+ from src.core import get_current_branch
7
+ from src.config import get_api_key, get_api_model, get_ai_provider
8
+ from src.ai_providers import call_ai_model
9
+
10
+ def execute_git_blame(file_path, start_line, end_line, commit_hash=None):
11
+ """Executa o git blame e retorna uma lista de hashes únicos."""
12
+ cmd = ["git", "blame", f"-L", f"{start_line},{end_line}"]
13
+ if commit_hash:
14
+ cmd.append(commit_hash)
15
+ cmd.extend(["--", file_path])
16
+
17
+ try:
18
+ result = subprocess.run(
19
+ cmd, capture_output=True, text=True, encoding="utf-8", errors="replace", check=True
20
+ )
21
+ hashes = set()
22
+ for line in result.stdout.strip().split('\n'):
23
+ if line:
24
+ match = re.match(r'^([a-fA-F0-9]+)\s', line)
25
+ if match:
26
+ commit = match.group(1)
27
+ if not commit.startswith('000000'):
28
+ hashes.add(commit)
29
+ return list(hashes)
30
+ except subprocess.CalledProcessError as e:
31
+ # Se falhar (ex: arquivo não existia naquele commit antigo), silenciamos e retornamos vazio
32
+ return []
33
+
34
+ def execute_git_show(commit_hash, file_path):
35
+ """Executa o git show para pegar o diff exato."""
36
+ cmd = ["git", "show", commit_hash, "--", file_path]
37
+ try:
38
+ result = subprocess.run(
39
+ cmd, capture_output=True, text=True, encoding="utf-8", errors="replace", check=True
40
+ )
41
+ return result.stdout
42
+ except subprocess.CalledProcessError:
43
+ return None
44
+
45
+ def get_commit_info(commit_hash):
46
+ """Busca autor, data e mensagem do commit."""
47
+ cmd = ["git", "show", "-s", "--format=%an|%ad|%s", "--date=short", commit_hash]
48
+ try:
49
+ res = subprocess.run(cmd, capture_output=True, text=True, encoding="utf-8", errors="replace", check=True)
50
+ parts = res.stdout.strip().split('|', 2)
51
+ if len(parts) == 3:
52
+ return {"author": parts[0], "date": parts[1], "message": parts[2]}
53
+ except:
54
+ pass
55
+ return {"author": "Desconhecido", "date": "Desconhecida", "message": "Sem mensagem"}
56
+
57
+ def analyze_commit_with_ai(commit_hash, file_path):
58
+ """Usa a IA para ler o diff e classificar como ORIGEM ou REFATORACAO."""
59
+ diff = execute_git_show(commit_hash, file_path)
60
+ if not diff:
61
+ return {"status": "ORIGEM", "motivo": "Diff não encontrado (arquivo possivelmente criado aqui)."}
62
+
63
+ provider = get_ai_provider()
64
+ api_key = get_api_key(provider)
65
+ if not api_key:
66
+ return {"status": "ORIGEM", "motivo": "Sem chave de API. Assumindo origem."}
67
+
68
+ # Usamos o modelo 'simple' (Flash/Lite) para economizar dinheiro no loop
69
+ api_model = get_api_model(provider, task_complexity="simple")
70
+
71
+ skill_path = os.path.join(os.getcwd(), ".gitpr.blame.md")
72
+ if os.path.exists(skill_path):
73
+ with open(skill_path, "r", encoding="utf-8") as f:
74
+ sys_inst = f.read()
75
+ else:
76
+ sys_inst = 'Você é um Arquiteto de Software. Analise o diff e determine se é a ORIGEM da regra (lógica nova) ou REFATORAÇÃO. Responda APENAS com JSON: {"status": "ORIGEM", "motivo": "Explique o que foi introduzido"} ou {"status": "REFATORACAO", "motivo": "Explique o que foi alterado"}'
77
+
78
+ prompt = (
79
+ f"Analise o diff do commit {commit_hash} e retorne o JSON solicitado.\n\n"
80
+ f"DIFF:\n{diff[:4000]}" # Limitamos a 4000 caracteres para não pesar a requisição
81
+ )
82
+
83
+ click.secho(f" 🤖 Consultando a IA ({api_model}) sobre o commit {commit_hash[:8]}...", fg="cyan", dim=True)
84
+
85
+ result_json = call_ai_model(provider, api_key, api_model, prompt, sys_inst)
86
+
87
+ if result_json and "status" in result_json:
88
+ return result_json
89
+
90
+ return {"status": "ORIGEM", "motivo": "IA não retornou formato válido."}
91
+
92
+ def run_blame_analysis(file_path, start_line, end_line):
93
+ """Motor de Loop Temporal que constrói a Timeline consolidada."""
94
+ click.secho(f"\n🔍 Iniciando Arqueologia de Código...", fg="cyan", bold=True)
95
+ click.echo(f"📍 Arquivo: {file_path} (Linhas: {start_line} até {end_line})")
96
+
97
+ initial_commits = execute_git_blame(file_path, start_line, end_line)
98
+
99
+ if not initial_commits:
100
+ click.secho("⚠️ Nenhum commit rastreável encontrado nestas linhas.", fg="yellow")
101
+ return
102
+
103
+ click.secho(f"✅ Encontrado(s) {len(initial_commits)} commit(s) na superfície. Iniciando viagem no tempo...\n", fg="green")
104
+
105
+ master_timeline = []
106
+ seen_hashes = set()
107
+
108
+ # LOOP DE COLETA DE DADOS
109
+ for base_commit in initial_commits:
110
+ current_commit = base_commit
111
+ depth = 0
112
+ max_depth = 4 # Trava de segurança para não rodar infinito em código legado
113
+
114
+ while depth < max_depth:
115
+ # Se já analisamos este commit em outra trilha, não gasta requisição à toa
116
+ if current_commit in seen_hashes:
117
+ break
118
+
119
+ seen_hashes.add(current_commit)
120
+ info = get_commit_info(current_commit)
121
+ ai_analysis = analyze_commit_with_ai(current_commit, file_path)
122
+
123
+ status = str(ai_analysis.get("status", "ORIGEM")).upper()
124
+ motivo = str(ai_analysis.get("motivo", ""))
125
+
126
+ master_timeline.append({
127
+ "hash": current_commit[:8],
128
+ "info": info,
129
+ "status": status,
130
+ "motivo": motivo,
131
+ "raw_date": info["date"] # Usado para ordenação
132
+ })
133
+
134
+ if status == "ORIGEM":
135
+ break
136
+
137
+ # É refatoração, vamos buscar o commit pai no passado
138
+ depth += 1
139
+ parent_hash = f"{current_commit}^"
140
+ parent_commits = execute_git_blame(file_path, start_line, end_line, parent_hash)
141
+
142
+ if not parent_commits:
143
+ break
144
+ current_commit = parent_commits[0]
145
+
146
+ # ORDENAÇÃO CRONOLÓGICA (Do mais antigo para o mais novo)
147
+ master_timeline.sort(key=lambda x: x["raw_date"])
148
+
149
+ # EXIBIÇÃO VISUAL NO TERMINAL (ÚNICA)
150
+ click.secho(f"\n📜 Histórico Consolidado da Regra (Linhas {start_line}-{end_line}):", fg="magenta", bold=True)
151
+
152
+ for item in master_timeline:
153
+ cor = "green" if item["status"] == "ORIGEM" else "yellow"
154
+ icone = "👶" if item["status"] == "ORIGEM" else "🔧"
155
+
156
+ click.secho(f"\n[{item['info']['date']}] {icone} {item['status']}: Por {item['info']['author']} (Commit: {item['hash']})", fg=cor, bold=True)
157
+ click.echo(f" └─ Mensagem: \"{item['info']['message']}\"")
158
+ if item["motivo"]:
159
+ click.secho(f" └─ Análise IA: {item['motivo']}", fg="cyan", dim=True)
160
+
161
+ click.echo("\n" + "-"*60 + "\n")
162
+
163
+ # GERAÇÃO DO RELATÓRIO MARKDOWN (ÚNICO)
164
+ click.secho("📝 Gerando relatório Markdown unificado com o resumo da IA...", fg="cyan")
165
+
166
+ branch_name = get_current_branch()
167
+ safe_branch_name = branch_name.replace("/", "-").replace("\\", "-")
168
+ current_time = datetime.now().strftime("%Y%m%d%H%M%S")
169
+
170
+ pattern = os.getenv("OUTPUT_FILE_NAME_BLAME", "{branch}_{datetime}_BLAME_REPORT.md")
171
+ output_filename = pattern.format(branch=safe_branch_name, datetime=current_time)
172
+
173
+ # Monta a Tabela Markdown
174
+ md_content = f"# Linha do tempo da regra investigada\n\n"
175
+ md_content += f"**Arquivo:** `{file_path}` (Linhas {start_line}-{end_line})\n\n"
176
+ md_content += "| Data | Commit | Autor | O quê |\n"
177
+ md_content += "|---|---|---|---|\n"
178
+
179
+ for item in master_timeline:
180
+ data_fmt = item['info']['date']
181
+ hash_curto = item['hash']
182
+ autor = item['info']['author']
183
+ msg_commit = item['info']['message']
184
+
185
+ # Pega a explicação da IA ou coloca um fallback seguro
186
+ explicacao_ia = item['motivo'] if item['motivo'] else "Alteração identificada na regra"
187
+
188
+ # Junta a explicação da IA com a mensagem do commit (Estilo Tabela de Referência)
189
+ motivo_final = f"{explicacao_ia} — *\"{msg_commit}\"*"
190
+
191
+ md_content += f"| {data_fmt} | `{hash_curto}` | {autor} | {motivo_final} |\n"
192
+
193
+ # IA gera o Resumo Analítico Final
194
+ summary_prompt = "Baseado na seguinte linha do tempo de commits de uma regra de negócio, escreva um único parágrafo resumindo a idade da regra, o autor original, o número de refatorações e deduza qual era a intenção original de negócio (o motivo real da regra existir no sistema).\n\n"
195
+ for item in master_timeline:
196
+ summary_prompt += f"[{item['info']['date']}] {item['info']['author']} ({item['hash']}) - {item['status']}: {item['motivo']}\n"
197
+
198
+ provider = get_ai_provider()
199
+ api_key = get_api_key(provider)
200
+ api_model = get_api_model(provider, task_complexity="advanced")
201
+ sys_inst = "Você é um Arquiteto de Software. Gere APENAS um objeto JSON no formato {\"resumo\": \"texto do resumo\"}."
202
+
203
+ click.secho(f" 🤖 Consultando a IA ({api_model}) para o Resumo Executivo...", fg="cyan", dim=True)
204
+ summary_json = call_ai_model(provider, api_key, api_model, summary_prompt, sys_inst)
205
+
206
+ resumo_texto = summary_json.get("resumo", "Resumo não disponível.") if summary_json else "Resumo não disponível."
207
+
208
+ md_content += f"\n**Resumo:** {resumo_texto}\n"
209
+
210
+ # Salva no disco
211
+ try:
212
+ with open(output_filename, "w", encoding="utf-8") as f:
213
+ f.write(md_content)
214
+ click.secho(f"✅ Relatório unificado salvo com sucesso: '{output_filename}'", fg="green", bold=True)
215
+ except Exception as e:
216
+ click.secho(f"❌ Erro ao salvar o relatório: {e}", fg="red")
@@ -20,7 +20,9 @@ DEFAULT_CONFIG = {
20
20
  "OUTPUT_FILE_NAME": "{branch}_{datetime}_PR_DESC.md",
21
21
  "OUTPUT_FILE_NAME_REVIEW": "{branch}_{datetime}_PR_REVIEW.txt",
22
22
  "OUTPUT_FILE_NAME_FULLREVIEW": "{branch}_{datetime}_PR_FULLREVIEW.txt",
23
- "OUTPUT_FILE_NAME_FILEREVIEW": "{branch}_{datetime}_FILE_REVIEW.txt"
23
+ "OUTPUT_FILE_NAME_FILEREVIEW": "{branch}_{datetime}_FILE_REVIEW.txt",
24
+ "OUTPUT_FILE_NAME_BLAME": "{branch}_{datetime}_BLAME_REPORT.md",
25
+ "OUTPUT_FILE_NAME_ISSUE": "{branch}_{datetime}_ISSUE.md"
24
26
  }
25
27
 
26
28
  def get_ai_provider():
@@ -151,4 +153,13 @@ def load_linter_rules():
151
153
  return []
152
154
  except Exception as e:
153
155
  click.secho(f"\n❌ Erro inesperado ao ler as regras do linter: {e}", fg="red")
154
- return []
156
+ return []
157
+
158
+ def get_github_token():
159
+ """Lê e desencripta o token de acesso pessoal (PAT) do GitHub."""
160
+ load_dotenv(ENV_FILE)
161
+ encrypted_token = os.getenv("GITHUB_TOKEN_ENCRYPTED")
162
+
163
+ if encrypted_token:
164
+ return decrypt_data(encrypted_token)
165
+ return None
@@ -76,6 +76,8 @@ def get_skill_context(action_type="pr"):
76
76
  target_file = ".gitpr.pr.md"
77
77
  elif action_type == "filereview": # NOVO!
78
78
  target_file = ".gitpr.filereview.md"
79
+ elif action_type == "issue":
80
+ target_file = ".gitpr.issue.md"
79
81
  else: # review ou fullreview
80
82
  target_file = ".gitpr.review.md"
81
83
 
@@ -188,6 +190,8 @@ def generate_skill_template():
188
190
  ".gitpr.review.md": "gitpr.review.md",
189
191
  ".gitpr.linter.yml": "gitpr.linter.yml",
190
192
  ".gitpr.filereview.md": "gitpr.filereview.md",
193
+ ".gitpr.blame.md": "gitpr.blame.md",
194
+ ".gitpr.issue.md": "gitpr.issue.md",
191
195
  }
192
196
 
193
197
  success_count = 0
@@ -0,0 +1,76 @@
1
+ import subprocess
2
+ import re
3
+ import os
4
+ import click
5
+ from src.ai_providers import call_ai_model
6
+ from src.cache import get_cached_response, save_cached_response
7
+ from src.config import get_api_key, get_api_model, get_ai_provider
8
+ from src.ai_providers import call_ai_model
9
+
10
+ def get_github_repo_info():
11
+ """Extrai o formato owner/repo do comando git remote -v."""
12
+ try:
13
+ result = subprocess.run(
14
+ ["git", "remote", "-v"],
15
+ capture_output=True,
16
+ text=True,
17
+ check=True
18
+ )
19
+
20
+ # Busca padrões como git@github.com:owner/repo.git ou https://github.com/owner/repo.git
21
+ match = re.search(r'github\.com[:/](.+?)/(.+?)(\.git)?\s+\(push\)', result.stdout)
22
+
23
+ if match:
24
+ owner = match.group(1)
25
+ repo = match.group(2).replace('.git', '')
26
+ return f"{owner}/{repo}"
27
+
28
+ return None
29
+ except subprocess.CalledProcessError:
30
+ return None
31
+
32
+ def generate_issue_content(diff_text):
33
+ """Envia o diff para a IA e retorna um dicionário com título e corpo da issue."""
34
+ if not diff_text or not diff_text.strip():
35
+ return None
36
+
37
+ provider = get_ai_provider()
38
+ api_key = get_api_key(provider)
39
+
40
+ if not api_key:
41
+ click.secho("❌ Erro: Chave de API não encontrada.", fg="red")
42
+ return None
43
+
44
+ # Utilizamos o modelo avançado para garantir a qualidade da estrutura da Issue
45
+ api_model = get_api_model(provider, task_complexity="advanced")
46
+
47
+ skill_path = os.path.join(os.getcwd(), ".gitpr.issue.md")
48
+ sys_inst = ""
49
+
50
+ if os.path.exists(skill_path):
51
+ with open(skill_path, "r", encoding="utf-8") as f:
52
+ sys_inst = f.read()
53
+ else:
54
+ sys_inst = "Você é um Arquiteto de Software. Siga o formato O Que / Por Que / Onde / Como para documentar a Issue."
55
+
56
+ prompt = (
57
+ f"Gere o objeto JSON solicitado seguindo as instruções de sistema para documentar a seguinte alteração:\n\n"
58
+ f"DIFF PARA ANÁLISE:\n{diff_text}"
59
+ )
60
+
61
+ # --- NOVO: Tenta recuperar do Cache ---
62
+ cached_data = get_cached_response("issue", prompt)
63
+ if cached_data:
64
+ click.secho("⚡ Resposta da Issue recuperada do cache local.", fg="green", dim=True)
65
+ return cached_data
66
+
67
+ click.secho(f"🤖 Estruturando a Issue usando {provider.capitalize()} ({api_model})...", fg="cyan", dim=True)
68
+
69
+ result_json = call_ai_model(provider, api_key, api_model, prompt, sys_inst)
70
+
71
+ if result_json and "titulo" in result_json and "corpo" in result_json:
72
+ # --- NOVO: Salva no Cache ---
73
+ save_cached_response("issue", "issue", prompt, result_json)
74
+ return result_json
75
+
76
+ return {"titulo": "Erro ao gerar título", "corpo": "Não foi possível gerar o corpo da issue pela IA."}
@@ -29,7 +29,7 @@ def print_banner():
29
29
  """
30
30
  click.secho(banner, fg="cyan", bold=True)
31
31
  click.secho(f" 🚀 Automação Inteligente de PRs com IA (v{__version__})", fg="yellow", bold=True)
32
- click.secho(" Opções: -c,--commit | -r,--review | -f,--fullreview | -l,--linter | -s,--skill | -u,--update | -ih,--installhooks | -h ou --help\n", fg="white", dim=True)
32
+ click.secho(" Opções: -c,--commit | -r,--review | -f,--fullreview | -l,--linter | -s,--skill | -u,--update | -ih,--installhooks | -is,--issue | -h ou --help\n", fg="white", dim=True)
33
33
 
34
34
 
35
35
  # Configuração nativa do Click para aceitar -h além de --help
@@ -45,8 +45,10 @@ def print_banner():
45
45
  @click.option('--hook', type=click.Path(), hidden=True, help="Caminho do arquivo de commit (uso interno dos hooks).")
46
46
  @click.option('-q', '--quiet', is_flag=True, hidden=True, help="Oculta o banner e logs não essenciais (uso interno).")
47
47
  @click.option('-i', '--input', type=click.Path(exists=True), help="Caminho de um arquivo específico para análise completa.")
48
+ @click.option('-b', '--blame', type=str, help="Analisa a origem de uma regra de negócio (ex: arquivo.py:10-20 ou apenas arquivo.py).")
49
+ @click.option('-is', '--issue', is_flag=True, help="Gera uma Issue padronizada das alterações atuais e abre a interface interativa.")
48
50
  @click.option('-p', '--provider', type=click.Choice(['gemini', 'deepseek']), help="Força a utilização de um provedor de IA específico nesta execução.")
49
- def cli(commit, review, fullreview, linter, skill, update, installhooks, hook, quiet, provider, input):
51
+ def cli(commit, review, fullreview, linter, skill, update, installhooks, hook, quiet, provider, input, blame, issue):
50
52
  """
51
53
  GitPR CLI - Automação de PRs e Code Review com IA.
52
54
 
@@ -137,6 +139,85 @@ def cli(commit, review, fullreview, linter, skill, update, installhooks, hook, q
137
139
  click.echo("---\n")
138
140
  return
139
141
 
142
+ # Módulo Arqueólogo (--blame)
143
+ if blame:
144
+ # Parser para separar o arquivo das linhas
145
+ if ":" in blame:
146
+ # Modo Direto: gitpr --blame arquivo:10-20
147
+ file_path, lines = blame.split(":", 1)
148
+ try:
149
+ if "-" in lines:
150
+ start_line, end_line = lines.split("-")
151
+ else:
152
+ start_line = end_line = lines
153
+ except ValueError:
154
+ click.secho("❌ Formato de linhas inválido. Use inicio-fim (ex: 10-20).", fg="red")
155
+ return
156
+ else:
157
+ # Modo Interativo: gitpr --blame arquivo
158
+ file_path = blame
159
+ if not os.path.exists(file_path):
160
+ click.secho(f"❌ O arquivo '{file_path}' não foi encontrado.", fg="red")
161
+ return
162
+
163
+ click.secho(f"📂 Arquivo selecionado: {file_path}", fg="cyan", bold=True)
164
+ lines_input = click.prompt("Quais linhas você deseja investigar? (Ex: 10-20 ou apenas 45)")
165
+
166
+ if "-" in lines_input:
167
+ start_line, end_line = lines_input.split("-")
168
+ else:
169
+ start_line = end_line = lines_input
170
+
171
+ # Validação final do arquivo
172
+ if not os.path.exists(file_path):
173
+ click.secho(f"❌ O arquivo '{file_path}' não foi encontrado.", fg="red")
174
+ return
175
+
176
+ # Aciona o motor
177
+ from src.blame_engine import run_blame_analysis
178
+ run_blame_analysis(file_path.strip(), start_line.strip(), end_line.strip())
179
+ return
180
+
181
+ # Módulo Issue (--issue)
182
+ if issue:
183
+ # Importações sob demanda para não atrasar a CLI se não for usar a TUI
184
+ from src.issue_engine import generate_issue_content, get_github_repo_info
185
+ from src.tui_issue import validate_or_request_github_token, IssueApp
186
+
187
+ diff_text = get_git_diff()
188
+ if not diff_text or not diff_text.strip():
189
+ click.secho("\n⚠️ Nenhum código novo encontrado. Faça alguma alteração antes de gerar a issue.\n", fg="yellow")
190
+ return
191
+
192
+ # Garante que as chaves da IA estão configuradas antes de chamar o motor
193
+ setup_environment()
194
+
195
+ # Gera o conteúdo com a IA
196
+ issue_data = generate_issue_content(diff_text)
197
+ if not issue_data:
198
+ return
199
+
200
+ # Pega informações do repositório
201
+ repo_info = get_github_repo_info()
202
+
203
+ # Valida ou solicita o Token PAT
204
+ github_token = validate_or_request_github_token(repo_info)
205
+
206
+ if not github_token:
207
+ click.secho("❌ Acesso cancelado. O Token do GitHub é obrigatório para esta ação.", fg="red")
208
+ return
209
+
210
+ # Roda a Interface Gráfica do Terminal
211
+ app = IssueApp(issue_data=issue_data, repo_info=repo_info, github_token=github_token)
212
+ app.run()
213
+
214
+ # Exibe a mensagem de retorno após fechar a TUI
215
+ if app.final_message:
216
+ cor = "green" if app.final_action in ["saved", "created"] else "red"
217
+ click.secho(f"\n{app.final_message}\n", fg=cor, bold=True)
218
+
219
+ return
220
+
140
221
  # Validação do Modo Input
141
222
  if input and not (review or fullreview):
142
223
  click.secho("\n❌ Erro: A opção --input (-i) só pode ser utilizada em conjunto com --review (-r) ou --fullreview (-f).", fg="red", bold=True)
@@ -0,0 +1,34 @@
1
+ import click
2
+ from dotenv import set_key
3
+ from src.security import encrypt_data
4
+ from src.config import get_github_token, ENV_FILE
5
+
6
+ # Importa a classe do aplicativo da nossa nova pasta de classes
7
+ from src.ui.issue_app import IssueApp
8
+
9
+ def validate_or_request_github_token(repo_info):
10
+ """Verifica se o PAT existe, caso contrário solicita ao usuário, encripta e salva."""
11
+ token = get_github_token()
12
+ if token:
13
+ return token
14
+
15
+ click.secho(f"\n🔐 Autenticação do GitHub Necessária", fg="cyan", bold=True)
16
+ click.echo("Para criar issues diretamente, precisamos de um Personal Access Token (PAT).")
17
+ click.echo("Clique no link abaixo para gerar um com a permissão 'repo' já selecionada:")
18
+
19
+ repo_param = repo_info if repo_info else "seu-repositorio"
20
+ url_token = f"https://github.com/settings/tokens/new?scopes=repo&description=GitPR+Token+({repo_param})"
21
+ click.secho(f"👉 {url_token}\n", fg="blue", underline=True)
22
+
23
+ # --- NOVO: Link dinâmico para a documentação técnica ---
24
+ click.secho("📚 Entenda por que precisamos do Token e como ele é protegido por criptografia:", fg="cyan", dim=True)
25
+ click.secho("👉 https://github.com/natanfiuza/gitpr/blob/main/docs/github-pat-integration.md\n", fg="blue", underline=True)
26
+
27
+ raw_token = click.prompt("Cole aqui o seu Token (PAT)", hide_input=True)
28
+
29
+ encrypted_token = encrypt_data(raw_token.strip())
30
+
31
+ set_key(ENV_FILE, "GITHUB_TOKEN_ENCRYPTED", encrypted_token)
32
+ click.secho("✅ Token encriptado e salvo com segurança no .env!\n", fg="green")
33
+
34
+ return raw_token.strip()
@@ -7,7 +7,7 @@ import click
7
7
  from datetime import datetime
8
8
 
9
9
  # Versão atual do seu executável local (Atualize isso a cada novo build!)
10
- __version__ = "0.0.13"
10
+ __version__ = "0.0.15"
11
11
  GITHUB_API_URL = "https://api.github.com/repos/natanfiuza/gitpr/releases/latest"
12
12
  PYPI_API_URL = "https://pypi.org/pypi/gitpr-cli/json"
13
13
 
File without changes
File without changes
File without changes
File without changes
File without changes