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.
- {gitpr_cli-0.0.24 → gitpr_cli-0.0.25}/PKG-INFO +1 -1
- {gitpr_cli-0.0.24 → gitpr_cli-0.0.25}/gitpr_cli.egg-info/PKG-INFO +1 -1
- {gitpr_cli-0.0.24 → gitpr_cli-0.0.25}/pyproject.toml +1 -1
- {gitpr_cli-0.0.24 → gitpr_cli-0.0.25}/src/blame_engine.py +2 -2
- {gitpr_cli-0.0.24 → gitpr_cli-0.0.25}/src/config.py +48 -1
- {gitpr_cli-0.0.24 → gitpr_cli-0.0.25}/src/core.py +14 -9
- {gitpr_cli-0.0.24 → gitpr_cli-0.0.25}/src/issue_engine.py +2 -2
- {gitpr_cli-0.0.24 → gitpr_cli-0.0.25}/src/main.py +2 -2
- {gitpr_cli-0.0.24 → gitpr_cli-0.0.25}/src/spinner.py +6 -3
- {gitpr_cli-0.0.24 → gitpr_cli-0.0.25}/src/updater.py +2 -2
- {gitpr_cli-0.0.24 → gitpr_cli-0.0.25}/LICENSE +0 -0
- {gitpr_cli-0.0.24 → gitpr_cli-0.0.25}/README.md +0 -0
- {gitpr_cli-0.0.24 → gitpr_cli-0.0.25}/gitpr_cli.egg-info/SOURCES.txt +0 -0
- {gitpr_cli-0.0.24 → gitpr_cli-0.0.25}/gitpr_cli.egg-info/dependency_links.txt +0 -0
- {gitpr_cli-0.0.24 → gitpr_cli-0.0.25}/gitpr_cli.egg-info/entry_points.txt +0 -0
- {gitpr_cli-0.0.24 → gitpr_cli-0.0.25}/gitpr_cli.egg-info/requires.txt +0 -0
- {gitpr_cli-0.0.24 → gitpr_cli-0.0.25}/gitpr_cli.egg-info/top_level.txt +0 -0
- {gitpr_cli-0.0.24 → gitpr_cli-0.0.25}/setup.cfg +0 -0
- {gitpr_cli-0.0.24 → gitpr_cli-0.0.25}/src/__init__.py +0 -0
- {gitpr_cli-0.0.24 → gitpr_cli-0.0.25}/src/ai_providers.py +0 -0
- {gitpr_cli-0.0.24 → gitpr_cli-0.0.25}/src/cache.py +0 -0
- {gitpr_cli-0.0.24 → gitpr_cli-0.0.25}/src/i18n.py +0 -0
- {gitpr_cli-0.0.24 → gitpr_cli-0.0.25}/src/linter_engine.py +0 -0
- {gitpr_cli-0.0.24 → gitpr_cli-0.0.25}/src/security.py +0 -0
- {gitpr_cli-0.0.24 → gitpr_cli-0.0.25}/src/tui_issue.py +0 -0
- {gitpr_cli-0.0.24 → gitpr_cli-0.0.25}/src/ui/__init__.py +0 -0
- {gitpr_cli-0.0.24 → gitpr_cli-0.0.25}/src/ui/help_screen.py +0 -0
- {gitpr_cli-0.0.24 → gitpr_cli-0.0.25}/src/ui/issue_app.py +0 -0
- {gitpr_cli-0.0.24 → gitpr_cli-0.0.25}/tests/test_core.py +0 -0
|
@@ -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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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 =
|
|
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
|
|
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=__("
|
|
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.
|
|
11
|
-
__lang_version__ = "v0.0.
|
|
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
|
|
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
|