gac 1.13.0__py3-none-any.whl → 3.8.1__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.
- gac/__version__.py +1 -1
- gac/ai.py +33 -47
- gac/ai_utils.py +113 -41
- gac/auth_cli.py +214 -0
- gac/cli.py +72 -2
- gac/config.py +63 -6
- gac/config_cli.py +26 -5
- gac/constants.py +178 -2
- gac/git.py +158 -12
- gac/init_cli.py +40 -125
- gac/language_cli.py +378 -0
- gac/main.py +868 -158
- gac/model_cli.py +429 -0
- gac/oauth/__init__.py +27 -0
- gac/oauth/claude_code.py +464 -0
- gac/oauth/qwen_oauth.py +323 -0
- gac/oauth/token_store.py +81 -0
- gac/preprocess.py +3 -3
- gac/prompt.py +573 -226
- gac/providers/__init__.py +49 -0
- gac/providers/anthropic.py +11 -1
- gac/providers/azure_openai.py +101 -0
- gac/providers/cerebras.py +11 -1
- gac/providers/chutes.py +11 -1
- gac/providers/claude_code.py +112 -0
- gac/providers/custom_anthropic.py +6 -2
- gac/providers/custom_openai.py +6 -3
- gac/providers/deepseek.py +11 -1
- gac/providers/fireworks.py +11 -1
- gac/providers/gemini.py +11 -1
- gac/providers/groq.py +5 -1
- gac/providers/kimi_coding.py +67 -0
- gac/providers/lmstudio.py +12 -1
- gac/providers/minimax.py +11 -1
- gac/providers/mistral.py +48 -0
- gac/providers/moonshot.py +48 -0
- gac/providers/ollama.py +11 -1
- gac/providers/openai.py +11 -1
- gac/providers/openrouter.py +11 -1
- gac/providers/qwen.py +76 -0
- gac/providers/replicate.py +110 -0
- gac/providers/streamlake.py +11 -1
- gac/providers/synthetic.py +11 -1
- gac/providers/together.py +11 -1
- gac/providers/zai.py +11 -1
- gac/security.py +1 -1
- gac/utils.py +272 -4
- gac/workflow_utils.py +217 -0
- {gac-1.13.0.dist-info → gac-3.8.1.dist-info}/METADATA +90 -27
- gac-3.8.1.dist-info/RECORD +56 -0
- {gac-1.13.0.dist-info → gac-3.8.1.dist-info}/WHEEL +1 -1
- gac-1.13.0.dist-info/RECORD +0 -41
- {gac-1.13.0.dist-info → gac-3.8.1.dist-info}/entry_points.txt +0 -0
- {gac-1.13.0.dist-info → gac-3.8.1.dist-info}/licenses/LICENSE +0 -0
gac/cli.py
CHANGED
|
@@ -6,29 +6,40 @@ Defines the Click-based command-line interface and delegates execution to the ma
|
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
8
|
import logging
|
|
9
|
+
import os
|
|
9
10
|
import sys
|
|
10
11
|
|
|
11
12
|
import click
|
|
13
|
+
from rich.console import Console
|
|
12
14
|
|
|
13
15
|
from gac import __version__
|
|
16
|
+
from gac.auth_cli import auth as auth_cli
|
|
14
17
|
from gac.config import load_config
|
|
15
18
|
from gac.config_cli import config as config_cli
|
|
16
|
-
from gac.constants import Logging
|
|
19
|
+
from gac.constants import Languages, Logging
|
|
17
20
|
from gac.diff_cli import diff as diff_cli
|
|
18
21
|
from gac.errors import handle_error
|
|
19
22
|
from gac.init_cli import init as init_cli
|
|
23
|
+
from gac.language_cli import language as language_cli
|
|
20
24
|
from gac.main import main
|
|
25
|
+
from gac.model_cli import model as model_cli
|
|
21
26
|
from gac.utils import setup_logging
|
|
22
27
|
|
|
23
28
|
config = load_config()
|
|
24
29
|
logger = logging.getLogger(__name__)
|
|
30
|
+
console = Console()
|
|
25
31
|
|
|
26
32
|
|
|
27
33
|
@click.group(invoke_without_command=True, context_settings={"ignore_unknown_options": True})
|
|
28
34
|
# Git workflow options
|
|
29
35
|
@click.option("--add-all", "-a", is_flag=True, help="Stage all changes before committing")
|
|
36
|
+
@click.option("--group", "-g", is_flag=True, help="Group changes into multiple logical commits")
|
|
37
|
+
@click.option(
|
|
38
|
+
"--interactive", "-i", is_flag=True, help="Ask interactive questions to gather more context for the commit message"
|
|
39
|
+
)
|
|
30
40
|
@click.option("--push", "-p", is_flag=True, help="Push changes to remote after committing")
|
|
31
41
|
@click.option("--dry-run", is_flag=True, help="Dry run the commit workflow")
|
|
42
|
+
@click.option("--message-only", is_flag=True, help="Output only the generated commit message without committing")
|
|
32
43
|
@click.option("--yes", "-y", is_flag=True, help="Skip confirmation prompt")
|
|
33
44
|
# Commit message options
|
|
34
45
|
@click.option("--one-liner", "-o", is_flag=True, help="Generate a single-line commit message")
|
|
@@ -43,6 +54,9 @@ logger = logging.getLogger(__name__)
|
|
|
43
54
|
@click.option("--hint", "-h", default="", help="Additional context to include in the prompt")
|
|
44
55
|
# Model options
|
|
45
56
|
@click.option("--model", "-m", help="Override the default model (format: 'provider:model_name')")
|
|
57
|
+
@click.option(
|
|
58
|
+
"--language", "-l", help="Override the language for commit messages (e.g., 'Spanish', 'es', 'zh-CN', 'ja')"
|
|
59
|
+
)
|
|
46
60
|
# Output options
|
|
47
61
|
@click.option("--quiet", "-q", is_flag=True, help="Suppress non-error output")
|
|
48
62
|
@click.option(
|
|
@@ -60,12 +74,25 @@ logger = logging.getLogger(__name__)
|
|
|
60
74
|
# Advanced options
|
|
61
75
|
@click.option("--no-verify", is_flag=True, help="Skip pre-commit and lefthook hooks when committing")
|
|
62
76
|
@click.option("--skip-secret-scan", is_flag=True, help="Skip security scan for secrets in staged changes")
|
|
77
|
+
@click.option(
|
|
78
|
+
"--no-verify-ssl",
|
|
79
|
+
is_flag=True,
|
|
80
|
+
help="Skip SSL certificate verification (useful for corporate proxies)",
|
|
81
|
+
)
|
|
82
|
+
@click.option(
|
|
83
|
+
"--hook-timeout",
|
|
84
|
+
type=int,
|
|
85
|
+
default=0,
|
|
86
|
+
help="Timeout for pre-commit and lefthook hooks in seconds (0 to use configuration)",
|
|
87
|
+
)
|
|
63
88
|
# Other options
|
|
64
89
|
@click.option("--version", is_flag=True, help="Show the version of the Git Auto Commit (gac) tool")
|
|
65
90
|
@click.pass_context
|
|
66
91
|
def cli(
|
|
67
92
|
ctx: click.Context,
|
|
68
93
|
add_all: bool = False,
|
|
94
|
+
group: bool = False,
|
|
95
|
+
interactive: bool = False,
|
|
69
96
|
log_level: str = str(config["log_level"]),
|
|
70
97
|
one_liner: bool = False,
|
|
71
98
|
push: bool = False,
|
|
@@ -75,11 +102,15 @@ def cli(
|
|
|
75
102
|
yes: bool = False,
|
|
76
103
|
hint: str = "",
|
|
77
104
|
model: str | None = None,
|
|
105
|
+
language: str | None = None,
|
|
78
106
|
version: bool = False,
|
|
79
107
|
dry_run: bool = False,
|
|
108
|
+
message_only: bool = False,
|
|
80
109
|
verbose: bool = False,
|
|
81
110
|
no_verify: bool = False,
|
|
82
111
|
skip_secret_scan: bool = False,
|
|
112
|
+
no_verify_ssl: bool = False,
|
|
113
|
+
hook_timeout: int = 0,
|
|
83
114
|
) -> None:
|
|
84
115
|
"""Git Auto Commit - Generate commit messages with AI."""
|
|
85
116
|
if ctx.invoked_subcommand is None:
|
|
@@ -92,15 +123,32 @@ def cli(
|
|
|
92
123
|
setup_logging(effective_log_level)
|
|
93
124
|
logger.info("Starting gac")
|
|
94
125
|
|
|
126
|
+
# Set SSL verification environment variable if flag is used or config is set
|
|
127
|
+
if no_verify_ssl or config.get("no_verify_ssl", False):
|
|
128
|
+
os.environ["GAC_NO_VERIFY_SSL"] = "true"
|
|
129
|
+
logger.info("SSL certificate verification disabled")
|
|
130
|
+
|
|
131
|
+
# Validate incompatible flag combinations
|
|
132
|
+
if message_only and group:
|
|
133
|
+
console.print("[red]Error: --message-only and --group options are mutually exclusive[/red]")
|
|
134
|
+
console.print("[yellow]--message-only is for generating a single commit message for external use[/yellow]")
|
|
135
|
+
console.print("[yellow]--group is for organizing multiple commits within the current workflow[/yellow]")
|
|
136
|
+
sys.exit(1)
|
|
137
|
+
|
|
95
138
|
# Determine if we should infer scope based on -s flag or always_include_scope setting
|
|
96
139
|
infer_scope = bool(scope or config.get("always_include_scope", False))
|
|
97
140
|
|
|
98
141
|
# Determine if verbose mode should be enabled based on -v flag or verbose config setting
|
|
99
142
|
use_verbose = bool(verbose or config.get("verbose", False))
|
|
100
143
|
|
|
144
|
+
# Resolve language code to full name if provided
|
|
145
|
+
resolved_language = Languages.resolve_code(language) if language else None
|
|
146
|
+
|
|
101
147
|
try:
|
|
102
148
|
main(
|
|
103
149
|
stage_all=add_all,
|
|
150
|
+
group=group,
|
|
151
|
+
interactive=interactive,
|
|
104
152
|
model=model,
|
|
105
153
|
hint=hint,
|
|
106
154
|
one_liner=one_liner,
|
|
@@ -110,9 +158,12 @@ def cli(
|
|
|
110
158
|
push=push,
|
|
111
159
|
quiet=quiet,
|
|
112
160
|
dry_run=dry_run,
|
|
161
|
+
message_only=message_only,
|
|
113
162
|
verbose=use_verbose,
|
|
114
163
|
no_verify=no_verify,
|
|
115
164
|
skip_secret_scan=skip_secret_scan or bool(config.get("skip_secret_scan", False)),
|
|
165
|
+
language=resolved_language,
|
|
166
|
+
hook_timeout=hook_timeout if hook_timeout > 0 else int(config.get("hook_timeout", 120) or 120),
|
|
116
167
|
)
|
|
117
168
|
except Exception as e:
|
|
118
169
|
handle_error(e, exit_program=True)
|
|
@@ -122,6 +173,8 @@ def cli(
|
|
|
122
173
|
|
|
123
174
|
ctx.obj = {
|
|
124
175
|
"add_all": add_all,
|
|
176
|
+
"group": group,
|
|
177
|
+
"interactive": interactive,
|
|
125
178
|
"log_level": log_level,
|
|
126
179
|
"one_liner": one_liner,
|
|
127
180
|
"push": push,
|
|
@@ -131,17 +184,34 @@ def cli(
|
|
|
131
184
|
"yes": yes,
|
|
132
185
|
"hint": hint,
|
|
133
186
|
"model": model,
|
|
187
|
+
"language": language,
|
|
134
188
|
"version": version,
|
|
135
189
|
"dry_run": dry_run,
|
|
190
|
+
"message_only": message_only,
|
|
136
191
|
"verbose": verbose,
|
|
137
192
|
"no_verify": no_verify,
|
|
138
193
|
"skip_secret_scan": skip_secret_scan,
|
|
194
|
+
"no_verify_ssl": no_verify_ssl,
|
|
195
|
+
"hook_timeout": hook_timeout,
|
|
139
196
|
}
|
|
140
197
|
|
|
141
198
|
|
|
199
|
+
cli.add_command(auth_cli)
|
|
142
200
|
cli.add_command(config_cli)
|
|
143
|
-
cli.add_command(init_cli)
|
|
144
201
|
cli.add_command(diff_cli)
|
|
202
|
+
cli.add_command(init_cli)
|
|
203
|
+
cli.add_command(language_cli)
|
|
204
|
+
cli.add_command(model_cli)
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
@click.command(context_settings=language_cli.context_settings)
|
|
208
|
+
@click.pass_context
|
|
209
|
+
def lang(ctx):
|
|
210
|
+
"""Set the language for commit messages interactively. (Alias for 'language')"""
|
|
211
|
+
ctx.forward(language_cli)
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
cli.add_command(lang) # Add the lang alias
|
|
145
215
|
|
|
146
216
|
if __name__ == "__main__":
|
|
147
217
|
cli()
|
gac/config.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""Configuration loading for gac.
|
|
2
2
|
|
|
3
|
-
Handles environment variable and .env file precedence for application settings.
|
|
3
|
+
Handles environment variable and .gac.env file precedence for application settings.
|
|
4
4
|
"""
|
|
5
5
|
|
|
6
6
|
import os
|
|
@@ -9,22 +9,70 @@ from pathlib import Path
|
|
|
9
9
|
from dotenv import load_dotenv
|
|
10
10
|
|
|
11
11
|
from gac.constants import EnvDefaults, Logging
|
|
12
|
+
from gac.errors import ConfigError
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def validate_config(config: dict[str, str | int | float | bool | None]) -> None:
|
|
16
|
+
"""Validate configuration values at load time.
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
config: Configuration dictionary to validate
|
|
20
|
+
|
|
21
|
+
Raises:
|
|
22
|
+
ConfigError: If any configuration value is invalid
|
|
23
|
+
"""
|
|
24
|
+
# Validate temperature (0.0 to 2.0)
|
|
25
|
+
if config.get("temperature") is not None:
|
|
26
|
+
temp = config["temperature"]
|
|
27
|
+
if not isinstance(temp, (int, float)):
|
|
28
|
+
raise ConfigError(f"temperature must be a number, got {type(temp).__name__}")
|
|
29
|
+
if not 0.0 <= temp <= 2.0:
|
|
30
|
+
raise ConfigError(f"temperature must be between 0.0 and 2.0, got {temp}")
|
|
31
|
+
|
|
32
|
+
# Validate max_output_tokens (1 to 100000)
|
|
33
|
+
if config.get("max_output_tokens") is not None:
|
|
34
|
+
tokens = config["max_output_tokens"]
|
|
35
|
+
if not isinstance(tokens, int):
|
|
36
|
+
raise ConfigError(f"max_output_tokens must be an integer, got {type(tokens).__name__}")
|
|
37
|
+
if tokens < 1 or tokens > 100000:
|
|
38
|
+
raise ConfigError(f"max_output_tokens must be between 1 and 100000, got {tokens}")
|
|
39
|
+
|
|
40
|
+
# Validate max_retries (1 to 10)
|
|
41
|
+
if config.get("max_retries") is not None:
|
|
42
|
+
retries = config["max_retries"]
|
|
43
|
+
if not isinstance(retries, int):
|
|
44
|
+
raise ConfigError(f"max_retries must be an integer, got {type(retries).__name__}")
|
|
45
|
+
if retries < 1 or retries > 10:
|
|
46
|
+
raise ConfigError(f"max_retries must be between 1 and 10, got {retries}")
|
|
47
|
+
|
|
48
|
+
# Validate warning_limit_tokens (must be positive)
|
|
49
|
+
if config.get("warning_limit_tokens") is not None:
|
|
50
|
+
warning_limit = config["warning_limit_tokens"]
|
|
51
|
+
if not isinstance(warning_limit, int):
|
|
52
|
+
raise ConfigError(f"warning_limit_tokens must be an integer, got {type(warning_limit).__name__}")
|
|
53
|
+
if warning_limit < 1:
|
|
54
|
+
raise ConfigError(f"warning_limit_tokens must be positive, got {warning_limit}")
|
|
55
|
+
|
|
56
|
+
# Validate hook_timeout (must be positive)
|
|
57
|
+
if config.get("hook_timeout") is not None:
|
|
58
|
+
hook_timeout = config["hook_timeout"]
|
|
59
|
+
if not isinstance(hook_timeout, int):
|
|
60
|
+
raise ConfigError(f"hook_timeout must be an integer, got {type(hook_timeout).__name__}")
|
|
61
|
+
if hook_timeout < 1:
|
|
62
|
+
raise ConfigError(f"hook_timeout must be positive, got {hook_timeout}")
|
|
12
63
|
|
|
13
64
|
|
|
14
65
|
def load_config() -> dict[str, str | int | float | bool | None]:
|
|
15
|
-
"""Load configuration from $HOME/.gac.env, then ./.gac.env
|
|
66
|
+
"""Load configuration from $HOME/.gac.env, then ./.gac.env, then environment variables."""
|
|
16
67
|
user_config = Path.home() / ".gac.env"
|
|
17
68
|
if user_config.exists():
|
|
18
69
|
load_dotenv(user_config)
|
|
19
70
|
|
|
20
|
-
# Check for
|
|
71
|
+
# Check for .gac.env in project directory
|
|
21
72
|
project_gac_env = Path(".gac.env")
|
|
22
|
-
project_env = Path(".env")
|
|
23
73
|
|
|
24
74
|
if project_gac_env.exists():
|
|
25
75
|
load_dotenv(project_gac_env, override=True)
|
|
26
|
-
elif project_env.exists():
|
|
27
|
-
load_dotenv(project_env, override=True)
|
|
28
76
|
|
|
29
77
|
config = {
|
|
30
78
|
"model": os.getenv("GAC_MODEL"),
|
|
@@ -37,7 +85,16 @@ def load_config() -> dict[str, str | int | float | bool | None]:
|
|
|
37
85
|
in ("true", "1", "yes", "on"),
|
|
38
86
|
"skip_secret_scan": os.getenv("GAC_SKIP_SECRET_SCAN", str(EnvDefaults.SKIP_SECRET_SCAN)).lower()
|
|
39
87
|
in ("true", "1", "yes", "on"),
|
|
88
|
+
"no_tiktoken": os.getenv("GAC_NO_TIKTOKEN", str(EnvDefaults.NO_TIKTOKEN)).lower() in ("true", "1", "yes", "on"),
|
|
89
|
+
"no_verify_ssl": os.getenv("GAC_NO_VERIFY_SSL", str(EnvDefaults.NO_VERIFY_SSL)).lower()
|
|
90
|
+
in ("true", "1", "yes", "on"),
|
|
40
91
|
"verbose": os.getenv("GAC_VERBOSE", str(EnvDefaults.VERBOSE)).lower() in ("true", "1", "yes", "on"),
|
|
92
|
+
"system_prompt_path": os.getenv("GAC_SYSTEM_PROMPT_PATH"),
|
|
93
|
+
"language": os.getenv("GAC_LANGUAGE"),
|
|
94
|
+
"translate_prefixes": os.getenv("GAC_TRANSLATE_PREFIXES", "false").lower() in ("true", "1", "yes", "on"),
|
|
95
|
+
"rtl_confirmed": os.getenv("GAC_RTL_CONFIRMED", "false").lower() in ("true", "1", "yes", "on"),
|
|
96
|
+
"hook_timeout": int(os.getenv("GAC_HOOK_TIMEOUT", EnvDefaults.HOOK_TIMEOUT)),
|
|
41
97
|
}
|
|
42
98
|
|
|
99
|
+
validate_config(config)
|
|
43
100
|
return config
|
gac/config_cli.py
CHANGED
|
@@ -18,13 +18,34 @@ def config():
|
|
|
18
18
|
@config.command()
|
|
19
19
|
def show() -> None:
|
|
20
20
|
"""Show all current config values."""
|
|
21
|
-
|
|
21
|
+
project_env_path = Path(".gac.env")
|
|
22
|
+
user_exists = GAC_ENV_PATH.exists()
|
|
23
|
+
project_exists = project_env_path.exists()
|
|
24
|
+
|
|
25
|
+
if not user_exists and not project_exists:
|
|
22
26
|
click.echo("No $HOME/.gac.env found.")
|
|
27
|
+
click.echo("No project-level .gac.env found.")
|
|
23
28
|
return
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
29
|
+
|
|
30
|
+
if user_exists:
|
|
31
|
+
click.echo(f"User config ({GAC_ENV_PATH}):")
|
|
32
|
+
with open(GAC_ENV_PATH, encoding="utf-8") as f:
|
|
33
|
+
for line in f:
|
|
34
|
+
click.echo(line.rstrip())
|
|
35
|
+
else:
|
|
36
|
+
click.echo("No $HOME/.gac.env found.")
|
|
37
|
+
|
|
38
|
+
if project_exists:
|
|
39
|
+
if user_exists:
|
|
40
|
+
click.echo("")
|
|
41
|
+
click.echo("Project config (./.gac.env):")
|
|
42
|
+
with open(project_env_path, encoding="utf-8") as f:
|
|
43
|
+
for line in f:
|
|
44
|
+
click.echo(line.rstrip())
|
|
45
|
+
click.echo("")
|
|
46
|
+
click.echo("Note: Project-level .gac.env overrides $HOME/.gac.env values for any duplicated variables.")
|
|
47
|
+
else:
|
|
48
|
+
click.echo("No project-level .gac.env found.")
|
|
28
49
|
|
|
29
50
|
|
|
30
51
|
@config.command()
|
gac/constants.py
CHANGED
|
@@ -20,11 +20,20 @@ class EnvDefaults:
|
|
|
20
20
|
|
|
21
21
|
MAX_RETRIES: int = 3
|
|
22
22
|
TEMPERATURE: float = 1
|
|
23
|
-
MAX_OUTPUT_TOKENS: int =
|
|
24
|
-
WARNING_LIMIT_TOKENS: int =
|
|
23
|
+
MAX_OUTPUT_TOKENS: int = 4096 # includes reasoning tokens
|
|
24
|
+
WARNING_LIMIT_TOKENS: int = 32768
|
|
25
25
|
ALWAYS_INCLUDE_SCOPE: bool = False
|
|
26
26
|
SKIP_SECRET_SCAN: bool = False
|
|
27
27
|
VERBOSE: bool = False
|
|
28
|
+
NO_TIKTOKEN: bool = False
|
|
29
|
+
NO_VERIFY_SSL: bool = False # Skip SSL certificate verification (for corporate proxies)
|
|
30
|
+
HOOK_TIMEOUT: int = 120 # Timeout for pre-commit and lefthook hooks in seconds
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class ProviderDefaults:
|
|
34
|
+
"""Default values for provider configurations."""
|
|
35
|
+
|
|
36
|
+
HTTP_TIMEOUT: int = 120 # seconds - timeout for HTTP requests to LLM providers
|
|
28
37
|
|
|
29
38
|
|
|
30
39
|
class Logging:
|
|
@@ -150,3 +159,170 @@ class CodePatternImportance:
|
|
|
150
159
|
r"\+\s*(test|describe|it|should)\s*\(": 1.1, # Test definitions
|
|
151
160
|
r"\+\s*(assert|expect)": 1.0, # Assertions
|
|
152
161
|
}
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
class Languages:
|
|
165
|
+
"""Language code mappings and utilities."""
|
|
166
|
+
|
|
167
|
+
# Language code to full name mapping
|
|
168
|
+
# Supports ISO 639-1 codes and common variants
|
|
169
|
+
CODE_MAP: dict[str, str] = {
|
|
170
|
+
# English
|
|
171
|
+
"en": "English",
|
|
172
|
+
# Chinese
|
|
173
|
+
"zh": "Simplified Chinese",
|
|
174
|
+
"zh-cn": "Simplified Chinese",
|
|
175
|
+
"zh-hans": "Simplified Chinese",
|
|
176
|
+
"zh-tw": "Traditional Chinese",
|
|
177
|
+
"zh-hant": "Traditional Chinese",
|
|
178
|
+
# Japanese
|
|
179
|
+
"ja": "Japanese",
|
|
180
|
+
# Korean
|
|
181
|
+
"ko": "Korean",
|
|
182
|
+
# Spanish
|
|
183
|
+
"es": "Spanish",
|
|
184
|
+
# Portuguese
|
|
185
|
+
"pt": "Portuguese",
|
|
186
|
+
# French
|
|
187
|
+
"fr": "French",
|
|
188
|
+
# German
|
|
189
|
+
"de": "German",
|
|
190
|
+
# Russian
|
|
191
|
+
"ru": "Russian",
|
|
192
|
+
# Hindi
|
|
193
|
+
"hi": "Hindi",
|
|
194
|
+
# Italian
|
|
195
|
+
"it": "Italian",
|
|
196
|
+
# Polish
|
|
197
|
+
"pl": "Polish",
|
|
198
|
+
# Turkish
|
|
199
|
+
"tr": "Turkish",
|
|
200
|
+
# Dutch
|
|
201
|
+
"nl": "Dutch",
|
|
202
|
+
# Vietnamese
|
|
203
|
+
"vi": "Vietnamese",
|
|
204
|
+
# Thai
|
|
205
|
+
"th": "Thai",
|
|
206
|
+
# Indonesian
|
|
207
|
+
"id": "Indonesian",
|
|
208
|
+
# Swedish
|
|
209
|
+
"sv": "Swedish",
|
|
210
|
+
# Arabic
|
|
211
|
+
"ar": "Arabic",
|
|
212
|
+
# Hebrew
|
|
213
|
+
"he": "Hebrew",
|
|
214
|
+
# Greek
|
|
215
|
+
"el": "Greek",
|
|
216
|
+
# Danish
|
|
217
|
+
"da": "Danish",
|
|
218
|
+
# Norwegian
|
|
219
|
+
"no": "Norwegian",
|
|
220
|
+
"nb": "Norwegian",
|
|
221
|
+
"nn": "Norwegian",
|
|
222
|
+
# Finnish
|
|
223
|
+
"fi": "Finnish",
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
# List of languages with display names and English names for CLI selection
|
|
227
|
+
# Format: (display_name, english_name)
|
|
228
|
+
LANGUAGES: list[tuple[str, str]] = [
|
|
229
|
+
("English", "English"),
|
|
230
|
+
("简体中文", "Simplified Chinese"),
|
|
231
|
+
("繁體中文", "Traditional Chinese"),
|
|
232
|
+
("日本語", "Japanese"),
|
|
233
|
+
("한국어", "Korean"),
|
|
234
|
+
("Español", "Spanish"),
|
|
235
|
+
("Português", "Portuguese"),
|
|
236
|
+
("Français", "French"),
|
|
237
|
+
("Deutsch", "German"),
|
|
238
|
+
("Русский", "Russian"),
|
|
239
|
+
("हिन्दी", "Hindi"),
|
|
240
|
+
("Italiano", "Italian"),
|
|
241
|
+
("Polski", "Polish"),
|
|
242
|
+
("Türkçe", "Turkish"),
|
|
243
|
+
("Nederlands", "Dutch"),
|
|
244
|
+
("Tiếng Việt", "Vietnamese"),
|
|
245
|
+
("ไทย", "Thai"),
|
|
246
|
+
("Bahasa Indonesia", "Indonesian"),
|
|
247
|
+
("Svenska", "Swedish"),
|
|
248
|
+
("العربية", "Arabic"),
|
|
249
|
+
("עברית", "Hebrew"),
|
|
250
|
+
("Ελληνικά", "Greek"),
|
|
251
|
+
("Dansk", "Danish"),
|
|
252
|
+
("Norsk", "Norwegian"),
|
|
253
|
+
("Suomi", "Finnish"),
|
|
254
|
+
("Custom", "Custom"),
|
|
255
|
+
]
|
|
256
|
+
|
|
257
|
+
@staticmethod
|
|
258
|
+
def resolve_code(language: str) -> str:
|
|
259
|
+
"""Resolve a language code to its full name.
|
|
260
|
+
|
|
261
|
+
Args:
|
|
262
|
+
language: Language name or code (e.g., 'Spanish', 'es', 'zh-CN')
|
|
263
|
+
|
|
264
|
+
Returns:
|
|
265
|
+
Full language name (e.g., 'Spanish', 'Simplified Chinese')
|
|
266
|
+
|
|
267
|
+
If the input is already a full language name, it's returned as-is.
|
|
268
|
+
If it's a recognized code, it's converted to the full name.
|
|
269
|
+
Otherwise, the input is returned unchanged (for custom languages).
|
|
270
|
+
"""
|
|
271
|
+
# Normalize the code to lowercase for lookup
|
|
272
|
+
code_lower = language.lower().strip()
|
|
273
|
+
|
|
274
|
+
# Check if it's a recognized code
|
|
275
|
+
if code_lower in Languages.CODE_MAP:
|
|
276
|
+
return Languages.CODE_MAP[code_lower]
|
|
277
|
+
|
|
278
|
+
# Return as-is (could be a full name or custom language)
|
|
279
|
+
return language
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
class CommitMessageConstants:
|
|
283
|
+
"""Constants for commit message generation and cleaning."""
|
|
284
|
+
|
|
285
|
+
# Conventional commit type prefixes
|
|
286
|
+
CONVENTIONAL_PREFIXES: list[str] = [
|
|
287
|
+
"feat",
|
|
288
|
+
"fix",
|
|
289
|
+
"docs",
|
|
290
|
+
"style",
|
|
291
|
+
"refactor",
|
|
292
|
+
"perf",
|
|
293
|
+
"test",
|
|
294
|
+
"build",
|
|
295
|
+
"ci",
|
|
296
|
+
"chore",
|
|
297
|
+
]
|
|
298
|
+
|
|
299
|
+
# XML tags that may leak from prompt templates into AI responses
|
|
300
|
+
XML_TAGS_TO_REMOVE: list[str] = [
|
|
301
|
+
"<git-status>",
|
|
302
|
+
"</git-status>",
|
|
303
|
+
"<git_status>",
|
|
304
|
+
"</git_status>",
|
|
305
|
+
"<git-diff>",
|
|
306
|
+
"</git-diff>",
|
|
307
|
+
"<git_diff>",
|
|
308
|
+
"</git_diff>",
|
|
309
|
+
"<repository_context>",
|
|
310
|
+
"</repository_context>",
|
|
311
|
+
"<instructions>",
|
|
312
|
+
"</instructions>",
|
|
313
|
+
"<format>",
|
|
314
|
+
"</format>",
|
|
315
|
+
"<conventions>",
|
|
316
|
+
"</conventions>",
|
|
317
|
+
]
|
|
318
|
+
|
|
319
|
+
# Indicators that mark the start of the actual commit message in AI responses
|
|
320
|
+
COMMIT_INDICATORS: list[str] = [
|
|
321
|
+
"# Your commit message:",
|
|
322
|
+
"Your commit message:",
|
|
323
|
+
"The commit message is:",
|
|
324
|
+
"Here's the commit message:",
|
|
325
|
+
"Commit message:",
|
|
326
|
+
"Final commit message:",
|
|
327
|
+
"# Commit Message",
|
|
328
|
+
]
|