gitpr-cli 0.0.24__tar.gz → 0.0.25__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 (29) hide show
  1. {gitpr_cli-0.0.24 → gitpr_cli-0.0.25}/PKG-INFO +1 -1
  2. {gitpr_cli-0.0.24 → gitpr_cli-0.0.25}/gitpr_cli.egg-info/PKG-INFO +1 -1
  3. {gitpr_cli-0.0.24 → gitpr_cli-0.0.25}/pyproject.toml +1 -1
  4. {gitpr_cli-0.0.24 → gitpr_cli-0.0.25}/src/blame_engine.py +2 -2
  5. {gitpr_cli-0.0.24 → gitpr_cli-0.0.25}/src/config.py +48 -1
  6. {gitpr_cli-0.0.24 → gitpr_cli-0.0.25}/src/core.py +14 -9
  7. {gitpr_cli-0.0.24 → gitpr_cli-0.0.25}/src/issue_engine.py +2 -2
  8. {gitpr_cli-0.0.24 → gitpr_cli-0.0.25}/src/main.py +2 -2
  9. {gitpr_cli-0.0.24 → gitpr_cli-0.0.25}/src/spinner.py +6 -3
  10. {gitpr_cli-0.0.24 → gitpr_cli-0.0.25}/src/updater.py +2 -2
  11. {gitpr_cli-0.0.24 → gitpr_cli-0.0.25}/LICENSE +0 -0
  12. {gitpr_cli-0.0.24 → gitpr_cli-0.0.25}/README.md +0 -0
  13. {gitpr_cli-0.0.24 → gitpr_cli-0.0.25}/gitpr_cli.egg-info/SOURCES.txt +0 -0
  14. {gitpr_cli-0.0.24 → gitpr_cli-0.0.25}/gitpr_cli.egg-info/dependency_links.txt +0 -0
  15. {gitpr_cli-0.0.24 → gitpr_cli-0.0.25}/gitpr_cli.egg-info/entry_points.txt +0 -0
  16. {gitpr_cli-0.0.24 → gitpr_cli-0.0.25}/gitpr_cli.egg-info/requires.txt +0 -0
  17. {gitpr_cli-0.0.24 → gitpr_cli-0.0.25}/gitpr_cli.egg-info/top_level.txt +0 -0
  18. {gitpr_cli-0.0.24 → gitpr_cli-0.0.25}/setup.cfg +0 -0
  19. {gitpr_cli-0.0.24 → gitpr_cli-0.0.25}/src/__init__.py +0 -0
  20. {gitpr_cli-0.0.24 → gitpr_cli-0.0.25}/src/ai_providers.py +0 -0
  21. {gitpr_cli-0.0.24 → gitpr_cli-0.0.25}/src/cache.py +0 -0
  22. {gitpr_cli-0.0.24 → gitpr_cli-0.0.25}/src/i18n.py +0 -0
  23. {gitpr_cli-0.0.24 → gitpr_cli-0.0.25}/src/linter_engine.py +0 -0
  24. {gitpr_cli-0.0.24 → gitpr_cli-0.0.25}/src/security.py +0 -0
  25. {gitpr_cli-0.0.24 → gitpr_cli-0.0.25}/src/tui_issue.py +0 -0
  26. {gitpr_cli-0.0.24 → gitpr_cli-0.0.25}/src/ui/__init__.py +0 -0
  27. {gitpr_cli-0.0.24 → gitpr_cli-0.0.25}/src/ui/help_screen.py +0 -0
  28. {gitpr_cli-0.0.24 → gitpr_cli-0.0.25}/src/ui/issue_app.py +0 -0
  29. {gitpr_cli-0.0.24 → gitpr_cli-0.0.25}/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.24
3
+ Version: 0.0.25
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
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: gitpr-cli
3
- Version: 0.0.24
3
+ Version: 0.0.25
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
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "gitpr-cli"
7
- version = "0.0.24"
7
+ version = "0.0.25"
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"
@@ -4,7 +4,7 @@ import re
4
4
  import os
5
5
  from datetime import datetime
6
6
  from src.core import get_current_branch
7
- from src.config import get_api_key, get_api_model, get_ai_provider
7
+ from src.config import get_api_key, get_api_model, get_ai_provider, resolve_skill_path
8
8
  from src.ai_providers import call_ai_model
9
9
  from src.i18n import __
10
10
 
