gac 1.9.1__tar.gz → 1.9.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of gac might be problematic. Click here for more details.

Files changed (35) hide show
  1. {gac-1.9.1 → gac-1.9.3}/PKG-INFO +3 -2
  2. {gac-1.9.1 → gac-1.9.3}/README.md +2 -1
  3. {gac-1.9.1 → gac-1.9.3}/pyproject.toml +24 -0
  4. {gac-1.9.1 → gac-1.9.3}/src/gac/__version__.py +1 -1
  5. {gac-1.9.1 → gac-1.9.3}/src/gac/ai_utils.py +2 -2
  6. {gac-1.9.1 → gac-1.9.3}/src/gac/cli.py +2 -2
  7. {gac-1.9.1 → gac-1.9.3}/src/gac/config.py +1 -1
  8. {gac-1.9.1 → gac-1.9.3}/src/gac/constants.py +2 -3
  9. {gac-1.9.1 → gac-1.9.3}/src/gac/git.py +4 -4
  10. {gac-1.9.1 → gac-1.9.3}/src/gac/init_cli.py +6 -4
  11. {gac-1.9.1 → gac-1.9.3}/src/gac/main.py +19 -8
  12. {gac-1.9.1 → gac-1.9.3}/src/gac/preprocess.py +1 -1
  13. {gac-1.9.1 → gac-1.9.3}/src/gac/security.py +1 -1
  14. {gac-1.9.1 → gac-1.9.3}/src/gac/utils.py +1 -1
  15. {gac-1.9.1 → gac-1.9.3}/.gitignore +0 -0
  16. {gac-1.9.1 → gac-1.9.3}/LICENSE +0 -0
  17. {gac-1.9.1 → gac-1.9.3}/src/gac/__init__.py +0 -0
  18. {gac-1.9.1 → gac-1.9.3}/src/gac/ai.py +0 -0
  19. {gac-1.9.1 → gac-1.9.3}/src/gac/config_cli.py +0 -0
  20. {gac-1.9.1 → gac-1.9.3}/src/gac/diff_cli.py +0 -0
  21. {gac-1.9.1 → gac-1.9.3}/src/gac/errors.py +0 -0
  22. {gac-1.9.1 → gac-1.9.3}/src/gac/prompt.py +0 -0
  23. {gac-1.9.1 → gac-1.9.3}/src/gac/providers/__init__.py +0 -0
  24. {gac-1.9.1 → gac-1.9.3}/src/gac/providers/anthropic.py +0 -0
  25. {gac-1.9.1 → gac-1.9.3}/src/gac/providers/cerebras.py +0 -0
  26. {gac-1.9.1 → gac-1.9.3}/src/gac/providers/chutes.py +0 -0
  27. {gac-1.9.1 → gac-1.9.3}/src/gac/providers/gemini.py +0 -0
  28. {gac-1.9.1 → gac-1.9.3}/src/gac/providers/groq.py +0 -0
  29. {gac-1.9.1 → gac-1.9.3}/src/gac/providers/lmstudio.py +0 -0
  30. {gac-1.9.1 → gac-1.9.3}/src/gac/providers/ollama.py +0 -0
  31. {gac-1.9.1 → gac-1.9.3}/src/gac/providers/openai.py +0 -0
  32. {gac-1.9.1 → gac-1.9.3}/src/gac/providers/openrouter.py +0 -0
  33. {gac-1.9.1 → gac-1.9.3}/src/gac/providers/streamlake.py +0 -0
  34. {gac-1.9.1 → gac-1.9.3}/src/gac/providers/synthetic.py +0 -0
  35. {gac-1.9.1 → gac-1.9.3}/src/gac/providers/zai.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: gac
