gac 2.2.0__py3-none-any.whl → 2.4.0__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/prompt.py CHANGED
@@ -246,30 +246,30 @@ DEFAULT_USER_TEMPLATE = """<hint>
246
246
  Additional context provided by the user: <hint_text></hint_text>
247
247
  </hint>
248
248
 
249
- <git_status>
250
- <status></status>
251
- </git_status>
249
+ <git_diff>
250
+ <diff></diff>
251
+ </git_diff>
252
252
 
253
253
  <git_diff_stat>
254
254
  <diff_stat></diff_stat>
255
255
  </git_diff_stat>
256
256
 
257
- <git_diff>
258
- <diff></diff>
259
- </git_diff>
257
+ <git_status>
258
+ <status></status>
259
+ </git_status>
260
+
261
+ <language_instructions>
262
+ IMPORTANT: You MUST write the entire commit message in <language_name></language_name>.
263
+ All text in the commit message, including the summary line and body, must be in <language_name></language_name>.
264
+ <prefix_instruction></prefix_instruction>
265
+ </language_instructions>
260
266
 
261
- <instructions>
267
+ <format_instructions>
262
268
  IMMEDIATELY AFTER ANALYZING THE CHANGES, RESPOND WITH ONLY THE COMMIT MESSAGE.
263
269
  DO NOT include any preamble, reasoning, explanations or anything other than the commit message itself.
264
270
  DO NOT use markdown formatting, headers, or code blocks.
265
271
  The entire response will be passed directly to 'git commit -m'.
266
-
267
- <language>
268
- IMPORTANT: You MUST write the entire commit message in <language_name></language_name>.
269
- All text in the commit message, including the summary line and body, must be in <language_name></language_name>.
270
- <prefix_instruction></prefix_instruction>
271
- </language>
272
- </instructions>"""
272
+ </format_instructions>"""
273
273
 
274
274
 
275
275
  # ============================================================================
@@ -518,7 +518,7 @@ The ENTIRE commit message, including the prefix, must be in {language}."""
518
518
 
519
519
  user_template = user_template.replace("<prefix_instruction></prefix_instruction>", prefix_instruction)
520
520
  else:
521
- user_template = _remove_template_section(user_template, "language")
521
+ user_template = _remove_template_section(user_template, "language_instructions")
522
522
  logger.debug("Using default language (English)")
523
523
 
524
524
  user_template = re.sub(r"\n(?:[ \t]*\n){2,}", "\n\n", user_template)
