gitpr-cli 0.0.14__tar.gz → 0.0.16__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.14 → gitpr_cli-0.0.16}/PKG-INFO +10 -2
  2. {gitpr_cli-0.0.14 → gitpr_cli-0.0.16}/README.md +9 -1
  3. {gitpr_cli-0.0.14 → gitpr_cli-0.0.16}/gitpr_cli.egg-info/PKG-INFO +10 -2
  4. {gitpr_cli-0.0.14 → gitpr_cli-0.0.16}/gitpr_cli.egg-info/SOURCES.txt +2 -0
  5. {gitpr_cli-0.0.14 → gitpr_cli-0.0.16}/pyproject.toml +1 -1
  6. {gitpr_cli-0.0.14 → gitpr_cli-0.0.16}/src/blame_engine.py +15 -7
  7. {gitpr_cli-0.0.14 → gitpr_cli-0.0.16}/src/cache.py +34 -1
  8. {gitpr_cli-0.0.14 → gitpr_cli-0.0.16}/src/config.py +12 -2
  9. {gitpr_cli-0.0.14 → gitpr_cli-0.0.16}/src/core.py +49 -2
  10. gitpr_cli-0.0.16/src/issue_engine.py +87 -0
  11. {gitpr_cli-0.0.14 → gitpr_cli-0.0.16}/src/main.py +102 -5
  12. gitpr_cli-0.0.16/src/tui_issue.py +34 -0
  13. {gitpr_cli-0.0.14 → gitpr_cli-0.0.16}/src/updater.py +1 -1
  14. {gitpr_cli-0.0.14 → gitpr_cli-0.0.16}/LICENSE +0 -0
  15. {gitpr_cli-0.0.14 → gitpr_cli-0.0.16}/gitpr_cli.egg-info/dependency_links.txt +0 -0
  16. {gitpr_cli-0.0.14 → gitpr_cli-0.0.16}/gitpr_cli.egg-info/entry_points.txt +0 -0
  17. {gitpr_cli-0.0.14 → gitpr_cli-0.0.16}/gitpr_cli.egg-info/requires.txt +0 -0
  18. {gitpr_cli-0.0.14 → gitpr_cli-0.0.16}/gitpr_cli.egg-info/top_level.txt +0 -0
  19. {gitpr_cli-0.0.14 → gitpr_cli-0.0.16}/setup.cfg +0 -0
  20. {gitpr_cli-0.0.14 → gitpr_cli-0.0.16}/src/__init__.py +0 -0
  21. {gitpr_cli-0.0.14 → gitpr_cli-0.0.16}/src/ai_providers.py +0 -0
  22. {gitpr_cli-0.0.14 → gitpr_cli-0.0.16}/src/linter_engine.py +0 -0
  23. {gitpr_cli-0.0.14 → gitpr_cli-0.0.16}/src/security.py +0 -0
  24. {gitpr_cli-0.0.14 → gitpr_cli-0.0.16}/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.14
3
+ Version: 0.0.16
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,10 @@ 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** e abre uma interface interativa (TUI) para edição ou envio direto via API REST. Esta funcionalidade possui **3 motores de contexto** dependendo da combinação de comandos:
130
+ * **Issue de Código Novo (`gitpr -is`):** Lê o `git diff` atual. **Por que usar:** Ideal para documentar rapidamente a tarefa que você acabou de programar, antes de commitar.
131
+ * **Issue de Épico/Release (`gitpr -is -ht`):** Lê o histórico completo da branch atual (Git Log + Cache de PRs). **Por que usar:** Ideal para gerar uma documentação consolidada de uma release inteira ou de uma *feature* grande que levou vários dias/commits para ser concluída.
132
+ * **Issue Arqueológica/Dívida Técnica (`gitpr -is -b arquivo:linhas`):** Lê a linha do tempo de uma regra específica. **Por que usar:** Ideal para documentar dívidas técnicas, explicando como um bloco de código legado evoluiu e por que precisa ser refatorado.
127
133
  * `-u` ou `--update`: Verifica e instala a versão mais recente do GitPR (Auto-Updater).
128
134
  * `-h` ou `--help`: Exibe o menu de ajuda.
