gac 2.2.0__tar.gz → 2.4.0__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.
- {gac-2.2.0 → gac-2.4.0}/PKG-INFO +36 -9
- {gac-2.2.0 → gac-2.4.0}/README.md +35 -8
- {gac-2.2.0 → gac-2.4.0}/src/gac/__version__.py +1 -1
- {gac-2.2.0 → gac-2.4.0}/src/gac/ai.py +26 -0
- {gac-2.2.0 → gac-2.4.0}/src/gac/ai_utils.py +28 -13
- {gac-2.2.0 → gac-2.4.0}/src/gac/cli.py +7 -1
- {gac-2.2.0 → gac-2.4.0}/src/gac/config.py +1 -0
- {gac-2.2.0 → gac-2.4.0}/src/gac/constants.py +1 -1
- {gac-2.2.0 → gac-2.4.0}/src/gac/git.py +107 -8
- gac-2.4.0/src/gac/init_cli.py +435 -0
- gac-2.4.0/src/gac/language_cli.py +250 -0
- gac-2.4.0/src/gac/main.py +718 -0
- {gac-2.2.0 → gac-2.4.0}/src/gac/prompt.py +101 -15
- {gac-2.2.0 → gac-2.4.0}/src/gac/security.py +1 -1
- {gac-2.2.0 → gac-2.4.0}/src/gac/utils.py +104 -3
- gac-2.4.0/src/gac/workflow_utils.py +131 -0
- gac-2.2.0/src/gac/init_cli.py +0 -203
- gac-2.2.0/src/gac/language_cli.py +0 -82
- gac-2.2.0/src/gac/main.py +0 -382
- {gac-2.2.0 → gac-2.4.0}/.gitignore +0 -0
- {gac-2.2.0 → gac-2.4.0}/LICENSE +0 -0
- {gac-2.2.0 → gac-2.4.0}/pyproject.toml +0 -0
- {gac-2.2.0 → gac-2.4.0}/src/gac/__init__.py +0 -0
- {gac-2.2.0 → gac-2.4.0}/src/gac/config_cli.py +0 -0
- {gac-2.2.0 → gac-2.4.0}/src/gac/diff_cli.py +0 -0
- {gac-2.2.0 → gac-2.4.0}/src/gac/errors.py +0 -0
- {gac-2.2.0 → gac-2.4.0}/src/gac/preprocess.py +0 -0
- {gac-2.2.0 → gac-2.4.0}/src/gac/providers/__init__.py +0 -0
- {gac-2.2.0 → gac-2.4.0}/src/gac/providers/anthropic.py +0 -0
- {gac-2.2.0 → gac-2.4.0}/src/gac/providers/cerebras.py +0 -0
- {gac-2.2.0 → gac-2.4.0}/src/gac/providers/chutes.py +0 -0
- {gac-2.2.0 → gac-2.4.0}/src/gac/providers/custom_anthropic.py +0 -0
- {gac-2.2.0 → gac-2.4.0}/src/gac/providers/custom_openai.py +0 -0
- {gac-2.2.0 → gac-2.4.0}/src/gac/providers/deepseek.py +0 -0
- {gac-2.2.0 → gac-2.4.0}/src/gac/providers/fireworks.py +0 -0
- {gac-2.2.0 → gac-2.4.0}/src/gac/providers/gemini.py +0 -0
- {gac-2.2.0 → gac-2.4.0}/src/gac/providers/groq.py +0 -0
- {gac-2.2.0 → gac-2.4.0}/src/gac/providers/lmstudio.py +0 -0
- {gac-2.2.0 → gac-2.4.0}/src/gac/providers/minimax.py +0 -0
- {gac-2.2.0 → gac-2.4.0}/src/gac/providers/mistral.py +0 -0
- {gac-2.2.0 → gac-2.4.0}/src/gac/providers/ollama.py +0 -0
- {gac-2.2.0 → gac-2.4.0}/src/gac/providers/openai.py +0 -0
- {gac-2.2.0 → gac-2.4.0}/src/gac/providers/openrouter.py +0 -0
- {gac-2.2.0 → gac-2.4.0}/src/gac/providers/streamlake.py +0 -0
- {gac-2.2.0 → gac-2.4.0}/src/gac/providers/synthetic.py +0 -0
- {gac-2.2.0 → gac-2.4.0}/src/gac/providers/together.py +0 -0
- {gac-2.2.0 → gac-2.4.0}/src/gac/providers/zai.py +0 -0
{gac-2.2.0 → gac-2.4.0}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: gac
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.4.0
|
|
4
4
|
Summary: LLM-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
|
|
@@ -41,6 +41,9 @@ Requires-Dist: twine; extra == 'dev'
|
|
|
41
41
|
Description-Content-Type: text/markdown
|
|
42
42
|
|
|
43
43
|
<!-- markdownlint-disable MD013 -->
|
|
44
|
+
<!-- markdownlint-disable MD033 MD036 -->
|
|
45
|
+
|
|
46
|
+
<div align="center">
|
|
44
47
|
|
|
45
48
|
# 🚀 Git Auto Commit (gac)
|
|
46
49
|
|
|
@@ -50,9 +53,11 @@ Description-Content-Type: text/markdown
|
|
|
50
53
|
[](https://app.codecov.io/gh/cellwebb/gac)
|
|
51
54
|
[](https://github.com/astral-sh/ruff)
|
|
52
55
|
[](https://mypy-lang.org/)
|
|
53
|
-
[](docs/CONTRIBUTING.md)
|
|
56
|
+
[](docs/en/CONTRIBUTING.md)
|
|
54
57
|
[](LICENSE)
|
|
55
58
|
|
|
59
|
+
**English** | [简体中文](README.zh-CN.md) | [繁體中文](README.zh-TW.md) | [日本語](README.ja.md) | [한국어](README.ko.md) | [हिन्दी](README.hi.md) | [Français](README.fr.md) | [Русский](README.ru.md) | [Español](README.es.md) | [Português](README.pt.md) | [Deutsch](README.de.md) | [Nederlands](README.nl.md)
|
|
60
|
+
|
|
56
61
|
**LLM-powered commit messages that understand your code!**
|
|
57
62
|
|
|
58
63
|
**Automate your commits!** Replace `git commit -m "..."` with `gac` for contextual, well-formatted commit messages generated by large language models!
|
|
@@ -67,12 +72,19 @@ Intelligent, contextual messages that explain the **why** behind your changes:
|
|
|
67
72
|
|
|
68
73
|
---
|
|
69
74
|
|
|
75
|
+
</div>
|
|
76
|
+
|
|
77
|
+
<!-- markdownlint-enable MD033 MD036 -->
|
|
78
|
+
|
|
79
|
+
<!-- markdownlint-enable MD033 MD036 -->
|
|
80
|
+
|
|
70
81
|
## Quick Start
|
|
71
82
|
|
|
72
83
|
### Use gac without installing
|
|
73
84
|
|
|
74
85
|
```bash
|
|
75
|
-
uvx gac init
|
|
86
|
+
uvx gac init # Configure your provider, model, and language
|
|
87
|
+
uvx gac model # Re-run provider/model setup without language prompts
|
|
76
88
|
uvx gac # Generate and commit with LLM
|
|
77
89
|
```
|
|
78
90
|
|
|
@@ -83,6 +95,7 @@ That's it! Review the generated message and confirm with `y`.
|
|
|
83
95
|
```bash
|
|
84
96
|
uv tool install gac
|
|
85
97
|
gac init
|
|
98
|
+
gac model
|
|
86
99
|
gac
|
|
87
100
|
```
|
|
88
101
|
|
|
@@ -108,6 +121,7 @@ uv tool upgrade gac
|
|
|
108
121
|
- **Understands intent**: Analyzes code structure, logic, and patterns to understand the "why" behind your changes, not just what changed
|
|
109
122
|
- **Semantic awareness**: Recognizes refactoring, bug fixes, features, and breaking changes to generate contextually appropriate messages
|
|
110
123
|
- **Intelligent filtering**: Prioritizes meaningful changes while ignoring generated files, dependencies, and artifacts
|
|
124
|
+
- **Intelligent commit grouping** - Automatically group related changes into multiple logical commits with `--group`
|
|
111
125
|
|
|
112
126
|
### 📝 **Multiple Message Formats**
|
|
113
127
|
|
|
@@ -175,6 +189,9 @@ gac -v -s
|
|
|
175
189
|
# Quick one-liner for small changes
|
|
176
190
|
gac -o
|
|
177
191
|
|
|
192
|
+
# Group changes into logically related commits
|
|
193
|
+
gac -ag
|
|
194
|
+
|
|
178
195
|
# Debug what the LLM sees
|
|
179
196
|
gac --show-prompt
|
|
180
197
|
|
|
@@ -216,6 +233,8 @@ The edit feature (`e`) provides rich in-place terminal editing, allowing you to:
|
|
|
216
233
|
|
|
217
234
|
Run `gac init` to configure your provider interactively, or set environment variables:
|
|
218
235
|
|
|
236
|
+
Need to change providers or models later without touching language settings? Use `gac model` for a streamlined flow that skips the language prompts.
|
|
237
|
+
|
|
219
238
|
```bash
|
|
220
239
|
# Example configuration
|
|
221
240
|
GAC_MODEL=anthropic:your-model-name
|
|
@@ -227,16 +246,24 @@ See `.gac.env.example` for all available options.
|
|
|
227
246
|
|
|
228
247
|
**Want commit messages in another language?** Run `gac language` to select from 25+ languages including Español, Français, 日本語, and more.
|
|
229
248
|
|
|
230
|
-
**Want to customize commit message style?** See [docs/CUSTOM_SYSTEM_PROMPTS.md](docs/CUSTOM_SYSTEM_PROMPTS.md) for guidance on writing custom system prompts.
|
|
249
|
+
**Want to customize commit message style?** See [docs/CUSTOM_SYSTEM_PROMPTS.md](docs/en/CUSTOM_SYSTEM_PROMPTS.md) for guidance on writing custom system prompts.
|
|
250
|
+
|
|
251
|
+
---
|
|
252
|
+
|
|
253
|
+
## Project Analytics
|
|
254
|
+
|
|
255
|
+
📊 **[View live usage analytics and statistics →](https://clickpy.clickhouse.com/dashboard/gac)**
|
|
256
|
+
|
|
257
|
+
Track real-time installation metrics and package download statistics.
|
|
231
258
|
|
|
232
259
|
---
|
|
233
260
|
|
|
234
261
|
## Getting Help
|
|
235
262
|
|
|
236
|
-
- **Full documentation**: [USAGE.md](USAGE.md) - Complete CLI reference
|
|
237
|
-
- **Custom prompts**: [CUSTOM_SYSTEM_PROMPTS.md](docs/CUSTOM_SYSTEM_PROMPTS.md) - Customize commit message style
|
|
238
|
-
- **Troubleshooting**: [TROUBLESHOOTING.md](docs/TROUBLESHOOTING.md) - Common issues and solutions
|
|
239
|
-
- **Contributing**: [CONTRIBUTING.md](docs/CONTRIBUTING.md) - Development setup and guidelines
|
|
263
|
+
- **Full documentation**: [docs/USAGE.md](docs/en/USAGE.md) - Complete CLI reference
|
|
264
|
+
- **Custom prompts**: [docs/CUSTOM_SYSTEM_PROMPTS.md](docs/en/CUSTOM_SYSTEM_PROMPTS.md) - Customize commit message style
|
|
265
|
+
- **Troubleshooting**: [docs/TROUBLESHOOTING.md](docs/en/TROUBLESHOOTING.md) - Common issues and solutions
|
|
266
|
+
- **Contributing**: [docs/CONTRIBUTING.md](docs/en/CONTRIBUTING.md) - Development setup and guidelines
|
|
240
267
|
|
|
241
268
|
---
|
|
242
269
|
|
|
@@ -246,7 +273,7 @@ See `.gac.env.example` for all available options.
|
|
|
246
273
|
|
|
247
274
|
Made with ❤️ for developers who want better commit messages
|
|
248
275
|
|
|
249
|
-
[⭐ Star us on GitHub](https://github.com/cellwebb/gac) • [🐛 Report issues](https://github.com/cellwebb/gac/issues) • [📖 Full docs](USAGE.md)
|
|
276
|
+
[⭐ Star us on GitHub](https://github.com/cellwebb/gac) • [🐛 Report issues](https://github.com/cellwebb/gac/issues) • [📖 Full docs](docs/en/USAGE.md)
|
|
250
277
|
|
|
251
278
|
</div>
|
|
252
279
|
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
<!-- markdownlint-disable MD013 -->
|
|
2
|
+
<!-- markdownlint-disable MD033 MD036 -->
|
|
3
|
+
|
|
4
|
+
<div align="center">
|
|
2
5
|
|
|
3
6
|
# 🚀 Git Auto Commit (gac)
|
|
4
7
|
|
|
@@ -8,9 +11,11 @@
|
|
|
8
11
|
[](https://app.codecov.io/gh/cellwebb/gac)
|
|
9
12
|
[](https://github.com/astral-sh/ruff)
|
|
10
13
|
[](https://mypy-lang.org/)
|
|
11
|
-
[](docs/CONTRIBUTING.md)
|
|
14
|
+
[](docs/en/CONTRIBUTING.md)
|
|
12
15
|
[](LICENSE)
|
|
13
16
|
|
|
17
|
+
**English** | [简体中文](README.zh-CN.md) | [繁體中文](README.zh-TW.md) | [日本語](README.ja.md) | [한국어](README.ko.md) | [हिन्दी](README.hi.md) | [Français](README.fr.md) | [Русский](README.ru.md) | [Español](README.es.md) | [Português](README.pt.md) | [Deutsch](README.de.md) | [Nederlands](README.nl.md)
|
|
18
|
+
|
|
14
19
|
**LLM-powered commit messages that understand your code!**
|
|
15
20
|
|
|
16
21
|
**Automate your commits!** Replace `git commit -m "..."` with `gac` for contextual, well-formatted commit messages generated by large language models!
|
|
@@ -25,12 +30,19 @@ Intelligent, contextual messages that explain the **why** behind your changes:
|
|
|
25
30
|
|
|
26
31
|
---
|
|
27
32
|
|
|
33
|
+
</div>
|
|
34
|
+
|
|
35
|
+
<!-- markdownlint-enable MD033 MD036 -->
|
|
36
|
+
|
|
37
|
+
<!-- markdownlint-enable MD033 MD036 -->
|
|
38
|
+
|
|
28
39
|
## Quick Start
|
|
29
40
|
|
|
30
41
|
### Use gac without installing
|
|
31
42
|
|
|
32
43
|
```bash
|
|
33
|
-
uvx gac init
|
|
44
|
+
uvx gac init # Configure your provider, model, and language
|
|
45
|
+
uvx gac model # Re-run provider/model setup without language prompts
|
|
34
46
|
uvx gac # Generate and commit with LLM
|
|
35
47
|
```
|
|
36
48
|
|
|
@@ -41,6 +53,7 @@ That's it! Review the generated message and confirm with `y`.
|
|
|
41
53
|
```bash
|
|
42
54
|
uv tool install gac
|
|
43
55
|
gac init
|
|
56
|
+
gac model
|
|
44
57
|
gac
|
|
45
58
|
```
|
|
46
59
|
|
|
@@ -66,6 +79,7 @@ uv tool upgrade gac
|
|
|
66
79
|
- **Understands intent**: Analyzes code structure, logic, and patterns to understand the "why" behind your changes, not just what changed
|
|
67
80
|
- **Semantic awareness**: Recognizes refactoring, bug fixes, features, and breaking changes to generate contextually appropriate messages
|
|
68
81
|
- **Intelligent filtering**: Prioritizes meaningful changes while ignoring generated files, dependencies, and artifacts
|
|
82
|
+
- **Intelligent commit grouping** - Automatically group related changes into multiple logical commits with `--group`
|
|
69
83
|
|
|
70
84
|
### 📝 **Multiple Message Formats**
|
|
71
85
|
|
|
@@ -133,6 +147,9 @@ gac -v -s
|
|
|
133
147
|
# Quick one-liner for small changes
|
|
134
148
|
gac -o
|
|
135
149
|
|
|
150
|
+
# Group changes into logically related commits
|
|
151
|
+
gac -ag
|
|
152
|
+
|
|
136
153
|
# Debug what the LLM sees
|
|
137
154
|
gac --show-prompt
|
|
138
155
|
|
|
@@ -174,6 +191,8 @@ The edit feature (`e`) provides rich in-place terminal editing, allowing you to:
|
|
|
174
191
|
|
|
175
192
|
Run `gac init` to configure your provider interactively, or set environment variables:
|
|
176
193
|
|
|
194
|
+
Need to change providers or models later without touching language settings? Use `gac model` for a streamlined flow that skips the language prompts.
|
|
195
|
+
|
|
177
196
|
```bash
|
|
178
197
|
# Example configuration
|
|
179
198
|
GAC_MODEL=anthropic:your-model-name
|
|
@@ -185,16 +204,24 @@ See `.gac.env.example` for all available options.
|
|
|
185
204
|
|
|
186
205
|
**Want commit messages in another language?** Run `gac language` to select from 25+ languages including Español, Français, 日本語, and more.
|
|
187
206
|
|
|
188
|
-
**Want to customize commit message style?** See [docs/CUSTOM_SYSTEM_PROMPTS.md](docs/CUSTOM_SYSTEM_PROMPTS.md) for guidance on writing custom system prompts.
|
|
207
|
+
**Want to customize commit message style?** See [docs/CUSTOM_SYSTEM_PROMPTS.md](docs/en/CUSTOM_SYSTEM_PROMPTS.md) for guidance on writing custom system prompts.
|
|
208
|
+
|
|
209
|
+
---
|
|
210
|
+
|
|
211
|
+
## Project Analytics
|
|
212
|
+
|
|
213
|
+
📊 **[View live usage analytics and statistics →](https://clickpy.clickhouse.com/dashboard/gac)**
|
|
214
|
+
|
|
215
|
+
Track real-time installation metrics and package download statistics.
|
|
189
216
|
|
|
190
217
|
---
|
|
191
218
|
|
|
192
219
|
## Getting Help
|
|
193
220
|
|
|
194
|
-
- **Full documentation**: [USAGE.md](USAGE.md) - Complete CLI reference
|
|
195
|
-
- **Custom prompts**: [CUSTOM_SYSTEM_PROMPTS.md](docs/CUSTOM_SYSTEM_PROMPTS.md) - Customize commit message style
|
|
196
|
-
- **Troubleshooting**: [TROUBLESHOOTING.md](docs/TROUBLESHOOTING.md) - Common issues and solutions
|
|
197
|
-
- **Contributing**: [CONTRIBUTING.md](docs/CONTRIBUTING.md) - Development setup and guidelines
|
|
221
|
+
- **Full documentation**: [docs/USAGE.md](docs/en/USAGE.md) - Complete CLI reference
|
|
222
|
+
- **Custom prompts**: [docs/CUSTOM_SYSTEM_PROMPTS.md](docs/en/CUSTOM_SYSTEM_PROMPTS.md) - Customize commit message style
|
|
223
|
+
- **Troubleshooting**: [docs/TROUBLESHOOTING.md](docs/en/TROUBLESHOOTING.md) - Common issues and solutions
|
|
224
|
+
- **Contributing**: [docs/CONTRIBUTING.md](docs/en/CONTRIBUTING.md) - Development setup and guidelines
|
|
198
225
|
|
|
199
226
|
---
|
|
200
227
|
|
|
@@ -204,7 +231,7 @@ See `.gac.env.example` for all available options.
|
|
|
204
231
|
|
|
205
232
|
Made with ❤️ for developers who want better commit messages
|
|
206
233
|
|
|
207
|
-
[⭐ Star us on GitHub](https://github.com/cellwebb/gac) • [🐛 Report issues](https://github.com/cellwebb/gac/issues) • [📖 Full docs](USAGE.md)
|
|
234
|
+
[⭐ Star us on GitHub](https://github.com/cellwebb/gac) • [🐛 Report issues](https://github.com/cellwebb/gac/issues) • [📖 Full docs](docs/en/USAGE.md)
|
|
208
235
|
|
|
209
236
|
</div>
|
|
210
237
|
|
|
@@ -42,6 +42,8 @@ def generate_commit_message(
|
|
|
42
42
|
max_tokens: int = EnvDefaults.MAX_OUTPUT_TOKENS,
|
|
43
43
|
max_retries: int = EnvDefaults.MAX_RETRIES,
|
|
44
44
|
quiet: bool = False,
|
|
45
|
+
is_group: bool = False,
|
|
46
|
+
skip_success_message: bool = False,
|
|
45
47
|
) -> str:
|
|
46
48
|
"""Generate a commit message using direct API calls to AI providers.
|
|
47
49
|
|
|
@@ -116,6 +118,8 @@ def generate_commit_message(
|
|
|
116
118
|
max_tokens=max_tokens,
|
|
117
119
|
max_retries=max_retries,
|
|
118
120
|
quiet=quiet,
|
|
121
|
+
is_group=is_group,
|
|
122
|
+
skip_success_message=skip_success_message,
|
|
119
123
|
)
|
|
120
124
|
except AIError:
|
|
121
125
|
# Re-raise AIError exceptions as-is to preserve error classification
|
|
@@ -123,3 +127,25 @@ def generate_commit_message(
|
|
|
123
127
|
except Exception as e:
|
|
124
128
|
logger.error(f"Failed to generate commit message: {e}")
|
|
125
129
|
raise AIError.model_error(f"Failed to generate commit message: {e}") from e
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def generate_grouped_commits(
|
|
133
|
+
model: str,
|
|
134
|
+
prompt: list[dict[str, str]],
|
|
135
|
+
temperature: float,
|
|
136
|
+
max_tokens: int,
|
|
137
|
+
max_retries: int,
|
|
138
|
+
quiet: bool = False,
|
|
139
|
+
skip_success_message: bool = False,
|
|
140
|
+
) -> str:
|
|
141
|
+
"""Generate grouped commits JSON response."""
|
|
142
|
+
return generate_commit_message(
|
|
143
|
+
model=model,
|
|
144
|
+
prompt=prompt,
|
|
145
|
+
temperature=temperature,
|
|
146
|
+
max_tokens=max_tokens,
|
|
147
|
+
max_retries=max_retries,
|
|
148
|
+
quiet=quiet,
|
|
149
|
+
is_group=True,
|
|
150
|
+
skip_success_message=skip_success_message,
|
|
151
|
+
)
|
|
@@ -83,6 +83,8 @@ def generate_with_retries(
|
|
|
83
83
|
max_tokens: int,
|
|
84
84
|
max_retries: int,
|
|
85
85
|
quiet: bool = False,
|
|
86
|
+
is_group: bool = False,
|
|
87
|
+
skip_success_message: bool = False,
|
|
86
88
|
) -> str:
|
|
87
89
|
"""Generate content with retry logic using direct API calls."""
|
|
88
90
|
# Parse model string to determine provider and actual model
|
|
@@ -121,10 +123,11 @@ def generate_with_retries(
|
|
|
121
123
|
raise AIError.model_error("No messages provided for AI generation")
|
|
122
124
|
|
|
123
125
|
# Set up spinner
|
|
126
|
+
message_type = "commit messages" if is_group else "commit message"
|
|
124
127
|
if quiet:
|
|
125
128
|
spinner = None
|
|
126
129
|
else:
|
|
127
|
-
spinner = Halo(text=f"Generating
|
|
130
|
+
spinner = Halo(text=f"Generating {message_type} with {provider} {model_name}...", spinner="dots")
|
|
128
131
|
spinner.start()
|
|
129
132
|
|
|
130
133
|
last_exception = None
|
|
@@ -132,7 +135,7 @@ def generate_with_retries(
|
|
|
132
135
|
|
|
133
136
|
for attempt in range(max_retries):
|
|
134
137
|
try:
|
|
135
|
-
if not quiet and attempt > 0:
|
|
138
|
+
if not quiet and not skip_success_message and attempt > 0:
|
|
136
139
|
if spinner:
|
|
137
140
|
spinner.text = f"Retry {attempt + 1}/{max_retries} with {provider} {model_name}..."
|
|
138
141
|
logger.info(f"Retry attempt {attempt + 1}/{max_retries}")
|
|
@@ -145,7 +148,10 @@ def generate_with_retries(
|
|
|
145
148
|
content = provider_func(model=model_name, messages=messages, temperature=temperature, max_tokens=max_tokens)
|
|
146
149
|
|
|
147
150
|
if spinner:
|
|
148
|
-
|
|
151
|
+
if skip_success_message:
|
|
152
|
+
spinner.stop() # Stop spinner without showing success/failure
|
|
153
|
+
else:
|
|
154
|
+
spinner.succeed(f"Generated {message_type} with {provider} {model_name}")
|
|
149
155
|
|
|
150
156
|
if content is not None and content.strip():
|
|
151
157
|
return content.strip() # type: ignore[no-any-return]
|
|
@@ -160,8 +166,8 @@ def generate_with_retries(
|
|
|
160
166
|
|
|
161
167
|
# For authentication and model errors, don't retry
|
|
162
168
|
if error_type in ["authentication", "model"]:
|
|
163
|
-
if spinner:
|
|
164
|
-
spinner.fail(f"Failed to generate
|
|
169
|
+
if spinner and not skip_success_message:
|
|
170
|
+
spinner.fail(f"Failed to generate {message_type} with {provider} {model_name}")
|
|
165
171
|
|
|
166
172
|
# Create the appropriate error type based on classification
|
|
167
173
|
if error_type == "authentication":
|
|
@@ -172,23 +178,32 @@ def generate_with_retries(
|
|
|
172
178
|
if attempt < max_retries - 1:
|
|
173
179
|
# Exponential backoff
|
|
174
180
|
wait_time = 2**attempt
|
|
175
|
-
if not quiet:
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
181
|
+
if not quiet and not skip_success_message:
|
|
182
|
+
if attempt == 0:
|
|
183
|
+
logger.warning(f"AI generation failed, retrying in {wait_time}s: {str(e)}")
|
|
184
|
+
else:
|
|
185
|
+
logger.warning(
|
|
186
|
+
f"AI generation failed (attempt {attempt + 1}), retrying in {wait_time}s: {str(e)}"
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
if spinner and not skip_success_message:
|
|
179
190
|
for i in range(wait_time, 0, -1):
|
|
180
191
|
spinner.text = f"Retry {attempt + 1}/{max_retries} in {i}s..."
|
|
181
192
|
time.sleep(1)
|
|
182
193
|
else:
|
|
183
194
|
time.sleep(wait_time)
|
|
184
195
|
else:
|
|
185
|
-
|
|
196
|
+
num_retries = max_retries
|
|
197
|
+
retry_word = "retry" if num_retries == 1 else "retries"
|
|
198
|
+
logger.error(f"AI generation failed after {num_retries} {retry_word}: {str(e)}")
|
|
186
199
|
|
|
187
|
-
if spinner:
|
|
188
|
-
spinner.fail(f"Failed to generate
|
|
200
|
+
if spinner and not skip_success_message:
|
|
201
|
+
spinner.fail(f"Failed to generate {message_type} with {provider} {model_name}")
|
|
189
202
|
|
|
190
203
|
# If we get here, all retries failed - use the last classified error type
|
|
191
|
-
|
|
204
|
+
num_retries = max_retries
|
|
205
|
+
retry_word = "retry" if num_retries == 1 else "retries"
|
|
206
|
+
error_message = f"Failed to generate {message_type} after {num_retries} {retry_word}"
|
|
192
207
|
if last_error_type == "authentication":
|
|
193
208
|
raise AIError.authentication_error(error_message) from last_exception
|
|
194
209
|
elif last_error_type == "rate_limit":
|
|
@@ -17,6 +17,7 @@ from gac.constants import Languages, Logging
|
|
|
17
17
|
from gac.diff_cli import diff as diff_cli
|
|
18
18
|
from gac.errors import handle_error
|
|
19
19
|
from gac.init_cli import init as init_cli
|
|
20
|
+
from gac.init_cli import model as model_cli
|
|
20
21
|
from gac.language_cli import language as language_cli
|
|
21
22
|
from gac.main import main
|
|
22
23
|
from gac.utils import setup_logging
|
|
@@ -28,6 +29,7 @@ logger = logging.getLogger(__name__)
|
|
|
28
29
|
@click.group(invoke_without_command=True, context_settings={"ignore_unknown_options": True})
|
|
29
30
|
# Git workflow options
|
|
30
31
|
@click.option("--add-all", "-a", is_flag=True, help="Stage all changes before committing")
|
|
32
|
+
@click.option("--group", "-g", is_flag=True, help="Group changes into multiple logical commits")
|
|
31
33
|
@click.option("--push", "-p", is_flag=True, help="Push changes to remote after committing")
|
|
32
34
|
@click.option("--dry-run", is_flag=True, help="Dry run the commit workflow")
|
|
33
35
|
@click.option("--yes", "-y", is_flag=True, help="Skip confirmation prompt")
|
|
@@ -70,6 +72,7 @@ logger = logging.getLogger(__name__)
|
|
|
70
72
|
def cli(
|
|
71
73
|
ctx: click.Context,
|
|
72
74
|
add_all: bool = False,
|
|
75
|
+
group: bool = False,
|
|
73
76
|
log_level: str = str(config["log_level"]),
|
|
74
77
|
one_liner: bool = False,
|
|
75
78
|
push: bool = False,
|
|
@@ -109,6 +112,7 @@ def cli(
|
|
|
109
112
|
try:
|
|
110
113
|
main(
|
|
111
114
|
stage_all=add_all,
|
|
115
|
+
group=group,
|
|
112
116
|
model=model,
|
|
113
117
|
hint=hint,
|
|
114
118
|
one_liner=one_liner,
|
|
@@ -131,6 +135,7 @@ def cli(
|
|
|
131
135
|
|
|
132
136
|
ctx.obj = {
|
|
133
137
|
"add_all": add_all,
|
|
138
|
+
"group": group,
|
|
134
139
|
"log_level": log_level,
|
|
135
140
|
"one_liner": one_liner,
|
|
136
141
|
"push": push,
|
|
@@ -150,9 +155,10 @@ def cli(
|
|
|
150
155
|
|
|
151
156
|
|
|
152
157
|
cli.add_command(config_cli)
|
|
158
|
+
cli.add_command(diff_cli)
|
|
153
159
|
cli.add_command(init_cli)
|
|
154
160
|
cli.add_command(language_cli)
|
|
155
|
-
cli.add_command(
|
|
161
|
+
cli.add_command(model_cli)
|
|
156
162
|
|
|
157
163
|
|
|
158
164
|
@click.command(context_settings=language_cli.context_settings)
|
|
@@ -41,6 +41,7 @@ def load_config() -> dict[str, str | int | float | bool | None]:
|
|
|
41
41
|
"system_prompt_path": os.getenv("GAC_SYSTEM_PROMPT_PATH"),
|
|
42
42
|
"language": os.getenv("GAC_LANGUAGE"),
|
|
43
43
|
"translate_prefixes": os.getenv("GAC_TRANSLATE_PREFIXES", "false").lower() in ("true", "1", "yes", "on"),
|
|
44
|
+
"rtl_confirmed": os.getenv("GAC_RTL_CONFIRMED", "false").lower() in ("true", "1", "yes", "on"),
|
|
44
45
|
}
|
|
45
46
|
|
|
46
47
|
return config
|
|
@@ -20,7 +20,7 @@ class EnvDefaults:
|
|
|
20
20
|
|
|
21
21
|
MAX_RETRIES: int = 3
|
|
22
22
|
TEMPERATURE: float = 1
|
|
23
|
-
MAX_OUTPUT_TOKENS: int =
|
|
23
|
+
MAX_OUTPUT_TOKENS: int = 4096 # includes reasoning tokens
|
|
24
24
|
WARNING_LIMIT_TOKENS: int = 32768
|
|
25
25
|
ALWAYS_INCLUDE_SCOPE: bool = False
|
|
26
26
|
SKIP_SECRET_SCAN: bool = False
|
|
@@ -14,6 +14,63 @@ from gac.utils import run_subprocess
|
|
|
14
14
|
logger = logging.getLogger(__name__)
|
|
15
15
|
|
|
16
16
|
|
|
17
|
+
def run_subprocess_with_encoding_fallback(
|
|
18
|
+
command: list[str], silent: bool = False, timeout: int = 60
|
|
19
|
+
) -> subprocess.CompletedProcess:
|
|
20
|
+
"""Run subprocess with encoding fallback, returning full CompletedProcess object.
|
|
21
|
+
|
|
22
|
+
This is used for cases where we need both stdout and stderr separately,
|
|
23
|
+
like pre-commit and lefthook hook execution.
|
|
24
|
+
|
|
25
|
+
Args:
|
|
26
|
+
command: List of command arguments
|
|
27
|
+
silent: If True, suppress debug logging
|
|
28
|
+
timeout: Command timeout in seconds
|
|
29
|
+
|
|
30
|
+
Returns:
|
|
31
|
+
CompletedProcess object with stdout, stderr, and returncode
|
|
32
|
+
"""
|
|
33
|
+
from gac.utils import get_safe_encodings
|
|
34
|
+
|
|
35
|
+
encodings = get_safe_encodings()
|
|
36
|
+
last_exception = None
|
|
37
|
+
|
|
38
|
+
for encoding in encodings:
|
|
39
|
+
try:
|
|
40
|
+
if not silent:
|
|
41
|
+
logger.debug(f"Running command: {' '.join(command)} (encoding: {encoding})")
|
|
42
|
+
|
|
43
|
+
result = subprocess.run(
|
|
44
|
+
command,
|
|
45
|
+
capture_output=True,
|
|
46
|
+
text=True,
|
|
47
|
+
check=False,
|
|
48
|
+
timeout=timeout,
|
|
49
|
+
encoding=encoding,
|
|
50
|
+
errors="replace",
|
|
51
|
+
)
|
|
52
|
+
return result
|
|
53
|
+
except UnicodeError as e:
|
|
54
|
+
last_exception = e
|
|
55
|
+
if not silent:
|
|
56
|
+
logger.debug(f"Failed to decode with {encoding}: {e}")
|
|
57
|
+
continue
|
|
58
|
+
except subprocess.TimeoutExpired:
|
|
59
|
+
raise
|
|
60
|
+
except Exception as e:
|
|
61
|
+
if not silent:
|
|
62
|
+
logger.debug(f"Command error: {e}")
|
|
63
|
+
# Try next encoding for non-timeout errors
|
|
64
|
+
last_exception = e
|
|
65
|
+
continue
|
|
66
|
+
|
|
67
|
+
# If we get here, all encodings failed
|
|
68
|
+
if last_exception:
|
|
69
|
+
raise subprocess.CalledProcessError(1, command, "", f"Encoding error: {last_exception}") from last_exception
|
|
70
|
+
else:
|
|
71
|
+
raise subprocess.CalledProcessError(1, command, "", "All encoding attempts failed")
|
|
72
|
+
|
|
73
|
+
|
|
17
74
|
def run_git_command(args: list[str], silent: bool = False, timeout: int = 30) -> str:
|
|
18
75
|
"""Run a git command and return the output."""
|
|
19
76
|
command = ["git"] + args
|
|
@@ -50,6 +107,48 @@ def get_staged_files(file_type: str | None = None, existing_only: bool = False)
|
|
|
50
107
|
return []
|
|
51
108
|
|
|
52
109
|
|
|
110
|
+
def get_staged_status() -> str:
|
|
111
|
+
"""Get formatted status of staged files only, excluding unstaged/untracked files.
|
|
112
|
+
|
|
113
|
+
Returns:
|
|
114
|
+
Formatted status string with M/A/D/R indicators
|
|
115
|
+
"""
|
|
116
|
+
try:
|
|
117
|
+
output = run_git_command(["diff", "--name-status", "--staged"])
|
|
118
|
+
if not output:
|
|
119
|
+
return "No changes staged for commit."
|
|
120
|
+
|
|
121
|
+
status_map = {
|
|
122
|
+
"M": "modified",
|
|
123
|
+
"A": "new file",
|
|
124
|
+
"D": "deleted",
|
|
125
|
+
"R": "renamed",
|
|
126
|
+
"C": "copied",
|
|
127
|
+
"T": "typechange",
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
status_lines = ["Changes to be committed:"]
|
|
131
|
+
for line in output.splitlines():
|
|
132
|
+
line = line.strip()
|
|
133
|
+
if not line:
|
|
134
|
+
continue
|
|
135
|
+
|
|
136
|
+
# Parse status line (e.g., "M\tfile.py" or "R100\told.py\tnew.py")
|
|
137
|
+
parts = line.split("\t")
|
|
138
|
+
if len(parts) < 2:
|
|
139
|
+
continue
|
|
140
|
+
|
|
141
|
+
change_type = parts[0][0] # First char is the status (M, A, D, R, etc.)
|
|
142
|
+
file_path = parts[-1] # Last part is the new/current file path
|
|
143
|
+
|
|
144
|
+
status_label = status_map.get(change_type, "modified")
|
|
145
|
+
status_lines.append(f"\t{status_label}: {file_path}")
|
|
146
|
+
|
|
147
|
+
return "\n".join(status_lines)
|
|
148
|
+
except GitError:
|
|
149
|
+
return "No changes staged for commit."
|
|
150
|
+
|
|
151
|
+
|
|
53
152
|
def get_diff(staged: bool = True, color: bool = True, commit1: str | None = None, commit2: str | None = None) -> str:
|
|
54
153
|
"""Get the diff between commits or working tree.
|
|
55
154
|
|
|
@@ -90,20 +189,20 @@ def get_diff(staged: bool = True, color: bool = True, commit1: str | None = None
|
|
|
90
189
|
|
|
91
190
|
def get_repo_root() -> str:
|
|
92
191
|
"""Get absolute path of repository root."""
|
|
93
|
-
result =
|
|
94
|
-
return result
|
|
192
|
+
result = run_git_command(["rev-parse", "--show-toplevel"])
|
|
193
|
+
return result
|
|
95
194
|
|
|
96
195
|
|
|
97
196
|
def get_current_branch() -> str:
|
|
98
197
|
"""Get name of current git branch."""
|
|
99
|
-
result =
|
|
100
|
-
return result
|
|
198
|
+
result = run_git_command(["rev-parse", "--abbrev-ref", "HEAD"])
|
|
199
|
+
return result
|
|
101
200
|
|
|
102
201
|
|
|
103
202
|
def get_commit_hash() -> str:
|
|
104
203
|
"""Get SHA-1 hash of current commit."""
|
|
105
|
-
result =
|
|
106
|
-
return result
|
|
204
|
+
result = run_git_command(["rev-parse", "HEAD"])
|
|
205
|
+
return result
|
|
107
206
|
|
|
108
207
|
|
|
109
208
|
def run_pre_commit_hooks() -> bool:
|
|
@@ -128,7 +227,7 @@ def run_pre_commit_hooks() -> bool:
|
|
|
128
227
|
# Run pre-commit hooks on staged files
|
|
129
228
|
logger.info("Running pre-commit hooks...")
|
|
130
229
|
# Run pre-commit and capture both stdout and stderr
|
|
131
|
-
result =
|
|
230
|
+
result = run_subprocess_with_encoding_fallback(["pre-commit", "run"])
|
|
132
231
|
|
|
133
232
|
if result.returncode == 0:
|
|
134
233
|
# All hooks passed
|
|
@@ -178,7 +277,7 @@ def run_lefthook_hooks() -> bool:
|
|
|
178
277
|
# Run lefthook hooks on staged files
|
|
179
278
|
logger.info("Running Lefthook hooks...")
|
|
180
279
|
# Run lefthook and capture both stdout and stderr
|
|
181
|
-
result =
|
|
280
|
+
result = run_subprocess_with_encoding_fallback(["lefthook", "run", "pre-commit"])
|
|
182
281
|
|
|
183
282
|
if result.returncode == 0:
|
|
184
283
|
# All hooks passed
|