@@ -526,6 +526,92 @@ The ENTIRE commit message, including the prefix, must be in {language}."""
526
526
  return system_template.strip(), user_template.strip()
527
527
 
528
528
 
529
+ def build_group_prompt(
530
+ status: str,
531
+ processed_diff: str,
532
+ diff_stat: str,
533
+ one_liner: bool,
534
+ hint: str,
535
+ infer_scope: bool,
536
+ verbose: bool,
537
+ system_template_path: str | None,
538
+ language: str | None,
539
+ translate_prefixes: bool,
540
+ ) -> tuple[str, str]:
541
+ """Build prompt for grouped commit generation (JSON output with multiple commits)."""
542
+ system_prompt, user_prompt = build_prompt(
543
+ status=status,
544
+ processed_diff=processed_diff,
545
+ diff_stat=diff_stat,
546
+ one_liner=one_liner,
547
+ hint=hint,
548
+ infer_scope=infer_scope,
549
+ verbose=verbose,
550
+ system_template_path=system_template_path,
551
+ language=language,
552
+ translate_prefixes=translate_prefixes,
553
+ )
554
+
555
+ user_prompt = _remove_template_section(user_prompt, "format_instructions")
556
+
557
+ grouping_instructions = """
558
+ <format_instructions>
559
+ Your task is to split the changed files into separate, logical commits. Think of this like sorting files into different folders where each file belongs in exactly one folder.
560
+
561
+ CRITICAL REQUIREMENT - Every File Used Exactly Once:
562
+ You must assign EVERY file from the diff to exactly ONE commit.
563
+ - NO file should be left out
564
+ - NO file should appear in multiple commits
565
+ - EVERY file must be used once and ONLY once
566
+
567
+ Think of it like dealing cards: Once you've dealt a card to a player, that card cannot be dealt to another player.
568
+
569
+ HOW TO SPLIT THE FILES:
570
+ 1. Review all changed files in the diff
571
+ 2. Group files by logical relationship (e.g., related features, bug fixes, documentation)
572
+ 3. Assign each file to exactly one commit based on what makes the most sense
573
+ 4. If a file could fit in multiple commits, pick the best fit and move on - do NOT duplicate it
574
+ 5. Continue until every single file has been assigned to a commit
575
+
576
+ ORDERING:
577
+ Order the commits in a logical sequence considering dependencies, natural progression, and overall workflow.
578
+
579
+ YOUR RESPONSE FORMAT:
580
+ Respond with valid JSON following this structure:
581
+ ```json
582
+ {
583
+ "commits": [
584
+ {
585
+ "files": ["src/auth/login.ts", "src/auth/logout.ts"],
586
+ "message": "<commit_message_conforming_to_prescribed_structure_and_format>"
587
+ },
588
+ {
589
+ "files": ["src/db/schema.sql", "src/db/migrations/001.sql"],
590
+ "message": "<commit_message_conforming_to_prescribed_structure_and_format>"
591
+ },
592
+ {
593
+ "files": ["tests/auth.test.ts", "tests/db.test.ts", "README.md"],
594
+ "message": "<commit_message_conforming_to_prescribed_structure_and_format>"
595
+ }
596
+ ]
597
+ }
598
+ ```
599
+
600
+ ☝️ Notice how EVERY file path in the example above appears exactly ONCE across all commits. "src/auth/login.ts" appears once. "tests/auth.test.ts" appears once. No file is repeated.
601
+
602
+ VALIDATION CHECKLIST - Before responding, verify:
603
+ □ Total files across all commits = Total files in the diff
604
+ □ Each file appears in exactly 1 commit (no duplicates, no omissions)
605
+ □ Every commit has at least one file
606
+ □ If you list all files from all commits and count them, you get the same count as unique files in the diff
607
+ </format_instructions>
608
+ """
609
+
610
+ user_prompt = user_prompt + grouping_instructions
611
+
612
+ return system_prompt, user_prompt
613
+
614
+
529
615
  # ============================================================================
530
616
  # Message Cleaning Helpers
531
617
  # ============================================================================
gac/security.py CHANGED
@@ -54,7 +54,7 @@ class SecretPatterns:
54
54
  PRIVATE_KEY = re.compile(r"-----BEGIN (?:RSA |EC |OPENSSH )?PRIVATE KEY-----")
55
55
 
56
56
  # Bearer Tokens (require actual token with specific characteristics)
57
- BEARER_TOKEN = re.compile(r"Bearer\s+[A-Za-z0-9]{20,}[/=]*\s", re.IGNORECASE)
57
+ BEARER_TOKEN = re.compile(r"Bearer\s+[A-Za-z0-9]{20,}[/=]*(?:\s|$)", re.IGNORECASE)
58
58
 
59
59
  # JWT Tokens
60
60
  JWT_TOKEN = re.compile(r"eyJ[A-Za-z0-9_-]+\.eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+")
gac/utils.py CHANGED
@@ -1,7 +1,9 @@
1
1
  """Utility functions for gac."""
2
2
 
3
+ import locale
3
4
  import logging
4
5
  import subprocess
6
+ import sys
5
7
 
6
8
  from rich.console import Console
7
9
  from rich.theme import Theme
@@ -65,18 +67,47 @@ def print_message(message: str, level: str = "info") -> None:
65
67
  console.print(message, style=level)
66
68
 
67
69
 
68
- def run_subprocess(
70
+ def get_safe_encodings() -> list[str]:
71
+ """Get a list of safe encodings to try for subprocess calls, in order of preference.
72
+
73
+ Returns:
74
+ List of encoding strings to try, with UTF-8 first
75
+ """
76
+ encodings = ["utf-8"]
77
+
78
+ # Add locale encoding as fallback
79
+ locale_encoding = locale.getpreferredencoding(False)
80
+ if locale_encoding and locale_encoding not in encodings:
81
+ encodings.append(locale_encoding)
82
+
83
+ # Windows-specific fallbacks
84
+ if sys.platform == "win32":
85
+ windows_encodings = ["cp65001", "cp936", "cp1252"] # UTF-8, GBK, Windows-1252
86
+ for enc in windows_encodings:
87
+ if enc not in encodings:
88
+ encodings.append(enc)
89
+
90
+ # Final fallback to system default
91
+ if "utf-8" not in encodings:
92
+ encodings.append("utf-8")
93
+
94
+ return encodings
95
+
96
+
97
+ def run_subprocess_with_encoding(
69
98
  command: list[str],
99
+ encoding: str,
70
100
  silent: bool = False,
71
101
  timeout: int = 60,
72
102
  check: bool = True,
73
103
  strip_output: bool = True,
74
104
  raise_on_error: bool = True,
75
105
  ) -> str:
76
- """Run a subprocess command safely and return the output.
106
+ """Run subprocess with a specific encoding, handling encoding errors gracefully.
77
107
 
78
108
  Args:
79
109
  command: List of command arguments
110
+ encoding: Specific encoding to use
80
111
  silent: If True, suppress debug logging
81
112
  timeout: Command timeout in seconds
82
113
  check: Whether to check return code (for compatibility)
@@ -91,7 +122,7 @@ def run_subprocess(
91
122
  subprocess.CalledProcessError: If the command fails and raise_on_error is True
92
123
  """
