git_auto 0.1.1 ā 0.2.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 +14 -0
- data/lib/git_auto/commands/commit_message_command.rb +5 -1
- data/lib/git_auto/commands/config_command.rb +1 -0
- data/lib/git_auto/commands/setup_command.rb +1 -0
- data/lib/git_auto/config/settings.rb +8 -8
- data/lib/git_auto/services/ai_service.rb +125 -74
- data/lib/git_auto/services/git_service.rb +13 -12
- data/lib/git_auto/validators/commit_message_validator.rb +11 -5
- data/lib/git_auto/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8ab4962fee719c30fca3c7b4577154fed588495e61b6d95e91bb66ae15f5504e
|
4
|
+
data.tar.gz: 837bac312ba13254250e8b149c97e823557e4099cf7459e40dc993db86468caf
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2f1aacf6b7c6635baf6f6bbeafabc980f39b6797201bb9eab4602d1845e52b14d7e0afd957047f305260c695830cffb51b92011d445705daea1712d6b62cc732
|
7
|
+
data.tar.gz: 85948f7190233c795197c49f833c249c55fe6e7f381c601deecc494a9aa1b16c56dfd02cbc268830749196a9a58ba24f69b1eff7adfa692b9d83df76270aecfc
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,17 @@
|
|
1
|
+
## [0.2.0] - 2024-12-15
|
2
|
+
|
3
|
+
### Added
|
4
|
+
- New "Minimal" commit message style option for more concise commits
|
5
|
+
- Display of AI model used in commit message generation for better transparency
|
6
|
+
|
7
|
+
### Fixed
|
8
|
+
- Improved Git staged changes detection for more reliable operation
|
9
|
+
- Improved commit message validation for both minimal and conventional formats
|
10
|
+
|
11
|
+
### Changed
|
12
|
+
- Updated AI service integration to support minimal commit style across OpenAI and Claude
|
13
|
+
- Improved error handling and user feedback during Git operations
|
14
|
+
|
1
15
|
## [0.1.1] - 2024-12-13
|
2
16
|
|
3
17
|
- Remove debug logging output from API requests for cleaner user experience
|
@@ -131,7 +131,11 @@ module GitAuto
|
|
131
131
|
end
|
132
132
|
|
133
133
|
def display_message_and_validation(formatted_message, validation)
|
134
|
-
|
134
|
+
provider = @settings.get(:ai_provider)
|
135
|
+
model = @settings.get(:ai_model)
|
136
|
+
model_info = "(#{provider}/#{model})".light_black
|
137
|
+
|
138
|
+
puts "\nš Generated commit message #{model_info}:".blue
|
135
139
|
puts formatted_message
|
136
140
|
|
137
141
|
display_validation_errors(validation[:errors]) if validation[:errors].any?
|
@@ -151,6 +151,7 @@ module GitAuto
|
|
151
151
|
|
152
152
|
def configure_commit_style
|
153
153
|
style = @prompt.select("Choose commit message style:", {
|
154
|
+
"Minimal (type: description)" => "minimal",
|
154
155
|
"Conventional (type(scope): description)" => "conventional",
|
155
156
|
"Simple (description only)" => "simple"
|
156
157
|
})
|
@@ -87,6 +87,7 @@ module GitAuto
|
|
87
87
|
@prompt.select(
|
88
88
|
"Select default commit message style:",
|
89
89
|
[
|
90
|
+
{ name: "Minimal (type: subject)", value: "minimal" },
|
90
91
|
{ name: "Conventional (type(scope): description)", value: "conventional" },
|
91
92
|
{ name: "Simple (verb + description)", value: "simple" },
|
92
93
|
{ name: "Detailed (summary + bullet points)", value: "detailed" }
|
@@ -67,7 +67,7 @@ module GitAuto
|
|
67
67
|
private
|
68
68
|
|
69
69
|
def ensure_config_dir
|
70
|
-
FileUtils.mkdir_p(CONFIG_DIR)
|
70
|
+
FileUtils.mkdir_p(CONFIG_DIR)
|
71
71
|
end
|
72
72
|
|
73
73
|
def load_settings
|
@@ -83,13 +83,13 @@ module GitAuto
|
|
83
83
|
raise Error, "Unsupported AI provider: #{options[:ai_provider]}"
|
84
84
|
end
|
85
85
|
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
86
|
+
return unless options[:ai_model]
|
87
|
+
|
88
|
+
provider = options[:ai_provider] || @settings[:ai_provider]
|
89
|
+
valid_models = SUPPORTED_PROVIDERS[provider][:models].values
|
90
|
+
return if valid_models.include?(options[:ai_model])
|
91
|
+
|
92
|
+
raise Error, "Unsupported AI model: #{options[:ai_model]}"
|
93
93
|
end
|
94
94
|
end
|
95
95
|
end
|
@@ -66,17 +66,27 @@ module GitAuto
|
|
66
66
|
end
|
67
67
|
|
68
68
|
def get_system_prompt(style, retry_attempt = 0)
|
69
|
-
base_prompt = case style
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
69
|
+
base_prompt = case style.to_s
|
70
|
+
when "minimal"
|
71
|
+
"You are an expert in writing minimal commit messages that follow the format: <type>: <description>\n" \
|
72
|
+
"Rules:\n" \
|
73
|
+
"1. ALWAYS start with a type from the list above\n" \
|
74
|
+
"2. NEVER include a scope\n" \
|
75
|
+
"3. Keep the message under 72 characters\n" \
|
76
|
+
"4. Use lowercase\n" \
|
77
|
+
"5. Use present tense\n" \
|
78
|
+
"6. Be descriptive but concise\n" \
|
79
|
+
"7. Do not include a period at the end"
|
80
|
+
when "conventional"
|
81
|
+
"You are an expert in writing conventional commit messages..."
|
82
|
+
else
|
83
|
+
"You are an expert in writing clear and concise git commit messages..."
|
84
|
+
end
|
75
85
|
|
76
86
|
# Add variation for retries
|
77
|
-
if retry_attempt
|
87
|
+
if retry_attempt.positive?
|
78
88
|
base_prompt += "\nPlease provide a different perspective or approach than previous attempts."
|
79
|
-
base_prompt += "\nBe more #{
|
89
|
+
base_prompt += "\nBe more #{["specific", "detailed", "creative", "concise"].sample} in this attempt."
|
80
90
|
end
|
81
91
|
|
82
92
|
base_prompt
|
@@ -110,14 +120,30 @@ module GitAuto
|
|
110
120
|
# If diff is too large, use the summarized version
|
111
121
|
diff = @diff_summarizer.summarize(diff) if diff.length > MAX_DIFF_SIZE
|
112
122
|
|
113
|
-
if style == "
|
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?
|
114
140
|
# Generate both scope and message in one call
|
115
141
|
message = case @settings.get(:ai_provider)
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
142
|
+
when "openai"
|
143
|
+
generate_openai_commit_message(diff, style)
|
144
|
+
when "claude"
|
145
|
+
generate_claude_commit_message(diff, style)
|
146
|
+
end
|
121
147
|
|
122
148
|
# Extract type and scope from the message
|
123
149
|
if message =~ /^(\w+)(?:\(([\w-]+)\))?:\s*(.+)$/
|
@@ -174,24 +200,41 @@ module GitAuto
|
|
174
200
|
|
175
201
|
# Only use temperature variations for retries
|
176
202
|
temperature = retry_attempt ? get_temperature(retry_attempt) : TEMPERATURE_VARIATIONS[0][:openai]
|
177
|
-
commit_types =
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
203
|
+
commit_types = ["feat", "fix", "docs", "style", "refactor", "test", "chore", "perf", "ci", "build",
|
204
|
+
"revert"].join("|")
|
205
|
+
|
206
|
+
system_message = case style.to_s
|
207
|
+
when "minimal"
|
208
|
+
"You are a commit message generator that MUST follow the minimal commit format: <type>: <description>\n" \
|
209
|
+
"Valid types are: #{commit_types}\n" \
|
210
|
+
"Rules:\n" \
|
211
|
+
"1. ALWAYS start with a type from the list above\n" \
|
212
|
+
"2. NEVER include a scope\n" \
|
213
|
+
"3. Keep the message under 72 characters\n" \
|
214
|
+
"4. Use lowercase\n" \
|
215
|
+
"5. Use present tense\n" \
|
216
|
+
"6. Be descriptive but concise\n" \
|
217
|
+
"7. Do not include a period at the end"
|
218
|
+
when "conventional"
|
219
|
+
"You are a commit message generator that MUST follow the conventional commit format: <type>(<scope>): <description>\n" \
|
220
|
+
"Valid types are: #{commit_types}\n" \
|
221
|
+
"Rules:\n" \
|
222
|
+
"1. ALWAYS start with a type from the list above\n" \
|
223
|
+
"2. ALWAYS use the exact format <type>(<scope>): <description>\n" \
|
224
|
+
"3. Keep the message under 72 characters\n" \
|
225
|
+
"4. Use lowercase\n" \
|
226
|
+
"5. Use present tense\n" \
|
227
|
+
"6. Be descriptive but concise\n" \
|
228
|
+
"7. Do not include a period at the end"
|
229
|
+
else
|
230
|
+
"You are an expert in writing clear and concise git commit messages..."
|
231
|
+
end
|
189
232
|
|
190
233
|
user_message = if scope
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
234
|
+
"Generate a conventional commit message with scope '#{scope}' for this diff:\n\n#{diff}"
|
235
|
+
else
|
236
|
+
"Generate a #{style} commit message for this diff:\n\n#{diff}"
|
237
|
+
end
|
195
238
|
|
196
239
|
payload = {
|
197
240
|
model: @settings.get(:ai_model),
|
@@ -206,8 +249,8 @@ module GitAuto
|
|
206
249
|
# log_api_request("openai", payload, temperature) if ENV["DEBUG"]
|
207
250
|
|
208
251
|
response = HTTP.auth("Bearer #{api_key}")
|
209
|
-
|
210
|
-
|
252
|
+
.headers(accept: "application/json")
|
253
|
+
.post(OPENAI_API_URL, json: payload)
|
211
254
|
|
212
255
|
handle_response(response)
|
213
256
|
end
|
@@ -218,33 +261,40 @@ module GitAuto
|
|
218
261
|
|
219
262
|
# Only use temperature variations for retries
|
220
263
|
temperature = retry_attempt ? get_temperature(retry_attempt) : TEMPERATURE_VARIATIONS[0][:claude]
|
221
|
-
|
264
|
+
commit_types = ["feat", "fix", "docs", "style", "refactor", "test", "chore", "perf", "ci", "build",
|
265
|
+
"revert"].join("|")
|
266
|
+
|
267
|
+
system_message = case style.to_s
|
268
|
+
when "minimal"
|
269
|
+
"You are a commit message generator that MUST follow the minimal commit format: <type>: <description>\n" \
|
270
|
+
"Valid types are: #{commit_types}\n" \
|
271
|
+
"Rules:\n" \
|
272
|
+
"1. ALWAYS start with a type from the list above\n" \
|
273
|
+
"2. NEVER include a scope\n" \
|
274
|
+
"3. Keep the message under 72 characters\n" \
|
275
|
+
"4. Use lowercase\n" \
|
276
|
+
"5. Use present tense\n" \
|
277
|
+
"6. Be descriptive but concise\n" \
|
278
|
+
"7. Do not include a period at the end"
|
279
|
+
when "conventional"
|
280
|
+
"You are a commit message generator that MUST follow the conventional commit format: <type>(<scope>): <description>\n" \
|
281
|
+
"Valid types are: #{commit_types}\n" \
|
282
|
+
"Rules:\n" \
|
283
|
+
"1. ALWAYS start with a type from the list above\n" \
|
284
|
+
"2. ALWAYS use the exact format <type>(<scope>): <description>\n" \
|
285
|
+
"3. Keep the message under 72 characters\n" \
|
286
|
+
"4. Use lowercase\n" \
|
287
|
+
"5. Use present tense\n" \
|
288
|
+
"6. Be descriptive but concise\n" \
|
289
|
+
"7. Do not include a period at the end"
|
290
|
+
else
|
291
|
+
"You are an expert in writing clear and concise git commit messages..."
|
292
|
+
end
|
222
293
|
|
223
|
-
commit_types = %w[feat fix docs style refactor test chore perf ci build revert].join('|')
|
224
294
|
user_message = if scope
|
225
|
-
"Generate
|
226
|
-
"Format: <type>: <description>\n" \
|
227
|
-
"Example: feat: add user authentication\n\n" \
|
228
|
-
"Rules:\n" \
|
229
|
-
"1. Keep the commit message under 72 characters\n" \
|
230
|
-
"2. Use lowercase\n" \
|
231
|
-
"3. Use present tense\n" \
|
232
|
-
"4. Make it unique and different from previous suggestions\n" \
|
233
|
-
"5. MUST start with one of the valid types followed by a colon\n\n" \
|
234
|
-
"Here's the diff:\n#{diff}" +
|
235
|
-
previous_suggestions_prompt
|
295
|
+
"Generate a conventional commit message with scope '#{scope}' for this diff:\n\n#{diff}"
|
236
296
|
else
|
237
|
-
"Generate
|
238
|
-
"Format: <type>: <description>\n" \
|
239
|
-
"Example: feat: add user authentication\n\n" \
|
240
|
-
"Rules:\n" \
|
241
|
-
"1. Keep the commit message under 72 characters\n" \
|
242
|
-
"2. Use lowercase\n" \
|
243
|
-
"3. Use present tense\n" \
|
244
|
-
"4. Make it unique and different from previous suggestions\n" \
|
245
|
-
"5. MUST start with one of the valid types followed by a colon\n\n" \
|
246
|
-
"Here's the diff:\n#{diff}" +
|
247
|
-
previous_suggestions_prompt
|
297
|
+
"Generate a #{style} commit message for this diff:\n\n#{diff}"
|
248
298
|
end
|
249
299
|
|
250
300
|
payload = {
|
@@ -253,7 +303,7 @@ module GitAuto
|
|
253
303
|
temperature: temperature,
|
254
304
|
top_k: 50,
|
255
305
|
top_p: 0.9,
|
256
|
-
system:
|
306
|
+
system: system_message,
|
257
307
|
messages: [
|
258
308
|
{
|
259
309
|
role: "user",
|
@@ -272,10 +322,10 @@ module GitAuto
|
|
272
322
|
# log_api_response(response.body)
|
273
323
|
|
274
324
|
response = HTTP.headers({
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
325
|
+
"Content-Type" => "application/json",
|
326
|
+
"x-api-key" => api_key,
|
327
|
+
"anthropic-version" => "2023-06-01"
|
328
|
+
}).post(CLAUDE_API_URL, json: payload)
|
279
329
|
|
280
330
|
message = handle_response(response)
|
281
331
|
message = message.downcase.strip
|
@@ -292,6 +342,8 @@ module GitAuto
|
|
292
342
|
"simple commit message"
|
293
343
|
when :scope, "scope"
|
294
344
|
"commit scope suggestion"
|
345
|
+
when :minimal, "minimal"
|
346
|
+
"minimal commit message"
|
295
347
|
else
|
296
348
|
"commit message"
|
297
349
|
end
|
@@ -309,6 +361,7 @@ module GitAuto
|
|
309
361
|
# puts "Debug - No content in response: #{json}"
|
310
362
|
raise Error, "No message content in response"
|
311
363
|
end
|
364
|
+
|
312
365
|
message.split("\n").first.strip
|
313
366
|
when "claude"
|
314
367
|
content = json.dig("content", 0, "text")
|
@@ -324,9 +377,7 @@ module GitAuto
|
|
324
377
|
|
325
378
|
message = lines.first
|
326
379
|
|
327
|
-
if message.nil? || !message.match?(/^[a-z]+:/)
|
328
|
-
raise Error, "No valid commit message found in response"
|
329
|
-
end
|
380
|
+
raise Error, "No valid commit message found in response" if message.nil? || !message.match?(/^[a-z]+:/)
|
330
381
|
|
331
382
|
message
|
332
383
|
end
|
@@ -348,18 +399,18 @@ module GitAuto
|
|
348
399
|
end
|
349
400
|
|
350
401
|
def infer_scope_from_diff(diff)
|
351
|
-
files = diff.scan(
|
402
|
+
files = diff.scan(%r{^diff --git.*?b/(.+)$}).flatten
|
352
403
|
return nil if files.empty?
|
353
404
|
|
354
405
|
scopes = files.map do |file|
|
355
|
-
parts = file.split(
|
406
|
+
parts = file.split("/")
|
356
407
|
if parts.length > 1
|
357
408
|
parts.first
|
358
409
|
else
|
359
|
-
basename = File.basename(file,
|
410
|
+
basename = File.basename(file, ".*")
|
360
411
|
|
361
412
|
if basename =~ /^(.*?)\d*$/
|
362
|
-
|
413
|
+
::Regexp.last_match(1)
|
363
414
|
else
|
364
415
|
basename
|
365
416
|
end
|
@@ -367,19 +418,19 @@ module GitAuto
|
|
367
418
|
end.compact
|
368
419
|
|
369
420
|
# Filter out overly generic scopes
|
370
|
-
scopes.reject! { |s|
|
421
|
+
scopes.reject! { |s| ["rb", "js", "py", "ts", "css", "html", "md"].include?(s) }
|
371
422
|
return nil if scopes.empty?
|
372
423
|
|
373
424
|
# Return the most common scope
|
374
425
|
scope = scopes.group_by(&:itself)
|
375
|
-
|
376
|
-
|
426
|
+
.max_by { |_, group| group.length }
|
427
|
+
&.first
|
377
428
|
|
378
429
|
# Convert to snake_case if needed
|
379
430
|
scope&.gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
|
380
|
-
|
381
|
-
|
382
|
-
|
431
|
+
&.gsub(/([a-z\d])([A-Z])/, '\1_\2')
|
432
|
+
&.tr("-", "_")
|
433
|
+
&.downcase
|
383
434
|
end
|
384
435
|
end
|
385
436
|
end
|
@@ -26,7 +26,7 @@ module GitAuto
|
|
26
26
|
|
27
27
|
def get_commit_history(limit = nil)
|
28
28
|
validate_git_repository!
|
29
|
-
format =
|
29
|
+
format = "%H%n%s%n%an%n%aI"
|
30
30
|
command = ["log", "--pretty=format:#{format}", "--no-merges"]
|
31
31
|
command << "-#{limit}" if limit
|
32
32
|
|
@@ -55,20 +55,23 @@ module GitAuto
|
|
55
55
|
private
|
56
56
|
|
57
57
|
def validate_git_repository!
|
58
|
-
|
59
|
-
|
60
|
-
|
58
|
+
return if File.directory?(".git")
|
59
|
+
|
60
|
+
raise Error, "Not a git repository (or any of the parent directories)"
|
61
61
|
end
|
62
62
|
|
63
63
|
def validate_staged_changes!
|
64
|
-
|
65
|
-
|
66
|
-
|
64
|
+
return if has_staged_changes?
|
65
|
+
|
66
|
+
raise Error, "No changes staged for commit"
|
67
67
|
end
|
68
68
|
|
69
69
|
def has_staged_changes?
|
70
|
-
|
71
|
-
|
70
|
+
# git diff --cached --quiet returns:
|
71
|
+
# - exit status 0 (success) if there are no changes
|
72
|
+
# - exit status 1 (failure) if there are changes
|
73
|
+
system("git diff --cached --quiet")
|
74
|
+
!$CHILD_STATUS.success?
|
72
75
|
end
|
73
76
|
|
74
77
|
def is_clean?
|
@@ -85,9 +88,7 @@ module GitAuto
|
|
85
88
|
def execute_git_command(*args)
|
86
89
|
output = IO.popen(["git", *args], err: [:child, :out], &:read)
|
87
90
|
|
88
|
-
unless $CHILD_STATUS.success?
|
89
|
-
raise Error, "Git command failed: git #{args.join(' ')}\n#{output}"
|
90
|
-
end
|
91
|
+
raise Error, "Git command failed: git #{args.join(" ")}\n#{output}" unless $CHILD_STATUS.success?
|
91
92
|
|
92
93
|
output
|
93
94
|
end
|
@@ -20,6 +20,12 @@ module GitAuto
|
|
20
20
|
"revert" => "Reverts a previous commit"
|
21
21
|
}.freeze
|
22
22
|
|
23
|
+
MINIMAL_COMMIT_PATTERN = /
|
24
|
+
^(?<type>#{TYPES.keys.join("|")}) # Commit type
|
25
|
+
:\s # Colon and space separator
|
26
|
+
(?<description>.+) # Commit description
|
27
|
+
/x
|
28
|
+
|
23
29
|
CONVENTIONAL_COMMIT_PATTERN = %r{
|
24
30
|
^(?<type>#{TYPES.keys.join("|")}) # Commit type
|
25
31
|
(\((?<scope>[a-z0-9/_-]+)\))? # Optional scope in parentheses
|
@@ -71,12 +77,12 @@ module GitAuto
|
|
71
77
|
|
72
78
|
errors << "Header exceeds #{HEADER_MAX_LENGTH} characters" if header.length > HEADER_MAX_LENGTH
|
73
79
|
|
74
|
-
# Validate header format for conventional commits
|
75
|
-
|
76
|
-
|
80
|
+
# Validate header format for conventional and minimal commits
|
81
|
+
minimal_pattern = MINIMAL_COMMIT_PATTERN
|
82
|
+
conventional_pattern = CONVENTIONAL_COMMIT_PATTERN
|
77
83
|
|
78
|
-
unless
|
79
|
-
errors << "Header must follow
|
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>"
|
80
86
|
end
|
81
87
|
|
82
88
|
# Suggest using lowercase for consistency
|
data/lib/git_auto/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: git_auto
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Guillermo Diaz
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-12-
|
11
|
+
date: 2024-12-16 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: thor
|