git-llm-tool 0.1.12__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.
- git_llm_tool/__init__.py +5 -0
- git_llm_tool/__main__.py +6 -0
- git_llm_tool/cli.py +167 -0
- git_llm_tool/commands/__init__.py +1 -0
- git_llm_tool/commands/changelog_cmd.py +189 -0
- git_llm_tool/commands/commit_cmd.py +134 -0
- git_llm_tool/core/__init__.py +1 -0
- git_llm_tool/core/config.py +352 -0
- git_llm_tool/core/diff_optimizer.py +206 -0
- git_llm_tool/core/exceptions.py +26 -0
- git_llm_tool/core/git_helper.py +250 -0
- git_llm_tool/core/jira_helper.py +238 -0
- git_llm_tool/core/rate_limiter.py +136 -0
- git_llm_tool/core/smart_chunker.py +262 -0
- git_llm_tool/core/token_counter.py +169 -0
- git_llm_tool/providers/__init__.py +21 -0
- git_llm_tool/providers/anthropic_langchain.py +42 -0
- git_llm_tool/providers/azure_openai_langchain.py +59 -0
- git_llm_tool/providers/base.py +203 -0
- git_llm_tool/providers/factory.py +85 -0
- git_llm_tool/providers/gemini_langchain.py +57 -0
- git_llm_tool/providers/langchain_base.py +608 -0
- git_llm_tool/providers/ollama_langchain.py +45 -0
- git_llm_tool/providers/openai_langchain.py +42 -0
- git_llm_tool-0.1.12.dist-info/LICENSE +21 -0
- git_llm_tool-0.1.12.dist-info/METADATA +645 -0
- git_llm_tool-0.1.12.dist-info/RECORD +29 -0
- git_llm_tool-0.1.12.dist-info/WHEEL +4 -0
- git_llm_tool-0.1.12.dist-info/entry_points.txt +3 -0
git_llm_tool/__init__.py
ADDED
git_llm_tool/__main__.py
ADDED
git_llm_tool/cli.py
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
"""Main CLI interface for git-llm-tool."""
|
|
2
|
+
|
|
3
|
+
import click
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
from git_llm_tool import __version__
|
|
7
|
+
from git_llm_tool.core.config import ConfigLoader, get_config
|
|
8
|
+
from git_llm_tool.core.exceptions import ConfigError
|
|
9
|
+
from git_llm_tool.commands.commit_cmd import execute_commit
|
|
10
|
+
from git_llm_tool.commands.changelog_cmd import execute_changelog
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@click.group()
|
|
14
|
+
@click.version_option(version=__version__)
|
|
15
|
+
@click.pass_context
|
|
16
|
+
def main(ctx):
|
|
17
|
+
"""AI-powered git commit message and changelog generator."""
|
|
18
|
+
ctx.ensure_object(dict)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@main.command()
|
|
22
|
+
@click.option(
|
|
23
|
+
"--apply", "-a", is_flag=True,
|
|
24
|
+
help="Apply the commit message directly without opening editor"
|
|
25
|
+
)
|
|
26
|
+
@click.option(
|
|
27
|
+
"--model", "-m",
|
|
28
|
+
help="LLM model to use (overrides config)"
|
|
29
|
+
)
|
|
30
|
+
@click.option(
|
|
31
|
+
"--language", "-l",
|
|
32
|
+
help="Output language (overrides config)"
|
|
33
|
+
)
|
|
34
|
+
@click.option(
|
|
35
|
+
"--verbose", "-v", is_flag=True,
|
|
36
|
+
help="Enable verbose output"
|
|
37
|
+
)
|
|
38
|
+
@click.option(
|
|
39
|
+
"--no-jira", is_flag=True,
|
|
40
|
+
help="Skip Jira ticket input"
|
|
41
|
+
)
|
|
42
|
+
@click.pass_context
|
|
43
|
+
def commit(ctx, apply, model, language, verbose, no_jira):
|
|
44
|
+
"""Generate AI-powered commit message from staged changes."""
|
|
45
|
+
execute_commit(apply=apply, model=model, language=language, verbose=verbose, no_jira=no_jira)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@main.command()
|
|
49
|
+
@click.option(
|
|
50
|
+
"--from", "from_ref",
|
|
51
|
+
help="Starting reference (default: last tag)"
|
|
52
|
+
)
|
|
53
|
+
@click.option(
|
|
54
|
+
"--to", "to_ref", default="HEAD",
|
|
55
|
+
help="Ending reference (default: HEAD)"
|
|
56
|
+
)
|
|
57
|
+
@click.option(
|
|
58
|
+
"--output", "-o",
|
|
59
|
+
help="Output file (default: stdout)"
|
|
60
|
+
)
|
|
61
|
+
@click.option(
|
|
62
|
+
"--force", "-f", is_flag=True,
|
|
63
|
+
help="Force overwrite existing output file"
|
|
64
|
+
)
|
|
65
|
+
@click.option(
|
|
66
|
+
"--verbose", "-v", is_flag=True,
|
|
67
|
+
help="Enable verbose output"
|
|
68
|
+
)
|
|
69
|
+
@click.pass_context
|
|
70
|
+
def changelog(ctx, from_ref, to_ref, output, force, verbose):
|
|
71
|
+
"""Generate changelog from git history."""
|
|
72
|
+
execute_changelog(from_ref=from_ref, to_ref=to_ref, output=output, force=force, verbose=verbose)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
@main.group()
|
|
76
|
+
def config():
|
|
77
|
+
"""Configuration management commands."""
|
|
78
|
+
pass
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
@config.command()
|
|
82
|
+
@click.argument("key")
|
|
83
|
+
@click.argument("value")
|
|
84
|
+
def set(key, value):
|
|
85
|
+
"""Set configuration value."""
|
|
86
|
+
try:
|
|
87
|
+
config_loader = ConfigLoader()
|
|
88
|
+
config_loader.set_value(key, value)
|
|
89
|
+
|
|
90
|
+
# Save to global config
|
|
91
|
+
config_loader.save_config()
|
|
92
|
+
|
|
93
|
+
click.echo(f"✅ Set {key} = {value}")
|
|
94
|
+
except ConfigError as e:
|
|
95
|
+
click.echo(f"❌ Configuration error: {e}", err=True)
|
|
96
|
+
except Exception as e:
|
|
97
|
+
click.echo(f"❌ Unexpected error: {e}", err=True)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
@config.command()
|
|
101
|
+
@click.argument("key", required=False)
|
|
102
|
+
def get(key):
|
|
103
|
+
"""Get configuration value(s)."""
|
|
104
|
+
try:
|
|
105
|
+
config = get_config()
|
|
106
|
+
|
|
107
|
+
if key:
|
|
108
|
+
# Get specific key
|
|
109
|
+
config_loader = ConfigLoader()
|
|
110
|
+
value = config_loader.get_value(key)
|
|
111
|
+
click.echo(f"{key} = {value}")
|
|
112
|
+
else:
|
|
113
|
+
# Show all configuration
|
|
114
|
+
click.echo("📋 Current Configuration:")
|
|
115
|
+
click.echo(f" llm.default_model = {config.llm.default_model}")
|
|
116
|
+
click.echo(f" llm.language = {config.llm.language}")
|
|
117
|
+
|
|
118
|
+
if config.llm.api_keys:
|
|
119
|
+
click.echo(" llm.api_keys:")
|
|
120
|
+
for provider, key_value in config.llm.api_keys.items():
|
|
121
|
+
# Hide API key for security
|
|
122
|
+
masked_key = key_value[:8] + "..." if len(key_value) > 8 else "***"
|
|
123
|
+
click.echo(f" {provider} = {masked_key}")
|
|
124
|
+
|
|
125
|
+
if config.llm.azure_openai:
|
|
126
|
+
click.echo(" llm.azure_openai:")
|
|
127
|
+
for key, value in config.llm.azure_openai.items():
|
|
128
|
+
click.echo(f" {key} = {value}")
|
|
129
|
+
|
|
130
|
+
click.echo(f" jira.enabled = {config.jira.enabled}")
|
|
131
|
+
if config.jira.ticket_pattern:
|
|
132
|
+
click.echo(f" jira.ticket_pattern = {config.jira.ticket_pattern}")
|
|
133
|
+
|
|
134
|
+
if config.editor.preferred_editor:
|
|
135
|
+
click.echo(f" editor.preferred_editor = {config.editor.preferred_editor}")
|
|
136
|
+
|
|
137
|
+
except ConfigError as e:
|
|
138
|
+
click.echo(f"❌ Configuration error: {e}", err=True)
|
|
139
|
+
except Exception as e:
|
|
140
|
+
click.echo(f"❌ Unexpected error: {e}", err=True)
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
@config.command()
|
|
144
|
+
def init():
|
|
145
|
+
"""Initialize configuration file."""
|
|
146
|
+
try:
|
|
147
|
+
config_path = Path.home() / ".git-llm-tool" / "config.yaml"
|
|
148
|
+
|
|
149
|
+
if config_path.exists():
|
|
150
|
+
if not click.confirm(f"Configuration file already exists at {config_path}. Overwrite?"):
|
|
151
|
+
click.echo("❌ Initialization cancelled.")
|
|
152
|
+
return
|
|
153
|
+
|
|
154
|
+
# Create default configuration
|
|
155
|
+
config_loader = ConfigLoader()
|
|
156
|
+
config_loader.save_config(config_path)
|
|
157
|
+
|
|
158
|
+
click.echo(f"✅ Configuration initialized at {config_path}")
|
|
159
|
+
click.echo("💡 You can now set API keys with:")
|
|
160
|
+
click.echo(" git-llm config set llm.api_keys.openai sk-your-key-here")
|
|
161
|
+
|
|
162
|
+
except Exception as e:
|
|
163
|
+
click.echo(f"❌ Failed to initialize configuration: {e}", err=True)
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
if __name__ == "__main__":
|
|
167
|
+
main()
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Commands module."""
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
"""Changelog command implementation."""
|
|
2
|
+
|
|
3
|
+
import click
|
|
4
|
+
import os
|
|
5
|
+
from datetime import datetime
|
|
6
|
+
from typing import Optional
|
|
7
|
+
|
|
8
|
+
from git_llm_tool.core.config import get_config
|
|
9
|
+
from git_llm_tool.core.git_helper import GitHelper
|
|
10
|
+
from git_llm_tool.core.exceptions import GitError, ApiError, ConfigError
|
|
11
|
+
from git_llm_tool.providers import get_provider
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _manage_changelog_file(new_content: str, verbose: bool = False) -> str:
|
|
15
|
+
"""Manage the changelog.md file in the repository root.
|
|
16
|
+
|
|
17
|
+
Args:
|
|
18
|
+
new_content: New changelog content to add
|
|
19
|
+
verbose: Enable verbose output
|
|
20
|
+
|
|
21
|
+
Returns:
|
|
22
|
+
Path to the changelog file
|
|
23
|
+
"""
|
|
24
|
+
# Get repository root
|
|
25
|
+
git_helper = GitHelper()
|
|
26
|
+
repo_info = git_helper.get_repository_info()
|
|
27
|
+
repo_root = repo_info.get('repository_root', os.getcwd())
|
|
28
|
+
|
|
29
|
+
changelog_path = os.path.join(repo_root, 'changelog.md')
|
|
30
|
+
|
|
31
|
+
# Get current date
|
|
32
|
+
current_date = datetime.now().strftime('%Y-%m-%d')
|
|
33
|
+
|
|
34
|
+
# Create header with date
|
|
35
|
+
header = f"\n## {current_date}\n\n"
|
|
36
|
+
|
|
37
|
+
# Clean the new content (remove any duplicate titles)
|
|
38
|
+
cleaned_content = new_content.strip()
|
|
39
|
+
if cleaned_content.startswith('# Changelog'):
|
|
40
|
+
# Remove the duplicate title line
|
|
41
|
+
lines = cleaned_content.split('\n')
|
|
42
|
+
lines = [line for line in lines[1:] if line.strip()] # Skip title and empty lines
|
|
43
|
+
cleaned_content = '\n'.join(lines)
|
|
44
|
+
|
|
45
|
+
# Prepare content to add
|
|
46
|
+
content_to_add = header + cleaned_content + "\n"
|
|
47
|
+
|
|
48
|
+
if os.path.exists(changelog_path):
|
|
49
|
+
if verbose:
|
|
50
|
+
click.echo(f"📝 Found existing changelog at {changelog_path}")
|
|
51
|
+
|
|
52
|
+
# Read existing content
|
|
53
|
+
try:
|
|
54
|
+
with open(changelog_path, 'r', encoding='utf-8') as f:
|
|
55
|
+
existing_content = f.read()
|
|
56
|
+
except IOError as e:
|
|
57
|
+
raise Exception(f"Failed to read existing changelog: {e}")
|
|
58
|
+
|
|
59
|
+
# Check if we're at the beginning of the file or need to add after title
|
|
60
|
+
if existing_content.strip().startswith('# '):
|
|
61
|
+
# Find the end of the title line
|
|
62
|
+
lines = existing_content.split('\n')
|
|
63
|
+
title_line = lines[0]
|
|
64
|
+
rest_content = '\n'.join(lines[1:])
|
|
65
|
+
|
|
66
|
+
# Insert new content after title
|
|
67
|
+
final_content = title_line + '\n' + content_to_add + rest_content
|
|
68
|
+
else:
|
|
69
|
+
# Prepend to existing content
|
|
70
|
+
final_content = content_to_add + existing_content
|
|
71
|
+
|
|
72
|
+
else:
|
|
73
|
+
if verbose:
|
|
74
|
+
click.echo(f"📄 Creating new changelog at {changelog_path}")
|
|
75
|
+
|
|
76
|
+
# Create new changelog with header
|
|
77
|
+
final_content = f"# Changelog\n{content_to_add}"
|
|
78
|
+
|
|
79
|
+
# Write the file
|
|
80
|
+
try:
|
|
81
|
+
with open(changelog_path, 'w', encoding='utf-8') as f:
|
|
82
|
+
f.write(final_content)
|
|
83
|
+
if verbose:
|
|
84
|
+
click.echo(f"✅ Updated changelog at {changelog_path}")
|
|
85
|
+
except IOError as e:
|
|
86
|
+
raise Exception(f"Failed to write changelog: {e}")
|
|
87
|
+
|
|
88
|
+
return changelog_path
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def execute_changelog(
|
|
92
|
+
from_ref: Optional[str] = None,
|
|
93
|
+
to_ref: str = "HEAD",
|
|
94
|
+
output: Optional[str] = None,
|
|
95
|
+
force: bool = False,
|
|
96
|
+
verbose: bool = False
|
|
97
|
+
) -> None:
|
|
98
|
+
"""Execute the changelog command logic.
|
|
99
|
+
|
|
100
|
+
Args:
|
|
101
|
+
from_ref: Starting reference (default: last tag)
|
|
102
|
+
to_ref: Ending reference (default: HEAD)
|
|
103
|
+
output: Output file path
|
|
104
|
+
force: Force overwrite existing file
|
|
105
|
+
verbose: Enable verbose output
|
|
106
|
+
"""
|
|
107
|
+
try:
|
|
108
|
+
# Load configuration
|
|
109
|
+
config = get_config()
|
|
110
|
+
|
|
111
|
+
if verbose:
|
|
112
|
+
click.echo(f"📄 Using model: {config.llm.default_model}")
|
|
113
|
+
click.echo(f"🌐 Using language: {config.llm.language}")
|
|
114
|
+
|
|
115
|
+
# Initialize Git helper
|
|
116
|
+
git_helper = GitHelper()
|
|
117
|
+
|
|
118
|
+
# Get commit messages in range
|
|
119
|
+
if verbose:
|
|
120
|
+
click.echo("📊 Getting commit messages...")
|
|
121
|
+
|
|
122
|
+
try:
|
|
123
|
+
commit_messages = git_helper.get_commit_messages(from_ref, to_ref)
|
|
124
|
+
except GitError as e:
|
|
125
|
+
click.echo(f"❌ {e}", err=True)
|
|
126
|
+
return
|
|
127
|
+
|
|
128
|
+
if verbose:
|
|
129
|
+
click.echo(f"📝 Found {len(commit_messages)} commits")
|
|
130
|
+
|
|
131
|
+
# Get LLM provider
|
|
132
|
+
try:
|
|
133
|
+
provider = get_provider(config)
|
|
134
|
+
if verbose:
|
|
135
|
+
click.echo(f"🤖 Using provider: {provider.__class__.__name__}")
|
|
136
|
+
except ApiError as e:
|
|
137
|
+
click.echo(f"❌ {e}", err=True)
|
|
138
|
+
return
|
|
139
|
+
|
|
140
|
+
# Generate changelog
|
|
141
|
+
click.echo("🤖 Generating changelog...")
|
|
142
|
+
|
|
143
|
+
try:
|
|
144
|
+
changelog = provider.generate_changelog(commit_messages)
|
|
145
|
+
except ApiError as e:
|
|
146
|
+
click.echo(f"❌ API Error: {e}", err=True)
|
|
147
|
+
return
|
|
148
|
+
|
|
149
|
+
if verbose:
|
|
150
|
+
click.echo(f"✨ Generated changelog ({len(changelog)} characters)")
|
|
151
|
+
|
|
152
|
+
# Output changelog
|
|
153
|
+
if output:
|
|
154
|
+
# Custom output file specified
|
|
155
|
+
if os.path.exists(output) and not force:
|
|
156
|
+
if not click.confirm(f"File {output} exists. Overwrite?"):
|
|
157
|
+
click.echo("❌ Changelog generation cancelled.")
|
|
158
|
+
return
|
|
159
|
+
|
|
160
|
+
# Write to custom file
|
|
161
|
+
try:
|
|
162
|
+
with open(output, 'w', encoding='utf-8') as f:
|
|
163
|
+
f.write(changelog)
|
|
164
|
+
click.echo(f"✅ Changelog saved to {output}")
|
|
165
|
+
except IOError as e:
|
|
166
|
+
click.echo(f"❌ Failed to write to {output}: {e}", err=True)
|
|
167
|
+
else:
|
|
168
|
+
# Auto-manage changelog.md in repository root
|
|
169
|
+
try:
|
|
170
|
+
changelog_path = _manage_changelog_file(changelog, verbose)
|
|
171
|
+
click.echo(f"✅ Changelog updated in {changelog_path}")
|
|
172
|
+
|
|
173
|
+
# Also show the generated content
|
|
174
|
+
if verbose:
|
|
175
|
+
click.echo("\n" + "="*60)
|
|
176
|
+
click.echo("📋 Generated Content:")
|
|
177
|
+
click.echo("="*60)
|
|
178
|
+
click.echo(changelog)
|
|
179
|
+
click.echo("="*60)
|
|
180
|
+
except Exception as e:
|
|
181
|
+
click.echo(f"❌ Failed to update changelog.md: {e}", err=True)
|
|
182
|
+
|
|
183
|
+
except ConfigError as e:
|
|
184
|
+
click.echo(f"❌ Configuration error: {e}", err=True)
|
|
185
|
+
except Exception as e:
|
|
186
|
+
click.echo(f"❌ Unexpected error: {e}", err=True)
|
|
187
|
+
if verbose:
|
|
188
|
+
import traceback
|
|
189
|
+
click.echo(traceback.format_exc(), err=True)
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
"""Commit command implementation."""
|
|
2
|
+
|
|
3
|
+
import click
|
|
4
|
+
from typing import Optional
|
|
5
|
+
|
|
6
|
+
from git_llm_tool.core.config import get_config
|
|
7
|
+
from git_llm_tool.core.git_helper import GitHelper
|
|
8
|
+
from git_llm_tool.core.jira_helper import JiraHelper
|
|
9
|
+
from git_llm_tool.core.exceptions import GitError, ApiError, ConfigError, JiraError
|
|
10
|
+
from git_llm_tool.providers import get_provider
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def execute_commit(
|
|
14
|
+
apply: bool = False,
|
|
15
|
+
model: Optional[str] = None,
|
|
16
|
+
language: Optional[str] = None,
|
|
17
|
+
verbose: bool = False,
|
|
18
|
+
no_jira: bool = False
|
|
19
|
+
) -> None:
|
|
20
|
+
"""Execute the commit command logic.
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
apply: Whether to apply commit directly without editor
|
|
24
|
+
model: Override model from config
|
|
25
|
+
language: Override language from config
|
|
26
|
+
verbose: Enable verbose output
|
|
27
|
+
no_jira: Skip Jira ticket input
|
|
28
|
+
"""
|
|
29
|
+
try:
|
|
30
|
+
# Load configuration
|
|
31
|
+
config = get_config()
|
|
32
|
+
|
|
33
|
+
# Override config with CLI parameters
|
|
34
|
+
if model:
|
|
35
|
+
config.llm.default_model = model
|
|
36
|
+
if language:
|
|
37
|
+
config.llm.language = language
|
|
38
|
+
|
|
39
|
+
if verbose:
|
|
40
|
+
click.echo(f"📄 Using model: {config.llm.default_model}")
|
|
41
|
+
click.echo(f"🌐 Using language: {config.llm.language}")
|
|
42
|
+
|
|
43
|
+
# Initialize Git helper
|
|
44
|
+
git_helper = GitHelper()
|
|
45
|
+
|
|
46
|
+
# Get staged diff
|
|
47
|
+
if verbose:
|
|
48
|
+
click.echo("📊 Getting staged changes...")
|
|
49
|
+
|
|
50
|
+
try:
|
|
51
|
+
diff = git_helper.get_staged_diff()
|
|
52
|
+
except GitError as e:
|
|
53
|
+
click.echo(f"❌ {e}", err=True)
|
|
54
|
+
click.echo("💡 Tip: Use 'git add' to stage files before committing", err=True)
|
|
55
|
+
return
|
|
56
|
+
|
|
57
|
+
if verbose:
|
|
58
|
+
click.echo(f"📝 Found {len(diff.splitlines())} lines of changes")
|
|
59
|
+
|
|
60
|
+
# Get LLM provider
|
|
61
|
+
try:
|
|
62
|
+
provider = get_provider(config)
|
|
63
|
+
if verbose:
|
|
64
|
+
click.echo(f"🤖 Using provider: {provider.__class__.__name__}")
|
|
65
|
+
except ApiError as e:
|
|
66
|
+
click.echo(f"❌ {e}", err=True)
|
|
67
|
+
return
|
|
68
|
+
|
|
69
|
+
# Initialize Jira helper and get Jira context
|
|
70
|
+
jira_ticket, work_hours = None, None
|
|
71
|
+
|
|
72
|
+
if not no_jira:
|
|
73
|
+
jira_helper = JiraHelper(config, git_helper)
|
|
74
|
+
|
|
75
|
+
try:
|
|
76
|
+
jira_helper.validate_config()
|
|
77
|
+
jira_ticket, work_hours = jira_helper.get_jira_context(verbose=verbose)
|
|
78
|
+
|
|
79
|
+
if jira_ticket or work_hours:
|
|
80
|
+
jira_info = jira_helper.format_jira_info(jira_ticket, work_hours)
|
|
81
|
+
click.echo(f"📋 Jira Info: {jira_info}")
|
|
82
|
+
except JiraError as e:
|
|
83
|
+
if verbose:
|
|
84
|
+
click.echo(f"⚠️ Jira processing skipped: {e}")
|
|
85
|
+
# Continue without Jira integration
|
|
86
|
+
pass
|
|
87
|
+
else:
|
|
88
|
+
if verbose:
|
|
89
|
+
click.echo("🔒 Jira integration skipped (--no-jira flag)")
|
|
90
|
+
|
|
91
|
+
# Generate commit message
|
|
92
|
+
click.echo("🤖 Generating commit message...")
|
|
93
|
+
|
|
94
|
+
try:
|
|
95
|
+
commit_message = provider.generate_commit_message(
|
|
96
|
+
diff=diff,
|
|
97
|
+
jira_ticket=jira_ticket,
|
|
98
|
+
work_hours=work_hours
|
|
99
|
+
)
|
|
100
|
+
except ApiError as e:
|
|
101
|
+
click.echo(f"❌ API Error: {e}", err=True)
|
|
102
|
+
return
|
|
103
|
+
|
|
104
|
+
if verbose:
|
|
105
|
+
click.echo(f"✨ Generated message: {commit_message}")
|
|
106
|
+
|
|
107
|
+
# Apply commit or open editor
|
|
108
|
+
if apply:
|
|
109
|
+
# Direct commit
|
|
110
|
+
try:
|
|
111
|
+
git_helper.commit_with_message(commit_message)
|
|
112
|
+
click.echo("✅ Commit applied successfully!")
|
|
113
|
+
click.echo(f"📝 Message: {commit_message}")
|
|
114
|
+
except GitError as e:
|
|
115
|
+
click.echo(f"❌ Commit failed: {e}", err=True)
|
|
116
|
+
else:
|
|
117
|
+
# Open editor for review
|
|
118
|
+
click.echo("📝 Opening editor for review...")
|
|
119
|
+
try:
|
|
120
|
+
committed = git_helper.open_commit_editor(commit_message, config)
|
|
121
|
+
if committed:
|
|
122
|
+
click.echo("✅ Commit created successfully!")
|
|
123
|
+
else:
|
|
124
|
+
click.echo("❌ Commit cancelled by user")
|
|
125
|
+
except GitError as e:
|
|
126
|
+
click.echo(f"❌ Editor error: {e}", err=True)
|
|
127
|
+
|
|
128
|
+
except ConfigError as e:
|
|
129
|
+
click.echo(f"❌ Configuration error: {e}", err=True)
|
|
130
|
+
except Exception as e:
|
|
131
|
+
click.echo(f"❌ Unexpected error: {e}", err=True)
|
|
132
|
+
if verbose:
|
|
133
|
+
import traceback
|
|
134
|
+
click.echo(traceback.format_exc(), err=True)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Core module."""
|