93
124
  if not silent:
94
- logger.debug(f"Running command: {' '.join(command)}")
125
+ logger.debug(f"Running command: {' '.join(command)} (encoding: {encoding})")
95
126
 
96
127
  try:
97
128
  result = subprocess.run(
@@ -100,6 +131,8 @@ def run_subprocess(
100
131
  text=True,
101
132
  check=False,
102
133
  timeout=timeout,
134
+ encoding=encoding,
135
+ errors="replace", # Replace problematic characters instead of crashing
103
136
  )
104
137
 
105
138
  should_raise = result.returncode != 0 and (check or raise_on_error)
@@ -123,6 +156,11 @@ def run_subprocess(
123
156
  if raise_on_error:
124
157
  raise
125
158
  return ""
159
+ except UnicodeError as e:
160
+ # This should be rare with errors="replace", but handle it just in case
161
+ if not silent:
162
+ logger.debug(f"Encoding error with {encoding}: {e}")
163
+ raise
126
164
  except Exception as e:
127
165
  if not silent:
128
166
  logger.debug(f"Command error: {e}")
@@ -132,6 +170,69 @@ def run_subprocess(
132
170
  return ""
133
171
 
134
172
 
173
+ def run_subprocess(
174
+ command: list[str],
175
+ silent: bool = False,
176
+ timeout: int = 60,
177
+ check: bool = True,
178
+ strip_output: bool = True,
179
+ raise_on_error: bool = True,
180
+ ) -> str:
181
+ """Run a subprocess command safely and return the output, trying multiple encodings.
182
+
183
+ Args:
184
+ command: List of command arguments
185
+ silent: If True, suppress debug logging
186
+ timeout: Command timeout in seconds
187
+ check: Whether to check return code (for compatibility)
188
+ strip_output: Whether to strip whitespace from output
189
+ raise_on_error: Whether to raise an exception on error
190
+
191
+ Returns:
192
+ Command output as string
193
+
194
+ Raises:
195
+ GacError: If the command times out
196
+ subprocess.CalledProcessError: If the command fails and raise_on_error is True
197
+
198
+ Note:
199
+ Tries multiple encodings in order: utf-8, locale encoding, platform-specific fallbacks
200
+ This prevents UnicodeDecodeError on systems with non-UTF-8 locales (e.g., Chinese Windows)
201
+ """
202
+ encodings = get_safe_encodings()
203
+ last_exception = None
204
+
205
+ for encoding in encodings:
206
+ try:
207
+ return run_subprocess_with_encoding(
208
+ command=command,
209
+ encoding=encoding,
210
+ silent=silent,
211
+ timeout=timeout,
212
+ check=check,
213
+ strip_output=strip_output,
214
+ raise_on_error=raise_on_error,
215
+ )
216
+ except UnicodeError as e:
217
+ last_exception = e
218
+ if not silent:
219
+ logger.debug(f"Failed to decode with {encoding}: {e}")
220
+ continue
221
+ except (subprocess.CalledProcessError, GacError, subprocess.TimeoutExpired):
222
+ # These are not encoding-related errors, so don't retry with other encodings
223
+ raise
224
+
225
+ # If we get here, all encodings failed with UnicodeError
226
+ if not silent:
227
+ logger.error(f"Failed to decode command output with any encoding: {encodings}")
228
+
229
+ # Raise the last UnicodeError we encountered
230
+ if last_exception:
231
+ raise subprocess.CalledProcessError(1, command, "", f"Encoding error: {last_exception}") from last_exception
232
+ else:
233
+ raise subprocess.CalledProcessError(1, command, "", "All encoding attempts failed")
234
+
235
+
135
236
  def edit_commit_message_inplace(message: str) -> str | None:
136
237
  """Edit commit message in-place using rich terminal editing.
137
238
 
gac/workflow_utils.py ADDED
@@ -0,0 +1,131 @@
1
+ import logging
2
+ import tempfile
3
+ from pathlib import Path
4
+
5
+ import click
6
+ from rich.console import Console
7
+
8
+ logger = logging.getLogger(__name__)
9
+ console = Console()
10
+
11
+
12
+ def handle_confirmation_loop(
13
+ commit_message: str,
14
+ conversation_messages: list[dict[str, str]],
15
+ quiet: bool,
16
+ model: str,
17
+ ) -> tuple[str, str, list[dict[str, str]]]:
18
+ from rich.panel import Panel
19
+
20
+ from gac.utils import edit_commit_message_inplace
21
+
22
+ while True:
23
+ response = click.prompt(
24
+ "Proceed with commit above? [y/n/r/e/<feedback>]",
25
+ type=str,
26
+ show_default=False,
27
+ ).strip()
28
+ response_lower = response.lower()
29
+
30
+ if response_lower in ["y", "yes"]:
31
+ return ("yes", commit_message, conversation_messages)
32
+ if response_lower in ["n", "no"]:
33
+ return ("no", commit_message, conversation_messages)
34
+ if response == "":
35
+ continue
36
+ if response_lower in ["e", "edit"]:
37
+ edited_message = edit_commit_message_inplace(commit_message)
38
+ if edited_message:
39
+ commit_message = edited_message
40
+ conversation_messages[-1] = {"role": "assistant", "content": commit_message}
41
+ logger.info("Commit message edited by user")
42
+ console.print("\n[bold green]Edited commit message:[/bold green]")
43
+ console.print(Panel(commit_message, title="Commit Message", border_style="cyan"))
44
+ else:
45
+ console.print("[yellow]Using previous message.[/yellow]")
46
+ console.print(Panel(commit_message, title="Commit Message", border_style="cyan"))
47
+ continue
48
+ if response_lower in ["r", "reroll"]:
49
+ msg = "Please provide an alternative commit message using the same repository context."
50
+ conversation_messages.append({"role": "user", "content": msg})
51
+ console.print("[cyan]Regenerating commit message...[/cyan]")
52
+ return ("regenerate", commit_message, conversation_messages)
53
+
54
+ msg = f"Please revise the commit message based on this feedback: {response}"
55
+ conversation_messages.append({"role": "user", "content": msg})
56
+ console.print(f"[cyan]Regenerating commit message with feedback: {response}[/cyan]")
57
+ return ("regenerate", commit_message, conversation_messages)
58
+
59
+
60
+ def execute_commit(commit_message: str, no_verify: bool) -> None:
61
+ from gac.git import run_git_command
62
+
63
+ commit_args = ["commit", "-m", commit_message]
64
+ if no_verify:
65
+ commit_args.append("--no-verify")
66
+ run_git_command(commit_args)
67
+ logger.info("Commit created successfully")
68
+ console.print("[green]Commit created successfully[/green]")
69
+
70
+
71
+ def check_token_warning(
72
+ prompt_tokens: int,
73
+ warning_limit: int,
74
+ require_confirmation: bool,
75
+ ) -> bool:
76
+ if warning_limit and prompt_tokens > warning_limit:
77
+ console.print(f"[yellow]⚠️ WARNING: Prompt has {prompt_tokens} tokens (limit: {warning_limit})[/yellow]")
78
+ if require_confirmation:
79
+ proceed = click.confirm("Do you want to continue anyway?", default=True)
80
+ if not proceed:
81
+ console.print("[yellow]Aborted due to token limit.[/yellow]")
82
+ return False
83
+ return True
84
+
85
+
86
+ def display_commit_message(commit_message: str, prompt_tokens: int, model: str, quiet: bool) -> None:
87
+ from rich.panel import Panel
88
+
89
+ from gac.ai_utils import count_tokens
90
+
91
+ console.print("[bold green]Generated commit message:[/bold green]")
92
+ console.print(Panel(commit_message, title="Commit Message", border_style="cyan"))
93
+
94
+ if not quiet:
95
+ completion_tokens = count_tokens(commit_message, model)
96
+ total_tokens = prompt_tokens + completion_tokens
97
+ console.print(
98
+ f"[dim]Token usage: {prompt_tokens} prompt + {completion_tokens} completion = {total_tokens} total[/dim]"
99
+ )
100
+
101
+
102
+ def restore_staging(staged_files: list[str], staged_diff: str | None = None) -> None:
103
+ """Restore the git staging area to a previous state.
104
+
105
+ Args:
106
+ staged_files: List of file paths that should be staged
107
+ staged_diff: Optional staged diff to reapply for partial staging
108
+ """
109
+ from gac.git import run_git_command
110
+
111
+ run_git_command(["reset", "HEAD"])
112
+
113
+ if staged_diff:
114
+ temp_path: Path | None = None
115
+ try:
116
+ with tempfile.NamedTemporaryFile("w", delete=False) as tmp:
117
+ tmp.write(staged_diff)
118
+ temp_path = Path(tmp.name)
119
+ run_git_command(["apply", "--cached", str(temp_path)])
120
+ return
121
+ except Exception as e:
122
+ logger.warning(f"Failed to reapply staged diff, falling back to file list: {e}")
123
+ finally:
124
+ if temp_path:
125
+ temp_path.unlink(missing_ok=True)
126
+
127
+ for file_path in staged_files:
128
+ try:
129
+ run_git_command(["add", file_path])
130
+ except Exception as e:
131
+ logger.warning(f"Failed to restore staging for {file_path}: {e}")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: gac
3
- Version: 2.2.0
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
  [![codecov](https://codecov.io/gh/cellwebb/gac/branch/main/graph/badge.svg)](https://app.codecov.io/gh/cellwebb/gac)
51
54
  [![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)
52
55
  [![mypy](https://img.shields.io/badge/mypy-checked-blue.svg)](https://mypy-lang.org/)
53
- [![Contributions welcome](https://img.shields.io/badge/contributions-welcome-brightgreen.svg)](docs/CONTRIBUTING.md)
56
+ [![Contributions welcome](https://img.shields.io/badge/contributions-welcome-brightgreen.svg)](docs/en/CONTRIBUTING.md)
54
57
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](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 # Configure your LLM provider
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,21 +1,22 @@
1
1
  gac/__init__.py,sha256=z9yGInqtycFIT3g1ca24r-A3699hKVaRqGUI79wsmMc,415
2
- gac/__version__.py,sha256=r4l_6jWyV87srDy0RVRKwXgzwJscJzmR4SM825CjGh0,66
3
- gac/ai.py,sha256=iBHeLsqe6iyFj86wbvEosyy4vkjAN1BlLQeqtb_rfmo,4303
4
- gac/ai_utils.py,sha256=094ujZVlbDnHM3HPxiBSCbGi_5MD6bOKCj2SjKVDDK0,7445
5
- gac/cli.py,sha256=SOrSfrlku99O7O8zev5hRVmADAmJ7AIkM7Z0dquuCbQ,5807
6
- gac/config.py,sha256=O9n09-sFOqlkf47vieEP7fI5I7uhu1cXn9PUZ5yiYkw,1974
2
+ gac/__version__.py,sha256=D_ezOWkaq6NqXOifgxVl-YxQ5yWx8XUH0WiBeGaE5As,66
3
+ gac/ai.py,sha256=6SQK4axBE0uEbF3eKVTvQtGL9X1TbxoBOrY7NuYIfiM,5009
4
+ gac/ai_utils.py,sha256=reJINfsKlX0HAg5HPlH4ImO6FvibzgRZ0ruG9u1cxa8,8312
5
+ gac/cli.py,sha256=ecmlZkejIM7uQgUun3S4dVht3f43_nCyibP3jm2hufQ,6057
6
+ gac/config.py,sha256=yyhcQjmWv5c0-AVg6U1X9gE89x-e0369IYjieqKDjW0,2078
7
7
  gac/config_cli.py,sha256=v9nFHZO1RvK9fzHyuUS6SG-BCLHMsdOMDwWamBhVVh4,1608
8
- gac/constants.py,sha256=zgylsiKnpBunoNzVT6RpAVe9m8cgxZrZ55kRN6ZP_cM,9586
8
+ gac/constants.py,sha256=yyvYycIfRJ9SZZIMIhwn1s6yohjcaNM-fGXl_R9w1dI,9586
9
9
  gac/diff_cli.py,sha256=wnVQ9OFGnM0d2Pj9WVjWbo0jxqIuRHVAwmb8wU9Pa3E,5676
10
10
  gac/errors.py,sha256=ysDIVRCd0YQVTOW3Q6YzdolxCdtkoQCAFf3_jrqbjUY,7916
11
- gac/git.py,sha256=g6tvph50zV-wrTWrxARYXEpl0NeI8-ffFwHoqhp3fSE,8033
12
- gac/init_cli.py,sha256=KvFOvjjyMpdJ3MhJFvXSuYjdfulPA6hCP11YXwjHrqw,8849
13
- gac/language_cli.py,sha256=J4xZNNrMvHamsjK4TCsOVj0lrjYDtLMuHlnTqN0-N_w,3024
14
- gac/main.py,sha256=fK48fGHeJ4qGsttbKDoMXs4Gj3NncFHz_F_cJZI70IQ,16159
11
+ gac/git.py,sha256=ss-ztVyNnU3zwigHTHUqUAx8RiTzhcrSxHMrdjU1VgE,11117
12
+ gac/init_cli.py,sha256=ZfC6oci9xiRVKHuOipH7RQvVI-79O_OymG7_ZZ6CyLM,19606
13
+ gac/language_cli.py,sha256=FEQf-k5KrhIpvx4XJvjqxTeE4qumNO00d-tgOAf83W4,8488
14
+ gac/main.py,sha256=r5zwAYBBn-qz9kix2Zn5yJ-ac3-42u6HaIoirybOaTQ,28784
15
15
  gac/preprocess.py,sha256=hk2p2X4-xVDvuy-T1VMzMa9k5fTUbhlWDyw89DCf81Q,15379
16
- gac/prompt.py,sha256=HLvsW3YQLdTfw2N9UgjZ0jWckUc1x76V7Kcqjcl8Fsk,28633
17
- gac/security.py,sha256=15Yp6YR8QC4eECJi1BUCkMteh_veZXUbLL6W8qGcDm4,9920
18
- gac/utils.py,sha256=owkUzwJBX8mi0VrP3HKxku5vJj_JlaShzTYwjjsHn-4,8126
16
+ gac/prompt.py,sha256=ofumb6DmxJceqZLUlUyLE9b7Mwx9BpIEHweKEV9eicw,31841
17
+ gac/security.py,sha256=QT91mBEo2Y7la-aXvKuF2zhWuoOSXb6PWKLJ93kSy2k,9926
18
+ gac/utils.py,sha256=gTpc9zLzy3-3L5k-V5uSeFI-NnSPabA1GGVwSgpeMSk,11709
19
+ gac/workflow_utils.py,sha256=fc0yPRBeA-7P2WiVPFa7A_NjXTW68UlbUv6_AXVYAzA,5023
19
20
  gac/providers/__init__.py,sha256=pT1xcKoZkPm6BWaxcAQ299-Ca5zZ31kf4DeQYAim9Tw,1367
20
21
  gac/providers/anthropic.py,sha256=VK5d1s1PmBNDwh_x7illQ2CIZIHNIYU28btVfizwQPs,2036
21
22
  gac/providers/cerebras.py,sha256=Ik8lhlsliGJVkgDgqlThfpra9tqbdYQZkaC4eNxRd9w,1648
@@ -36,8 +37,8 @@ gac/providers/streamlake.py,sha256=KAA2ZnpuEI5imzvdWVWUhEBHSP0BMnprKXte6CbwBWY,2
36
37
  gac/providers/synthetic.py,sha256=sRMIJTS9LpcXd9A7qp_ZjZxdqtTKRn9fl1W4YwJZP4c,1855
37
38
  gac/providers/together.py,sha256=1bUIVHfYzcEDw4hQPE8qV6hjc2JNHPv_khVgpk2IJxI,1667
38
39
  gac/providers/zai.py,sha256=kywhhrCfPBu0rElZyb-iENxQxxpVGykvePuL4xrXlaU,2739
39
- gac-2.2.0.dist-info/METADATA,sha256=Z5Vv7oBzqWKVr7lDmk_HFjq3kF13DNnCkMnXKZDKRtA,9609
40
- gac-2.2.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
41
- gac-2.2.0.dist-info/entry_points.txt,sha256=tdjN-XMmcWfL92swuRAjT62bFLOAwk9bTMRLGP5Z4aI,36
42
- gac-2.2.0.dist-info/licenses/LICENSE,sha256=vOab37NouL1PNs5BswnPayrMCqaN2sqLfMQfqPDrpZg,1103
43
- gac-2.2.0.dist-info/RECORD,,
40
+ gac-2.4.0.dist-info/METADATA,sha256=aNQRaKtaYr-aEmsrT769NVFoH4gJKI8CxmN_sESUrpo,10784
41
+ gac-2.4.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
42
+ gac-2.4.0.dist-info/entry_points.txt,sha256=tdjN-XMmcWfL92swuRAjT62bFLOAwk9bTMRLGP5Z4aI,36
43
+ gac-2.4.0.dist-info/licenses/LICENSE,sha256=vOab37NouL1PNs5BswnPayrMCqaN2sqLfMQfqPDrpZg,1103
44
+ gac-2.4.0.dist-info/RECORD,,
File without changes