129
135
 
@@ -175,7 +181,8 @@ Se você deseja implementar o GitPR como uma barreira de qualidade automatizada
175
181
 
176
182
  * [**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
183
  * [**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
-
184
+ * [**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.
185
+ * [**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
186
 
180
187
  ## ⚡ Sistema de Cache Local (Economia de Quota)
181
188
 
@@ -189,6 +196,7 @@ Nunca mais se preocupe em baixar novas versões manualmente. O GitPR possui um G
189
196
  * Em cada execução, ele verifica silenciosamente se há um novo release oficial na API do GitHub.
190
197
  * Você pode forçar a busca e instalação rodando `gitpr --update` ou `gitpr -u`.
191
198
  * A ferramenta utiliza a técnica de *Hot-Swap*, baixando o novo `.exe` e substituindo a versão antiga de forma transparente.
199
+
192
200
  ## Publicar no PyPi
193
201
 
194
202
  ```bash
@@ -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,10 @@ 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** e abre uma interface interativa (TUI) para edição ou envio direto via API REST. Esta funcionalidade possui **3 motores de contexto** dependendo da combinação de comandos:
114
+ * **Issue de Código Novo (`gitpr -is`):** Lê o `git diff` atual. **Por que usar:** Ideal para documentar rapidamente a tarefa que você acabou de programar, antes de commitar.
115
+ * **Issue de Épico/Release (`gitpr -is -ht`):** Lê o histórico completo da branch atual (Git Log + Cache de PRs). **Por que usar:** Ideal para gerar uma documentação consolidada de uma release inteira ou de uma *feature* grande que levou vários dias/commits para ser concluída.
116
+ * **Issue Arqueológica/Dívida Técnica (`gitpr -is -b arquivo:linhas`):** Lê a linha do tempo de uma regra específica. **Por que usar:** Ideal para documentar dívidas técnicas, explicando como um bloco de código legado evoluiu e por que precisa ser refatorado.
111
117
  * `-u` ou `--update`: Verifica e instala a versão mais recente do GitPR (Auto-Updater).
112
118
  * `-h` ou `--help`: Exibe o menu de ajuda.
113
119
 
@@ -159,7 +165,8 @@ Se você deseja implementar o GitPR como uma barreira de qualidade automatizada
159
165
 
160
166
  * [**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
167
  * [**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
-
168
+ * [**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.
169
+ * [**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
170
 
164
171
  ## ⚡ Sistema de Cache Local (Economia de Quota)
165
172
 
@@ -173,6 +180,7 @@ Nunca mais se preocupe em baixar novas versões manualmente. O GitPR possui um G
173
180
  * Em cada execução, ele verifica silenciosamente se há um novo release oficial na API do GitHub.
174
181
  * Você pode forçar a busca e instalação rodando `gitpr --update` ou `gitpr -u`.
175
182
  * A ferramenta utiliza a técnica de *Hot-Swap*, baixando o novo `.exe` e substituindo a versão antiga de forma transparente.
183
+
176
184
  ## Publicar no PyPi
177
185
 
178
186
  ```bash
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: gitpr-cli
3
- Version: 0.0.14
3
+ Version: 0.0.16
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,10 @@ 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** e abre uma interface interativa (TUI) para edição ou envio direto via API REST. Esta funcionalidade possui **3 motores de contexto** dependendo da combinação de comandos:
130
+ * **Issue de Código Novo (`gitpr -is`):** Lê o `git diff` atual. **Por que usar:** Ideal para documentar rapidamente a tarefa que você acabou de programar, antes de commitar.
131
+ * **Issue de Épico/Release (`gitpr -is -ht`):** Lê o histórico completo da branch atual (Git Log + Cache de PRs). **Por que usar:** Ideal para gerar uma documentação consolidada de uma release inteira ou de uma *feature* grande que levou vários dias/commits para ser concluída.
132
+ * **Issue Arqueológica/Dívida Técnica (`gitpr -is -b arquivo:linhas`):** Lê a linha do tempo de uma regra específica. **Por que usar:** Ideal para documentar dívidas técnicas, explicando como um bloco de código legado evoluiu e por que precisa ser refatorado.
127
133
  * `-u` ou `--update`: Verifica e instala a versão mais recente do GitPR (Auto-Updater).
128
134
  * `-h` ou `--help`: Exibe o menu de ajuda.
129
135
 
@@ -175,7 +181,8 @@ Se você deseja implementar o GitPR como uma barreira de qualidade automatizada
175
181
 
176
182
  * [**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
183
  * [**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
-
184
+ * [**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.
185
+ * [**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
186
 
180
187
  ## ⚡ Sistema de Cache Local (Economia de Quota)
181
188
 
@@ -189,6 +196,7 @@ Nunca mais se preocupe em baixar novas versões manualmente. O GitPR possui um G
189
196
  * Em cada execução, ele verifica silenciosamente se há um novo release oficial na API do GitHub.
190
197
  * Você pode forçar a busca e instalação rodando `gitpr --update` ou `gitpr -u`.
191
198
  * A ferramenta utiliza a técnica de *Hot-Swap*, baixando o novo `.exe` e substituindo a versão antiga de forma transparente.
199
+
192
200
  ## Publicar no PyPi
193
201
 
194
202
  ```bash
@@ -13,8 +13,10 @@ src/blame_engine.py
13
13
  src/cache.py
14
14
  src/config.py
15
15
  src/core.py
16
+ src/issue_engine.py
16
17
  src/linter_engine.py
17
18
  src/main.py
18
19
  src/security.py
20
+ src/tui_issue.py
19
21
  src/updater.py
20
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.14"
7
+ version = "0.0.16"
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"
@@ -89,19 +89,23 @@ def analyze_commit_with_ai(commit_hash, file_path):
89
89
 
90
90
  return {"status": "ORIGEM", "motivo": "IA não retornou formato válido."}
91
91
 
92
- def run_blame_analysis(file_path, start_line, end_line):
92
+ def run_blame_analysis(file_path, start_line, end_line, return_data=False):
93
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})")
94
+
95
+ # Se for acionado para retorno de dados (via --issue), silencia os prints
96
+ if not return_data:
97
+ click.secho(f"\n🔍 Iniciando Arqueologia de Código...", fg="cyan", bold=True)
98
+ click.echo(f"📍 Arquivo: {file_path} (Linhas: {start_line} até {end_line})")
96
99
 
97
100
  initial_commits = execute_git_blame(file_path, start_line, end_line)
98
101
 
99
102
  if not initial_commits:
100
- click.secho("⚠️ Nenhum commit rastreável encontrado nestas linhas.", fg="yellow")
101
- return
103
+ if not return_data:
104
+ click.secho("⚠️ Nenhum commit rastreável encontrado nestas linhas.", fg="yellow")
105
+ return [] if return_data else None
102
106
 
103
- click.secho(f"✅ Encontrado(s) {len(initial_commits)} commit(s) na superfície. Iniciando viagem no tempo...\n", fg="green")
104
-
107
+ if not return_data:
108
+ click.secho(f"✅ Encontrado(s) {len(initial_commits)} commit(s) na superfície. Iniciando viagem no tempo...\n", fg="green")
105
109
  master_timeline = []
106
110
  seen_hashes = set()
107
111
 
@@ -146,6 +150,10 @@ def run_blame_analysis(file_path, start_line, end_line):
146
150
  # ORDENAÇÃO CRONOLÓGICA (Do mais antigo para o mais novo)
147
151
  master_timeline.sort(key=lambda x: x["raw_date"])
148
152
 
153
+ # Retorno Direto para a IA
154
+ if return_data:
155
+ return master_timeline
156
+
149
157
  # EXIBIÇÃO VISUAL NO TERMINAL (ÚNICA)
150
158
  click.secho(f"\n📜 Histórico Consolidado da Regra (Linhas {start_line}-{end_line}):", fg="magenta", bold=True)
151
159
 
@@ -3,6 +3,7 @@ import json
3
3
  import hashlib
4
4
  from datetime import datetime
5
5
  from pathlib import Path
6
+ from src.core import get_current_branch
6
7
 
7
8
  def get_cache_base_dir():
8
9
  """Retorna o caminho ~/.gitpr/cache/prompts/"""
@@ -34,9 +35,11 @@ def save_cached_response(action_folder, action_type, prompt_text, response_dict)
34
35
  folder_path.mkdir(parents=True, exist_ok=True)
35
36
 
36
37
  cache_file = folder_path / f"{md5_hash}.json"
38
+ current_branch = get_current_branch()
37
39
 
38
40
  cache_data = {
39
41
  "md5": md5_hash,
42
+ "branch": current_branch,
40
43
  "datetime": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
41
44
  "action_type": action_type,
42
45
  "prompt": prompt_text,
@@ -47,4 +50,34 @@ def save_cached_response(action_folder, action_type, prompt_text, response_dict)
47
50
  with open(cache_file, "w", encoding="utf-8") as f:
48
51
  json.dump(cache_data, f, indent=2, ensure_ascii=False)
49
52
  except IOError:
50
- pass # Falha silenciosa no cache para não travar a ferramenta
53
+ pass # Falha silenciosa no cache para não travar a ferramenta
54
+
55
+ def get_cached_pr_descriptions(branch_name):
56
+ """Busca no cache todos os PRs gerados historicamente para esta branch."""
57
+ pr_cache_folder = get_cache_base_dir() / "pr_desc"
58
+ history_texts = []
59
+
60
+ if not pr_cache_folder.exists():
61
+ return ""
62
+
63
+ for cache_file in pr_cache_folder.glob("*.json"):
64
+ try:
65
+ with open(cache_file, "r", encoding="utf-8") as f:
66
+ data = json.load(f)
67
+
68
+ # Verifica se o cache pertence à branch solicitada
69
+ if data.get("branch") == branch_name:
70
+ response_dict = data.get("response", {})
71
+ pr_desc = response_dict.get("pr_description")
72
+ if pr_desc:
73
+ date_str = data.get("datetime", "Data desconhecida")
74
+ history_texts.append(f"[{date_str}]\n{pr_desc}\n")
75
+ except (json.JSONDecodeError, IOError):
76
+ continue
77
+
78
+ if history_texts:
79
+ # Ordena cronologicamente (usando a data extraída no colchete)
80
+ history_texts.sort()
81
+ return "=== HISTÓRICO DE PRs DA IA ===\n" + "\n".join(history_texts)
82
+
83
+ return ""
@@ -21,7 +21,8 @@ DEFAULT_CONFIG = {
21
21
  "OUTPUT_FILE_NAME_REVIEW": "{branch}_{datetime}_PR_REVIEW.txt",
22
22
  "OUTPUT_FILE_NAME_FULLREVIEW": "{branch}_{datetime}_PR_FULLREVIEW.txt",
23
23
  "OUTPUT_FILE_NAME_FILEREVIEW": "{branch}_{datetime}_FILE_REVIEW.txt",
24
- "OUTPUT_FILE_NAME_BLAME": "{branch}_{datetime}_BLAME_REPORT.md"
24
+ "OUTPUT_FILE_NAME_BLAME": "{branch}_{datetime}_BLAME_REPORT.md",
25
+ "OUTPUT_FILE_NAME_ISSUE": "{branch}_{datetime}_ISSUE.md"
25
26
  }
26
27
 
27
28
  def get_ai_provider():
@@ -152,4 +153,13 @@ def load_linter_rules():
152
153
  return []
153
154
  except Exception as e:
154
155
  click.secho(f"\n❌ Erro inesperado ao ler as regras do linter: {e}", fg="red")
155
- 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
@@ -7,11 +7,12 @@ import urllib.request
7
7
  import urllib.error
8
8
  from google import genai
9
9
  from src.security import decrypt_data
10
- from src.cache import get_cached_response, save_cached_response
10
+ from src.cache import get_cached_response, save_cached_response, get_cached_pr_descriptions
11
11
  from src.config import get_api_key, get_api_model
12
12
  from src.ai_providers import call_ai_model
13
13
 
14
14
 
15
+
15
16
  def get_git_diff():
16
17
  """Executa 'git diff HEAD' e retorna a saída, alertando sobre arquivos não monitorados (untracked)."""
17
18
  try:
@@ -76,6 +77,8 @@ def get_skill_context(action_type="pr"):
76
77
  target_file = ".gitpr.pr.md"
77
78
  elif action_type == "filereview": # NOVO!
78
79
  target_file = ".gitpr.filereview.md"
80
+ elif action_type == "issue":
81
+ target_file = ".gitpr.issue.md"
79
82
  else: # review ou fullreview
80
83
  target_file = ".gitpr.review.md"
81
84
 
@@ -189,6 +192,7 @@ def generate_skill_template():
189
192
  ".gitpr.linter.yml": "gitpr.linter.yml",
190
193
  ".gitpr.filereview.md": "gitpr.filereview.md",
191
194
  ".gitpr.blame.md": "gitpr.blame.md",
195
+ ".gitpr.issue.md": "gitpr.issue.md",
192
196
  }
193
197
 
194
198
  success_count = 0
@@ -309,4 +313,47 @@ def install_git_hooks():
309
313
  except Exception as e:
310
314
  click.secho(f"⚠️ Falha ao instalar {hook_name}: {e}", fg="yellow")
311
315
 
312
- return success_count == len(hooks_to_install)
316
+ return success_count == len(hooks_to_install)
317
+
318
+ def get_branch_history_text():
319
+ """Compila o Git Log e o Cache de PRs da branch atual para gerar o contexto épico."""
320
+ branch = get_current_branch()
321
+ base_branch = get_base_branch()
322
+
323
+ click.secho(f"🔄 Compilando histórico da branch '{branch}' contra '{base_branch}'...", fg="cyan")
324
+
325
+ hybrid_context = f"Resumo Histórico da Branch: {branch}\n\n"
326
+
327
+ # Pega os Commits reais do Git
328
+ try:
329
+ # Pega a linha do tempo desde o merge base
330
+ merge_base_res = subprocess.run(
331
+ ["git", "merge-base", f"origin/{base_branch}", "HEAD"],
332
+ capture_output=True, text=True, check=True
333
+ )
334
+ ancestor_hash = merge_base_res.stdout.strip()
335
+
336
+ # Formato: Hash | Data | Autor | Mensagem
337
+ git_log_res = subprocess.run(
338
+ ["git", "log", f"{ancestor_hash}..HEAD", "--format=%h | %ad | %an | %s", "--date=short"],
339
+ capture_output=True, text=True, encoding="utf-8", check=True
340
+ )
341
+ git_log = git_log_res.stdout.strip()
342
+
343
+ hybrid_context += "=== COMMITS REGISTRADOS ===\n"
344
+ if git_log:
345
+ hybrid_context += f"{git_log}\n\n"
346
+ else:
347
+ hybrid_context += "Nenhum commit exclusivo encontrado nesta branch.\n\n"
348
+
349
+ except subprocess.CalledProcessError as e:
350
+ click.secho(f"⚠️ Aviso: Não foi possível obter o Git Log: {e.stderr}", fg="yellow")
351
+
352
+ # Pega a memória histórica da IA (Cache de PRs antigos dessa branch)
353
+ cached_prs = get_cached_pr_descriptions(branch)
354
+ if cached_prs:
355
+ hybrid_context += f"{cached_prs}\n"
356
+ else:
357
+ hybrid_context += "=== HISTÓRICO DE PRs DA IA ===\nNenhum PR anterior gerado por IA encontrado em cache para esta branch.\n"
358
+
359
+ return hybrid_context
@@ -0,0 +1,87 @@
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(context_text, context_type="diff"):
33
+ """Envia o contexto (diff, blame ou history) para a IA e retorna um dicionário da issue."""
34
+ if not context_text or not str(context_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
+ # Cérebro Adaptativo (Prompt Dinâmico)
57
+ if context_type == "blame":
58
+ target_action = "documentar a evolução arquitetural, refatorações e a dívida técnica desta regra de negócio baseando-se no histórico de commits."
59
+ data_label = "LINHA DO TEMPO DA REGRA (DO MAIS ANTIGO PARA O MAIS NOVO):"
60
+ elif context_type == "history":
61
+ target_action = "documentar o Épico/Release detalhando todas as funcionalidades implementadas baseando-se no histórico integral da branch."
62
+ data_label = "HISTÓRICO CONSOLIDADO DA BRANCH (COMMITS + PRS ANTIGOS):"
63
+ else:
64
+ target_action = "documentar a seguinte alteração de código recém introduzida."
65
+ data_label = "DIFF PARA ANÁLISE:"
66
+
67
+ prompt = (
68
+ f"Gere o objeto JSON solicitado seguindo as instruções de sistema para {target_action}\n\n"
69
+ f"{data_label}\n{context_text}"
70
+ )
71
+
72
+ # Tenta recuperar do Cache
73
+ cached_data = get_cached_response("issue", prompt)
74
+ if cached_data:
75
+ click.secho("⚡ Resposta da Issue recuperada do cache local.", fg="green", dim=True)
76
+ return cached_data
77
+
78
+ click.secho(f"🤖 Estruturando a Issue usando {provider.capitalize()} ({api_model})...", fg="cyan", dim=True)
79
+
80
+ result_json = call_ai_model(provider, api_key, api_model, prompt, sys_inst)
81
+
82
+ if result_json and "titulo" in result_json and "corpo" in result_json:
83
+ # Salva no Cache
84
+ save_cached_response("issue", "issue", prompt, result_json)
85
+ return result_json
86
+
87
+ return {"titulo": "Erro ao gerar título", "corpo": "Não foi possível gerar o corpo da issue pela IA."}
@@ -12,7 +12,8 @@ from src.core import (
12
12
  get_current_branch,
13
13
  generate_pr_content,
14
14
  generate_skill_template,
15
- install_git_hooks
15
+ install_git_hooks,
16
+ get_branch_history_text
16
17
  )
17
18
  from src.linter_engine import parse_diff_and_lint
18
19
 
@@ -29,7 +30,7 @@ def print_banner():
29
30
  """
30
31
  click.secho(banner, fg="cyan", bold=True)
31
32
  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)
33
+ 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
34
 
34
35
 
35
36
  # Configuração nativa do Click para aceitar -h além de --help
@@ -46,8 +47,10 @@ def print_banner():
46
47
  @click.option('-q', '--quiet', is_flag=True, hidden=True, help="Oculta o banner e logs não essenciais (uso interno).")
47
48
  @click.option('-i', '--input', type=click.Path(exists=True), help="Caminho de um arquivo específico para análise completa.")
48
49
  @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).")
50
+ @click.option('-ht', '--history', is_flag=True, help="Usa todo o histórico da branch (Git Log + Cache de PR) como contexto para gerar a issue.")
51
+ @click.option('-is', '--issue', is_flag=True, help="Gera uma Issue padronizada das alterações atuais e abre a interface interativa.")
49
52
  @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.")
50
- def cli(commit, review, fullreview, linter, skill, update, installhooks, hook, quiet, provider, input, blame):
53
+ def cli(commit, review, fullreview, linter, skill, update, installhooks, hook, quiet, provider, input, blame,history, issue):
51
54
  """
52
55
  GitPR CLI - Automação de PRs e Code Review com IA.
53
56
 
@@ -139,7 +142,7 @@ def cli(commit, review, fullreview, linter, skill, update, installhooks, hook, q
139
142
  return
140
143
 
141
144
  # Módulo Arqueólogo (--blame)
142
- if blame:
145
+ if blame and not issue:
143
146
  # Parser para separar o arquivo das linhas
144
147
  if ":" in blame:
145
148
  # Modo Direto: gitpr --blame arquivo:10-20
@@ -176,7 +179,101 @@ def cli(commit, review, fullreview, linter, skill, update, installhooks, hook, q
176
179
  from src.blame_engine import run_blame_analysis
177
180
  run_blame_analysis(file_path.strip(), start_line.strip(), end_line.strip())
178
181
  return
179
-
182
+
183
+ # Módulo Issue (Híbrido)
184
+ if issue:
185
+ from src.issue_engine import generate_issue_content, get_github_repo_info
186
+ from src.tui_issue import validate_or_request_github_token, IssueApp
187
+
188
+ setup_environment()
189
+
190
+ context_text = ""
191
+ context_type = "diff"
192
+
193
+ # Roteador de Contexto Inteligente
194
+ if history:
195
+ context_type = "history"
196
+ context_text = get_branch_history_text()
197
+ if not context_text or ("Nenhum commit exclusivo" in context_text and "Nenhum PR anterior" in context_text):
198
+ click.secho("\n⚠️ Nenhum histórico encontrado para esta branch.\n", fg="yellow")
199
+ return
200
+
201
+ elif blame:
202
+ context_type = "blame"
203
+ # Reaproveita o parser de arquivo/linhas da opção blame
204
+ if ":" in blame:
205
+ file_path, lines = blame.split(":", 1)
206
+ try:
207
+ start_line, end_line = lines.split("-") if "-" in lines else (lines, lines)
208
+ except ValueError:
209
+ click.secho("❌ Formato de linhas inválido. Use inicio-fim (ex: 10-20).", fg="red")
210
+ return
211
+ else:
212
+ file_path = blame
213
+ if not os.path.exists(file_path):
214
+ click.secho(f"❌ O arquivo '{file_path}' não foi encontrado.", fg="red")
215
+ return
216
+ click.secho(f"📂 Arquivo selecionado: {file_path}", fg="cyan", bold=True)
217
+ lines_input = click.prompt("Quais linhas você deseja investigar? (Ex: 10-20)")
218
+ start_line, end_line = lines_input.split("-") if "-" in lines_input else (lines_input, lines_input)
219
+
220
+ if not os.path.exists(file_path.strip()):
221
+ click.secho(f"❌ O arquivo '{file_path}' não foi encontrado.", fg="red")
222
+ return
223
+
224
+ from src.blame_engine import run_blame_analysis
225
+ click.secho("🔍 Extraindo linha do tempo arqueológica em background...", fg="cyan")
226
+
227
+ # Aciona o arqueólogo no modo silencioso
228
+ timeline = run_blame_analysis(file_path.strip(), start_line.strip(), end_line.strip(), return_data=True)
229
+
230
+ if not timeline:
231
+ click.secho("\n⚠️ Nenhum histórico rastreável para alimentar a issue.\n", fg="yellow")
232
+ return
233
+
234
+ # Traduz a lista de dicionários para um texto legível para a IA
235
+ formatted_timeline = []
236
+ for item in timeline:
237
+ formatted_timeline.append(
238
+ f"[{item['raw_date']}] Commit {item['hash']} por {item['info']['author']}:\n"
239
+ f"Ação: {item['status']}\n"
240
+ f"Motivo IA: {item['motivo']}\n"
241
+ )
242
+ context_text = "\n".join(formatted_timeline)
243
+
244
+ else:
245
+ context_type = "diff"
246
+ context_text = get_git_diff()
247
+ if not context_text or not context_text.strip():
248
+ click.secho("\n⚠️ Nenhum código novo encontrado. Faça alguma alteração antes de gerar a issue.\n", fg="yellow")
249
+ return
250
+
251
+ # Gera o conteúdo com a IA enviando o idioma correto
252
+ issue_data = generate_issue_content(context_text, context_type=context_type)
253
+ if not issue_data:
254
+ return
255
+
256
+ # Pega informações do repositório
257
+ repo_info = get_github_repo_info()
258
+
259
+ # Valida ou solicita o Token PAT
260
+ github_token = validate_or_request_github_token(repo_info)
261
+
262
+ if not github_token:
263
+ click.secho("❌ Acesso cancelado. O Token do GitHub é obrigatório para esta ação.", fg="red")
264
+ return
265
+
266
+ # Roda a Interface Gráfica do Terminal
267
+ app = IssueApp(issue_data=issue_data, repo_info=repo_info, github_token=github_token)
268
+ app.run()
269
+
270
+ # Exibe a mensagem de retorno após fechar a TUI
271
+ if app.final_message:
272
+ cor = "green" if app.final_action in ["saved", "created"] else "red"
273
+ click.secho(f"\n{app.final_message}\n", fg=cor, bold=True)
274
+
275
+ return
276
+
180
277
  # Validação do Modo Input
181
278
  if input and not (review or fullreview):
182
279
  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
+ # 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.14"
10
+ __version__ = "0.0.16"
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