gac 1.9.1__py3-none-any.whl → 1.9.3__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/ai_utils.py +2 -2
- gac/cli.py +2 -2
- gac/config.py +1 -1
- gac/constants.py +2 -3
- gac/git.py +4 -4
- gac/init_cli.py +6 -4
- gac/main.py +19 -8
- gac/preprocess.py +1 -1
- gac/security.py +1 -1
- gac/utils.py +1 -1
- {gac-1.9.1.dist-info → gac-1.9.3.dist-info}/METADATA +3 -2
- {gac-1.9.1.dist-info → gac-1.9.3.dist-info}/RECORD +16 -16
- {gac-1.9.1.dist-info → gac-1.9.3.dist-info}/WHEEL +0 -0
- {gac-1.9.1.dist-info → gac-1.9.3.dist-info}/entry_points.txt +0 -0
- {gac-1.9.1.dist-info → gac-1.9.3.dist-info}/licenses/LICENSE +0 -0
gac/__version__.py
CHANGED
gac/ai_utils.py
CHANGED
|
@@ -38,7 +38,7 @@ def extract_text_content(content: str | list[dict[str, str]] | dict[str, Any]) -
|
|
|
38
38
|
elif isinstance(content, list):
|
|
39
39
|
return "\n".join(msg["content"] for msg in content if isinstance(msg, dict) and "content" in msg)
|
|
40
40
|
elif isinstance(content, dict) and "content" in content:
|
|
41
|
-
return content["content"]
|
|
41
|
+
return content["content"] # type: ignore[no-any-return]
|
|
42
42
|
return ""
|
|
43
43
|
|
|
44
44
|
|
|
@@ -144,7 +144,7 @@ def generate_with_retries(
|
|
|
144
144
|
spinner.succeed(f"Generated commit message with {provider} {model_name}")
|
|
145
145
|
|
|
146
146
|
if content is not None and content.strip():
|
|
147
|
-
return content.strip()
|
|
147
|
+
return content.strip() # type: ignore[no-any-return]
|
|
148
148
|
else:
|
|
149
149
|
logger.warning(f"Empty or None content received from {provider} {model_name}: {repr(content)}")
|
|
150
150
|
raise AIError.model_error("Empty response from AI model")
|
gac/cli.py
CHANGED
|
@@ -66,7 +66,7 @@ logger = logging.getLogger(__name__)
|
|
|
66
66
|
def cli(
|
|
67
67
|
ctx: click.Context,
|
|
68
68
|
add_all: bool = False,
|
|
69
|
-
log_level: str = config["log_level"],
|
|
69
|
+
log_level: str = str(config["log_level"]),
|
|
70
70
|
one_liner: bool = False,
|
|
71
71
|
push: bool = False,
|
|
72
72
|
show_prompt: bool = False,
|
|
@@ -74,7 +74,7 @@ def cli(
|
|
|
74
74
|
quiet: bool = False,
|
|
75
75
|
yes: bool = False,
|
|
76
76
|
hint: str = "",
|
|
77
|
-
model: str = None,
|
|
77
|
+
model: str | None = None,
|
|
78
78
|
version: bool = False,
|
|
79
79
|
dry_run: bool = False,
|
|
80
80
|
verbose: bool = False,
|
gac/config.py
CHANGED
|
@@ -11,7 +11,7 @@ from dotenv import load_dotenv
|
|
|
11
11
|
from gac.constants import EnvDefaults, Logging
|
|
12
12
|
|
|
13
13
|
|
|
14
|
-
def load_config() -> dict[str, str | int | float | bool]:
|
|
14
|
+
def load_config() -> dict[str, str | int | float | bool | None]:
|
|
15
15
|
"""Load configuration from $HOME/.gac.env, then ./.gac.env or ./.env, then environment variables."""
|
|
16
16
|
user_config = Path.home() / ".gac.env"
|
|
17
17
|
if user_config.exists():
|
gac/constants.py
CHANGED
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
import os
|
|
4
4
|
from enum import Enum
|
|
5
|
-
from re import Pattern
|
|
6
5
|
|
|
7
6
|
|
|
8
7
|
class FileStatus(Enum):
|
|
@@ -48,7 +47,7 @@ class FilePatterns:
|
|
|
48
47
|
"""Patterns for identifying special file types."""
|
|
49
48
|
|
|
50
49
|
# Regex patterns to detect binary file changes in git diffs (e.g., images or other non-text files)
|
|
51
|
-
BINARY: list[
|
|
50
|
+
BINARY: list[str] = [
|
|
52
51
|
r"Binary files .* differ",
|
|
53
52
|
r"GIT binary patch",
|
|
54
53
|
]
|
|
@@ -129,7 +128,7 @@ class CodePatternImportance:
|
|
|
129
128
|
|
|
130
129
|
# Regex patterns to detect code structure changes in git diffs (e.g., class, function, import)
|
|
131
130
|
# Note: The patterns are prefixed with "+" to match only added and modified lines
|
|
132
|
-
PATTERNS: dict[
|
|
131
|
+
PATTERNS: dict[str, float] = {
|
|
133
132
|
# Structure changes
|
|
134
133
|
r"\+\s*(class|interface|enum)\s+\w+": 1.8, # Class/interface/enum definitions
|
|
135
134
|
r"\+\s*(def|function|func)\s+\w+\s*\(": 1.5, # Function definitions
|
gac/git.py
CHANGED
|
@@ -120,8 +120,8 @@ def run_pre_commit_hooks() -> bool:
|
|
|
120
120
|
# Check if pre-commit is installed and configured
|
|
121
121
|
try:
|
|
122
122
|
# First check if pre-commit is installed
|
|
123
|
-
|
|
124
|
-
if not
|
|
123
|
+
version_check = run_subprocess(["pre-commit", "--version"], silent=True, raise_on_error=False)
|
|
124
|
+
if not version_check:
|
|
125
125
|
logger.debug("pre-commit not installed, skipping hooks")
|
|
126
126
|
return True
|
|
127
127
|
|
|
@@ -170,8 +170,8 @@ def run_lefthook_hooks() -> bool:
|
|
|
170
170
|
# Check if lefthook is installed and configured
|
|
171
171
|
try:
|
|
172
172
|
# First check if lefthook is installed
|
|
173
|
-
|
|
174
|
-
if not
|
|
173
|
+
version_check = run_subprocess(["lefthook", "--version"], silent=True, raise_on_error=False)
|
|
174
|
+
if not version_check:
|
|
175
175
|
logger.debug("Lefthook not installed, skipping hooks")
|
|
176
176
|
return True
|
|
177
177
|
|
gac/init_cli.py
CHANGED
|
@@ -17,7 +17,7 @@ def _prompt_required_text(prompt: str) -> str | None:
|
|
|
17
17
|
return None
|
|
18
18
|
value = response.strip()
|
|
19
19
|
if value:
|
|
20
|
-
return value
|
|
20
|
+
return value # type: ignore[no-any-return]
|
|
21
21
|
click.echo("A value is required. Please try again.")
|
|
22
22
|
|
|
23
23
|
|
|
@@ -34,7 +34,7 @@ def init() -> None:
|
|
|
34
34
|
providers = [
|
|
35
35
|
("Anthropic", "claude-haiku-4-5"),
|
|
36
36
|
("Cerebras", "qwen-3-coder-480b"),
|
|
37
|
-
("Chutes
|
|
37
|
+
("Chutes", "zai-org/GLM-4.6-FP8"),
|
|
38
38
|
("Gemini", "gemini-2.5-flash"),
|
|
39
39
|
("Groq", "meta-llama/llama-4-maverick-17b-128e-instruct"),
|
|
40
40
|
("LM Studio", "gemma3"),
|
|
@@ -51,7 +51,7 @@ def init() -> None:
|
|
|
51
51
|
if not provider:
|
|
52
52
|
click.echo("Provider selection cancelled. Exiting.")
|
|
53
53
|
return
|
|
54
|
-
provider_key = provider.lower().replace(".", "").replace(" ", "-")
|
|
54
|
+
provider_key = provider.lower().replace(".", "").replace(" ", "-")
|
|
55
55
|
|
|
56
56
|
is_ollama = provider_key == "ollama"
|
|
57
57
|
is_lmstudio = provider_key == "lm-studio"
|
|
@@ -104,7 +104,9 @@ def init() -> None:
|
|
|
104
104
|
|
|
105
105
|
api_key = questionary.password(api_key_prompt).ask()
|
|
106
106
|
if api_key:
|
|
107
|
-
if
|
|
107
|
+
if is_lmstudio:
|
|
108
|
+
api_key_name = "LMSTUDIO_API_KEY"
|
|
109
|
+
elif is_zai:
|
|
108
110
|
api_key_name = "ZAI_API_KEY"
|
|
109
111
|
else:
|
|
110
112
|
api_key_name = f"{provider_key.upper()}_API_KEY"
|
gac/main.py
CHANGED
|
@@ -57,18 +57,27 @@ def main(
|
|
|
57
57
|
handle_error(GitError("Not in a git repository"), exit_program=True)
|
|
58
58
|
|
|
59
59
|
if model is None:
|
|
60
|
-
|
|
61
|
-
if
|
|
60
|
+
model_from_config = config["model"]
|
|
61
|
+
if model_from_config is None:
|
|
62
62
|
handle_error(
|
|
63
63
|
AIError.model_error(
|
|
64
64
|
"gac init hasn't been run yet. Please run 'gac init' to set up your configuration, then try again."
|
|
65
65
|
),
|
|
66
66
|
exit_program=True,
|
|
67
67
|
)
|
|
68
|
+
model = str(model_from_config)
|
|
68
69
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
70
|
+
temperature_val = config["temperature"]
|
|
71
|
+
assert temperature_val is not None
|
|
72
|
+
temperature = float(temperature_val)
|
|
73
|
+
|
|
74
|
+
max_tokens_val = config["max_output_tokens"]
|
|
75
|
+
assert max_tokens_val is not None
|
|
76
|
+
max_output_tokens = int(max_tokens_val)
|
|
77
|
+
|
|
78
|
+
max_retries_val = config["max_retries"]
|
|
79
|
+
assert max_retries_val is not None
|
|
80
|
+
max_retries = int(max_retries_val)
|
|
72
81
|
|
|
73
82
|
if stage_all and (not dry_run):
|
|
74
83
|
logger.info("Staging all changes")
|
|
@@ -168,8 +177,8 @@ def main(
|
|
|
168
177
|
|
|
169
178
|
# Preprocess the diff before passing to build_prompt
|
|
170
179
|
logger.debug(f"Preprocessing diff ({len(diff)} characters)")
|
|
171
|
-
|
|
172
|
-
processed_diff = preprocess_diff(diff, token_limit=Utility.DEFAULT_DIFF_TOKEN_LIMIT, model=
|
|
180
|
+
assert model is not None
|
|
181
|
+
processed_diff = preprocess_diff(diff, token_limit=Utility.DEFAULT_DIFF_TOKEN_LIMIT, model=model)
|
|
173
182
|
logger.debug(f"Processed diff ({len(processed_diff)} characters)")
|
|
174
183
|
|
|
175
184
|
system_prompt, user_prompt = build_prompt(
|
|
@@ -197,7 +206,9 @@ def main(
|
|
|
197
206
|
# Count tokens for both prompts
|
|
198
207
|
prompt_tokens = count_tokens(system_prompt, model) + count_tokens(user_prompt, model)
|
|
199
208
|
|
|
200
|
-
|
|
209
|
+
warning_limit_val = config.get("warning_limit_tokens", EnvDefaults.WARNING_LIMIT_TOKENS)
|
|
210
|
+
assert warning_limit_val is not None
|
|
211
|
+
warning_limit = int(warning_limit_val)
|
|
201
212
|
if warning_limit and prompt_tokens > warning_limit:
|
|
202
213
|
console.print(
|
|
203
214
|
f"[yellow]⚠️ WARNING: Prompt contains {prompt_tokens} tokens, which exceeds the warning limit of "
|
gac/preprocess.py
CHANGED
|
@@ -143,7 +143,7 @@ def extract_binary_file_summary(section: str) -> str:
|
|
|
143
143
|
return extract_filtered_file_summary(section, "[Binary file change]")
|
|
144
144
|
|
|
145
145
|
|
|
146
|
-
def extract_filtered_file_summary(section: str, change_type: str = None) -> str:
|
|
146
|
+
def extract_filtered_file_summary(section: str, change_type: str | None = None) -> str:
|
|
147
147
|
"""Extract a summary of filtered file changes from a diff section.
|
|
148
148
|
|
|
149
149
|
Args:
|
gac/security.py
CHANGED
|
@@ -179,7 +179,7 @@ def scan_diff_section(section: str) -> list[DetectedSecret]:
|
|
|
179
179
|
Returns:
|
|
180
180
|
List of detected secrets
|
|
181
181
|
"""
|
|
182
|
-
secrets = []
|
|
182
|
+
secrets: list[DetectedSecret] = []
|
|
183
183
|
file_path = extract_file_path_from_diff_section(section)
|
|
184
184
|
|
|
185
185
|
if not file_path:
|
gac/utils.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: gac
|
|
3
|
-
Version: 1.9.
|
|
3
|
+
Version: 1.9.3
|
|
4
4
|
Summary: AI-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
|
|
@@ -50,7 +50,8 @@ Description-Content-Type: text/markdown
|
|
|
50
50
|
[](https://www.python.org/downloads/)
|
|
51
51
|
[](https://github.com/cellwebb/gac/actions)
|
|
52
52
|
[](https://app.codecov.io/gh/cellwebb/gac)
|
|
53
|
-
[](https://github.com/astral-sh/ruff)
|
|
54
|
+
[](https://mypy-lang.org/)
|
|
54
55
|
[](docs/CONTRIBUTING.md)
|
|
55
56
|
[](LICENSE)
|
|
56
57
|
|
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
gac/__init__.py,sha256=z9yGInqtycFIT3g1ca24r-A3699hKVaRqGUI79wsmMc,415
|
|
2
|
-
gac/__version__.py,sha256=
|
|
2
|
+
gac/__version__.py,sha256=bAfkjMjFPqHxyCDtIve6MEc-rYfJ1cDzL1dp4Kpo_QU,66
|
|
3
3
|
gac/ai.py,sha256=-0rIZQCHC7yOEmkLtCLDzeInnrm960hVpiEELi8NM_U,3513
|
|
4
|
-
gac/ai_utils.py,sha256=
|
|
5
|
-
gac/cli.py,sha256=
|
|
6
|
-
gac/config.py,sha256=
|
|
4
|
+
gac/ai_utils.py,sha256=eqrpiBTueCgSOxUC2b4Ei0G4DM1GMt866sJEIhWpytU,7333
|
|
5
|
+
gac/cli.py,sha256=crUUI6osYtE3QAZ7r6DRlVk9gR3X2PctzS1sssVQ9_g,5070
|
|
6
|
+
gac/config.py,sha256=n3TkQYBqSKkH68QUM6M7kwSK83ghmItoh0p5ZDFnhHA,1746
|
|
7
7
|
gac/config_cli.py,sha256=v9nFHZO1RvK9fzHyuUS6SG-BCLHMsdOMDwWamBhVVh4,1608
|
|
8
|
-
gac/constants.py,sha256=
|
|
8
|
+
gac/constants.py,sha256=y3qMMwAne5LUk5FexIzEDxEFWnuPwaZk9aFZEdBZ3gw,4947
|
|
9
9
|
gac/diff_cli.py,sha256=wnVQ9OFGnM0d2Pj9WVjWbo0jxqIuRHVAwmb8wU9Pa3E,5676
|
|
10
10
|
gac/errors.py,sha256=ysDIVRCd0YQVTOW3Q6YzdolxCdtkoQCAFf3_jrqbjUY,7916
|
|
11
|
-
gac/git.py,sha256=
|
|
12
|
-
gac/init_cli.py,sha256=
|
|
13
|
-
gac/main.py,sha256=
|
|
14
|
-
gac/preprocess.py,sha256=
|
|
11
|
+
gac/git.py,sha256=g6tvph50zV-wrTWrxARYXEpl0NeI8-ffFwHoqhp3fSE,8033
|
|
12
|
+
gac/init_cli.py,sha256=wq2MMi1xQrbVTib-5BsVdbPXQkPStB3G3Q8VnSKiKFQ,4740
|
|
13
|
+
gac/main.py,sha256=pQUvrJt3cqxKV7b7w7U_Gb9QOQMnoSB7tI0s_3uZAI0,16163
|
|
14
|
+
gac/preprocess.py,sha256=aMxsjGxy9YP752NWjgf0KP5Sn6p8keIJAGlMYr8jDgQ,15373
|
|
15
15
|
gac/prompt.py,sha256=d_kBXmhf3bDVLyDj8J7AS7GBAxF2jlc8lXoHX3Dzi5k,24255
|
|
16
|
-
gac/security.py,sha256=
|
|
17
|
-
gac/utils.py,sha256=
|
|
16
|
+
gac/security.py,sha256=15Yp6YR8QC4eECJi1BUCkMteh_veZXUbLL6W8qGcDm4,9920
|
|
17
|
+
gac/utils.py,sha256=nV42-brIHW_fBg7x855GM8nRrqEBbRzTSweg-GTyGE8,3971
|
|
18
18
|
gac/providers/__init__.py,sha256=ejIM5vvmfTp7vfJSNeQQPIEJusOkKTUZpUE7OeWBc9Y,876
|
|
19
19
|
gac/providers/anthropic.py,sha256=VK5d1s1PmBNDwh_x7illQ2CIZIHNIYU28btVfizwQPs,2036
|
|
20
20
|
gac/providers/cerebras.py,sha256=Ik8lhlsliGJVkgDgqlThfpra9tqbdYQZkaC4eNxRd9w,1648
|
|
@@ -28,8 +28,8 @@ gac/providers/openrouter.py,sha256=H3ce8JcRUYq1I30lOjGESdX7jfoPkW3mKAYnc2aYfBw,2
|
|
|
28
28
|
gac/providers/streamlake.py,sha256=KAA2ZnpuEI5imzvdWVWUhEBHSP0BMnprKXte6CbwBWY,2047
|
|
29
29
|
gac/providers/synthetic.py,sha256=sRMIJTS9LpcXd9A7qp_ZjZxdqtTKRn9fl1W4YwJZP4c,1855
|
|
30
30
|
gac/providers/zai.py,sha256=kywhhrCfPBu0rElZyb-iENxQxxpVGykvePuL4xrXlaU,2739
|
|
31
|
-
gac-1.9.
|
|
32
|
-
gac-1.9.
|
|
33
|
-
gac-1.9.
|
|
34
|
-
gac-1.9.
|
|
35
|
-
gac-1.9.
|
|
31
|
+
gac-1.9.3.dist-info/METADATA,sha256=eIfvTaTy23On_QW6wNM4YPFtuS5gaNMDiEw3fko8UYE,9409
|
|
32
|
+
gac-1.9.3.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
33
|
+
gac-1.9.3.dist-info/entry_points.txt,sha256=tdjN-XMmcWfL92swuRAjT62bFLOAwk9bTMRLGP5Z4aI,36
|
|
34
|
+
gac-1.9.3.dist-info/licenses/LICENSE,sha256=vOab37NouL1PNs5BswnPayrMCqaN2sqLfMQfqPDrpZg,1103
|
|
35
|
+
gac-1.9.3.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|