@@ -69,7 +69,7 @@ def analyze_commit_with_ai(commit_hash, file_path):
69
69
  # Use the 'simple' model (Flash/Lite) to save money in the loop
70
70
  api_model = get_api_model(provider, task_complexity="simple")
71
71
 
72
- skill_path = os.path.join(os.getcwd(), ".gitpr.blame.md")
72
+ skill_path = resolve_skill_path(".gitpr.blame.md")
73
73
  if os.path.exists(skill_path):
74
74
  with open(skill_path, "r", encoding="utf-8") as f:
75
75
  sys_inst = f.read()
@@ -1,6 +1,7 @@
1
1
  import os
2
2
  import sys
3
3
  import socket
4
+ import shutil
4
5
  import click
5
6
  import yaml
6
7
  from pathlib import Path
@@ -28,6 +29,41 @@ DEFAULT_CONFIG = {
28
29
  "OUTPUT_FILE_NAME_ISSUE": "{branch}_{datetime}_ISSUE.md"
29
30
  }
30
31
 
32
+ def get_skill_dir():
33
+ """Returns the absolute path to the project's skill folder (.gitpr/skill)."""
34
+ return os.path.join(os.getcwd(), ".gitpr", "skill")
35
+
36
+
37
+ def resolve_skill_path(filename):
38
+ """
39
+ Resolves the path of a skill/config file (e.g.: .gitpr.commit.md).
40
+
41
+ The canonical location is the '.gitpr/skill/' folder inside the project.
42
+ For backward compatibility, if the file still lives in the project root,
43
+ it is transparently migrated (moved) into '.gitpr/skill/'.
44
+
45
+ Always returns the path inside '.gitpr/skill/' (whether the file exists or
46
+ not), unless a migration failed — in that case it falls back to the legacy
47
+ root path so the tool keeps working.
48
+ """
49
+ skill_dir = get_skill_dir()
50
+ target_path = os.path.join(skill_dir, filename)
51
+ legacy_path = os.path.join(os.getcwd(), filename)
52
+
53
+ # Migrate a legacy root file into the skill folder (only if not already there)
54
+ if os.path.exists(legacy_path) and not os.path.exists(target_path):
55
+ try:
56
+ os.makedirs(skill_dir, exist_ok=True)
57
+ shutil.move(legacy_path, target_path)
58
+ click.secho(__("📦 Skill file {filename} moved to .gitpr/skill/", filename=filename), fg="cyan", dim=True)
59
+ except Exception as e:
60
+ # If moving fails, fall back to the legacy location so the tool keeps working
61
+ click.secho(__("⚠️ Warning: Could not move {filename} to .gitpr/skill/ ({error})", filename=filename, error=str(e)), fg="yellow")
62
+ return legacy_path
63
+
64
+ return target_path
65
+
66
+
31
67
  def get_ai_provider():
32
68
  """Returns the configured default AI provider, or 'gemini' as fallback."""
33
69
  load_dotenv(ENV_FILE)
@@ -37,6 +73,11 @@ def get_api_key(provider):
37
73
  """Reads and decrypts the API key corresponding to the chosen provider."""
38
74
  load_dotenv(ENV_FILE)
39
75
 
76
+ # Suporte a CI/CD: Tenta ler a chave raw primeiro (ex: injetada via GitHub Secrets)
77
+ raw_key = os.getenv(f"{provider.upper()}_API_KEY")
78
+ if raw_key:
79
+ return raw_key
80
+
40
81
  if provider == "gemini":
41
82
  encrypted_key = os.getenv("GEMINI_API_KEY_ENCRYPTED")
42
83
  elif provider == "deepseek":
@@ -99,6 +140,12 @@ def setup_environment():
99
140
  # Check if the chosen provider's key exists
100
141
  api_key = get_api_key(provider)
101
142
  if not api_key:
143
+ # 🛡️ Escudo de CI/CD: Impede que o prompt trave a pipeline do GitHub Actions
144
+ if os.getenv("CI") or os.getenv("GITHUB_ACTIONS"):
145
+ click.secho(__("❌ Error: API Key not configured for provider '{provider}' in the CI/CD environment.", provider=provider), fg="red")
146
+ click.secho(__("💡 Tip: Pass the key as an environment variable (e.g., GEMINI_API_KEY)."), fg="yellow")
147
+ sys.exit(1)
148
+
102
149
  click.secho(__("🔑 API Key for {provider} not found.", provider=provider.capitalize()), fg="yellow")
