git_auto 0.2.1 → 0.3.0
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +33 -3
- data/README.md +4 -1
- data/exe/git-auto +7 -0
- data/lib/git_auto/commands/commit_message_command.rb +34 -26
- data/lib/git_auto/commands/config_command.rb +20 -6
- data/lib/git_auto/config/settings.rb +11 -0
- data/lib/git_auto/services/ai_service.rb +273 -84
- data/lib/git_auto/services/git_service.rb +7 -2
- data/lib/git_auto/validators/commit_message_validator.rb +16 -7
- data/lib/git_auto/version.rb +1 -1
- metadata +5 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 504c10b71930f57d82c42ee4602203394ba6ab930249fd8c2184b3fe4c13010d
|
4
|
+
data.tar.gz: 631dfec3f8b6530bb5ad7c04c7daab58ef469685a9853b471fa07ed6c151da19
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 12dd89f31b4815e2dd829b2596a02bed1aeb991fe830f621448aa9b3bb70c0d8dcd634844faf0c1174e39506f36e42c7882d2999cb8a29c3826fa9f55a8a8720
|
7
|
+
data.tar.gz: 3705440bfca64d8ab4eb84c0ce0b94d2724da839dbc79821689474cb9729bdc01b9a29bdfcd2d88e5cd74918b34ceda807a8ff346cb8dac693647cd4fb2c74ba
|
data/CHANGELOG.md
CHANGED
@@ -5,14 +5,44 @@ All notable changes to this project will be documented in this file.
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
7
7
|
|
8
|
-
## [0.
|
8
|
+
## [0.3.0] - 2025-01-06
|
9
|
+
|
10
|
+
### Added
|
11
|
+
- Support for Google Gemini 2.5 Flash Preview AI model
|
12
|
+
- Gemini-specific error handling and messages
|
13
|
+
- Integration tests for Gemini API
|
14
|
+
|
15
|
+
### Changed
|
16
|
+
- Updated provider selection in setup and config commands to be dynamic
|
17
|
+
- Enhanced error messages to be provider-specific
|
18
|
+
|
19
|
+
## [0.2.2] - 2025-03-22
|
20
|
+
|
21
|
+
### Fixed
|
22
|
+
- Fixed API key storage in credential store when using `config set` command
|
23
|
+
- Fixed commit message validation to properly handle different styles (conventional, minimal, simple, detailed)
|
24
|
+
- Extended conventional commit scope pattern to allow dots in file names (e.g., `index.js`)
|
25
|
+
- Improved AI prompts to consistently generate lowercase commit messages
|
26
|
+
|
27
|
+
### Added
|
28
|
+
- Enhanced detailed commit style to include multi-line messages with:
|
29
|
+
- Concise summary line
|
30
|
+
- Detailed bullet points explaining changes
|
31
|
+
- Technical context and reasoning
|
32
|
+
|
33
|
+
## [0.2.1] - 2024-12-19
|
9
34
|
|
10
35
|
### Fixed
|
36
|
+
- Improve Claude AI response handling for conventional commit messages
|
37
|
+
- Make system prompts more explicit to ensure consistent output format
|
11
38
|
- Fixed error when displaying repository status with no staged files
|
12
39
|
- Improved error handling in repository status display
|
13
|
-
- Simplified status output when no changes are staged
|
14
40
|
|
15
|
-
|
41
|
+
### Changed
|
42
|
+
- Update system prompts to be more strict and specific
|
43
|
+
- Enhance commit message validation for Claude responses
|
44
|
+
|
45
|
+
## [0.2.0] - 2024-12-15
|
16
46
|
|
17
47
|
### Added
|
18
48
|
- Support for multiple AI providers (OpenAI and Anthropic)
|
data/README.md
CHANGED
@@ -17,6 +17,7 @@ GitAuto is a Ruby gem that streamlines your git workflow by automatically genera
|
|
17
17
|
- 🤖 **AI Providers**: Supports multiple AI providers:
|
18
18
|
- OpenAI (GPT-4o, GPT-4o mini)
|
19
19
|
- Anthropic (Claude 3.5 Sonnet, Claude 3.5 Haiku)
|
20
|
+
- Google (Gemini 2.5 Flash)
|
20
21
|
- 🔒 **Secure Storage**: Your API keys are encrypted using AES-256-CBC and stored securely
|
21
22
|
|
22
23
|
## Requirements ⚙️
|
@@ -26,6 +27,7 @@ GitAuto is a Ruby gem that streamlines your git workflow by automatically genera
|
|
26
27
|
- 🎟️ One magical ingredient: an API key! Choose your AI companion:
|
27
28
|
- 🔑 OpenAI API key ([Get one here](https://platform.openai.com/api-keys))
|
28
29
|
- 🗝️ Anthropic API key ([Get one here](https://console.anthropic.com/))
|
30
|
+
- 🌟 Google Gemini API key ([Get one here](https://makersuite.google.com/app/apikey))
|
29
31
|
|
30
32
|
That's it! Say goodbye to "misc fixes" and hello to commits that actually tell a story. Your future self will thank you! 🎩✨
|
31
33
|
|
@@ -108,8 +110,9 @@ git-auto commit
|
|
108
110
|
|
109
111
|
Here's what we're planning for future releases:
|
110
112
|
|
111
|
-
- 🤖 Support for Google Gemini AI
|
112
113
|
- 📝 Automatic PR description generation
|
114
|
+
- 🎯 Custom commit message templates
|
115
|
+
- 🔄 Integration with Git hooks
|
113
116
|
- More exciting features coming soon!
|
114
117
|
|
115
118
|
## Contributing 🤝
|
data/exe/git-auto
ADDED
@@ -19,6 +19,7 @@ module GitAuto
|
|
19
19
|
@git_service = Services::GitService.new
|
20
20
|
@ai_service = Services::AIService.new(@settings)
|
21
21
|
@history_service = Services::HistoryService.new
|
22
|
+
@validator = Validators::CommitMessageValidator.new(get_commit_style)
|
22
23
|
@retry_count = 0
|
23
24
|
end
|
24
25
|
|
@@ -27,8 +28,11 @@ module GitAuto
|
|
27
28
|
status = @git_service.repository_status
|
28
29
|
validate_repository(status)
|
29
30
|
|
31
|
+
# Get staged files
|
32
|
+
staged_files = @git_service.get_staged_files
|
33
|
+
|
30
34
|
# Get and validate changes
|
31
|
-
diff = @git_service.get_staged_diff
|
35
|
+
diff = @git_service.get_staged_diff(staged_files)
|
32
36
|
validate_changes(diff)
|
33
37
|
|
34
38
|
# Show diff preview if requested
|
@@ -112,6 +116,24 @@ module GitAuto
|
|
112
116
|
message
|
113
117
|
end
|
114
118
|
|
119
|
+
# Message Validation Methods
|
120
|
+
def validate_message(message)
|
121
|
+
result = @validator.validate(message)
|
122
|
+
|
123
|
+
if result[:errors].any?
|
124
|
+
puts "\n❌ Validation errors:".red
|
125
|
+
result[:errors].each { |error| puts @validator.format_error(error) }
|
126
|
+
end
|
127
|
+
|
128
|
+
if result[:warnings].any?
|
129
|
+
puts "\n⚠️ Suggestions:".yellow
|
130
|
+
result[:warnings].each { |warning| puts @validator.format_warning(warning) }
|
131
|
+
end
|
132
|
+
|
133
|
+
puts "\nPlease edit the message to fix these errors." if result[:errors].any?
|
134
|
+
result
|
135
|
+
end
|
136
|
+
|
115
137
|
# Message Handling Methods
|
116
138
|
def handle_message(message, diff)
|
117
139
|
formatted = Formatters::MessageFormatter.new.format(message)
|
@@ -124,37 +146,23 @@ module GitAuto
|
|
124
146
|
end
|
125
147
|
end
|
126
148
|
|
127
|
-
def validate_message(message)
|
128
|
-
validator = Validators::CommitMessageValidator.new
|
129
|
-
validator.validate(message)
|
130
|
-
end
|
131
|
-
|
132
149
|
def display_message_and_validation(formatted_message, validation)
|
133
|
-
|
134
|
-
model = @settings.get(:ai_model)
|
135
|
-
model_info = "(#{provider}/#{model})".light_black
|
136
|
-
|
137
|
-
puts "\n📝 Generated commit message #{model_info}:".blue
|
150
|
+
puts "\n📝 Generated commit message (#{@settings.get(:ai_provider)}/#{@settings.get(:ai_model)}):".blue
|
138
151
|
puts formatted_message
|
139
152
|
|
140
|
-
|
153
|
+
return unless validation[:errors].any? || validation[:warnings].any?
|
141
154
|
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
155
|
+
if validation[:errors].any?
|
156
|
+
puts "\n❌ Validation errors:".red
|
157
|
+
validation[:errors].each { |error| puts @validator.format_error(error) }
|
158
|
+
end
|
146
159
|
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
puts "\nPlease edit the message to fix these errors.".yellow
|
152
|
-
end
|
160
|
+
if validation[:warnings].any?
|
161
|
+
puts "\n⚠️ Suggestions:".yellow
|
162
|
+
validation[:warnings].each { |warning| puts @validator.format_warning(warning) }
|
163
|
+
end
|
153
164
|
|
154
|
-
|
155
|
-
puts "\n⚠️ Suggestions:".yellow
|
156
|
-
validator = Validators::CommitMessageValidator.new
|
157
|
-
warnings.each { |warning| puts validator.format_warning(warning) }
|
165
|
+
puts "\nPlease edit the message to fix these errors." if validation[:errors].any?
|
158
166
|
end
|
159
167
|
|
160
168
|
def prompt_user_action
|
@@ -58,8 +58,20 @@ module GitAuto
|
|
58
58
|
exit 1
|
59
59
|
end
|
60
60
|
|
61
|
-
|
62
|
-
|
61
|
+
case key.to_s
|
62
|
+
when "openai_api_key"
|
63
|
+
@credential_store.store_api_key(value, "openai")
|
64
|
+
puts "✓ OpenAI API key updated".green
|
65
|
+
when "claude_api_key"
|
66
|
+
@credential_store.store_api_key(value, "claude")
|
67
|
+
puts "✓ Claude API key updated".green
|
68
|
+
when "gemini_api_key"
|
69
|
+
@credential_store.store_api_key(value, "gemini")
|
70
|
+
puts "✓ Gemini API key updated".green
|
71
|
+
else
|
72
|
+
@settings.set(key.to_sym, value)
|
73
|
+
puts "✓ Setting '#{key}' updated to '#{value}'".green
|
74
|
+
end
|
63
75
|
end
|
64
76
|
|
65
77
|
def interactive_config
|
@@ -113,10 +125,12 @@ module GitAuto
|
|
113
125
|
end
|
114
126
|
|
115
127
|
def configure_ai_provider
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
128
|
+
# Use the supported providers from settings
|
129
|
+
provider_choices = Config::Settings::SUPPORTED_PROVIDERS.map do |key, info|
|
130
|
+
{ name: info[:name], value: key }
|
131
|
+
end
|
132
|
+
|
133
|
+
provider = @prompt.select("Choose AI provider:", provider_choices)
|
120
134
|
|
121
135
|
@settings.save(ai_provider: provider)
|
122
136
|
puts "✓ AI provider updated to #{provider}".green
|
@@ -25,6 +25,12 @@ module GitAuto
|
|
25
25
|
"GPT-4o" => "gpt-4o",
|
26
26
|
"GPT-4o mini" => "gpt-4o-mini"
|
27
27
|
}
|
28
|
+
},
|
29
|
+
"gemini" => {
|
30
|
+
name: "Google (Gemini 2.5 Flash)",
|
31
|
+
models: {
|
32
|
+
"Gemini 2.5 Flash Preview" => "gemini-2.5-flash-preview-05-20"
|
33
|
+
}
|
28
34
|
}
|
29
35
|
}.freeze
|
30
36
|
|
@@ -52,6 +58,11 @@ module GitAuto
|
|
52
58
|
@settings[key.to_sym]
|
53
59
|
end
|
54
60
|
|
61
|
+
def set(key, value)
|
62
|
+
@settings[key.to_sym] = value
|
63
|
+
save
|
64
|
+
end
|
65
|
+
|
55
66
|
def all
|
56
67
|
@settings
|
57
68
|
end
|
@@ -14,6 +14,7 @@ module GitAuto
|
|
14
14
|
|
15
15
|
OPENAI_API_URL = "https://api.openai.com/v1/chat/completions"
|
16
16
|
CLAUDE_API_URL = "https://api.anthropic.com/v1/messages"
|
17
|
+
GEMINI_API_URL = "https://generativelanguage.googleapis.com/v1beta/models"
|
17
18
|
MAX_DIFF_SIZE = 10_000
|
18
19
|
MAX_RETRIES = 3
|
19
20
|
BACKOFF_BASE = 2
|
@@ -25,10 +26,10 @@ module GitAuto
|
|
25
26
|
end
|
26
27
|
|
27
28
|
TEMPERATURE_VARIATIONS = [
|
28
|
-
{ openai: 0.7, claude: 0.7 },
|
29
|
-
{ openai: 0.8, claude: 0.8 },
|
30
|
-
{ openai: 0.9, claude: 0.9 },
|
31
|
-
{ openai: 1.0, claude: 1.0 }
|
29
|
+
{ openai: 0.7, claude: 0.7, gemini: 0.7 },
|
30
|
+
{ openai: 0.8, claude: 0.8, gemini: 0.8 },
|
31
|
+
{ openai: 0.9, claude: 0.9, gemini: 0.9 },
|
32
|
+
{ openai: 1.0, claude: 1.0, gemini: 1.0 }
|
32
33
|
].freeze
|
33
34
|
|
34
35
|
def initialize(settings)
|
@@ -39,10 +40,13 @@ module GitAuto
|
|
39
40
|
@@temperature ||= TEMPERATURE_VARIATIONS[0]
|
40
41
|
@request_count = 0
|
41
42
|
@previous_suggestions = []
|
43
|
+
@debug_mode = ENV["GIT_AUTO_DEBUG"] == "true"
|
42
44
|
end
|
43
45
|
|
44
46
|
def log_api_request(provider, payload, temperature)
|
45
|
-
|
47
|
+
return unless @debug_mode
|
48
|
+
|
49
|
+
puts "\n=== API Request ##{@request_count += 1} ===".yellow
|
46
50
|
puts "Provider: #{provider}"
|
47
51
|
puts "Temperature: #{temperature}"
|
48
52
|
puts "Full Payload:"
|
@@ -51,7 +55,9 @@ module GitAuto
|
|
51
55
|
end
|
52
56
|
|
53
57
|
def log_api_response(response_body)
|
54
|
-
|
58
|
+
return unless @debug_mode
|
59
|
+
|
60
|
+
puts "\n=== API Response ===".yellow
|
55
61
|
puts JSON.pretty_generate(JSON.parse(response_body.to_s))
|
56
62
|
puts "===================="
|
57
63
|
end
|
@@ -73,14 +79,45 @@ module GitAuto
|
|
73
79
|
"1. ALWAYS start with a type from the list above\n" \
|
74
80
|
"2. NEVER include a scope\n" \
|
75
81
|
"3. Keep the message under 72 characters\n" \
|
76
|
-
"4.
|
82
|
+
"4. ALWAYS use lowercase - this is mandatory\n" \
|
77
83
|
"5. Use present tense\n" \
|
78
84
|
"6. Be descriptive but concise\n" \
|
79
85
|
"7. Do not include a period at the end"
|
80
86
|
when "conventional"
|
81
|
-
"You are an expert in writing conventional commit messages
|
87
|
+
"You are an expert in writing conventional commit messages that follow the format: <type>(<scope>): <description>\n" \
|
88
|
+
"Rules:\n" \
|
89
|
+
"1. ALWAYS start with a type from the list above\n" \
|
90
|
+
"2. Include a scope in parentheses when relevant\n" \
|
91
|
+
"3. Keep the message under 72 characters\n" \
|
92
|
+
"4. ALWAYS use lowercase - this is mandatory\n" \
|
93
|
+
"5. Use present tense\n" \
|
94
|
+
"6. Be descriptive but concise\n" \
|
95
|
+
"7. Do not include a period at the end"
|
96
|
+
when "detailed"
|
97
|
+
"You are an expert in writing detailed commit messages. Your message MUST follow this format:\n" \
|
98
|
+
"<summary line>\n" \
|
99
|
+
"\n" \
|
100
|
+
"<detailed description>\n" \
|
101
|
+
"\n" \
|
102
|
+
"Rules:\n" \
|
103
|
+
"1. First line is a summary under 72 characters\n" \
|
104
|
+
"2. ALWAYS use lowercase - this is mandatory\n" \
|
105
|
+
"3. ALWAYS include a blank line after the summary\n" \
|
106
|
+
"4. ALWAYS include a detailed description explaining:\n" \
|
107
|
+
" - What changes were made\n" \
|
108
|
+
" - Why the changes were necessary\n" \
|
109
|
+
" - Any technical details worth noting\n" \
|
110
|
+
"5. Use bullet points for multiple changes\n" \
|
111
|
+
"6. Use present tense\n" \
|
112
|
+
"7. You can use periods in the detailed description"
|
82
113
|
else
|
83
|
-
"You are an expert in writing clear and concise git commit messages
|
114
|
+
"You are an expert in writing clear and concise git commit messages.\n" \
|
115
|
+
"Rules:\n" \
|
116
|
+
"1. Keep the message under 72 characters\n" \
|
117
|
+
"2. ALWAYS use lowercase - this is mandatory\n" \
|
118
|
+
"3. Use present tense\n" \
|
119
|
+
"4. Be descriptive but concise\n" \
|
120
|
+
"5. Do not include a period at the end"
|
84
121
|
end
|
85
122
|
|
86
123
|
# Add variation for retries
|
@@ -115,51 +152,17 @@ module GitAuto
|
|
115
152
|
end
|
116
153
|
|
117
154
|
def generate_commit_message(diff, style: :conventional, scope: nil)
|
118
|
-
raise EmptyDiffError
|
155
|
+
raise EmptyDiffError, "No changes to commit" if diff.empty?
|
119
156
|
|
120
157
|
# If diff is too large, use the summarized version
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
message = case @settings.get(:ai_provider)
|
125
|
-
when "openai"
|
126
|
-
generate_openai_commit_message(diff, style)
|
127
|
-
when "claude"
|
128
|
-
generate_claude_commit_message(diff, style)
|
129
|
-
end
|
130
|
-
|
131
|
-
# Extract type and description from the message
|
132
|
-
if message =~ /^(\w+):\s*(.+)$/
|
133
|
-
type = ::Regexp.last_match(1)
|
134
|
-
description = ::Regexp.last_match(2)
|
135
|
-
return "#{type}: #{description}"
|
136
|
-
end
|
137
|
-
|
138
|
-
return message
|
139
|
-
elsif style.to_s == "conventional" && scope.nil?
|
140
|
-
# Generate both scope and message in one call
|
141
|
-
message = case @settings.get(:ai_provider)
|
142
|
-
when "openai"
|
143
|
-
generate_openai_commit_message(diff, style)
|
144
|
-
when "claude"
|
145
|
-
generate_claude_commit_message(diff, style)
|
146
|
-
end
|
147
|
-
|
148
|
-
# Extract type and scope from the message
|
149
|
-
if message =~ /^(\w+)(?:\(([\w-]+)\))?:\s*(.+)$/
|
150
|
-
type = ::Regexp.last_match(1)
|
151
|
-
existing_scope = ::Regexp.last_match(2)
|
152
|
-
description = ::Regexp.last_match(3)
|
153
|
-
|
154
|
-
# If we got a scope in the message, use it, otherwise generate one
|
155
|
-
scope ||= existing_scope || infer_scope_from_diff(diff)
|
156
|
-
return scope ? "#{type}(#{scope}): #{description}" : "#{type}: #{description}"
|
157
|
-
end
|
158
|
-
|
159
|
-
# If message doesn't match expected format, just return it as is
|
160
|
-
return message
|
158
|
+
if diff.length > MAX_DIFF_SIZE
|
159
|
+
puts "\n⚠️ Diff is large, using summarized version...".yellow if @debug_mode
|
160
|
+
diff = @diff_summarizer.summarize(diff)
|
161
161
|
end
|
162
162
|
|
163
|
+
# Store the commit style in settings for use in handle_response
|
164
|
+
@settings.set(:commit_style, style.to_s)
|
165
|
+
|
163
166
|
retries = 0
|
164
167
|
begin
|
165
168
|
case @settings.get(:ai_provider)
|
@@ -167,6 +170,8 @@ module GitAuto
|
|
167
170
|
generate_openai_commit_message(diff, style, scope)
|
168
171
|
when "claude"
|
169
172
|
generate_claude_commit_message(diff, style, scope)
|
173
|
+
when "gemini"
|
174
|
+
generate_gemini_commit_message(diff, style, scope)
|
170
175
|
else
|
171
176
|
raise GitAuto::Errors::InvalidProviderError, "Invalid AI provider specified"
|
172
177
|
end
|
@@ -190,8 +195,7 @@ module GitAuto
|
|
190
195
|
def previous_suggestions_prompt
|
191
196
|
return "" if @previous_suggestions.empty?
|
192
197
|
|
193
|
-
"\nPrevious suggestions that you MUST NOT repeat:\n"
|
194
|
-
@previous_suggestions.map { |s| "- #{s}" }.join("\n")
|
198
|
+
"\nPrevious suggestions that you MUST NOT repeat:\n#{@previous_suggestions.map { |s| "- #{s}" }.join("\n")}"
|
195
199
|
end
|
196
200
|
|
197
201
|
def generate_openai_commit_message(diff, style, scope = nil, retry_attempt = nil)
|
@@ -211,7 +215,7 @@ module GitAuto
|
|
211
215
|
"1. ALWAYS start with a type from the list above\n" \
|
212
216
|
"2. NEVER include a scope\n" \
|
213
217
|
"3. Keep the message under 72 characters\n" \
|
214
|
-
"4.
|
218
|
+
"4. ALWAYS use lowercase - this is mandatory\n" \
|
215
219
|
"5. Use present tense\n" \
|
216
220
|
"6. Be descriptive but concise\n" \
|
217
221
|
"7. Do not include a period at the end"
|
@@ -221,14 +225,39 @@ module GitAuto
|
|
221
225
|
"2. Use format: <type>(<scope>): <description>\n" \
|
222
226
|
"3. Valid types are: #{commit_types}\n" \
|
223
227
|
"4. Keep under 72 characters\n" \
|
224
|
-
"5.
|
228
|
+
"5. ALWAYS use lowercase - this is mandatory\n" \
|
225
229
|
"6. Use present tense\n" \
|
226
230
|
"7. Be descriptive but concise\n" \
|
227
231
|
"8. No period at the end\n" \
|
228
232
|
"9. NO explanations or additional text\n" \
|
229
233
|
"10. NO markdown formatting"
|
234
|
+
when "detailed"
|
235
|
+
"You are a commit message generator that MUST follow this format EXACTLY:\n" \
|
236
|
+
"<summary line>\n" \
|
237
|
+
"\n" \
|
238
|
+
"<detailed description>\n" \
|
239
|
+
"\n" \
|
240
|
+
"Rules:\n" \
|
241
|
+
"1. First line is a summary under 72 characters\n" \
|
242
|
+
"2. ALWAYS use lowercase - this is mandatory\n" \
|
243
|
+
"3. ALWAYS include a blank line after the summary\n" \
|
244
|
+
"4. ALWAYS include a detailed description explaining:\n" \
|
245
|
+
" - What changes were made\n" \
|
246
|
+
" - Why the changes were necessary\n" \
|
247
|
+
" - Any technical details worth noting\n" \
|
248
|
+
"5. Use bullet points for multiple changes\n" \
|
249
|
+
"6. Use present tense\n" \
|
250
|
+
"7. You can use periods in the detailed description\n" \
|
251
|
+
"8. NO explanations or additional text\n" \
|
252
|
+
"9. NO markdown formatting"
|
230
253
|
else
|
231
|
-
"You are an expert in writing clear and concise git commit messages
|
254
|
+
"You are an expert in writing clear and concise git commit messages.\n" \
|
255
|
+
"Rules:\n" \
|
256
|
+
"1. Keep the message under 72 characters\n" \
|
257
|
+
"2. ALWAYS use lowercase - this is mandatory\n" \
|
258
|
+
"3. Use present tense\n" \
|
259
|
+
"4. Be descriptive but concise\n" \
|
260
|
+
"5. Do not include a period at the end"
|
232
261
|
end
|
233
262
|
|
234
263
|
user_message = if scope
|
@@ -246,13 +275,14 @@ module GitAuto
|
|
246
275
|
temperature: temperature
|
247
276
|
}
|
248
277
|
|
249
|
-
|
250
|
-
# log_api_request("openai", payload, temperature) if ENV["DEBUG"]
|
278
|
+
log_api_request("openai", payload, temperature) if @debug_mode
|
251
279
|
|
252
280
|
response = HTTP.auth("Bearer #{api_key}")
|
253
281
|
.headers(accept: "application/json")
|
254
282
|
.post(OPENAI_API_URL, json: payload)
|
255
283
|
|
284
|
+
log_api_response(response.body) if @debug_mode
|
285
|
+
|
256
286
|
handle_response(response)
|
257
287
|
end
|
258
288
|
|
@@ -273,7 +303,7 @@ module GitAuto
|
|
273
303
|
"1. ALWAYS start with a type from the list above\n" \
|
274
304
|
"2. NEVER include a scope\n" \
|
275
305
|
"3. Keep the message under 72 characters\n" \
|
276
|
-
"4.
|
306
|
+
"4. ALWAYS use lowercase - this is mandatory\n" \
|
277
307
|
"5. Use present tense\n" \
|
278
308
|
"6. Be descriptive but concise\n" \
|
279
309
|
"7. Do not include a period at the end"
|
@@ -283,14 +313,39 @@ module GitAuto
|
|
283
313
|
"2. Use format: <type>(<scope>): <description>\n" \
|
284
314
|
"3. Valid types are: #{commit_types}\n" \
|
285
315
|
"4. Keep under 72 characters\n" \
|
286
|
-
"5.
|
316
|
+
"5. ALWAYS use lowercase - this is mandatory\n" \
|
287
317
|
"6. Use present tense\n" \
|
288
318
|
"7. Be descriptive but concise\n" \
|
289
319
|
"8. No period at the end\n" \
|
290
320
|
"9. NO explanations or additional text\n" \
|
291
321
|
"10. NO markdown formatting"
|
322
|
+
when "detailed"
|
323
|
+
"You are a commit message generator that MUST follow this format EXACTLY:\n" \
|
324
|
+
"<summary line>\n" \
|
325
|
+
"\n" \
|
326
|
+
"<detailed description>\n" \
|
327
|
+
"\n" \
|
328
|
+
"Rules:\n" \
|
329
|
+
"1. First line is a summary under 72 characters\n" \
|
330
|
+
"2. ALWAYS use lowercase - this is mandatory\n" \
|
331
|
+
"3. ALWAYS include a blank line after the summary\n" \
|
332
|
+
"4. ALWAYS include a detailed description explaining:\n" \
|
333
|
+
" - What changes were made\n" \
|
334
|
+
" - Why the changes were necessary\n" \
|
335
|
+
" - Any technical details worth noting\n" \
|
336
|
+
"5. Use bullet points for multiple changes\n" \
|
337
|
+
"6. Use present tense\n" \
|
338
|
+
"7. You can use periods in the detailed description\n" \
|
339
|
+
"8. NO explanations or additional text\n" \
|
340
|
+
"9. NO markdown formatting"
|
292
341
|
else
|
293
|
-
"You are an expert in writing clear and concise git commit messages
|
342
|
+
"You are an expert in writing clear and concise git commit messages.\n" \
|
343
|
+
"Rules:\n" \
|
344
|
+
"1. Keep the message under 72 characters\n" \
|
345
|
+
"2. ALWAYS use lowercase - this is mandatory\n" \
|
346
|
+
"3. Use present tense\n" \
|
347
|
+
"4. Be descriptive but concise\n" \
|
348
|
+
"5. Do not include a period at the end"
|
294
349
|
end
|
295
350
|
|
296
351
|
user_message = if scope
|
@@ -319,16 +374,120 @@ module GitAuto
|
|
319
374
|
]
|
320
375
|
}
|
321
376
|
|
322
|
-
|
323
|
-
|
324
|
-
# log_api_response(response.body)
|
325
|
-
|
377
|
+
log_api_request("claude", payload, temperature) if @debug_mode
|
378
|
+
|
326
379
|
response = HTTP.headers({
|
327
380
|
"Content-Type" => "application/json",
|
328
381
|
"x-api-key" => api_key,
|
329
382
|
"anthropic-version" => "2023-06-01"
|
330
383
|
}).post(CLAUDE_API_URL, json: payload)
|
331
384
|
|
385
|
+
log_api_response(response.body) if @debug_mode
|
386
|
+
|
387
|
+
message = handle_response(response)
|
388
|
+
message = message.downcase.strip
|
389
|
+
message = message.sub(/\.$/, "") # Remove trailing period if present
|
390
|
+
add_suggestion(message)
|
391
|
+
end
|
392
|
+
|
393
|
+
def generate_gemini_commit_message(diff, style, scope = nil, retry_attempt = nil)
|
394
|
+
api_key = @credential_store.get_api_key("gemini")
|
395
|
+
raise APIKeyError, "Gemini API key is not set. Please set it using `git_auto config`" unless api_key
|
396
|
+
|
397
|
+
# Only use temperature variations for retries
|
398
|
+
temperature = retry_attempt ? get_temperature(retry_attempt) : TEMPERATURE_VARIATIONS[0][:gemini]
|
399
|
+
commit_types = ["feat", "fix", "docs", "style", "refactor", "test", "chore", "perf", "ci", "build",
|
400
|
+
"revert"].join("|")
|
401
|
+
|
402
|
+
system_message = case style.to_s
|
403
|
+
when "minimal"
|
404
|
+
"You are a commit message generator that MUST follow the minimal commit format: <type>: <description>\n" \
|
405
|
+
"Valid types are: #{commit_types}\n" \
|
406
|
+
"Rules:\n" \
|
407
|
+
"1. ALWAYS start with a type from the list above\n" \
|
408
|
+
"2. NEVER include a scope\n" \
|
409
|
+
"3. Keep the message under 72 characters\n" \
|
410
|
+
"4. ALWAYS use lowercase - this is mandatory\n" \
|
411
|
+
"5. Use present tense\n" \
|
412
|
+
"6. Be descriptive but concise\n" \
|
413
|
+
"7. Do not include a period at the end"
|
414
|
+
when "conventional"
|
415
|
+
"You are a commit message generator that MUST follow these rules EXACTLY:\n" \
|
416
|
+
"1. ONLY output a single line containing the commit message\n" \
|
417
|
+
"2. Use format: <type>(<scope>): <description>\n" \
|
418
|
+
"3. Valid types are: #{commit_types}\n" \
|
419
|
+
"4. Keep under 72 characters\n" \
|
420
|
+
"5. ALWAYS use lowercase - this is mandatory\n" \
|
421
|
+
"6. Use present tense\n" \
|
422
|
+
"7. Be descriptive but concise\n" \
|
423
|
+
"8. No period at the end\n" \
|
424
|
+
"9. NO explanations or additional text\n" \
|
425
|
+
"10. NO markdown formatting"
|
426
|
+
when "detailed"
|
427
|
+
"You are a commit message generator that MUST follow this format EXACTLY:\n" \
|
428
|
+
"<summary line>\n" \
|
429
|
+
"\n" \
|
430
|
+
"<detailed description>\n" \
|
431
|
+
"\n" \
|
432
|
+
"Rules:\n" \
|
433
|
+
"1. First line is a summary under 72 characters\n" \
|
434
|
+
"2. ALWAYS use lowercase - this is mandatory\n" \
|
435
|
+
"3. ALWAYS include a blank line after the summary\n" \
|
436
|
+
"4. ALWAYS include a detailed description explaining:\n" \
|
437
|
+
" - What changes were made\n" \
|
438
|
+
" - Why the changes were necessary\n" \
|
439
|
+
" - Any technical details worth noting\n" \
|
440
|
+
"5. Use bullet points for multiple changes\n" \
|
441
|
+
"6. Use present tense\n" \
|
442
|
+
"7. You can use periods in the detailed description\n" \
|
443
|
+
"8. NO explanations or additional text\n" \
|
444
|
+
"9. NO markdown formatting"
|
445
|
+
else
|
446
|
+
"You are an expert in writing clear and concise git commit messages.\n" \
|
447
|
+
"Rules:\n" \
|
448
|
+
"1. Keep the message under 72 characters\n" \
|
449
|
+
"2. ALWAYS use lowercase - this is mandatory\n" \
|
450
|
+
"3. Use present tense\n" \
|
451
|
+
"4. Be descriptive but concise\n" \
|
452
|
+
"5. Do not include a period at the end"
|
453
|
+
end
|
454
|
+
|
455
|
+
user_message = if scope
|
456
|
+
"Generate a conventional commit message with scope '#{scope}' for this diff:\n\n#{diff}"
|
457
|
+
else
|
458
|
+
"Generate a #{style} commit message for this diff:\n\n#{diff}"
|
459
|
+
end
|
460
|
+
|
461
|
+
model = @settings.get(:ai_model)
|
462
|
+
url = "#{GEMINI_API_URL}/#{model}:generateContent?key=#{api_key}"
|
463
|
+
|
464
|
+
payload = {
|
465
|
+
contents: [
|
466
|
+
{
|
467
|
+
parts: [
|
468
|
+
{
|
469
|
+
text: "#{system_message}\n\n#{user_message}"
|
470
|
+
}
|
471
|
+
]
|
472
|
+
}
|
473
|
+
],
|
474
|
+
generationConfig: {
|
475
|
+
temperature: temperature,
|
476
|
+
topK: 40,
|
477
|
+
topP: 0.95,
|
478
|
+
candidateCount: 1,
|
479
|
+
maxOutputTokens: 1024
|
480
|
+
}
|
481
|
+
}
|
482
|
+
|
483
|
+
log_api_request("gemini", payload, temperature) if @debug_mode
|
484
|
+
|
485
|
+
response = HTTP.headers({
|
486
|
+
"Content-Type" => "application/json"
|
487
|
+
}).post(url, json: payload)
|
488
|
+
|
489
|
+
log_api_response(response.body) if @debug_mode
|
490
|
+
|
332
491
|
message = handle_response(response)
|
333
492
|
message = message.downcase.strip
|
334
493
|
message = message.sub(/\.$/, "") # Remove trailing period if present
|
@@ -346,6 +505,8 @@ module GitAuto
|
|
346
505
|
"commit scope suggestion"
|
347
506
|
when :minimal, "minimal"
|
348
507
|
"minimal commit message"
|
508
|
+
when :detailed, "detailed"
|
509
|
+
"detailed commit message"
|
349
510
|
else
|
350
511
|
"commit message"
|
351
512
|
end
|
@@ -355,38 +516,47 @@ module GitAuto
|
|
355
516
|
case response.code
|
356
517
|
when 200
|
357
518
|
json = JSON.parse(response.body.to_s)
|
358
|
-
puts "\nDebug - API Response: #{json.inspect}"
|
359
519
|
|
360
520
|
case @settings.get(:ai_provider)
|
361
521
|
when "openai"
|
362
522
|
message = json.dig("choices", 0, "message", "content")
|
363
|
-
if message.nil? || message.empty?
|
364
|
-
puts "Debug - No content in response: #{json}"
|
365
|
-
raise Error, "No message content in response"
|
366
|
-
end
|
523
|
+
raise Error, "No message content in response" if message.nil? || message.empty?
|
367
524
|
|
368
|
-
|
369
|
-
|
525
|
+
# For detailed style, keep the full message
|
526
|
+
if @settings.get(:commit_style) == "detailed"
|
527
|
+
message.strip
|
528
|
+
else
|
529
|
+
message.split("\n").first.strip
|
530
|
+
end
|
370
531
|
|
371
532
|
when "claude"
|
372
533
|
content = json.dig("content", 0, "text")
|
373
|
-
|
534
|
+
raise Error, "No message content in response" if content.nil? || content.empty?
|
374
535
|
|
375
|
-
|
376
|
-
|
377
|
-
|
536
|
+
# For detailed style, keep the full message
|
537
|
+
if @settings.get(:commit_style) == "detailed"
|
538
|
+
content.strip
|
539
|
+
else
|
540
|
+
# Extract the first actual commit message from the response
|
541
|
+
commit_message = content.scan(/(?:feat|fix|docs|style|refactor|test|chore|perf|ci|build|revert)(?:\([^)]+\))?:.*/)&.first
|
542
|
+
raise Error, "No valid commit message found in response" if commit_message.nil?
|
543
|
+
commit_message.strip
|
378
544
|
end
|
379
545
|
|
380
|
-
|
381
|
-
|
546
|
+
when "gemini"
|
547
|
+
content = json.dig("candidates", 0, "content", "parts", 0, "text")
|
548
|
+
raise Error, "No message content in response" if content.nil? || content.empty?
|
382
549
|
|
383
|
-
|
384
|
-
|
385
|
-
|
550
|
+
# For detailed style, keep the full message
|
551
|
+
if @settings.get(:commit_style) == "detailed"
|
552
|
+
content.strip
|
553
|
+
else
|
554
|
+
# Clean up the response and extract just the commit message
|
555
|
+
lines = content.strip.split("\n")
|
556
|
+
# Find the first line that looks like a commit message
|
557
|
+
commit_line = lines.find { |line| line.match(/^(feat|fix|docs|style|refactor|test|chore|perf|ci|build|revert)/) }
|
558
|
+
commit_line || lines.first.strip
|
386
559
|
end
|
387
|
-
|
388
|
-
puts "Debug - Extracted commit message: #{commit_message}"
|
389
|
-
commit_message.strip
|
390
560
|
end
|
391
561
|
when 401
|
392
562
|
raise APIKeyError, "Invalid API key" unless ENV["RACK_ENV"] == "test"
|
@@ -398,8 +568,27 @@ module GitAuto
|
|
398
568
|
|
399
569
|
"test commit message"
|
400
570
|
|
571
|
+
when 403
|
572
|
+
# Gemini-specific error for invalid API key
|
573
|
+
provider = @settings.get(:ai_provider)
|
574
|
+
if provider == "gemini"
|
575
|
+
raise APIKeyError, "Invalid Gemini API key. Please check your API key at https://makersuite.google.com/app/apikey"
|
576
|
+
else
|
577
|
+
raise APIKeyError, "Access forbidden. Please check your API key."
|
578
|
+
end
|
579
|
+
|
401
580
|
when 429
|
402
|
-
|
581
|
+
provider = @settings.get(:ai_provider)
|
582
|
+
case provider
|
583
|
+
when "gemini"
|
584
|
+
raise RateLimitError, "Gemini API rate limit exceeded. Please wait a moment and try again."
|
585
|
+
when "openai"
|
586
|
+
raise RateLimitError, "OpenAI API rate limit exceeded. Please try again later."
|
587
|
+
when "claude"
|
588
|
+
raise RateLimitError, "Claude API rate limit exceeded. Please try again later."
|
589
|
+
else
|
590
|
+
raise RateLimitError, "Rate limit exceeded"
|
591
|
+
end
|
403
592
|
else
|
404
593
|
raise Error, "API request failed with status #{response.code}: #{response.body}"
|
405
594
|
end
|
@@ -7,9 +7,14 @@ module GitAuto
|
|
7
7
|
class GitService
|
8
8
|
class Error < StandardError; end
|
9
9
|
|
10
|
-
def get_staged_diff
|
10
|
+
def get_staged_diff(files = nil)
|
11
11
|
validate_git_repository!
|
12
|
-
|
12
|
+
if files
|
13
|
+
files = [files] unless files.is_a?(Array)
|
14
|
+
execute_git_command("diff", "--cached", "--", *files)
|
15
|
+
else
|
16
|
+
execute_git_command("diff", "--cached")
|
17
|
+
end
|
13
18
|
end
|
14
19
|
|
15
20
|
def get_staged_files
|
@@ -28,11 +28,15 @@ module GitAuto
|
|
28
28
|
|
29
29
|
CONVENTIONAL_COMMIT_PATTERN = %r{
|
30
30
|
^(?<type>#{TYPES.keys.join("|")}) # Commit type
|
31
|
-
(\((?<scope>[a-z0-9/_
|
31
|
+
(\((?<scope>[a-z0-9/_\.-]+)\))? # Optional scope in parentheses
|
32
32
|
:\s # Colon and space separator
|
33
33
|
(?<description>.+) # Commit description
|
34
34
|
}x
|
35
35
|
|
36
|
+
def initialize(style = "conventional")
|
37
|
+
@style = style.to_s
|
38
|
+
end
|
39
|
+
|
36
40
|
def validate(message)
|
37
41
|
errors = []
|
38
42
|
warnings = []
|
@@ -77,12 +81,17 @@ module GitAuto
|
|
77
81
|
|
78
82
|
errors << "Header exceeds #{HEADER_MAX_LENGTH} characters" if header.length > HEADER_MAX_LENGTH
|
79
83
|
|
80
|
-
# Validate header format
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
errors << "Header must follow
|
84
|
+
# Validate header format based on style
|
85
|
+
case @style
|
86
|
+
when "conventional"
|
87
|
+
errors << "Header must follow conventional format: <type>(<scope>): <description>" unless CONVENTIONAL_COMMIT_PATTERN.match?(header)
|
88
|
+
when "minimal"
|
89
|
+
errors << "Header must follow minimal format: <type>: <description>" unless MINIMAL_COMMIT_PATTERN.match?(header)
|
90
|
+
when "simple", "detailed"
|
91
|
+
# No specific format required for simple and detailed styles
|
92
|
+
else
|
93
|
+
# For unknown styles, suggest using conventional format
|
94
|
+
warnings << "Unknown style '#{@style}', consider using conventional format: <type>(<scope>): <description>"
|
86
95
|
end
|
87
96
|
|
88
97
|
# Suggest using lowercase for consistency
|
data/lib/git_auto/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: git_auto
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Guillermo Diaz
|
8
|
-
autorequire:
|
9
8
|
bindir: exe
|
10
9
|
cert_chain: []
|
11
|
-
date:
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
12
11
|
dependencies:
|
13
12
|
- !ruby/object:Gem::Dependency
|
14
13
|
name: thor
|
@@ -212,6 +211,7 @@ description: GitAuto streamlines your git workflow by automatically generating m
|
|
212
211
|
email:
|
213
212
|
- diazgdev@gmail.com
|
214
213
|
executables:
|
214
|
+
- git-auto
|
215
215
|
- git_auto
|
216
216
|
extensions: []
|
217
217
|
extra_rdoc_files: []
|
@@ -219,6 +219,7 @@ files:
|
|
219
219
|
- CHANGELOG.md
|
220
220
|
- LICENSE.txt
|
221
221
|
- README.md
|
222
|
+
- exe/git-auto
|
222
223
|
- exe/git_auto
|
223
224
|
- lib/git_auto.rb
|
224
225
|
- lib/git_auto/cli.rb
|
@@ -246,7 +247,6 @@ metadata:
|
|
246
247
|
source_code_uri: https://github.com/diazgdev/git_auto
|
247
248
|
changelog_uri: https://github.com/diazgdev/git_auto/blob/main/CHANGELOG.md
|
248
249
|
rubygems_mfa_required: 'true'
|
249
|
-
post_install_message:
|
250
250
|
rdoc_options: []
|
251
251
|
require_paths:
|
252
252
|
- lib
|
@@ -261,8 +261,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
261
261
|
- !ruby/object:Gem::Version
|
262
262
|
version: '0'
|
263
263
|
requirements: []
|
264
|
-
rubygems_version: 3.
|
265
|
-
signing_key:
|
264
|
+
rubygems_version: 3.6.8
|
266
265
|
specification_version: 4
|
267
266
|
summary: AI-powered git commit messages using OpenAI or Anthropic APIs
|
268
267
|
test_files: []
|