gac 3.10.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.

Files changed (67) hide show
  1. gac/__init__.py +15 -0
  2. gac/__version__.py +3 -0
  3. gac/ai.py +109 -0
  4. gac/ai_utils.py +246 -0
  5. gac/auth_cli.py +214 -0
  6. gac/cli.py +218 -0
  7. gac/commit_executor.py +62 -0
  8. gac/config.py +125 -0
  9. gac/config_cli.py +95 -0
  10. gac/constants.py +328 -0
  11. gac/diff_cli.py +159 -0
  12. gac/errors.py +231 -0
  13. gac/git.py +372 -0
  14. gac/git_state_validator.py +184 -0
  15. gac/grouped_commit_workflow.py +423 -0
  16. gac/init_cli.py +70 -0
  17. gac/interactive_mode.py +182 -0
  18. gac/language_cli.py +377 -0
  19. gac/main.py +476 -0
  20. gac/model_cli.py +430 -0
  21. gac/oauth/__init__.py +27 -0
  22. gac/oauth/claude_code.py +464 -0
  23. gac/oauth/qwen_oauth.py +327 -0
  24. gac/oauth/token_store.py +81 -0
  25. gac/preprocess.py +511 -0
  26. gac/prompt.py +878 -0
  27. gac/prompt_builder.py +88 -0
  28. gac/providers/README.md +437 -0
  29. gac/providers/__init__.py +80 -0
  30. gac/providers/anthropic.py +17 -0
  31. gac/providers/azure_openai.py +57 -0
  32. gac/providers/base.py +329 -0
  33. gac/providers/cerebras.py +15 -0
  34. gac/providers/chutes.py +25 -0
  35. gac/providers/claude_code.py +79 -0
  36. gac/providers/custom_anthropic.py +103 -0
  37. gac/providers/custom_openai.py +44 -0
  38. gac/providers/deepseek.py +15 -0
  39. gac/providers/error_handler.py +139 -0
  40. gac/providers/fireworks.py +15 -0
  41. gac/providers/gemini.py +90 -0
  42. gac/providers/groq.py +15 -0
  43. gac/providers/kimi_coding.py +27 -0
  44. gac/providers/lmstudio.py +80 -0
  45. gac/providers/minimax.py +15 -0
  46. gac/providers/mistral.py +15 -0
  47. gac/providers/moonshot.py +15 -0
  48. gac/providers/ollama.py +73 -0
  49. gac/providers/openai.py +32 -0
  50. gac/providers/openrouter.py +21 -0
  51. gac/providers/protocol.py +71 -0
  52. gac/providers/qwen.py +64 -0
  53. gac/providers/registry.py +58 -0
  54. gac/providers/replicate.py +156 -0
  55. gac/providers/streamlake.py +31 -0
  56. gac/providers/synthetic.py +40 -0
  57. gac/providers/together.py +15 -0
  58. gac/providers/zai.py +31 -0
  59. gac/py.typed +0 -0
  60. gac/security.py +293 -0
  61. gac/utils.py +401 -0
  62. gac/workflow_utils.py +217 -0
  63. gac-3.10.3.dist-info/METADATA +283 -0
  64. gac-3.10.3.dist-info/RECORD +67 -0
  65. gac-3.10.3.dist-info/WHEEL +4 -0
  66. gac-3.10.3.dist-info/entry_points.txt +2 -0
  67. gac-3.10.3.dist-info/licenses/LICENSE +16 -0