103
150
  raw_key = click.prompt(__("Paste your {provider} API key here", provider=provider.capitalize()), hide_input=True)
104
151
 
@@ -136,7 +183,7 @@ def load_linter_rules():
136
183
  Loads the static linter rules from the .gitpr.linter.yml file.
137
184
  Returns a list of rules or an empty list if the file does not exist.
138
185
  """
139
- file_path = os.path.join(os.getcwd(), ".gitpr.linter.yml")
186
+ file_path = resolve_skill_path(".gitpr.linter.yml")
140
187
 
141
188
  # If the file does not exist in the project, it's not an error. There are simply no rules to apply.
142
189
  if not os.path.exists(file_path):
@@ -9,7 +9,7 @@ import urllib.error
9
9
  from google import genai
10
10
  from src.security import decrypt_data
11
11
  from src.cache import get_cached_response, save_cached_response, get_cached_pr_descriptions
12
- from src.config import get_api_key, get_api_model
12
+ from src.config import get_api_key, get_api_model, get_skill_dir, resolve_skill_path
13
13
  from src.ai_providers import call_ai_model
14
14
  from src.i18n import __, CURRENT_LANG
15
15
 
@@ -130,10 +130,10 @@ def get_skill_context(action_type="pr"):
130
130
  else: # review or fullreview
131
131
  target_file = ".gitpr.review.md"
132
132
 
133
- skill_file = os.path.join(os.getcwd(), target_file)
133
+ skill_file = resolve_skill_path(target_file)
134
134
 
135
135
  # Fallback to the old file (for backward compatibility with previous version users)
136
- legacy_file = os.path.join(os.getcwd(), ".gitpr.md")
136
+ legacy_file = resolve_skill_path(".gitpr.md")
137
137
 
138
138
  # Check the new one first; if not found, try the old one
139
139
  file_to_load = skill_file if os.path.exists(skill_file) else (legacy_file if os.path.exists(legacy_file) else None)
@@ -252,11 +252,16 @@ def generate_skill_template():
252
252
  }
253
253
 
254
254
  success_count = 0
255
-
255
+
256
+ # Templates now live inside the project's .gitpr/skill/ folder
257
+ skill_dir = get_skill_dir()
258
+ os.makedirs(skill_dir, exist_ok=True)
259
+
256
260
  for local_name, remote_name in files_to_download.items():
257
- file_path = os.path.join(os.getcwd(), local_name)
261
+ # Migrate any legacy root file into .gitpr/skill/ and resolve final path
262
+ file_path = resolve_skill_path(local_name)
258
263
  url = base_url + remote_name
259
-
264
+
260
265
  if os.path.exists(file_path):
261
266
  click.secho(__("⚠️ File {local_name} already exists in this directory. It will not be overwritten.", local_name=local_name), fg="yellow")
262
267
  continue
@@ -278,9 +283,9 @@ def generate_skill_template():
278
283
 
279
284
  if success_count > 0:
280
285
  click.secho(__("\n✅ Base templates successfully configured!"), fg="green", bold=True)
281
- click.echo(__("You can now open the generated files and customize the tool's behavior for your project:\n"))
282
- click.echo(__(" 1. Architecture rules for AI in '.gitpr.pr.md' and '.gitpr.review.md'\n"))
283
- click.echo(__(" 2. Local regex rules in '.gitpr.linter.yml'\n"))
286
+ click.echo(__("You can now open the generated files in '.gitpr/skill/' and customize the tool's behavior for your project:\n"))
287
+ click.echo(__(" 1. Architecture rules for AI in '.gitpr/skill/.gitpr.pr.md' and '.gitpr/skill/.gitpr.review.md'\n"))
288
+ click.echo(__(" 2. Local regex rules in '.gitpr/skill/.gitpr.linter.yml'\n"))
284
289
  else:
285
290
  click.echo(__("\nNo new files were downloaded."))
286
291
 
@@ -4,7 +4,7 @@ import os
4
4
  import click
5
5
  from src.ai_providers import call_ai_model
6
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
7
+ from src.config import get_api_key, get_api_model, get_ai_provider, resolve_skill_path
8
8
  from src.ai_providers import call_ai_model
9
9
  from src.i18n import __
10
10
 
@@ -45,7 +45,7 @@ def generate_issue_content(context_text, context_type="diff"):
45
45
  # Use the advanced model to ensure Issue structure quality
46
46
  api_model = get_api_model(provider, task_complexity="advanced")
47
47
 
48
- skill_path = os.path.join(os.getcwd(), ".gitpr.issue.md")
48
+ skill_path = resolve_skill_path(".gitpr.issue.md")
49
49
  sys_inst = ""
50
50
 
51
51
  if os.path.exists(skill_path):
@@ -66,7 +66,7 @@ HELP_MAP: dict[str, dict[str, str]] = {
66
66
  'skill': {
67
67
  'url': get_doc_url('skill-template.md'),
68
68
  'title': __('Skills and Templates System (--skill)'),
69
- 'description': __('Downloads template files (.gitpr.*.md and .gitpr.linter.yml) from the official repository to the project root. These files allow customizing the AI behavior according to your team\'s rules. NEVER overwrites existing local files.'),
69
+ 'description': __('Downloads template files (.gitpr.*.md and .gitpr.linter.yml) from the official repository into the project\'s .gitpr/skill/ folder. These files allow customizing the AI behavior according to your team\'s rules. NEVER overwrites existing local files.'),
70
70
  },
71
71
  'update': {
72
72
  'url': get_doc_url('auto-update.md'),
@@ -124,7 +124,7 @@ HELP_PRIORITY: dict[str, int] = {
124
124
  @click.option('-r', '--review', is_flag=True, help=__("Performs a code review of local changes (git diff)."))
125
125
  @click.option('-f', '--fullreview', is_flag=True, help=__("Performs a code review of all changes since the remote main branch (origin/main)."))
126
126
  @click.option('-l', '--linter', is_flag=True, help=__("Runs only the local static linter (ideal for CI/CD)."))
127
- @click.option('-s', '--skill', is_flag=True, help=__("Generates the .gitpr.md template file in the current folder."))
127
+ @click.option('-s', '--skill', is_flag=True, help=__("Downloads the skill template files into the .gitpr/skill/ folder."))
128
128
  @click.option('-u', '--update', is_flag=True, help=__("Checks and installs the latest version of GitPR."))
129
129
  @click.option('-ih', '--installhooks', is_flag=True, help=__("Automatically installs validation Git Hooks in the project."))
130
130
  @click.option('--hook', type=click.Path(), hidden=True, help=__("Commit file path (internal hook use)."))
@@ -12,7 +12,7 @@ import threading
12
12
  import urllib.request
13
13
  from pathlib import Path
14
14
  from dotenv import load_dotenv, set_key
15
- from src.i18n import __
15
+ from src.i18n import __, CURRENT_LANG
16
16
 
17
17
  # ANSI color codes
18
18
  MAGENTA = '\033[35m'
@@ -35,10 +35,13 @@ WORD_COLORS = [
35
35
  # Caracteres braille unicode que simulam um giro
36
36
  BRAILLE_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"]
37
37
 
38
- # URL do template remoto com a lista de palavras
38
+ # URL do template remoto com a lista de palavras (language-aware).
39
+ # English (en) = original file without suffix; other languages get a suffix
40
+ # (e.g.: gitpr.thinking-words.pt_br.md), mirroring generate_skill_template().
41
+ _LANG_SUFFIX = "" if CURRENT_LANG.startswith("en") else f".{CURRENT_LANG}"
39
42
  THINKING_WORDS_URL = (
40
43
  "https://raw.githubusercontent.com/natanfiuza/gitpr/"
41
- "refs/heads/main/templates/gitpr.thinking-words.md"
44
+ f"refs/heads/main/templates/gitpr.thinking-words{_LANG_SUFFIX}.md"
42
45
  )
43
46
 
44
47
  # Fallback interno caso o download falhe
@@ -7,8 +7,8 @@ import click
7
7
  from datetime import datetime
8
8
 
9
9
  # Current version of your local executable (Update this on every new build!)
10
- __version__ = "0.0.24" # GitPR current version
11
- __lang_version__ = "v0.0.2" # Language dictionary version control
10
+ __version__ = "0.0.25" # GitPR current version
11
+ __lang_version__ = "v0.0.3" # Language dictionary version control
12
12
  GITHUB_API_URL = "https://api.github.com/repos/natanfiuza/gitpr/releases/latest"
13
13
  PYPI_API_URL = "https://pypi.org/pypi/gitpr-cli/json"
14
14
 
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes