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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 22c8ad3c04eb8a867d883b2691754ad36929589071ecad9c2fb7e064405ccc3b
4
- data.tar.gz: 820042feceec36279aa4a1de8fb396d9c30d58257ded1ba0976b8d1c843ce2d6
3
+ metadata.gz: 504c10b71930f57d82c42ee4602203394ba6ab930249fd8c2184b3fe4c13010d
4
+ data.tar.gz: 631dfec3f8b6530bb5ad7c04c7daab58ef469685a9853b471fa07ed6c151da19
5
5
  SHA512:
6
- metadata.gz: 3cc3d42b26dad5d0425201c8e9dfd674fe4ffef4fd89ba26671a2d0bbfb4c2f053ae31a92afdd8279015c3688f9c62c0fb846ad2a8464737b8c5fa6f4f0dd38d
7
- data.tar.gz: 853b80adf98ed8b3be60f2bf6a84f4c861d564c01bd67981adec30091cfd85136a8f3ab7b3479a0159c5aec797016ea37eef20f872446f163e5256423a454417
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.2.1] - 2024-03-21
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
- ## [0.2.0] - 2024-03-20
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
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "git_auto"
5
+ require "git_auto/cli"
6
+
7
+ GitAuto::CLI.start(ARGV)
@@ -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
- provider = @settings.get(:ai_provider)
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
- display_validation_errors(validation[:errors]) if validation[:errors].any?
153
+ return unless validation[:errors].any? || validation[:warnings].any?
141
154
 
142
- return unless validation[:warnings].any?
143
-
144
- display_validation_warnings(validation[:warnings])
145
- end
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
- def display_validation_errors(errors)
148
- puts "\n❌ Validation errors:".red
149
- validator = Validators::CommitMessageValidator.new
150
- errors.each { |error| puts validator.format_error(error) }
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
- def display_validation_warnings(warnings)
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
- @settings.set(key.to_sym, value)
62
- puts "✓ Setting '#{key}' updated to '#{value}'".green
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
- provider = @prompt.select("Choose AI provider:", {
117
- "OpenAI (GPT-4, GPT-3.5 Turbo)" => "openai",
118
- "Anthropic (Claude 3.5 Sonnet, Claude 3.5 Haiku)" => "claude"
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
- puts "\n=== API Request ##{@request_count += 1} ==="
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
- puts "\n=== API Response ==="
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. Use lowercase\n" \
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 if diff.nil? || diff.strip.empty?
155
+ raise EmptyDiffError, "No changes to commit" if diff.empty?
119
156
 
120
157
  # If diff is too large, use the summarized version
121
- diff = @diff_summarizer.summarize(diff) if diff.length > MAX_DIFF_SIZE
122
-
123
- if style.to_s == "minimal"
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. Use lowercase\n" \
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. Use lowercase\n" \
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
- # Uncomment the following line to see the API request and response details for debugging
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. Use lowercase\n" \
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. Use lowercase\n" \
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
- # Uncomment the following lines to see the API request and response details for debugging
323
- # log_api_request("claude", payload, temperature)
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
- puts "Debug - OpenAI message: #{message}"
369
- message.split("\n").first.strip
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
- puts "Debug - Claude content: #{content.inspect}"
534
+ raise Error, "No message content in response" if content.nil? || content.empty?
374
535
 
375
- if content.nil? || content.empty?
376
- puts "Debug - No content in response: #{json}"
377
- raise Error, "No message content in response"
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
- # Extract the first actual commit message from the response
381
- commit_message = content.scan(/(?:feat|fix|docs|style|refactor|test|chore|perf|ci|build|revert)(?:\([^)]+\))?:.*/)&.first
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
- if commit_message.nil?
384
- puts "Debug - No valid commit message pattern found in content"
385
- raise Error, "No valid commit message found in response"
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
- raise RateLimitError, "Rate limit exceeded"
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
- execute_git_command("diff", "--cached")
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/_-]+)\))? # Optional scope in parentheses
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 for conventional and minimal commits
81
- minimal_pattern = MINIMAL_COMMIT_PATTERN
82
- conventional_pattern = CONVENTIONAL_COMMIT_PATTERN
83
-
84
- unless minimal_pattern.match?(header) || conventional_pattern.match?(header)
85
- errors << "Header must follow either minimal format: <type>: <description> or conventional format: <type>(<scope>): <description>"
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module GitAuto
4
- VERSION = "0.2.1"
4
+ VERSION = "0.3.0"
5
5
  end
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.2.1
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: 2024-12-19 00:00:00.000000000 Z
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.5.23
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: []