3
- Version: 1.9.1
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
  [![Python](https://img.shields.io/badge/python-3.10%20|%203.11%20|%203.12%20|%203.13%20|%203.14-blue.svg)](https://www.python.org/downloads/)
51
51
  [![Build Status](https://github.com/cellwebb/gac/actions/workflows/ci.yml/badge.svg)](https://github.com/cellwebb/gac/actions)
52
52
  [![codecov](https://codecov.io/gh/cellwebb/gac/branch/main/graph/badge.svg)](https://app.codecov.io/gh/cellwebb/gac)
53
- [![Code Style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
53
+ [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
54
+ [![mypy](https://img.shields.io/badge/mypy-checked-blue.svg)](https://mypy-lang.org/)
54
55
  [![Contributions welcome](https://img.shields.io/badge/contributions-welcome-brightgreen.svg)](docs/CONTRIBUTING.md)
55
56
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
56
57
 
@@ -6,7 +6,8 @@
6
6
  [![Python](https://img.shields.io/badge/python-3.10%20|%203.11%20|%203.12%20|%203.13%20|%203.14-blue.svg)](https://www.python.org/downloads/)
7
7
  [![Build Status](https://github.com/cellwebb/gac/actions/workflows/ci.yml/badge.svg)](https://github.com/cellwebb/gac/actions)
8
8
  [![codecov](https://codecov.io/gh/cellwebb/gac/branch/main/graph/badge.svg)](https://app.codecov.io/gh/cellwebb/gac)
9
- [![Code Style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
9
+ [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
10
+ [![mypy](https://img.shields.io/badge/mypy-checked-blue.svg)](https://mypy-lang.org/)
10
11
  [![Contributions welcome](https://img.shields.io/badge/contributions-welcome-brightgreen.svg)](docs/CONTRIBUTING.md)
11
12
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
12
13
 
@@ -205,6 +205,29 @@ markers = [
205
205
  ]
206
206
  addopts = "-m 'not integration'"
207
207
 
208
+ [tool.mypy]
209
+ python_version = "3.10"
210
+ warn_return_any = true
211
+ warn_unused_configs = true
212
+ disallow_untyped_defs = false
213
+ disallow_incomplete_defs = false
214
+ check_untyped_defs = true
215
+ no_implicit_optional = true
216
+ warn_redundant_casts = true
217
+ warn_unused_ignores = true
218
+ warn_no_return = true
219
+ warn_unreachable = true
220
+ strict_equality = true
221
+ show_error_codes = true
222
+
223
+ [[tool.mypy.overrides]]
224
+ module = "gac.providers.*"
225
+ warn_return_any = false
226
+
227
+ [[tool.mypy.overrides]]
228
+ module = "halo"
229
+ ignore_missing_imports = true
230
+
208
231
  [template.plugins.default]
209
232
  tests = true
210
233
  src-layout = true
@@ -215,4 +238,5 @@ dev = [
215
238
  "pytest-asyncio>=1.2.0",
216
239
  "pytest-cov>=7.0.0",
217
240
  "pre-commit",
241
+ "mypy>=1.8.0",
218
242
  ]
@@ -1,3 +1,3 @@
1
1
  """Version information for gac package."""
2
2
 
3
- __version__ = "1.9.1"
3
+ __version__ = "1.9.3"
@@ -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")
@@ -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,
@@ -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():
@@ -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[Pattern[str]] = [
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[Pattern[str], float] = {
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
@@ -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
- result = run_subprocess(["pre-commit", "--version"], silent=True, raise_on_error=False)
124
- if not result:
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
- result = run_subprocess(["lefthook", "--version"], silent=True, raise_on_error=False)
174
- if not result:
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
 
@@ -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.ai", "zai-org/GLM-4.6-FP8"),
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(" ", "-").replace("syntheticnew", "synthetic")
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 is_zai:
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"
@@ -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
- model = config["model"]
61
- if model is None:
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
- temperature = config["temperature"]
70
- max_output_tokens = config["max_output_tokens"]
71
- max_retries = config["max_retries"]
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
- model_id = model or config["model"]
172
- processed_diff = preprocess_diff(diff, token_limit=Utility.DEFAULT_DIFF_TOKEN_LIMIT, model=model_id)
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
- warning_limit = config.get("warning_limit_tokens", EnvDefaults.WARNING_LIMIT_TOKENS)
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 "
@@ -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:
@@ -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:
@@ -36,7 +36,7 @@ def setup_logging(
36
36
  level=log_level,
37
37
  format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
38
38
  datefmt="%Y-%m-%d %H:%M:%S",
39
- **kwargs,
39
+ **kwargs, # type: ignore[arg-type]
40
40
  )
41
41
 
42
42
  if suppress_noisy:
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes