gac 1.14.0__py3-none-any.whl → 2.0.0__py3-none-any.whl
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.
Potentially problematic release.
This version of gac might be problematic. Click here for more details.
- gac/__version__.py +1 -1
- gac/cli.py +12 -1
- gac/config.py +2 -0
- gac/constants.py +87 -0
- gac/init_cli.py +75 -0
- gac/language_cli.py +111 -0
- gac/main.py +29 -24
- gac/prompt.py +43 -10
- {gac-1.14.0.dist-info → gac-2.0.0.dist-info}/METADATA +21 -10
- {gac-1.14.0.dist-info → gac-2.0.0.dist-info}/RECORD +13 -12
- {gac-1.14.0.dist-info → gac-2.0.0.dist-info}/WHEEL +0 -0
- {gac-1.14.0.dist-info → gac-2.0.0.dist-info}/entry_points.txt +0 -0
- {gac-1.14.0.dist-info → gac-2.0.0.dist-info}/licenses/LICENSE +0 -0
gac/__version__.py
CHANGED
gac/cli.py
CHANGED
|
@@ -13,10 +13,11 @@ import click
|
|
|
13
13
|
from gac import __version__
|
|
14
14
|
from gac.config import load_config
|
|
15
15
|
from gac.config_cli import config as config_cli
|
|
16
|
-
from gac.constants import Logging
|
|
16
|
+
from gac.constants import Languages, Logging
|
|
17
17
|
from gac.diff_cli import diff as diff_cli
|
|
18
18
|
from gac.errors import handle_error
|
|
19
19
|
from gac.init_cli import init as init_cli
|
|
20
|
+
from gac.language_cli import language as language_cli
|
|
20
21
|
from gac.main import main
|
|
21
22
|
from gac.utils import setup_logging
|
|
22
23
|
|
|
@@ -43,6 +44,9 @@ logger = logging.getLogger(__name__)
|
|
|
43
44
|
@click.option("--hint", "-h", default="", help="Additional context to include in the prompt")
|
|
44
45
|
# Model options
|
|
45
46
|
@click.option("--model", "-m", help="Override the default model (format: 'provider:model_name')")
|
|
47
|
+
@click.option(
|
|
48
|
+
"--language", "-l", help="Override the language for commit messages (e.g., 'Spanish', 'es', 'zh-CN', 'ja')"
|
|
49
|
+
)
|
|
46
50
|
# Output options
|
|
47
51
|
@click.option("--quiet", "-q", is_flag=True, help="Suppress non-error output")
|
|
48
52
|
@click.option(
|
|
@@ -75,6 +79,7 @@ def cli(
|
|
|
75
79
|
yes: bool = False,
|
|
76
80
|
hint: str = "",
|
|
77
81
|
model: str | None = None,
|
|
82
|
+
language: str | None = None,
|
|
78
83
|
version: bool = False,
|
|
79
84
|
dry_run: bool = False,
|
|
80
85
|
verbose: bool = False,
|
|
@@ -98,6 +103,9 @@ def cli(
|
|
|
98
103
|
# Determine if verbose mode should be enabled based on -v flag or verbose config setting
|
|
99
104
|
use_verbose = bool(verbose or config.get("verbose", False))
|
|
100
105
|
|
|
106
|
+
# Resolve language code to full name if provided
|
|
107
|
+
resolved_language = Languages.resolve_code(language) if language else None
|
|
108
|
+
|
|
101
109
|
try:
|
|
102
110
|
main(
|
|
103
111
|
stage_all=add_all,
|
|
@@ -113,6 +121,7 @@ def cli(
|
|
|
113
121
|
verbose=use_verbose,
|
|
114
122
|
no_verify=no_verify,
|
|
115
123
|
skip_secret_scan=skip_secret_scan or bool(config.get("skip_secret_scan", False)),
|
|
124
|
+
language=resolved_language,
|
|
116
125
|
)
|
|
117
126
|
except Exception as e:
|
|
118
127
|
handle_error(e, exit_program=True)
|
|
@@ -131,6 +140,7 @@ def cli(
|
|
|
131
140
|
"yes": yes,
|
|
132
141
|
"hint": hint,
|
|
133
142
|
"model": model,
|
|
143
|
+
"language": language,
|
|
134
144
|
"version": version,
|
|
135
145
|
"dry_run": dry_run,
|
|
136
146
|
"verbose": verbose,
|
|
@@ -141,6 +151,7 @@ def cli(
|
|
|
141
151
|
|
|
142
152
|
cli.add_command(config_cli)
|
|
143
153
|
cli.add_command(init_cli)
|
|
154
|
+
cli.add_command(language_cli)
|
|
144
155
|
cli.add_command(diff_cli)
|
|
145
156
|
|
|
146
157
|
if __name__ == "__main__":
|
gac/config.py
CHANGED
|
@@ -39,6 +39,8 @@ def load_config() -> dict[str, str | int | float | bool | None]:
|
|
|
39
39
|
in ("true", "1", "yes", "on"),
|
|
40
40
|
"verbose": os.getenv("GAC_VERBOSE", str(EnvDefaults.VERBOSE)).lower() in ("true", "1", "yes", "on"),
|
|
41
41
|
"system_prompt_path": os.getenv("GAC_SYSTEM_PROMPT_PATH"),
|
|
42
|
+
"language": os.getenv("GAC_LANGUAGE"),
|
|
43
|
+
"translate_prefixes": os.getenv("GAC_TRANSLATE_PREFIXES", "false").lower() in ("true", "1", "yes", "on"),
|
|
42
44
|
}
|
|
43
45
|
|
|
44
46
|
return config
|
gac/constants.py
CHANGED
|
@@ -152,6 +152,93 @@ class CodePatternImportance:
|
|
|
152
152
|
}
|
|
153
153
|
|
|
154
154
|
|
|
155
|
+
class Languages:
|
|
156
|
+
"""Language code mappings and utilities."""
|
|
157
|
+
|
|
158
|
+
# Language code to full name mapping
|
|
159
|
+
# Supports ISO 639-1 codes and common variants
|
|
160
|
+
CODE_MAP: dict[str, str] = {
|
|
161
|
+
# English
|
|
162
|
+
"en": "English",
|
|
163
|
+
# Chinese
|
|
164
|
+
"zh": "Simplified Chinese",
|
|
165
|
+
"zh-cn": "Simplified Chinese",
|
|
166
|
+
"zh-hans": "Simplified Chinese",
|
|
167
|
+
"zh-tw": "Traditional Chinese",
|
|
168
|
+
"zh-hant": "Traditional Chinese",
|
|
169
|
+
# Japanese
|
|
170
|
+
"ja": "Japanese",
|
|
171
|
+
# Korean
|
|
172
|
+
"ko": "Korean",
|
|
173
|
+
# Spanish
|
|
174
|
+
"es": "Spanish",
|
|
175
|
+
# Portuguese
|
|
176
|
+
"pt": "Portuguese",
|
|
177
|
+
# French
|
|
178
|
+
"fr": "French",
|
|
179
|
+
# German
|
|
180
|
+
"de": "German",
|
|
181
|
+
# Russian
|
|
182
|
+
"ru": "Russian",
|
|
183
|
+
# Hindi
|
|
184
|
+
"hi": "Hindi",
|
|
185
|
+
# Italian
|
|
186
|
+
"it": "Italian",
|
|
187
|
+
# Polish
|
|
188
|
+
"pl": "Polish",
|
|
189
|
+
# Turkish
|
|
190
|
+
"tr": "Turkish",
|
|
191
|
+
# Dutch
|
|
192
|
+
"nl": "Dutch",
|
|
193
|
+
# Vietnamese
|
|
194
|
+
"vi": "Vietnamese",
|
|
195
|
+
# Thai
|
|
196
|
+
"th": "Thai",
|
|
197
|
+
# Indonesian
|
|
198
|
+
"id": "Indonesian",
|
|
199
|
+
# Swedish
|
|
200
|
+
"sv": "Swedish",
|
|
201
|
+
# Arabic
|
|
202
|
+
"ar": "Arabic",
|
|
203
|
+
# Hebrew
|
|
204
|
+
"he": "Hebrew",
|
|
205
|
+
# Greek
|
|
206
|
+
"el": "Greek",
|
|
207
|
+
# Danish
|
|
208
|
+
"da": "Danish",
|
|
209
|
+
# Norwegian
|
|
210
|
+
"no": "Norwegian",
|
|
211
|
+
"nb": "Norwegian",
|
|
212
|
+
"nn": "Norwegian",
|
|
213
|
+
# Finnish
|
|
214
|
+
"fi": "Finnish",
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
@staticmethod
|
|
218
|
+
def resolve_code(language: str) -> str:
|
|
219
|
+
"""Resolve a language code to its full name.
|
|
220
|
+
|
|
221
|
+
Args:
|
|
222
|
+
language: Language name or code (e.g., 'Spanish', 'es', 'zh-CN')
|
|
223
|
+
|
|
224
|
+
Returns:
|
|
225
|
+
Full language name (e.g., 'Spanish', 'Simplified Chinese')
|
|
226
|
+
|
|
227
|
+
If the input is already a full language name, it's returned as-is.
|
|
228
|
+
If it's a recognized code, it's converted to the full name.
|
|
229
|
+
Otherwise, the input is returned unchanged (for custom languages).
|
|
230
|
+
"""
|
|
231
|
+
# Normalize the code to lowercase for lookup
|
|
232
|
+
code_lower = language.lower().strip()
|
|
233
|
+
|
|
234
|
+
# Check if it's a recognized code
|
|
235
|
+
if code_lower in Languages.CODE_MAP:
|
|
236
|
+
return Languages.CODE_MAP[code_lower]
|
|
237
|
+
|
|
238
|
+
# Return as-is (could be a full name or custom language)
|
|
239
|
+
return language
|
|
240
|
+
|
|
241
|
+
|
|
155
242
|
class CommitMessageConstants:
|
|
156
243
|
"""Constants for commit message generation and cleaning."""
|
|
157
244
|
|
gac/init_cli.py
CHANGED
|
@@ -151,4 +151,79 @@ def init() -> None:
|
|
|
151
151
|
elif is_ollama or is_lmstudio:
|
|
152
152
|
click.echo("Skipping API key. You can add one later if needed.")
|
|
153
153
|
|
|
154
|
+
# Language selection
|
|
155
|
+
click.echo("\n")
|
|
156
|
+
languages = [
|
|
157
|
+
("English", "English"),
|
|
158
|
+
("简体中文", "Simplified Chinese"),
|
|
159
|
+
("繁體中文", "Traditional Chinese"),
|
|
160
|
+
("日本語", "Japanese"),
|
|
161
|
+
("한국어", "Korean"),
|
|
162
|
+
("Español", "Spanish"),
|
|
163
|
+
("Português", "Portuguese"),
|
|
164
|
+
("Français", "French"),
|
|
165
|
+
("Deutsch", "German"),
|
|
166
|
+
("Русский", "Russian"),
|
|
167
|
+
("हिन्दी", "Hindi"),
|
|
168
|
+
("Italiano", "Italian"),
|
|
169
|
+
("Polski", "Polish"),
|
|
170
|
+
("Türkçe", "Turkish"),
|
|
171
|
+
("Nederlands", "Dutch"),
|
|
172
|
+
("Tiếng Việt", "Vietnamese"),
|
|
173
|
+
("ไทย", "Thai"),
|
|
174
|
+
("Bahasa Indonesia", "Indonesian"),
|
|
175
|
+
("Svenska", "Swedish"),
|
|
176
|
+
("العربية", "Arabic"),
|
|
177
|
+
("עברית", "Hebrew"),
|
|
178
|
+
("Ελληνικά", "Greek"),
|
|
179
|
+
("Dansk", "Danish"),
|
|
180
|
+
("Norsk", "Norwegian"),
|
|
181
|
+
("Suomi", "Finnish"),
|
|
182
|
+
("Custom", "Custom"),
|
|
183
|
+
]
|
|
184
|
+
|
|
185
|
+
display_names = [lang[0] for lang in languages]
|
|
186
|
+
language_selection = questionary.select(
|
|
187
|
+
"Select a language for commit messages:", choices=display_names, use_shortcuts=True, use_arrow_keys=True
|
|
188
|
+
).ask()
|
|
189
|
+
|
|
190
|
+
if not language_selection:
|
|
191
|
+
click.echo("Language selection cancelled. Using English (default).")
|
|
192
|
+
elif language_selection == "English":
|
|
193
|
+
click.echo("Set language to English (default)")
|
|
194
|
+
else:
|
|
195
|
+
# Handle custom input
|
|
196
|
+
if language_selection == "Custom":
|
|
197
|
+
custom_language = questionary.text("Enter the language name (e.g., 'Spanish', 'Français', '日本語'):").ask()
|
|
198
|
+
if not custom_language or not custom_language.strip():
|
|
199
|
+
click.echo("No language entered. Using English (default).")
|
|
200
|
+
language_value = None
|
|
201
|
+
else:
|
|
202
|
+
language_value = custom_language.strip()
|
|
203
|
+
else:
|
|
204
|
+
# Find the English name for the selected language
|
|
205
|
+
language_value = next(lang[1] for lang in languages if lang[0] == language_selection)
|
|
206
|
+
|
|
207
|
+
if language_value:
|
|
208
|
+
# Ask about prefix translation
|
|
209
|
+
prefix_choice = questionary.select(
|
|
210
|
+
"How should conventional commit prefixes be handled?",
|
|
211
|
+
choices=[
|
|
212
|
+
"Keep prefixes in English (feat:, fix:, etc.)",
|
|
213
|
+
f"Translate prefixes into {language_value}",
|
|
214
|
+
],
|
|
215
|
+
).ask()
|
|
216
|
+
|
|
217
|
+
if not prefix_choice:
|
|
218
|
+
click.echo("Prefix translation selection cancelled. Using English prefixes.")
|
|
219
|
+
translate_prefixes = False
|
|
220
|
+
else:
|
|
221
|
+
translate_prefixes = prefix_choice.startswith("Translate prefixes")
|
|
222
|
+
|
|
223
|
+
# Set the language and prefix translation preference
|
|
224
|
+
set_key(str(GAC_ENV_PATH), "GAC_LANGUAGE", language_value)
|
|
225
|
+
set_key(str(GAC_ENV_PATH), "GAC_TRANSLATE_PREFIXES", "true" if translate_prefixes else "false")
|
|
226
|
+
click.echo(f"Set GAC_LANGUAGE={language_value}")
|
|
227
|
+
click.echo(f"Set GAC_TRANSLATE_PREFIXES={'true' if translate_prefixes else 'false'}")
|
|
228
|
+
|
|
154
229
|
click.echo(f"\ngac environment setup complete. You can edit {GAC_ENV_PATH} to update values later.")
|
gac/language_cli.py
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
"""CLI for selecting commit message language interactively."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
import click
|
|
6
|
+
import questionary
|
|
7
|
+
from dotenv import set_key, unset_key
|
|
8
|
+
|
|
9
|
+
GAC_ENV_PATH = Path.home() / ".gac.env"
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@click.command()
|
|
13
|
+
def language() -> None:
|
|
14
|
+
"""Set the language for commit messages interactively."""
|
|
15
|
+
click.echo("Select a language for your commit messages:\n")
|
|
16
|
+
|
|
17
|
+
# Languages sorted by programmer population likelihood
|
|
18
|
+
# Based on GitHub statistics and global developer demographics
|
|
19
|
+
languages = [
|
|
20
|
+
("English", "English"),
|
|
21
|
+
("简体中文", "Simplified Chinese"),
|
|
22
|
+
("繁體中文", "Traditional Chinese"),
|
|
23
|
+
("日本語", "Japanese"),
|
|
24
|
+
("한국어", "Korean"),
|
|
25
|
+
("Español", "Spanish"),
|
|
26
|
+
("Português", "Portuguese"),
|
|
27
|
+
("Français", "French"),
|
|
28
|
+
("Deutsch", "German"),
|
|
29
|
+
("Русский", "Russian"),
|
|
30
|
+
("हिन्दी", "Hindi"),
|
|
31
|
+
("Italiano", "Italian"),
|
|
32
|
+
("Polski", "Polish"),
|
|
33
|
+
("Türkçe", "Turkish"),
|
|
34
|
+
("Nederlands", "Dutch"),
|
|
35
|
+
("Tiếng Việt", "Vietnamese"),
|
|
36
|
+
("ไทย", "Thai"),
|
|
37
|
+
("Bahasa Indonesia", "Indonesian"),
|
|
38
|
+
("Svenska", "Swedish"),
|
|
39
|
+
("العربية", "Arabic"),
|
|
40
|
+
("עברית", "Hebrew"),
|
|
41
|
+
("Ελληνικά", "Greek"),
|
|
42
|
+
("Dansk", "Danish"),
|
|
43
|
+
("Norsk", "Norwegian"),
|
|
44
|
+
("Suomi", "Finnish"),
|
|
45
|
+
("Custom", "Custom"),
|
|
46
|
+
]
|
|
47
|
+
|
|
48
|
+
display_names = [lang[0] for lang in languages]
|
|
49
|
+
selection = questionary.select(
|
|
50
|
+
"Choose your language:", choices=display_names, use_shortcuts=True, use_arrow_keys=True
|
|
51
|
+
).ask()
|
|
52
|
+
|
|
53
|
+
if not selection:
|
|
54
|
+
click.echo("Language selection cancelled.")
|
|
55
|
+
return
|
|
56
|
+
|
|
57
|
+
# Ensure .gac.env exists
|
|
58
|
+
if not GAC_ENV_PATH.exists():
|
|
59
|
+
GAC_ENV_PATH.touch()
|
|
60
|
+
click.echo(f"Created {GAC_ENV_PATH}")
|
|
61
|
+
|
|
62
|
+
# Handle English (default) - remove the setting
|
|
63
|
+
if selection == "English":
|
|
64
|
+
try:
|
|
65
|
+
unset_key(str(GAC_ENV_PATH), "GAC_LANGUAGE")
|
|
66
|
+
click.echo("✓ Set language to English (default)")
|
|
67
|
+
click.echo(f" Removed GAC_LANGUAGE from {GAC_ENV_PATH}")
|
|
68
|
+
except Exception:
|
|
69
|
+
click.echo("✓ Set language to English (default)")
|
|
70
|
+
return
|
|
71
|
+
|
|
72
|
+
# Handle custom input
|
|
73
|
+
if selection == "Custom":
|
|
74
|
+
custom_language = questionary.text("Enter the language name (e.g., 'Spanish', 'Français', '日本語'):").ask()
|
|
75
|
+
if not custom_language or not custom_language.strip():
|
|
76
|
+
click.echo("No language entered. Cancelled.")
|
|
77
|
+
return
|
|
78
|
+
language_value = custom_language.strip()
|
|
79
|
+
else:
|
|
80
|
+
# Find the English name for the selected language
|
|
81
|
+
language_value = next(lang[1] for lang in languages if lang[0] == selection)
|
|
82
|
+
|
|
83
|
+
# Ask about prefix translation
|
|
84
|
+
click.echo() # Blank line for spacing
|
|
85
|
+
prefix_choice = questionary.select(
|
|
86
|
+
"How should conventional commit prefixes be handled?",
|
|
87
|
+
choices=[
|
|
88
|
+
"Keep prefixes in English (feat:, fix:, etc.)",
|
|
89
|
+
f"Translate prefixes into {language_value}",
|
|
90
|
+
],
|
|
91
|
+
).ask()
|
|
92
|
+
|
|
93
|
+
if not prefix_choice:
|
|
94
|
+
click.echo("Prefix translation selection cancelled.")
|
|
95
|
+
return
|
|
96
|
+
|
|
97
|
+
translate_prefixes = prefix_choice.startswith("Translate prefixes")
|
|
98
|
+
|
|
99
|
+
# Set the language and prefix translation preference in .gac.env
|
|
100
|
+
set_key(str(GAC_ENV_PATH), "GAC_LANGUAGE", language_value)
|
|
101
|
+
set_key(str(GAC_ENV_PATH), "GAC_TRANSLATE_PREFIXES", "true" if translate_prefixes else "false")
|
|
102
|
+
|
|
103
|
+
click.echo(f"\n✓ Set language to {selection}")
|
|
104
|
+
click.echo(f" GAC_LANGUAGE={language_value}")
|
|
105
|
+
if translate_prefixes:
|
|
106
|
+
click.echo(" GAC_TRANSLATE_PREFIXES=true")
|
|
107
|
+
click.echo("\n Prefixes will be translated (e.g., 'corrección:' instead of 'fix:')")
|
|
108
|
+
else:
|
|
109
|
+
click.echo(" GAC_TRANSLATE_PREFIXES=false")
|
|
110
|
+
click.echo(f"\n Prefixes will remain in English (e.g., 'fix: <{language_value} description>')")
|
|
111
|
+
click.echo(f"\n Configuration saved to {GAC_ENV_PATH}")
|
gac/main.py
CHANGED
|
@@ -46,6 +46,7 @@ def main(
|
|
|
46
46
|
verbose: bool = False,
|
|
47
47
|
no_verify: bool = False,
|
|
48
48
|
skip_secret_scan: bool = False,
|
|
49
|
+
language: str | None = None,
|
|
49
50
|
) -> None:
|
|
50
51
|
"""Main application logic for gac."""
|
|
51
52
|
try:
|
|
@@ -186,6 +187,14 @@ def main(
|
|
|
186
187
|
system_template_path_value if isinstance(system_template_path_value, str) else None
|
|
187
188
|
)
|
|
188
189
|
|
|
190
|
+
# Use language parameter if provided, otherwise fall back to config
|
|
191
|
+
if language is None:
|
|
192
|
+
language_value = config.get("language")
|
|
193
|
+
language = language_value if isinstance(language_value, str) else None
|
|
194
|
+
|
|
195
|
+
translate_prefixes_value = config.get("translate_prefixes")
|
|
196
|
+
translate_prefixes: bool = bool(translate_prefixes_value) if isinstance(translate_prefixes_value, bool) else False
|
|
197
|
+
|
|
189
198
|
system_prompt, user_prompt = build_prompt(
|
|
190
199
|
status=status,
|
|
191
200
|
processed_diff=processed_diff,
|
|
@@ -195,6 +204,8 @@ def main(
|
|
|
195
204
|
infer_scope=infer_scope,
|
|
196
205
|
verbose=verbose,
|
|
197
206
|
system_template_path=system_template_path,
|
|
207
|
+
language=language,
|
|
208
|
+
translate_prefixes=translate_prefixes,
|
|
198
209
|
)
|
|
199
210
|
|
|
200
211
|
if show_prompt:
|
|
@@ -244,10 +255,8 @@ def main(
|
|
|
244
255
|
max_retries=max_retries,
|
|
245
256
|
quiet=quiet,
|
|
246
257
|
)
|
|
247
|
-
#
|
|
248
|
-
commit_message = clean_commit_message(
|
|
249
|
-
raw_commit_message, enforce_conventional_commits=(system_template_path is None)
|
|
250
|
-
)
|
|
258
|
+
# Clean the commit message (no automatic prefix enforcement)
|
|
259
|
+
commit_message = clean_commit_message(raw_commit_message)
|
|
251
260
|
|
|
252
261
|
logger.info("Generated commit message:")
|
|
253
262
|
logger.info(commit_message)
|
|
@@ -266,10 +275,11 @@ def main(
|
|
|
266
275
|
)
|
|
267
276
|
|
|
268
277
|
if require_confirmation:
|
|
269
|
-
# Custom prompt that accepts y/n/r or "r <feedback (optional)>"
|
|
270
278
|
while True:
|
|
271
279
|
response = click.prompt(
|
|
272
|
-
"Proceed with commit above? [y/n/r
|
|
280
|
+
"Proceed with commit above? [y/n/r/<feedback>]",
|
|
281
|
+
type=str,
|
|
282
|
+
show_default=False,
|
|
273
283
|
).strip()
|
|
274
284
|
response_lower = response.lower()
|
|
275
285
|
|
|
@@ -278,27 +288,22 @@ def main(
|
|
|
278
288
|
if response_lower in ["n", "no"]:
|
|
279
289
|
console.print("[yellow]Prompt not accepted. Exiting...[/yellow]")
|
|
280
290
|
sys.exit(0)
|
|
281
|
-
if
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
reroll_feedback = response[2:].strip()
|
|
289
|
-
feedback_message = (
|
|
290
|
-
f"Please revise the commit message based on this feedback: {reroll_feedback}"
|
|
291
|
-
)
|
|
292
|
-
console.print(f"[cyan]Regenerating commit message with feedback: {reroll_feedback}[/cyan]")
|
|
293
|
-
|
|
291
|
+
if response == "":
|
|
292
|
+
continue
|
|
293
|
+
if response_lower in ["r", "reroll"]:
|
|
294
|
+
feedback_message = (
|
|
295
|
+
"Please provide an alternative commit message using the same repository context."
|
|
296
|
+
)
|
|
297
|
+
console.print("[cyan]Regenerating commit message...[/cyan]")
|
|
294
298
|
conversation_messages.append({"role": "user", "content": feedback_message})
|
|
295
|
-
|
|
296
|
-
console.print() # Add blank line for readability
|
|
299
|
+
console.print()
|
|
297
300
|
break
|
|
298
301
|
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
)
|
|
302
|
+
feedback_message = f"Please revise the commit message based on this feedback: {response}"
|
|
303
|
+
console.print(f"[cyan]Regenerating commit message with feedback: {response}[/cyan]")
|
|
304
|
+
conversation_messages.append({"role": "user", "content": feedback_message})
|
|
305
|
+
console.print()
|
|
306
|
+
break
|
|
302
307
|
|
|
303
308
|
if response_lower in ["y", "yes"]:
|
|
304
309
|
break
|
gac/prompt.py
CHANGED
|
@@ -39,12 +39,12 @@ When changes span multiple areas:
|
|
|
39
39
|
|
|
40
40
|
<format>
|
|
41
41
|
<one_liner>
|
|
42
|
-
Create a single-line commit message
|
|
42
|
+
Create a single-line commit message.
|
|
43
43
|
Your message should be clear, concise, and descriptive of the core change.
|
|
44
44
|
Use present tense ("Add feature" not "Added feature").
|
|
45
45
|
</one_liner><multi_line>
|
|
46
46
|
Create a commit message with:
|
|
47
|
-
- First line: A concise summary
|
|
47
|
+
- First line: A concise summary that could stand alone
|
|
48
48
|
- Blank line after the summary
|
|
49
49
|
- Detailed body with multiple bullet points explaining the key changes
|
|
50
50
|
- Focus on WHY changes were made, not just WHAT was changed
|
|
@@ -55,7 +55,7 @@ When changes span multiple areas:
|
|
|
55
55
|
|
|
56
56
|
Your commit message MUST follow this structure:
|
|
57
57
|
|
|
58
|
-
Line 1: A concise summary (
|
|
58
|
+
Line 1: A concise summary (that could stand alone) with conventional commit prefix
|
|
59
59
|
Line 2: BLANK LINE (required)
|
|
60
60
|
Lines 3+: Detailed multi-paragraph body with the following sections:
|
|
61
61
|
|
|
@@ -263,6 +263,12 @@ IMMEDIATELY AFTER ANALYZING THE CHANGES, RESPOND WITH ONLY THE COMMIT MESSAGE.
|
|
|
263
263
|
DO NOT include any preamble, reasoning, explanations or anything other than the commit message itself.
|
|
264
264
|
DO NOT use markdown formatting, headers, or code blocks.
|
|
265
265
|
The entire response will be passed directly to 'git commit -m'.
|
|
266
|
+
|
|
267
|
+
<language>
|
|
268
|
+
IMPORTANT: You MUST write the entire commit message in <language_name></language_name>.
|
|
269
|
+
All text in the commit message, including the summary line and body, must be in <language_name></language_name>.
|
|
270
|
+
<prefix_instruction></prefix_instruction>
|
|
271
|
+
</language>
|
|
266
272
|
</instructions>"""
|
|
267
273
|
|
|
268
274
|
|
|
@@ -449,6 +455,8 @@ def build_prompt(
|
|
|
449
455
|
hint: str = "",
|
|
450
456
|
verbose: bool = False,
|
|
451
457
|
system_template_path: str | None = None,
|
|
458
|
+
language: str | None = None,
|
|
459
|
+
translate_prefixes: bool = False,
|
|
452
460
|
) -> tuple[str, str]:
|
|
453
461
|
"""Build system and user prompts for the AI model using the provided templates and git information.
|
|
454
462
|
|
|
@@ -461,6 +469,8 @@ def build_prompt(
|
|
|
461
469
|
hint: Optional hint to guide the AI
|
|
462
470
|
verbose: Whether to generate detailed commit messages with motivation, architecture, and impact sections
|
|
463
471
|
system_template_path: Optional path to custom system template
|
|
472
|
+
language: Optional language for commit messages (e.g., "Spanish", "French", "Japanese")
|
|
473
|
+
translate_prefixes: Whether to translate conventional commit prefixes (default: False keeps them in English)
|
|
464
474
|
|
|
465
475
|
Returns:
|
|
466
476
|
Tuple of (system_prompt, user_prompt) ready to be sent to an AI model
|
|
@@ -484,6 +494,33 @@ def build_prompt(
|
|
|
484
494
|
user_template = _remove_template_section(user_template, "hint")
|
|
485
495
|
logger.debug("No hint provided")
|
|
486
496
|
|
|
497
|
+
if language:
|
|
498
|
+
user_template = user_template.replace("<language_name></language_name>", language)
|
|
499
|
+
|
|
500
|
+
# Set prefix instruction based on translate_prefixes setting
|
|
501
|
+
if translate_prefixes:
|
|
502
|
+
prefix_instruction = f"""CRITICAL: You MUST translate the conventional commit prefix into {language}.
|
|
503
|
+
DO NOT use English prefixes like 'feat:', 'fix:', 'docs:', etc.
|
|
504
|
+
Instead, translate them into {language} equivalents.
|
|
505
|
+
Examples:
|
|
506
|
+
- 'feat:' → translate to {language} word for 'feature' or 'add'
|
|
507
|
+
- 'fix:' → translate to {language} word for 'fix' or 'correct'
|
|
508
|
+
- 'docs:' → translate to {language} word for 'documentation'
|
|
509
|
+
The ENTIRE commit message, including the prefix, must be in {language}."""
|
|
510
|
+
logger.debug(f"Set commit message language to: {language} (with prefix translation)")
|
|
511
|
+
else:
|
|
512
|
+
prefix_instruction = (
|
|
513
|
+
"The conventional commit prefix (feat:, fix:, etc.) should remain in English, but everything after the prefix must be in "
|
|
514
|
+
+ language
|
|
515
|
+
+ "."
|
|
516
|
+
)
|
|
517
|
+
logger.debug(f"Set commit message language to: {language} (English prefixes)")
|
|
518
|
+
|
|
519
|
+
user_template = user_template.replace("<prefix_instruction></prefix_instruction>", prefix_instruction)
|
|
520
|
+
else:
|
|
521
|
+
user_template = _remove_template_section(user_template, "language")
|
|
522
|
+
logger.debug("Using default language (English)")
|
|
523
|
+
|
|
487
524
|
user_template = re.sub(r"\n(?:[ \t]*\n){2,}", "\n\n", user_template)
|
|
488
525
|
|
|
489
526
|
return system_template.strip(), user_template.strip()
|
|
@@ -636,20 +673,18 @@ def _normalize_whitespace(message: str) -> str:
|
|
|
636
673
|
# ============================================================================
|
|
637
674
|
|
|
638
675
|
|
|
639
|
-
def clean_commit_message(message: str
|
|
676
|
+
def clean_commit_message(message: str) -> str:
|
|
640
677
|
"""Clean up a commit message generated by an AI model.
|
|
641
678
|
|
|
642
679
|
This function:
|
|
643
680
|
1. Removes any preamble or reasoning text
|
|
644
681
|
2. Removes code block markers and formatting
|
|
645
682
|
3. Removes XML tags that might have leaked into the response
|
|
646
|
-
4.
|
|
647
|
-
5.
|
|
683
|
+
4. Fixes double type prefix issues (e.g., "chore: feat(scope):")
|
|
684
|
+
5. Normalizes whitespace
|
|
648
685
|
|
|
649
686
|
Args:
|
|
650
687
|
message: Raw commit message from AI
|
|
651
|
-
enforce_conventional_commits: If True, ensures message has conventional commit prefix.
|
|
652
|
-
Set to False when using custom system prompts.
|
|
653
688
|
|
|
654
689
|
Returns:
|
|
655
690
|
Cleaned commit message ready for use
|
|
@@ -660,7 +695,5 @@ def clean_commit_message(message: str, enforce_conventional_commits: bool = True
|
|
|
660
695
|
message = _extract_commit_from_reasoning(message)
|
|
661
696
|
message = _remove_xml_tags(message)
|
|
662
697
|
message = _fix_double_prefix(message)
|
|
663
|
-
if enforce_conventional_commits:
|
|
664
|
-
message = _ensure_conventional_prefix(message)
|
|
665
698
|
message = _normalize_whitespace(message)
|
|
666
699
|
return message
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: gac
|
|
3
|
-
Version:
|
|
3
|
+
Version: 2.0.0
|
|
4
4
|
Summary: LLM-powered Git commit message generator with multi-provider support
|
|
5
5
|
Project-URL: Homepage, https://github.com/cellwebb/gac
|
|
6
6
|
Project-URL: Documentation, https://github.com/cellwebb/gac#readme
|
|
@@ -108,9 +108,16 @@ gac
|
|
|
108
108
|
- **Standard** (default): Summary with bullet points explaining implementation details
|
|
109
109
|
- **Verbose** (-v flag): Comprehensive explanations including motivation, technical approach, and impact analysis
|
|
110
110
|
|
|
111
|
+
### 🌍 **Multilingual Support**
|
|
112
|
+
|
|
113
|
+
- **25+ languages**: Generate commit messages in English, Chinese, Japanese, Korean, Spanish, French, German, and 20+ more languages
|
|
114
|
+
- **Flexible translation**: Choose to keep conventional commit prefixes in English for tool compatibility, or fully translate them
|
|
115
|
+
- **Multiple workflows**: Set a default language with `gac language`, or use `-l <language>` flag for one-time overrides
|
|
116
|
+
- **Native script support**: Full support for non-Latin scripts including CJK, Cyrillic, Arabic, and more
|
|
117
|
+
|
|
111
118
|
### 💻 **Developer Experience**
|
|
112
119
|
|
|
113
|
-
- **Interactive feedback**:
|
|
120
|
+
- **Interactive feedback**: Type `r` to reroll, or directly type your feedback like `make it shorter` or `focus on the bug fix`
|
|
114
121
|
- **One-command workflows**: Complete workflows with flags like `gac -ayp` (stage all, auto-confirm, push)
|
|
115
122
|
- **Git integration**: Respects pre-commit and lefthook hooks, running them before expensive LLM operations
|
|
116
123
|
|
|
@@ -133,7 +140,7 @@ git add .
|
|
|
133
140
|
# Generate and commit with LLM
|
|
134
141
|
gac
|
|
135
142
|
|
|
136
|
-
# Review → y (commit) | n (cancel) | r (reroll)
|
|
143
|
+
# Review → y (commit) | n (cancel) | r (reroll) | or type feedback
|
|
137
144
|
```
|
|
138
145
|
|
|
139
146
|
### Common Commands
|
|
@@ -168,18 +175,20 @@ gac --show-prompt
|
|
|
168
175
|
gac --skip-secret-scan
|
|
169
176
|
```
|
|
170
177
|
|
|
171
|
-
### Interactive
|
|
178
|
+
### Interactive Feedback System
|
|
172
179
|
|
|
173
|
-
Not happy with the result?
|
|
180
|
+
Not happy with the result? You have two options:
|
|
174
181
|
|
|
175
182
|
```bash
|
|
176
|
-
# Simple reroll
|
|
183
|
+
# Simple reroll (no feedback)
|
|
177
184
|
r
|
|
178
185
|
|
|
179
|
-
#
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
186
|
+
# Or just type your feedback directly - no prefix needed!
|
|
187
|
+
make it shorter and focus on the performance improvement
|
|
188
|
+
use conventional commit format with scope
|
|
189
|
+
explain the security implications
|
|
190
|
+
|
|
191
|
+
# Press Enter on empty input to see the prompt again
|
|
183
192
|
```
|
|
184
193
|
|
|
185
194
|
---
|
|
@@ -197,6 +206,8 @@ ANTHROPIC_API_KEY=your_key_here
|
|
|
197
206
|
|
|
198
207
|
See `.gac.env.example` for all available options.
|
|
199
208
|
|
|
209
|
+
**Want commit messages in another language?** Run `gac language` to select from 25+ languages including Español, Français, 日本語, and more.
|
|
210
|
+
|
|
200
211
|
**Want to customize commit message style?** See [docs/CUSTOM_SYSTEM_PROMPTS.md](docs/CUSTOM_SYSTEM_PROMPTS.md) for guidance on writing custom system prompts.
|
|
201
212
|
|
|
202
213
|
---
|
|
@@ -1,18 +1,19 @@
|
|
|
1
1
|
gac/__init__.py,sha256=z9yGInqtycFIT3g1ca24r-A3699hKVaRqGUI79wsmMc,415
|
|
2
|
-
gac/__version__.py,sha256=
|
|
2
|
+
gac/__version__.py,sha256=sXY5h9N-vjDIgAsvaFio9v3KucauBzZ9AFdd_Oe8M28,66
|
|
3
3
|
gac/ai.py,sha256=fg642la4yMecOwfZHQ7Ixl6z-5_qj9Q1SxwVMnPDCcY,4244
|
|
4
4
|
gac/ai_utils.py,sha256=EDkw0nnwnV5Ba2CLEo2HC15-L5BZtGJATin5Az0ZHkg,7426
|
|
5
|
-
gac/cli.py,sha256=
|
|
6
|
-
gac/config.py,sha256=
|
|
5
|
+
gac/cli.py,sha256=TV1IWwcRmEfbqXLlfGmTms5NCEqaJXUGIdFxmOg0tC0,5546
|
|
6
|
+
gac/config.py,sha256=O9n09-sFOqlkf47vieEP7fI5I7uhu1cXn9PUZ5yiYkw,1974
|
|
7
7
|
gac/config_cli.py,sha256=v9nFHZO1RvK9fzHyuUS6SG-BCLHMsdOMDwWamBhVVh4,1608
|
|
8
|
-
gac/constants.py,sha256
|
|
8
|
+
gac/constants.py,sha256=-VYqL1M99RAmEwQHTe6lMYVkPHSSYQJUvzYvBp8Bmck,8494
|
|
9
9
|
gac/diff_cli.py,sha256=wnVQ9OFGnM0d2Pj9WVjWbo0jxqIuRHVAwmb8wU9Pa3E,5676
|
|
10
10
|
gac/errors.py,sha256=ysDIVRCd0YQVTOW3Q6YzdolxCdtkoQCAFf3_jrqbjUY,7916
|
|
11
11
|
gac/git.py,sha256=g6tvph50zV-wrTWrxARYXEpl0NeI8-ffFwHoqhp3fSE,8033
|
|
12
|
-
gac/init_cli.py,sha256=
|
|
13
|
-
gac/
|
|
12
|
+
gac/init_cli.py,sha256=wyyPNjO47IcwFrO-jrqJ5rs1SsKdDJnhuRO83heh1tw,9700
|
|
13
|
+
gac/language_cli.py,sha256=xmmIonIhOR83VtRFj5Dy8JDtpa5qCNbNxSEZt-1T0a8,4040
|
|
14
|
+
gac/main.py,sha256=LJhONOP_w09TE7HOd4T_NiAzmiqemY2N7EZ6biqwVQE,15221
|
|
14
15
|
gac/preprocess.py,sha256=hk2p2X4-xVDvuy-T1VMzMa9k5fTUbhlWDyw89DCf81Q,15379
|
|
15
|
-
gac/prompt.py,sha256=
|
|
16
|
+
gac/prompt.py,sha256=HLvsW3YQLdTfw2N9UgjZ0jWckUc1x76V7Kcqjcl8Fsk,28633
|
|
16
17
|
gac/security.py,sha256=15Yp6YR8QC4eECJi1BUCkMteh_veZXUbLL6W8qGcDm4,9920
|
|
17
18
|
gac/utils.py,sha256=nV42-brIHW_fBg7x855GM8nRrqEBbRzTSweg-GTyGE8,3971
|
|
18
19
|
gac/providers/__init__.py,sha256=3WTzh3ngAdvR40eezpMMFD7Zibb-LxexDYUcSm4axQI,1305
|
|
@@ -34,8 +35,8 @@ gac/providers/streamlake.py,sha256=KAA2ZnpuEI5imzvdWVWUhEBHSP0BMnprKXte6CbwBWY,2
|
|
|
34
35
|
gac/providers/synthetic.py,sha256=sRMIJTS9LpcXd9A7qp_ZjZxdqtTKRn9fl1W4YwJZP4c,1855
|
|
35
36
|
gac/providers/together.py,sha256=1bUIVHfYzcEDw4hQPE8qV6hjc2JNHPv_khVgpk2IJxI,1667
|
|
36
37
|
gac/providers/zai.py,sha256=kywhhrCfPBu0rElZyb-iENxQxxpVGykvePuL4xrXlaU,2739
|
|
37
|
-
gac-
|
|
38
|
-
gac-
|
|
39
|
-
gac-
|
|
40
|
-
gac-
|
|
41
|
-
gac-
|
|
38
|
+
gac-2.0.0.dist-info/METADATA,sha256=X4MCo_VM2qkYzwREOCmOZSpV5YC_NZbTH9o4yHOyuHg,8909
|
|
39
|
+
gac-2.0.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
40
|
+
gac-2.0.0.dist-info/entry_points.txt,sha256=tdjN-XMmcWfL92swuRAjT62bFLOAwk9bTMRLGP5Z4aI,36
|
|
41
|
+
gac-2.0.0.dist-info/licenses/LICENSE,sha256=vOab37NouL1PNs5BswnPayrMCqaN2sqLfMQfqPDrpZg,1103
|
|
42
|
+
gac-2.0.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|