gac/constants.py ADDED
@@ -0,0 +1,328 @@
1
+ """Constants for the Git Auto Commit (gac) project."""
2
+
3
+ import os
4
+ from enum import Enum
5
+
6
+
7
+ class FileStatus(Enum):
8
+ """File status for Git operations."""
9
+
10
+ MODIFIED = "M"
11
+ ADDED = "A"
12
+ DELETED = "D"
13
+ RENAMED = "R"
14
+ COPIED = "C"
15
+ UNTRACKED = "?"
16
+
17
+
18
+ class EnvDefaults:
19
+ """Default values for environment variables."""
20
+
21
+ MAX_RETRIES: int = 3
22
+ TEMPERATURE: float = 1
23
+ MAX_OUTPUT_TOKENS: int = 4096 # includes reasoning tokens
24
+ WARNING_LIMIT_TOKENS: int = 32768
25
+ ALWAYS_INCLUDE_SCOPE: bool = False
26
+ SKIP_SECRET_SCAN: bool = False
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
37
+
38
+
39
+ class Logging:
40
+ """Logging configuration constants."""
41
+
42
+ DEFAULT_LEVEL: str = "WARNING"
43
+ LEVELS: list[str] = ["DEBUG", "INFO", "WARNING", "ERROR"]
44
+
45
+
46
+ class Utility:
47
+ """General utility constants."""
48
+
49
+ DEFAULT_ENCODING: str = "cl100k_base" # llm encoding
50
+ DEFAULT_DIFF_TOKEN_LIMIT: int = 15000 # Maximum tokens for diff processing
51
+ MAX_WORKERS: int = os.cpu_count() or 4 # Maximum number of parallel workers
52
+ MAX_DISPLAYED_SECRET_LENGTH: int = 50 # Maximum length for displaying secrets
53
+
54
+
55
+ class FilePatterns:
56
+ """Patterns for identifying special file types."""
57
+
58
+ # Regex patterns to detect binary file changes in git diffs (e.g., images or other non-text files)
59
+ BINARY: list[str] = [
60
+ r"Binary files .* differ",
61
+ r"GIT binary patch",
62
+ ]
63
+
64
+ # Regex patterns to detect minified files in git diffs (e.g., JavaScript or CSS files)
65
+ MINIFIED_EXTENSIONS: list[str] = [
66
+ ".min.js",
67
+ ".min.css",
68
+ ".bundle.js",
69
+ ".bundle.css",
70
+ ".compressed.js",
71
+ ".compressed.css",
72
+ ".opt.js",
73
+ ".opt.css",
74
+ ]
75
+
76
+ # Regex patterns to detect build directories in git diffs (e.g., dist, build, vendor, etc.)
77
+ BUILD_DIRECTORIES: list[str] = [
78
+ "/dist/",
79
+ "/build/",
80
+ "/vendor/",
81
+ "/node_modules/",
82
+ "/assets/vendor/",
83
+ "/public/build/",
84
+ "/static/dist/",
85
+ ]
86
+
87
+
88
+ class FileTypeImportance:
89
+ """Importance multipliers for different file types."""
90
+
91
+ EXTENSIONS: dict[str, float] = {
92
+ # Programming languages
93
+ ".py": 5.0, # Python
94
+ ".js": 4.5, # JavaScript
95
+ ".ts": 4.5, # TypeScript
96
+ ".jsx": 4.8, # React JS
97
+ ".tsx": 4.8, # React TS
98
+ ".go": 4.5, # Go
99
+ ".rs": 4.5, # Rust
100
+ ".java": 4.2, # Java
101
+ ".c": 4.2, # C
102
+ ".h": 4.2, # C/C++ header
103
+ ".cpp": 4.2, # C++
104
+ ".rb": 4.2, # Ruby
105
+ ".php": 4.0, # PHP
106
+ ".scala": 4.0, # Scala
107
+ ".swift": 4.0, # Swift
108
+ ".kt": 4.0, # Kotlin
109
+ # Configuration
110
+ ".json": 3.5, # JSON config
111
+ ".yaml": 3.8, # YAML config
112
+ ".yml": 3.8, # YAML config
113
+ ".toml": 3.8, # TOML config
114
+ ".ini": 3.5, # INI config
115
+ ".env": 3.5, # Environment variables
116
+ # Documentation
117
+ ".md": 2.5, # Markdown (reduced to prioritize code changes)
118
+ ".rst": 2.5, # reStructuredText (reduced to prioritize code changes)
119
+ # Web
120
+ ".html": 3.5, # HTML
121
+ ".css": 3.5, # CSS
122
+ ".scss": 3.5, # SCSS
123
+ ".svg": 2.5, # SVG graphics
124
+ # Build & CI
125
+ "Dockerfile": 4.0, # Docker
126
+ ".github/workflows": 4.0, # GitHub Actions
127
+ "CMakeLists.txt": 3.8, # CMake
128
+ "Makefile": 3.8, # Make
129
+ "package.json": 4.2, # NPM package
130
+ "pyproject.toml": 4.2, # Python project
131
+ "requirements.txt": 4.0, # Python requirements
132
+ }
133
+
134
+
135
+ class CodePatternImportance:
136
+ """Importance multipliers for different code patterns."""
137
+
138
+ # Regex patterns to detect code structure changes in git diffs (e.g., class, function, import)
139
+ # Note: The patterns are prefixed with "+" to match only added and modified lines
140
+ PATTERNS: dict[str, float] = {
141
+ # Structure changes
142
+ r"\+\s*(class|interface|enum)\s+\w+": 1.8, # Class/interface/enum definitions
143
+ r"\+\s*(def|function|func)\s+\w+\s*\(": 1.5, # Function definitions
144
+ r"\+\s*(import|from .* import)": 1.3, # Imports
145
+ r"\+\s*(public|private|protected)\s+\w+": 1.2, # Access modifiers
146
+ # Configuration changes
147
+ r"\+\s*\"(dependencies|devDependencies)\"": 1.4, # Package dependencies
148
+ r"\+\s*version[\"\s:=]+[0-9.]+": 1.3, # Version changes
149
+ # Logic changes
150
+ r"\+\s*(if|else|elif|switch|case|for|while)[\s(]": 1.2, # Control structures
151
+ r"\+\s*(try|catch|except|finally)[\s:]": 1.2, # Exception handling
152
+ r"\+\s*return\s+": 1.1, # Return statements
153
+ r"\+\s*await\s+": 1.1, # Async/await
154
+ # Comments & docs
155
+ r"\+\s*(//|#|/\*|\*\*)\s*TODO": 1.2, # TODOs
156
+ r"\+\s*(//|#|/\*|\*\*)\s*FIX": 1.3, # FIXes
157
+ r"\+\s*(\"\"\"|\'\'\')": 1.1, # Docstrings
158
+ # Test code
159
+ r"\+\s*(test|describe|it|should)\s*\(": 1.1, # Test definitions
160
+ r"\+\s*(assert|expect)": 1.0, # Assertions
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
+ ]
gac/diff_cli.py ADDED
@@ -0,0 +1,159 @@
1
+ # flake8: noqa: E304
2
+
3
+ """Git diff display command for gac.
4
+
5
+ This module implements the 'gac diff' subcommand which displays git diffs with various
6
+ filtering and formatting options. It provides a convenient way to view staged or unstaged
7
+ changes, compare commits, and apply smart filtering to focus on meaningful code changes.
8
+
9
+ Key features:
10
+ - Display staged or unstaged changes
11
+ - Compare specific commits or branches
12
+ - Filter out binary files, minified files, and lockfiles
13
+ - Smart truncation of large diffs based on token limits
14
+ - Colored output support for better readability
15
+ - Integration with gac's preprocessing logic for cleaner diffs
16
+
17
+ The diff command is particularly useful for:
18
+ - Previewing what changes will be included in the commit message
19
+ - Reviewing filtered diffs before committing
20
+ - Comparing code changes between branches or commits
21
+ - Understanding what files have been modified in the staging area
22
+ """
23
+
24
+ import logging
25
+ import sys
26
+
27
+ import click
28
+
29
+ from gac.errors import GitError, with_error_handling
30
+ from gac.git import get_diff, get_staged_files
31
+ from gac.preprocess import (
32
+ filter_binary_and_minified,
33
+ smart_truncate_diff,
34
+ split_diff_into_sections,
35
+ )
36
+ from gac.utils import print_message, setup_logging
37
+
38
+
39
+ def _diff_implementation(
40
+ filter: bool,
41
+ truncate: bool,
42
+ max_tokens: int | None,
43
+ staged: bool,
44
+ color: bool,
45
+ commit1: str | None = None,
46
+ commit2: str | None = None,
47
+ ) -> None:
48
+ """Implementation of the diff command logic for easier testing."""
49
+ setup_logging()
50
+ # Get a logger for this module instead of using the return value of setup_logging
51
+ logger = logging.getLogger(__name__)
52
+ logger.debug("Running diff command")
53
+
54
+ # If we're comparing specific commits, don't need to check for staged changes
55
+ if not (commit1 or commit2):
56
+ # Check if there are staged changes
57
+ staged_files = get_staged_files()
58
+ if not staged_files and staged:
59
+ print_message("No staged changes found. Use 'git add' to stage changes.", level="error")
60
+ sys.exit(1)
61
+
62
+ try:
63
+ diff_text = get_diff(staged=staged, color=color, commit1=commit1, commit2=commit2)
64
+ if not diff_text:
65
+ print_message("No changes to display.", level="error")
66
+ sys.exit(1)
67
+ except GitError as e:
68
+ print_message(f"Error getting diff: {str(e)}", level="error")
69
+ sys.exit(1)
70
+
71
+ if filter:
72
+ diff_text = filter_binary_and_minified(diff_text)
73
+ if not diff_text:
74
+ print_message("No changes to display after filtering.", level="error")
75
+ sys.exit(1)
76
+
77
+ if truncate:
78
+ # Convert the diff text to the format expected by smart_truncate_diff
79
+ # (list of tuples with (section, score))
80
+ if isinstance(diff_text, str):
81
+ sections = split_diff_into_sections(diff_text)
82
+ scored_sections = [(section, 1.0) for section in sections]
83
+ diff_text = smart_truncate_diff(scored_sections, max_tokens or 1000, "anthropic:claude-3-haiku-latest")
84
+
85
+ if color:
86
+ # Use git's colored diff output
87
+ print(diff_text)
88
+ else:
89
+ # Strip ANSI color codes if color is disabled
90
+ # This is a simple approach - a more robust solution would use a library like 'strip-ansi'
91
+ import re
92
+
93
+ ansi_escape = re.compile(r"\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])")
94
+ print(ansi_escape.sub("", diff_text))
95
+
96
+
97
+ @click.command(name="diff")
98
+ # Content filtering options
99
+ @click.option(
100
+ "--filter/--no-filter",
101
+ default=True,
102
+ help="Filter out binary files, minified files, and lockfiles",
103
+ )
104
+ # Display options
105
+ @click.option(
106
+ "--color/--no-color",
107
+ default=True,
108
+ help="Show colored diff output",
109
+ )
110
+ # Diff source options
111
+ @click.option(
112
+ "--staged/--unstaged",
113
+ default=True,
114
+ help="Show staged changes (default) or unstaged changes",
115
+ )
116
+ # Size control options
117
+ @click.option(
118
+ "--truncate/--no-truncate",
119
+ default=True,
120
+ help="Truncate large diffs to a reasonable size",
121
+ )
122
+ @click.option(
123
+ "--max-tokens",
124
+ default=None,
125
+ type=int,
126
+ help="Maximum number of tokens to include in the diff",
127
+ )
128
+ @click.argument("commit1", required=False)
129
+ @click.argument("commit2", required=False)
130
+ @with_error_handling(GitError, "Failed to display diff")
131
+ def diff(
132
+ filter: bool,
133
+ truncate: bool,
134
+ max_tokens: int | None,
135
+ staged: bool,
136
+ color: bool,
137
+ commit1: str | None = None,
138
+ commit2: str | None = None,
139
+ ) -> None:
140
+ """
141
+ Display the diff of staged or unstaged changes.
142
+
143
+ This command shows the raw diff without generating a commit message.
144
+
145
+ You can also compare specific commits or branches by providing one or two arguments:
146
+ gac diff <commit1> - Shows diff between working tree and <commit1>
147
+ gac diff <commit1> <commit2> - Shows diff between <commit1> and <commit2>
148
+
149
+ Commit references can be commit hashes, branch names, or other Git references.
150
+ """
151
+ _diff_implementation(
152
+ filter=filter,
153
+ truncate=truncate,
154
+ max_tokens=max_tokens,
155
+ staged=staged,
156
+ color=color,
157
+ commit1=commit1,
158
+ commit2=commit2,
159
+ )