gac 3.6.0__py3-none-any.whl → 3.10.10__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/__init__.py +4 -6
- gac/__version__.py +1 -1
- gac/ai_utils.py +59 -43
- gac/auth_cli.py +181 -36
- gac/cli.py +26 -9
- gac/commit_executor.py +59 -0
- gac/config.py +81 -2
- gac/config_cli.py +19 -7
- gac/constants/__init__.py +34 -0
- gac/constants/commit.py +63 -0
- gac/constants/defaults.py +40 -0
- gac/constants/file_patterns.py +110 -0
- gac/constants/languages.py +119 -0
- gac/diff_cli.py +0 -22
- gac/errors.py +8 -2
- gac/git.py +6 -6
- gac/git_state_validator.py +193 -0
- gac/grouped_commit_workflow.py +458 -0
- gac/init_cli.py +2 -1
- gac/interactive_mode.py +179 -0
- gac/language_cli.py +0 -1
- gac/main.py +231 -926
- gac/model_cli.py +67 -11
- gac/model_identifier.py +70 -0
- gac/oauth/__init__.py +26 -0
- gac/oauth/claude_code.py +89 -22
- gac/oauth/qwen_oauth.py +327 -0
- gac/oauth/token_store.py +81 -0
- gac/oauth_retry.py +161 -0
- gac/postprocess.py +155 -0
- gac/prompt.py +21 -479
- gac/prompt_builder.py +88 -0
- gac/providers/README.md +437 -0
- gac/providers/__init__.py +70 -78
- gac/providers/anthropic.py +12 -46
- gac/providers/azure_openai.py +48 -88
- gac/providers/base.py +329 -0
- gac/providers/cerebras.py +10 -33
- gac/providers/chutes.py +16 -62
- gac/providers/claude_code.py +64 -87
- gac/providers/custom_anthropic.py +51 -81
- gac/providers/custom_openai.py +29 -83
- gac/providers/deepseek.py +10 -33
- gac/providers/error_handler.py +139 -0
- gac/providers/fireworks.py +10 -33
- gac/providers/gemini.py +66 -63
- gac/providers/groq.py +10 -58
- gac/providers/kimi_coding.py +19 -55
- gac/providers/lmstudio.py +64 -43
- gac/providers/minimax.py +10 -33
- gac/providers/mistral.py +10 -33
- gac/providers/moonshot.py +10 -33
- gac/providers/ollama.py +56 -33
- gac/providers/openai.py +30 -36
- gac/providers/openrouter.py +15 -52
- gac/providers/protocol.py +71 -0
- gac/providers/qwen.py +64 -0
- gac/providers/registry.py +58 -0
- gac/providers/replicate.py +140 -82
- gac/providers/streamlake.py +26 -46
- gac/providers/synthetic.py +35 -37
- gac/providers/together.py +10 -33
- gac/providers/zai.py +29 -57
- gac/py.typed +0 -0
- gac/security.py +1 -1
- gac/templates/__init__.py +1 -0
- gac/templates/question_generation.txt +60 -0
- gac/templates/system_prompt.txt +224 -0
- gac/templates/user_prompt.txt +28 -0
- gac/utils.py +36 -6
- gac/workflow_context.py +162 -0
- gac/workflow_utils.py +3 -8
- {gac-3.6.0.dist-info → gac-3.10.10.dist-info}/METADATA +6 -4
- gac-3.10.10.dist-info/RECORD +79 -0
- gac/constants.py +0 -321
- gac-3.6.0.dist-info/RECORD +0 -53
- {gac-3.6.0.dist-info → gac-3.10.10.dist-info}/WHEEL +0 -0
- {gac-3.6.0.dist-info → gac-3.10.10.dist-info}/entry_points.txt +0 -0
- {gac-3.6.0.dist-info → gac-3.10.10.dist-info}/licenses/LICENSE +0 -0
gac/config.py
CHANGED
|
@@ -5,13 +5,89 @@ Handles environment variable and .gac.env file precedence for application settin
|
|
|
5
5
|
|
|
6
6
|
import os
|
|
7
7
|
from pathlib import Path
|
|
8
|
+
from typing import TypedDict
|
|
8
9
|
|
|
9
10
|
from dotenv import load_dotenv
|
|
10
11
|
|
|
11
12
|
from gac.constants import EnvDefaults, Logging
|
|
13
|
+
from gac.errors import ConfigError
|
|
12
14
|
|
|
13
15
|
|
|
14
|
-
|
|
16
|
+
class GACConfig(TypedDict, total=False):
|
|
17
|
+
"""TypedDict for GAC configuration values.
|
|
18
|
+
|
|
19
|
+
Fields that can be None or omitted are marked with total=False.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
model: str | None
|
|
23
|
+
temperature: float
|
|
24
|
+
max_output_tokens: int
|
|
25
|
+
max_retries: int
|
|
26
|
+
log_level: str
|
|
27
|
+
warning_limit_tokens: int
|
|
28
|
+
always_include_scope: bool
|
|
29
|
+
skip_secret_scan: bool
|
|
30
|
+
no_tiktoken: bool
|
|
31
|
+
no_verify_ssl: bool
|
|
32
|
+
verbose: bool
|
|
33
|
+
system_prompt_path: str | None
|
|
34
|
+
language: str | None
|
|
35
|
+
translate_prefixes: bool
|
|
36
|
+
rtl_confirmed: bool
|
|
37
|
+
hook_timeout: int
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def validate_config(config: GACConfig) -> None:
|
|
41
|
+
"""Validate configuration values at load time.
|
|
42
|
+
|
|
43
|
+
Args:
|
|
44
|
+
config: Configuration dictionary to validate
|
|
45
|
+
|
|
46
|
+
Raises:
|
|
47
|
+
ConfigError: If any configuration value is invalid
|
|
48
|
+
"""
|
|
49
|
+
# Validate temperature (0.0 to 2.0)
|
|
50
|
+
if config.get("temperature") is not None:
|
|
51
|
+
temp = config["temperature"]
|
|
52
|
+
if not isinstance(temp, (int, float)):
|
|
53
|
+
raise ConfigError(f"temperature must be a number, got {type(temp).__name__}")
|
|
54
|
+
if not 0.0 <= temp <= 2.0:
|
|
55
|
+
raise ConfigError(f"temperature must be between 0.0 and 2.0, got {temp}")
|
|
56
|
+
|
|
57
|
+
# Validate max_output_tokens (1 to 100000)
|
|
58
|
+
if config.get("max_output_tokens") is not None:
|
|
59
|
+
tokens = config["max_output_tokens"]
|
|
60
|
+
if not isinstance(tokens, int):
|
|
61
|
+
raise ConfigError(f"max_output_tokens must be an integer, got {type(tokens).__name__}")
|
|
62
|
+
if tokens < 1 or tokens > 100000:
|
|
63
|
+
raise ConfigError(f"max_output_tokens must be between 1 and 100000, got {tokens}")
|
|
64
|
+
|
|
65
|
+
# Validate max_retries (1 to 10)
|
|
66
|
+
if config.get("max_retries") is not None:
|
|
67
|
+
retries = config["max_retries"]
|
|
68
|
+
if not isinstance(retries, int):
|
|
69
|
+
raise ConfigError(f"max_retries must be an integer, got {type(retries).__name__}")
|
|
70
|
+
if retries < 1 or retries > 10:
|
|
71
|
+
raise ConfigError(f"max_retries must be between 1 and 10, got {retries}")
|
|
72
|
+
|
|
73
|
+
# Validate warning_limit_tokens (must be positive)
|
|
74
|
+
if config.get("warning_limit_tokens") is not None:
|
|
75
|
+
warning_limit = config["warning_limit_tokens"]
|
|
76
|
+
if not isinstance(warning_limit, int):
|
|
77
|
+
raise ConfigError(f"warning_limit_tokens must be an integer, got {type(warning_limit).__name__}")
|
|
78
|
+
if warning_limit < 1:
|
|
79
|
+
raise ConfigError(f"warning_limit_tokens must be positive, got {warning_limit}")
|
|
80
|
+
|
|
81
|
+
# Validate hook_timeout (must be positive)
|
|
82
|
+
if config.get("hook_timeout") is not None:
|
|
83
|
+
hook_timeout = config["hook_timeout"]
|
|
84
|
+
if not isinstance(hook_timeout, int):
|
|
85
|
+
raise ConfigError(f"hook_timeout must be an integer, got {type(hook_timeout).__name__}")
|
|
86
|
+
if hook_timeout < 1:
|
|
87
|
+
raise ConfigError(f"hook_timeout must be positive, got {hook_timeout}")
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def load_config() -> GACConfig:
|
|
15
91
|
"""Load configuration from $HOME/.gac.env, then ./.gac.env, then environment variables."""
|
|
16
92
|
user_config = Path.home() / ".gac.env"
|
|
17
93
|
if user_config.exists():
|
|
@@ -23,7 +99,7 @@ def load_config() -> dict[str, str | int | float | bool | None]:
|
|
|
23
99
|
if project_gac_env.exists():
|
|
24
100
|
load_dotenv(project_gac_env, override=True)
|
|
25
101
|
|
|
26
|
-
config = {
|
|
102
|
+
config: GACConfig = {
|
|
27
103
|
"model": os.getenv("GAC_MODEL"),
|
|
28
104
|
"temperature": float(os.getenv("GAC_TEMPERATURE", EnvDefaults.TEMPERATURE)),
|
|
29
105
|
"max_output_tokens": int(os.getenv("GAC_MAX_OUTPUT_TOKENS", EnvDefaults.MAX_OUTPUT_TOKENS)),
|
|
@@ -35,6 +111,8 @@ def load_config() -> dict[str, str | int | float | bool | None]:
|
|
|
35
111
|
"skip_secret_scan": os.getenv("GAC_SKIP_SECRET_SCAN", str(EnvDefaults.SKIP_SECRET_SCAN)).lower()
|
|
36
112
|
in ("true", "1", "yes", "on"),
|
|
37
113
|
"no_tiktoken": os.getenv("GAC_NO_TIKTOKEN", str(EnvDefaults.NO_TIKTOKEN)).lower() in ("true", "1", "yes", "on"),
|
|
114
|
+
"no_verify_ssl": os.getenv("GAC_NO_VERIFY_SSL", str(EnvDefaults.NO_VERIFY_SSL)).lower()
|
|
115
|
+
in ("true", "1", "yes", "on"),
|
|
38
116
|
"verbose": os.getenv("GAC_VERBOSE", str(EnvDefaults.VERBOSE)).lower() in ("true", "1", "yes", "on"),
|
|
39
117
|
"system_prompt_path": os.getenv("GAC_SYSTEM_PROMPT_PATH"),
|
|
40
118
|
"language": os.getenv("GAC_LANGUAGE"),
|
|
@@ -43,4 +121,5 @@ def load_config() -> dict[str, str | int | float | bool | None]:
|
|
|
43
121
|
"hook_timeout": int(os.getenv("GAC_HOOK_TIMEOUT", EnvDefaults.HOOK_TIMEOUT)),
|
|
44
122
|
}
|
|
45
123
|
|
|
124
|
+
validate_config(config)
|
|
46
125
|
return config
|
gac/config_cli.py
CHANGED
|
@@ -10,7 +10,7 @@ GAC_ENV_PATH = Path.home() / ".gac.env"
|
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
@click.group()
|
|
13
|
-
def config():
|
|
13
|
+
def config() -> None:
|
|
14
14
|
"""Manage gac configuration."""
|
|
15
15
|
pass
|
|
16
16
|
|
|
@@ -18,6 +18,8 @@ def config():
|
|
|
18
18
|
@config.command()
|
|
19
19
|
def show() -> None:
|
|
20
20
|
"""Show all current config values."""
|
|
21
|
+
from dotenv import dotenv_values
|
|
22
|
+
|
|
21
23
|
project_env_path = Path(".gac.env")
|
|
22
24
|
user_exists = GAC_ENV_PATH.exists()
|
|
23
25
|
project_exists = project_env_path.exists()
|
|
@@ -29,9 +31,14 @@ def show() -> None:
|
|
|
29
31
|
|
|
30
32
|
if user_exists:
|
|
31
33
|
click.echo(f"User config ({GAC_ENV_PATH}):")
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
34
|
+
user_config = dotenv_values(str(GAC_ENV_PATH))
|
|
35
|
+
for key, value in sorted(user_config.items()):
|
|
36
|
+
if value is not None:
|
|
37
|
+
if any(sensitive in key.lower() for sensitive in ["key", "token", "secret"]):
|
|
38
|
+
display_value = "***hidden***"
|
|
39
|
+
else:
|
|
40
|
+
display_value = value
|
|
41
|
+
click.echo(f" {key}={display_value}")
|
|
35
42
|
else:
|
|
36
43
|
click.echo("No $HOME/.gac.env found.")
|
|
37
44
|
|
|
@@ -39,9 +46,14 @@ def show() -> None:
|
|
|
39
46
|
if user_exists:
|
|
40
47
|
click.echo("")
|
|
41
48
|
click.echo("Project config (./.gac.env):")
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
49
|
+
project_config = dotenv_values(str(project_env_path))
|
|
50
|
+
for key, value in sorted(project_config.items()):
|
|
51
|
+
if value is not None:
|
|
52
|
+
if any(sensitive in key.lower() for sensitive in ["key", "token", "secret"]):
|
|
53
|
+
display_value = "***hidden***"
|
|
54
|
+
else:
|
|
55
|
+
display_value = value
|
|
56
|
+
click.echo(f" {key}={display_value}")
|
|
45
57
|
click.echo("")
|
|
46
58
|
click.echo("Note: Project-level .gac.env overrides $HOME/.gac.env values for any duplicated variables.")
|
|
47
59
|
else:
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"""Constants for the Git Auto Commit (gac) project.
|
|
2
|
+
|
|
3
|
+
This package provides all constants used throughout gac, organized into
|
|
4
|
+
logical modules:
|
|
5
|
+
|
|
6
|
+
- defaults: Environment defaults, provider defaults, logging, and utility constants
|
|
7
|
+
- file_patterns: File pattern matching and importance weighting
|
|
8
|
+
- languages: Language code mappings for internationalization
|
|
9
|
+
- commit: Git file status and commit message constants
|
|
10
|
+
|
|
11
|
+
All constants are re-exported from this package for backward compatibility.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from gac.constants.commit import CommitMessageConstants, FileStatus
|
|
15
|
+
from gac.constants.defaults import EnvDefaults, Logging, ProviderDefaults, Utility
|
|
16
|
+
from gac.constants.file_patterns import CodePatternImportance, FilePatterns, FileTypeImportance
|
|
17
|
+
from gac.constants.languages import Languages
|
|
18
|
+
|
|
19
|
+
__all__ = [
|
|
20
|
+
# From defaults
|
|
21
|
+
"EnvDefaults",
|
|
22
|
+
"ProviderDefaults",
|
|
23
|
+
"Logging",
|
|
24
|
+
"Utility",
|
|
25
|
+
# From file_patterns
|
|
26
|
+
"FilePatterns",
|
|
27
|
+
"FileTypeImportance",
|
|
28
|
+
"CodePatternImportance",
|
|
29
|
+
# From languages
|
|
30
|
+
"Languages",
|
|
31
|
+
# From commit
|
|
32
|
+
"FileStatus",
|
|
33
|
+
"CommitMessageConstants",
|
|
34
|
+
]
|
gac/constants/commit.py
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"""Constants for git operations and commit message generation."""
|
|
2
|
+
|
|
3
|
+
from enum import Enum
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class FileStatus(Enum):
|
|
7
|
+
"""File status for Git operations."""
|
|
8
|
+
|
|
9
|
+
MODIFIED = "M"
|
|
10
|
+
ADDED = "A"
|
|
11
|
+
DELETED = "D"
|
|
12
|
+
RENAMED = "R"
|
|
13
|
+
COPIED = "C"
|
|
14
|
+
UNTRACKED = "?"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class CommitMessageConstants:
|
|
18
|
+
"""Constants for commit message generation and cleaning."""
|
|
19
|
+
|
|
20
|
+
# Conventional commit type prefixes
|
|
21
|
+
CONVENTIONAL_PREFIXES: list[str] = [
|
|
22
|
+
"feat",
|
|
23
|
+
"fix",
|
|
24
|
+
"docs",
|
|
25
|
+
"style",
|
|
26
|
+
"refactor",
|
|
27
|
+
"perf",
|
|
28
|
+
"test",
|
|
29
|
+
"build",
|
|
30
|
+
"ci",
|
|
31
|
+
"chore",
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
# XML tags that may leak from prompt templates into AI responses
|
|
35
|
+
XML_TAGS_TO_REMOVE: list[str] = [
|
|
36
|
+
"<git-status>",
|
|
37
|
+
"</git-status>",
|
|
38
|
+
"<git_status>",
|
|
39
|
+
"</git_status>",
|
|
40
|
+
"<git-diff>",
|
|
41
|
+
"</git-diff>",
|
|
42
|
+
"<git_diff>",
|
|
43
|
+
"</git_diff>",
|
|
44
|
+
"<repository_context>",
|
|
45
|
+
"</repository_context>",
|
|
46
|
+
"<instructions>",
|
|
47
|
+
"</instructions>",
|
|
48
|
+
"<format>",
|
|
49
|
+
"</format>",
|
|
50
|
+
"<conventions>",
|
|
51
|
+
"</conventions>",
|
|
52
|
+
]
|
|
53
|
+
|
|
54
|
+
# Indicators that mark the start of the actual commit message in AI responses
|
|
55
|
+
COMMIT_INDICATORS: list[str] = [
|
|
56
|
+
"# Your commit message:",
|
|
57
|
+
"Your commit message:",
|
|
58
|
+
"The commit message is:",
|
|
59
|
+
"Here's the commit message:",
|
|
60
|
+
"Commit message:",
|
|
61
|
+
"Final commit message:",
|
|
62
|
+
"# Commit Message",
|
|
63
|
+
]
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"""Default values for environment variables and provider configurations."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class EnvDefaults:
|
|
7
|
+
"""Default values for environment variables."""
|
|
8
|
+
|
|
9
|
+
MAX_RETRIES: int = 3
|
|
10
|
+
TEMPERATURE: float = 1
|
|
11
|
+
MAX_OUTPUT_TOKENS: int = 4096 # includes reasoning tokens
|
|
12
|
+
WARNING_LIMIT_TOKENS: int = 32768
|
|
13
|
+
ALWAYS_INCLUDE_SCOPE: bool = False
|
|
14
|
+
SKIP_SECRET_SCAN: bool = False
|
|
15
|
+
VERBOSE: bool = False
|
|
16
|
+
NO_TIKTOKEN: bool = False
|
|
17
|
+
NO_VERIFY_SSL: bool = False # Skip SSL certificate verification (for corporate proxies)
|
|
18
|
+
HOOK_TIMEOUT: int = 120 # Timeout for pre-commit and lefthook hooks in seconds
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class ProviderDefaults:
|
|
22
|
+
"""Default values for provider configurations."""
|
|
23
|
+
|
|
24
|
+
HTTP_TIMEOUT: int = 120 # seconds - timeout for HTTP requests to LLM providers
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class Logging:
|
|
28
|
+
"""Logging configuration constants."""
|
|
29
|
+
|
|
30
|
+
DEFAULT_LEVEL: str = "WARNING"
|
|
31
|
+
LEVELS: list[str] = ["DEBUG", "INFO", "WARNING", "ERROR"]
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class Utility:
|
|
35
|
+
"""General utility constants."""
|
|
36
|
+
|
|
37
|
+
DEFAULT_ENCODING: str = "cl100k_base" # llm encoding
|
|
38
|
+
DEFAULT_DIFF_TOKEN_LIMIT: int = 15000 # Maximum tokens for diff processing
|
|
39
|
+
MAX_WORKERS: int = os.cpu_count() or 4 # Maximum number of parallel workers
|
|
40
|
+
MAX_DISPLAYED_SECRET_LENGTH: int = 50 # Maximum length for displaying secrets
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
"""File pattern constants for identifying special file types and importance weighting."""
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class FilePatterns:
|
|
5
|
+
"""Patterns for identifying special file types."""
|
|
6
|
+
|
|
7
|
+
# Regex patterns to detect binary file changes in git diffs (e.g., images or other non-text files)
|
|
8
|
+
BINARY: list[str] = [
|
|
9
|
+
r"Binary files .* differ",
|
|
10
|
+
r"GIT binary patch",
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
# Regex patterns to detect minified files in git diffs (e.g., JavaScript or CSS files)
|
|
14
|
+
MINIFIED_EXTENSIONS: list[str] = [
|
|
15
|
+
".min.js",
|
|
16
|
+
".min.css",
|
|
17
|
+
".bundle.js",
|
|
18
|
+
".bundle.css",
|
|
19
|
+
".compressed.js",
|
|
20
|
+
".compressed.css",
|
|
21
|
+
".opt.js",
|
|
22
|
+
".opt.css",
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
# Regex patterns to detect build directories in git diffs (e.g., dist, build, vendor, etc.)
|
|
26
|
+
BUILD_DIRECTORIES: list[str] = [
|
|
27
|
+
"/dist/",
|
|
28
|
+
"/build/",
|
|
29
|
+
"/vendor/",
|
|
30
|
+
"/node_modules/",
|
|
31
|
+
"/assets/vendor/",
|
|
32
|
+
"/public/build/",
|
|
33
|
+
"/static/dist/",
|
|
34
|
+
]
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class FileTypeImportance:
|
|
38
|
+
"""Importance multipliers for different file types."""
|
|
39
|
+
|
|
40
|
+
EXTENSIONS: dict[str, float] = {
|
|
41
|
+
# Programming languages
|
|
42
|
+
".py": 5.0, # Python
|
|
43
|
+
".js": 4.5, # JavaScript
|
|
44
|
+
".ts": 4.5, # TypeScript
|
|
45
|
+
".jsx": 4.8, # React JS
|
|
46
|
+
".tsx": 4.8, # React TS
|
|
47
|
+
".go": 4.5, # Go
|
|
48
|
+
".rs": 4.5, # Rust
|
|
49
|
+
".java": 4.2, # Java
|
|
50
|
+
".c": 4.2, # C
|
|
51
|
+
".h": 4.2, # C/C++ header
|
|
52
|
+
".cpp": 4.2, # C++
|
|
53
|
+
".rb": 4.2, # Ruby
|
|
54
|
+
".php": 4.0, # PHP
|
|
55
|
+
".scala": 4.0, # Scala
|
|
56
|
+
".swift": 4.0, # Swift
|
|
57
|
+
".kt": 4.0, # Kotlin
|
|
58
|
+
# Configuration
|
|
59
|
+
".json": 3.5, # JSON config
|
|
60
|
+
".yaml": 3.8, # YAML config
|
|
61
|
+
".yml": 3.8, # YAML config
|
|
62
|
+
".toml": 3.8, # TOML config
|
|
63
|
+
".ini": 3.5, # INI config
|
|
64
|
+
".env": 3.5, # Environment variables
|
|
65
|
+
# Documentation
|
|
66
|
+
".md": 2.5, # Markdown (reduced to prioritize code changes)
|
|
67
|
+
".rst": 2.5, # reStructuredText (reduced to prioritize code changes)
|
|
68
|
+
# Web
|
|
69
|
+
".html": 3.5, # HTML
|
|
70
|
+
".css": 3.5, # CSS
|
|
71
|
+
".scss": 3.5, # SCSS
|
|
72
|
+
".svg": 2.5, # SVG graphics
|
|
73
|
+
# Build & CI
|
|
74
|
+
"Dockerfile": 4.0, # Docker
|
|
75
|
+
".github/workflows": 4.0, # GitHub Actions
|
|
76
|
+
"CMakeLists.txt": 3.8, # CMake
|
|
77
|
+
"Makefile": 3.8, # Make
|
|
78
|
+
"package.json": 4.2, # NPM package
|
|
79
|
+
"pyproject.toml": 4.2, # Python project
|
|
80
|
+
"requirements.txt": 4.0, # Python requirements
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
class CodePatternImportance:
|
|
85
|
+
"""Importance multipliers for different code patterns."""
|
|
86
|
+
|
|
87
|
+
# Regex patterns to detect code structure changes in git diffs (e.g., class, function, import)
|
|
88
|
+
# Note: The patterns are prefixed with "+" to match only added and modified lines
|
|
89
|
+
PATTERNS: dict[str, float] = {
|
|
90
|
+
# Structure changes
|
|
91
|
+
r"\+\s*(class|interface|enum)\s+\w+": 1.8, # Class/interface/enum definitions
|
|
92
|
+
r"\+\s*(def|function|func)\s+\w+\s*\(": 1.5, # Function definitions
|
|
93
|
+
r"\+\s*(import|from .* import)": 1.3, # Imports
|
|
94
|
+
r"\+\s*(public|private|protected)\s+\w+": 1.2, # Access modifiers
|
|
95
|
+
# Configuration changes
|
|
96
|
+
r"\+\s*\"(dependencies|devDependencies)\"": 1.4, # Package dependencies
|
|
97
|
+
r"\+\s*version[\"\s:=]+[0-9.]+": 1.3, # Version changes
|
|
98
|
+
# Logic changes
|
|
99
|
+
r"\+\s*(if|else|elif|switch|case|for|while)[\s(]": 1.2, # Control structures
|
|
100
|
+
r"\+\s*(try|catch|except|finally)[\s:]": 1.2, # Exception handling
|
|
101
|
+
r"\+\s*return\s+": 1.1, # Return statements
|
|
102
|
+
r"\+\s*await\s+": 1.1, # Async/await
|
|
103
|
+
# Comments & docs
|
|
104
|
+
r"\+\s*(//|#|/\*|\*\*)\s*TODO": 1.2, # TODOs
|
|
105
|
+
r"\+\s*(//|#|/\*|\*\*)\s*FIX": 1.3, # FIXes
|
|
106
|
+
r"\+\s*(\"\"\"|\'\'\')": 1.1, # Docstrings
|
|
107
|
+
# Test code
|
|
108
|
+
r"\+\s*(test|describe|it|should)\s*\(": 1.1, # Test definitions
|
|
109
|
+
r"\+\s*(assert|expect)": 1.0, # Assertions
|
|
110
|
+
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
"""Language code mappings and utilities for internationalization."""
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class Languages:
|
|
5
|
+
"""Language code mappings and utilities."""
|
|
6
|
+
|
|
7
|
+
# Language code to full name mapping
|
|
8
|
+
# Supports ISO 639-1 codes and common variants
|
|
9
|
+
CODE_MAP: dict[str, str] = {
|
|
10
|
+
# English
|
|
11
|
+
"en": "English",
|
|
12
|
+
# Chinese
|
|
13
|
+
"zh": "Simplified Chinese",
|
|
14
|
+
"zh-cn": "Simplified Chinese",
|
|
15
|
+
"zh-hans": "Simplified Chinese",
|
|
16
|
+
"zh-tw": "Traditional Chinese",
|
|
17
|
+
"zh-hant": "Traditional Chinese",
|
|
18
|
+
# Japanese
|
|
19
|
+
"ja": "Japanese",
|
|
20
|
+
# Korean
|
|
21
|
+
"ko": "Korean",
|
|
22
|
+
# Spanish
|
|
23
|
+
"es": "Spanish",
|
|
24
|
+
# Portuguese
|
|
25
|
+
"pt": "Portuguese",
|
|
26
|
+
# French
|
|
27
|
+
"fr": "French",
|
|
28
|
+
# German
|
|
29
|
+
"de": "German",
|
|
30
|
+
# Russian
|
|
31
|
+
"ru": "Russian",
|
|
32
|
+
# Hindi
|
|
33
|
+
"hi": "Hindi",
|
|
34
|
+
# Italian
|
|
35
|
+
"it": "Italian",
|
|
36
|
+
# Polish
|
|
37
|
+
"pl": "Polish",
|
|
38
|
+
# Turkish
|
|
39
|
+
"tr": "Turkish",
|
|
40
|
+
# Dutch
|
|
41
|
+
"nl": "Dutch",
|
|
42
|
+
# Vietnamese
|
|
43
|
+
"vi": "Vietnamese",
|
|
44
|
+
# Thai
|
|
45
|
+
"th": "Thai",
|
|
46
|
+
# Indonesian
|
|
47
|
+
"id": "Indonesian",
|
|
48
|
+
# Swedish
|
|
49
|
+
"sv": "Swedish",
|
|
50
|
+
# Arabic
|
|
51
|
+
"ar": "Arabic",
|
|
52
|
+
# Hebrew
|
|
53
|
+
"he": "Hebrew",
|
|
54
|
+
# Greek
|
|
55
|
+
"el": "Greek",
|
|
56
|
+
# Danish
|
|
57
|
+
"da": "Danish",
|
|
58
|
+
# Norwegian
|
|
59
|
+
"no": "Norwegian",
|
|
60
|
+
"nb": "Norwegian",
|
|
61
|
+
"nn": "Norwegian",
|
|
62
|
+
# Finnish
|
|
63
|
+
"fi": "Finnish",
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
# List of languages with display names and English names for CLI selection
|
|
67
|
+
# Format: (display_name, english_name)
|
|
68
|
+
LANGUAGES: list[tuple[str, str]] = [
|
|
69
|
+
("English", "English"),
|
|
70
|
+
("简体中文", "Simplified Chinese"),
|
|
71
|
+
("繁體中文", "Traditional Chinese"),
|
|
72
|
+
("日本語", "Japanese"),
|
|
73
|
+
("한국어", "Korean"),
|
|
74
|
+
("Español", "Spanish"),
|
|
75
|
+
("Português", "Portuguese"),
|
|
76
|
+
("Français", "French"),
|
|
77
|
+
("Deutsch", "German"),
|
|
78
|
+
("Русский", "Russian"),
|
|
79
|
+
("हिन्दी", "Hindi"),
|
|
80
|
+
("Italiano", "Italian"),
|
|
81
|
+
("Polski", "Polish"),
|
|
82
|
+
("Türkçe", "Turkish"),
|
|
83
|
+
("Nederlands", "Dutch"),
|
|
84
|
+
("Tiếng Việt", "Vietnamese"),
|
|
85
|
+
("ไทย", "Thai"),
|
|
86
|
+
("Bahasa Indonesia", "Indonesian"),
|
|
87
|
+
("Svenska", "Swedish"),
|
|
88
|
+
("العربية", "Arabic"),
|
|
89
|
+
("עברית", "Hebrew"),
|
|
90
|
+
("Ελληνικά", "Greek"),
|
|
91
|
+
("Dansk", "Danish"),
|
|
92
|
+
("Norsk", "Norwegian"),
|
|
93
|
+
("Suomi", "Finnish"),
|
|
94
|
+
("Custom", "Custom"),
|
|
95
|
+
]
|
|
96
|
+
|
|
97
|
+
@staticmethod
|
|
98
|
+
def resolve_code(language: str) -> str:
|
|
99
|
+
"""Resolve a language code to its full name.
|
|
100
|
+
|
|
101
|
+
Args:
|
|
102
|
+
language: Language name or code (e.g., 'Spanish', 'es', 'zh-CN')
|
|
103
|
+
|
|
104
|
+
Returns:
|
|
105
|
+
Full language name (e.g., 'Spanish', 'Simplified Chinese')
|
|
106
|
+
|
|
107
|
+
If the input is already a full language name, it's returned as-is.
|
|
108
|
+
If it's a recognized code, it's converted to the full name.
|
|
109
|
+
Otherwise, the input is returned unchanged (for custom languages).
|
|
110
|
+
"""
|
|
111
|
+
# Normalize the code to lowercase for lookup
|
|
112
|
+
code_lower = language.lower().strip()
|
|
113
|
+
|
|
114
|
+
# Check if it's a recognized code
|
|
115
|
+
if code_lower in Languages.CODE_MAP:
|
|
116
|
+
return Languages.CODE_MAP[code_lower]
|
|
117
|
+
|
|
118
|
+
# Return as-is (could be a full name or custom language)
|
|
119
|
+
return language
|
gac/diff_cli.py
CHANGED
|
@@ -157,25 +157,3 @@ def diff(
|
|
|
157
157
|
commit1=commit1,
|
|
158
158
|
commit2=commit2,
|
|
159
159
|
)
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
# Function for testing only
|
|
163
|
-
def _callback_for_testing(
|
|
164
|
-
filter: bool,
|
|
165
|
-
truncate: bool,
|
|
166
|
-
max_tokens: int | None,
|
|
167
|
-
staged: bool,
|
|
168
|
-
color: bool,
|
|
169
|
-
commit1: str | None = None,
|
|
170
|
-
commit2: str | None = None,
|
|
171
|
-
) -> None:
|
|
172
|
-
"""A version of the diff command callback that can be called directly from tests."""
|
|
173
|
-
_diff_implementation(
|
|
174
|
-
filter=filter,
|
|
175
|
-
truncate=truncate,
|
|
176
|
-
max_tokens=max_tokens,
|
|
177
|
-
staged=staged,
|
|
178
|
-
color=color,
|
|
179
|
-
commit1=commit1,
|
|
180
|
-
commit2=commit2,
|
|
181
|
-
)
|
gac/errors.py
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
import logging
|
|
4
4
|
import sys
|
|
5
5
|
from collections.abc import Callable
|
|
6
|
-
from typing import TypeVar
|
|
6
|
+
from typing import Any, TypeVar
|
|
7
7
|
|
|
8
8
|
from rich.console import Console
|
|
9
9
|
|
|
@@ -113,6 +113,12 @@ class SecurityError(GacError):
|
|
|
113
113
|
exit_code = 6
|
|
114
114
|
|
|
115
115
|
|
|
116
|
+
class HookError(GacError):
|
|
117
|
+
"""Error when pre-commit or lefthook hooks fail."""
|
|
118
|
+
|
|
119
|
+
exit_code = 1
|
|
120
|
+
|
|
121
|
+
|
|
116
122
|
# Simplified error hierarchy - we use a single AIError class with error codes
|
|
117
123
|
# instead of multiple subclasses for better maintainability
|
|
118
124
|
|
|
@@ -216,7 +222,7 @@ def with_error_handling(
|
|
|
216
222
|
"""
|
|
217
223
|
|
|
218
224
|
def decorator(func: Callable[..., T]) -> Callable[..., T | None]:
|
|
219
|
-
def wrapper(*args, **kwargs) -> T | None:
|
|
225
|
+
def wrapper(*args: Any, **kwargs: Any) -> T | None:
|
|
220
226
|
try:
|
|
221
227
|
return func(*args, **kwargs)
|
|
222
228
|
except Exception as e:
|
gac/git.py
CHANGED
|
@@ -16,7 +16,7 @@ logger = logging.getLogger(__name__)
|
|
|
16
16
|
|
|
17
17
|
def run_subprocess_with_encoding_fallback(
|
|
18
18
|
command: list[str], silent: bool = False, timeout: int = 60
|
|
19
|
-
) -> subprocess.CompletedProcess:
|
|
19
|
+
) -> subprocess.CompletedProcess[str]:
|
|
20
20
|
"""Run subprocess with encoding fallback, returning full CompletedProcess object.
|
|
21
21
|
|
|
22
22
|
This is used for cases where we need both stdout and stderr separately,
|
|
@@ -57,7 +57,7 @@ def run_subprocess_with_encoding_fallback(
|
|
|
57
57
|
continue
|
|
58
58
|
except subprocess.TimeoutExpired:
|
|
59
59
|
raise
|
|
60
|
-
except
|
|
60
|
+
except (subprocess.SubprocessError, OSError, FileNotFoundError) as e:
|
|
61
61
|
if not silent:
|
|
62
62
|
logger.debug(f"Command error: {e}")
|
|
63
63
|
# Try next encoding for non-timeout errors
|
|
@@ -182,7 +182,7 @@ def get_diff(staged: bool = True, color: bool = True, commit1: str | None = None
|
|
|
182
182
|
|
|
183
183
|
output = run_git_command(args)
|
|
184
184
|
return output
|
|
185
|
-
except
|
|
185
|
+
except (subprocess.SubprocessError, OSError, FileNotFoundError) as e:
|
|
186
186
|
logger.error(f"Failed to get diff: {str(e)}")
|
|
187
187
|
raise GitError(f"Failed to get diff: {str(e)}") from e
|
|
188
188
|
|
|
@@ -246,7 +246,7 @@ def run_pre_commit_hooks(hook_timeout: int = 120) -> bool:
|
|
|
246
246
|
else:
|
|
247
247
|
logger.error(f"Pre-commit hooks failed with exit code {result.returncode}")
|
|
248
248
|
return False
|
|
249
|
-
except
|
|
249
|
+
except (subprocess.SubprocessError, OSError, FileNotFoundError, PermissionError) as e:
|
|
250
250
|
logger.debug(f"Error running pre-commit: {e}")
|
|
251
251
|
# If pre-commit isn't available, don't block the commit
|
|
252
252
|
return True
|
|
@@ -296,7 +296,7 @@ def run_lefthook_hooks(hook_timeout: int = 120) -> bool:
|
|
|
296
296
|
else:
|
|
297
297
|
logger.error(f"Lefthook hooks failed with exit code {result.returncode}")
|
|
298
298
|
return False
|
|
299
|
-
except
|
|
299
|
+
except (subprocess.SubprocessError, OSError, FileNotFoundError, PermissionError) as e:
|
|
300
300
|
logger.debug(f"Error running Lefthook: {e}")
|
|
301
301
|
# If lefthook isn't available, don't block the commit
|
|
302
302
|
return True
|
|
@@ -320,7 +320,7 @@ def push_changes() -> bool:
|
|
|
320
320
|
else:
|
|
321
321
|
logger.error(f"Failed to push changes: {error_msg}")
|
|
322
322
|
return False
|
|
323
|
-
except
|
|
323
|
+
except (subprocess.SubprocessError, OSError, ConnectionError) as e:
|
|
324
324
|
logger.error(f"Failed to push changes: {e}")
|
|
325
325
|
return False
|
|
326
326
|
|