commitgpt 0.3.10 ā 0.3.12
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/README.md +37 -1
- data/lib/commitgpt/cli.rb +5 -0
- data/lib/commitgpt/commit_ai.rb +172 -38
- data/lib/commitgpt/config_manager.rb +22 -0
- data/lib/commitgpt/setup_wizard.rb +18 -0
- data/lib/commitgpt/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ab9b9d8c6894ba37e2e16881e28fc2772336f5056f53330f91451818c5a28e36
|
|
4
|
+
data.tar.gz: e4a4d6a33c7a0270725af5e79f1da44bac5111c9810bb799231631336a5aa4c5
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7610f6291cffa1b70c7d9735096962a7e1a25f62d5b6e512c6688d0d80d1cd71961798d5867e77c0bc94dd58db027cd76168ecf833ab07fb886818b370d88616
|
|
7
|
+
data.tar.gz: fbf38cd92931dddfdb1acdf9d7a8ec290d33376fab4fd769a480f6e45a4da8a495624e8143dd58592a51294ecd5c959316143744e9ee3eb6fce395b09cfd09fc
|
data/README.md
CHANGED
|
@@ -17,9 +17,12 @@ The easiest way to install and keep CommitGPT updated.
|
|
|
17
17
|
|
|
18
18
|
```bash
|
|
19
19
|
brew tap ZPVIP/commitgpt https://github.com/ZPVIP/commitgpt
|
|
20
|
+
brew trust zpvip/commitgpt
|
|
20
21
|
brew install commitgpt
|
|
21
22
|
```
|
|
22
23
|
|
|
24
|
+
> **Note**: Recent Homebrew versions refuse to load formulae from third-party taps until you trust them, so `brew trust` is required before installing.
|
|
25
|
+
|
|
23
26
|
**Upgrade:**
|
|
24
27
|
|
|
25
28
|
```bash
|
|
@@ -194,8 +197,41 @@ docs: update API documentation
|
|
|
194
197
|
š update API documentation
|
|
195
198
|
```
|
|
196
199
|
|
|
200
|
+
### Choose Commit Message Detail Level
|
|
201
|
+
Select how much the commit message says:
|
|
202
|
+
```bash
|
|
203
|
+
$ aicm -d
|
|
204
|
+
# or
|
|
205
|
+
$ aicm --detail
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
- **Concise** - Subject line only (default)
|
|
209
|
+
- **Detailed** - Subject line, an explanatory paragraph, and a bullet list of notable changes
|
|
210
|
+
|
|
211
|
+
This is independent of the format above: the format decides how the subject line is written, the detail level decides how much follows it. Your selection is saved as `commit_detail` in `~/.config/commitgpt/config.yml`.
|
|
212
|
+
|
|
213
|
+
#### Detail Examples
|
|
214
|
+
|
|
215
|
+
**Concise:**
|
|
216
|
+
```
|
|
217
|
+
feat(dfb): store and expand DFB event and speed logs
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
**Detailed:**
|
|
221
|
+
```
|
|
222
|
+
feat(dfb): store and expand DFB event and speed logs
|
|
223
|
+
|
|
224
|
+
Persist DFB device logs ingested from the proactive `evt` (event log) and
|
|
225
|
+
`lge` (speed stats) topics, one record per message.
|
|
226
|
+
|
|
227
|
+
- Add `dfb_event_logs` and `dfb_speed_stats` tables and models, each with an
|
|
228
|
+
idempotent unique index so re-delivery and overlapping backfills are safe.
|
|
229
|
+
- Associate both log tables on `Product`.
|
|
230
|
+
- Update `README_DFB.md` to document the cloud tables.
|
|
231
|
+
```
|
|
232
|
+
|
|
197
233
|
### Check Configuration
|
|
198
|
-
View your current configuration (Provider, Model, Format, Base URL, Diff Len):
|
|
234
|
+
View your current configuration (Provider, Model, Format, Detail, Base URL, Diff Len):
|
|
199
235
|
```bash
|
|
200
236
|
$ aicm help
|
|
201
237
|
```
|
data/lib/commitgpt/cli.rb
CHANGED
|
@@ -14,6 +14,7 @@ module CommitGpt
|
|
|
14
14
|
method_option :verbose, aliases: '-v', type: :boolean, desc: 'Show git diff being sent to AI'
|
|
15
15
|
method_option :provider, aliases: '-p', type: :boolean, desc: 'Switch active provider'
|
|
16
16
|
method_option :format, aliases: '-f', type: :boolean, desc: 'Choose commit message format'
|
|
17
|
+
method_option :detail, aliases: '-d', type: :boolean, desc: 'Choose commit message detail level'
|
|
17
18
|
def generate
|
|
18
19
|
if options[:provider]
|
|
19
20
|
CommitGpt::SetupWizard.new.switch_provider
|
|
@@ -21,6 +22,8 @@ module CommitGpt
|
|
|
21
22
|
CommitGpt::SetupWizard.new.change_model
|
|
22
23
|
elsif options[:format]
|
|
23
24
|
CommitGpt::SetupWizard.new.choose_format
|
|
25
|
+
elsif options[:detail]
|
|
26
|
+
CommitGpt::SetupWizard.new.choose_detail
|
|
24
27
|
else
|
|
25
28
|
CommitGpt::CommitAi.new.aicm(verbose: options[:verbose])
|
|
26
29
|
end
|
|
@@ -42,6 +45,7 @@ module CommitGpt
|
|
|
42
45
|
shell.say ' -m, --models # Interactive model selection'
|
|
43
46
|
shell.say ' -p, --provider # Switch active provider'
|
|
44
47
|
shell.say ' -f, --format # Choose commit message format'
|
|
48
|
+
shell.say ' -d, --detail # Choose commit message detail level'
|
|
45
49
|
shell.say ' -v, --verbose # Show git diff being sent to AI'
|
|
46
50
|
shell.say ''
|
|
47
51
|
|
|
@@ -61,6 +65,7 @@ module CommitGpt
|
|
|
61
65
|
shell.say " Provider: #{config['name'].green}"
|
|
62
66
|
shell.say " Model: #{config['model'].cyan}"
|
|
63
67
|
shell.say " Format: #{format.capitalize.yellow}"
|
|
68
|
+
shell.say " Detail: #{CommitGpt::ConfigManager.get_commit_detail.capitalize.yellow}"
|
|
64
69
|
shell.say " Base URL: #{config['base_url']}"
|
|
65
70
|
shell.say " Diff Len: #{config['diff_len']}"
|
|
66
71
|
shell.say " Timeout: #{CommitGpt::ConfigManager.get_model_fetch_timeout}s"
|
data/lib/commitgpt/commit_ai.rb
CHANGED
|
@@ -25,6 +25,26 @@ module CommitGpt
|
|
|
25
25
|
'gitmoji' => ':emoji: <commit message>'
|
|
26
26
|
}.freeze
|
|
27
27
|
|
|
28
|
+
# Worked example of the detailed shape. Models follow a sample far more
|
|
29
|
+
# reliably than a description of one.
|
|
30
|
+
DETAILED_EXAMPLE = <<~EXAMPLE
|
|
31
|
+
Here is an example of the expected output, showing the exact shape only.
|
|
32
|
+
Your subject line must still follow the format above:
|
|
33
|
+
|
|
34
|
+
feat(dfb): store and expand DFB event and speed logs
|
|
35
|
+
|
|
36
|
+
Persist DFB device logs ingested from the proactive evt (event log) and
|
|
37
|
+
lge (speed stats) topics, one record per message.
|
|
38
|
+
|
|
39
|
+
- Add dfb_event_logs and dfb_speed_stats tables and models, each with an
|
|
40
|
+
idempotent unique index so re-delivery and overlapping backfills are safe.
|
|
41
|
+
- Add the canonical event-code table and the loader that expands a device
|
|
42
|
+
event byte into a human label and tags; unknown codes fall back to
|
|
43
|
+
"Unknown Event".
|
|
44
|
+
- Associate both log tables on Product.
|
|
45
|
+
- Update README_DFB.md to document the new cloud tables.
|
|
46
|
+
EXAMPLE
|
|
47
|
+
|
|
28
48
|
# Conventional commit types based on aicommits implementation
|
|
29
49
|
CONVENTIONAL_TYPES = {
|
|
30
50
|
'docs' => 'Documentation only changes',
|
|
@@ -133,6 +153,72 @@ module CommitGpt
|
|
|
133
153
|
end
|
|
134
154
|
|
|
135
155
|
@commit_format = ConfigManager.get_commit_format
|
|
156
|
+
@commit_detail = ConfigManager.get_commit_detail
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
# True when the message should carry a body, not just a subject line
|
|
160
|
+
def detailed?
|
|
161
|
+
@commit_detail == 'detailed'
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
# Streaming cutoff. Generous because models that ignore disable_reasoning
|
|
165
|
+
# spend it on inline <think> text before the message itself arrives.
|
|
166
|
+
def max_content_chars
|
|
167
|
+
detailed? ? 6000 : 1500
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
def reasoning_disabled_max_tokens
|
|
171
|
+
detailed? ? 1200 : 300
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
# Some models stream their chain of thought inside content instead of the
|
|
175
|
+
# reasoning_content field, either wrapped in <think>...</think> or with only
|
|
176
|
+
# the closing tag when the chat template pre-fills the opener. Keep what
|
|
177
|
+
# comes after the thinking, never the thinking itself.
|
|
178
|
+
def strip_reasoning(content)
|
|
179
|
+
text = content.gsub(%r{<think(?:ing)?>.*?</think(?:ing)?>}m, '')
|
|
180
|
+
text = text.rpartition(%r{</think(?:ing)?>}).last if text.match?(%r{</think(?:ing)?>})
|
|
181
|
+
# Opener with no closer: the model never came back from thinking
|
|
182
|
+
text.sub(/<think(?:ing)?>.*\z/m, '')
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
# Header printed before the streamed message starts arriving
|
|
186
|
+
def content_prefix
|
|
187
|
+
detailed? ? "⦠Commit message:\n" : '⦠Commit message: git commit -am "'
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
# Closing quote that matches content_prefix; a body has no quote to close
|
|
191
|
+
def content_suffix
|
|
192
|
+
detailed? ? '' : '"'
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
# Editor git will actually use, resolved through its own precedence chain
|
|
196
|
+
def git_editor
|
|
197
|
+
editor = `git var GIT_EDITOR 2>/dev/null`.strip
|
|
198
|
+
editor.empty? ? 'your editor' : editor
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
# Models like to wrap the whole message in a fence despite being told not to
|
|
202
|
+
def strip_code_fence(text)
|
|
203
|
+
return text unless text.start_with?('```')
|
|
204
|
+
|
|
205
|
+
text.sub(/\A```[a-zA-Z]*\n?/, '').sub(/\n?```\s*\z/, '')
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
# Trim the model output down to what git should receive
|
|
209
|
+
def finalize_message(full_content)
|
|
210
|
+
content = strip_reasoning(full_content)
|
|
211
|
+
|
|
212
|
+
unless detailed?
|
|
213
|
+
# Take only the first non-empty line to avoid repetition or multi-line garbage
|
|
214
|
+
first_line = content.split("\n").map(&:strip).reject(&:empty?).first
|
|
215
|
+
return first_line&.gsub(/\A["']|["']\z/, '') || ''
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
body = strip_code_fence(content.strip).gsub(/\A["']|["']\z/, '').strip
|
|
219
|
+
# Keep the subject/body/bullets structure but drop trailing spaces and
|
|
220
|
+
# collapse the extra blank lines models like to emit between sections
|
|
221
|
+
body.split("\n").map(&:rstrip).join("\n").gsub(/\n{3,}/, "\n\n")
|
|
136
222
|
end
|
|
137
223
|
|
|
138
224
|
def aicm(verbose: false)
|
|
@@ -150,9 +236,10 @@ module CommitGpt
|
|
|
150
236
|
|
|
151
237
|
case action
|
|
152
238
|
when :commit
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
239
|
+
# Pass argv directly: a body with quotes, backticks or newlines must
|
|
240
|
+
# not be re-interpreted by the shell
|
|
241
|
+
puts "\nā Executing: git commit -m <message>".yellow
|
|
242
|
+
system('git', 'commit', '-m', ai_commit_message)
|
|
156
243
|
puts "\n\n"
|
|
157
244
|
puts `git log -1`
|
|
158
245
|
break
|
|
@@ -160,15 +247,15 @@ module CommitGpt
|
|
|
160
247
|
puts "\n"
|
|
161
248
|
next
|
|
162
249
|
when :edit
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
if
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
250
|
+
# Let git open the message in the user's editor: it honours the
|
|
251
|
+
# GIT_EDITOR/core.editor/VISUAL/EDITOR chain, handles multi-line
|
|
252
|
+
# bodies, and aborts by itself if the message is left empty
|
|
253
|
+
puts "\nā Opening #{git_editor} with the generated message...".yellow
|
|
254
|
+
if system('git', 'commit', '-e', '-m', ai_commit_message)
|
|
255
|
+
puts "\n"
|
|
256
|
+
puts `git log -1`
|
|
170
257
|
else
|
|
171
|
-
|
|
258
|
+
puts 'ā Commit aborted.'.red
|
|
172
259
|
end
|
|
173
260
|
break
|
|
174
261
|
when :exit
|
|
@@ -219,7 +306,7 @@ module CommitGpt
|
|
|
219
306
|
prompt.select('Action:') do |menu|
|
|
220
307
|
menu.choice 'Commit', :commit
|
|
221
308
|
menu.choice 'Regenerate', :regenerate
|
|
222
|
-
menu.choice 'Edit', :edit
|
|
309
|
+
menu.choice 'Edit in editor', :edit
|
|
223
310
|
menu.choice 'Exit without commit', :exit
|
|
224
311
|
end
|
|
225
312
|
rescue TTY::Reader::InputInterrupt, Interrupt
|
|
@@ -279,7 +366,11 @@ module CommitGpt
|
|
|
279
366
|
# rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity, Metrics/BlockLength
|
|
280
367
|
def generate_commit(diff = '', chunk_label: nil)
|
|
281
368
|
# Build format-specific prompt
|
|
282
|
-
base_prompt =
|
|
369
|
+
base_prompt = if detailed?
|
|
370
|
+
'Generate a git commit message in present tense that precisely describes the key changes in the following code diff. Focus on what was changed and why, not just file names.'
|
|
371
|
+
else
|
|
372
|
+
'Generate a concise git commit message title in present tense that precisely describes the key changes in the following code diff. Focus on what was changed, not just file names. Provide only the title, no description or body.'
|
|
373
|
+
end
|
|
283
374
|
|
|
284
375
|
format_instruction = case @commit_format
|
|
285
376
|
when 'conventional'
|
|
@@ -290,17 +381,42 @@ module CommitGpt
|
|
|
290
381
|
''
|
|
291
382
|
end
|
|
292
383
|
|
|
293
|
-
format_spec =
|
|
384
|
+
format_spec = if detailed?
|
|
385
|
+
"The subject line must be in format:\n#{COMMIT_FORMATS[@commit_format]}\n\n#{DETAILED_EXAMPLE}"
|
|
386
|
+
else
|
|
387
|
+
"The output response must be in format:\n#{COMMIT_FORMATS[@commit_format]}"
|
|
388
|
+
end
|
|
389
|
+
|
|
390
|
+
rules = if detailed?
|
|
391
|
+
[
|
|
392
|
+
'- The first line is the subject: a maximum of 100 characters, no trailing period.',
|
|
393
|
+
'- Then one blank line, then a body paragraph of 1-3 sentences explaining what changed and why.',
|
|
394
|
+
'- Then one blank line, then a bullet list where each bullet starts with "- " and describes one notable change.',
|
|
395
|
+
'- Wrap body and bullet text at 72 characters.',
|
|
396
|
+
'- Cover the whole diff in 3-6 bullets. Group related files into one bullet instead of listing every file.',
|
|
397
|
+
'- Be specific: include concrete details (file, table, method, package names) rather than generic statements.',
|
|
398
|
+
'- Do not use markdown headings, bold, or code fences. Plain text only.',
|
|
399
|
+
'- Write the message directly. Do not think out loud, do not draft and revise, do not explain your reasoning, do not mention the diff or these instructions.',
|
|
400
|
+
'- Do not emit <think> tags or any text before the subject line.',
|
|
401
|
+
'- Stop immediately after the last bullet. Do not add a summary, a closing remark, or a second version of the message.',
|
|
402
|
+
'- Exclude anything unnecessary such as translation. Your entire response will be passed directly into git commit.',
|
|
403
|
+
'- Return ONLY the commit message, nothing else. No explanations, no introductions, no surrounding quotes.'
|
|
404
|
+
]
|
|
405
|
+
else
|
|
406
|
+
[
|
|
407
|
+
'- Commit message must be a maximum of 100 characters.',
|
|
408
|
+
'- Exclude anything unnecessary such as translation. Your entire response will be passed directly into git commit.',
|
|
409
|
+
'- IMPORTANT: Do not include any explanations, introductions, or additional text. Do not wrap the commit message in quotes or any other formatting. The commit message must not exceed 100 characters. Respond with ONLY the commit message text.',
|
|
410
|
+
'- Be specific: include concrete details (package names, versions, functionality) rather than generic statements.',
|
|
411
|
+
'- Return ONLY the commit message, nothing else.'
|
|
412
|
+
]
|
|
413
|
+
end
|
|
294
414
|
|
|
295
415
|
system_content = [
|
|
296
416
|
base_prompt,
|
|
297
417
|
'Message language: English.',
|
|
298
418
|
'Rules:',
|
|
299
|
-
|
|
300
|
-
'- Exclude anything unnecessary such as translation. Your entire response will be passed directly into git commit.',
|
|
301
|
-
'- IMPORTANT: Do not include any explanations, introductions, or additional text. Do not wrap the commit message in quotes or any other formatting. The commit message must not exceed 100 characters. Respond with ONLY the commit message text.',
|
|
302
|
-
'- Be specific: include concrete details (package names, versions, functionality) rather than generic statements.',
|
|
303
|
-
'- Return ONLY the commit message, nothing else.',
|
|
419
|
+
*rules,
|
|
304
420
|
format_instruction,
|
|
305
421
|
format_spec
|
|
306
422
|
].reject(&:empty?).join("\n")
|
|
@@ -331,7 +447,9 @@ module CommitGpt
|
|
|
331
447
|
|
|
332
448
|
if can_disable_reasoning
|
|
333
449
|
payload[:disable_reasoning] = true
|
|
334
|
-
|
|
450
|
+
# SGLang/vLLM back ends honour this where disable_reasoning is ignored
|
|
451
|
+
payload[:chat_template_kwargs] = { enable_thinking: false }
|
|
452
|
+
payload[:max_tokens] = reasoning_disabled_max_tokens
|
|
335
453
|
else
|
|
336
454
|
payload[:max_tokens] = configured_max_tokens
|
|
337
455
|
end
|
|
@@ -379,7 +497,7 @@ module CommitGpt
|
|
|
379
497
|
error_msg += " Raw: #{error_body}" unless error_body.to_s.strip.empty?
|
|
380
498
|
end
|
|
381
499
|
|
|
382
|
-
if can_disable_reasoning && (error_msg =~ /parameter|reasoning|unsupported/i || response.code == '400')
|
|
500
|
+
if can_disable_reasoning && (error_msg =~ /parameter|reasoning|template|unsupported/i || response.code == '400')
|
|
383
501
|
puts "ā Provider does not support 'disable_reasoning'. Updating config and retrying...".yellow
|
|
384
502
|
ConfigManager.update_provider(provider_config['name'], { 'can_disable_reasoning' => false })
|
|
385
503
|
@is_retrying = true
|
|
@@ -430,13 +548,13 @@ module CommitGpt
|
|
|
430
548
|
if chunk_label
|
|
431
549
|
print "ā #{chunk_label}: ".magenta
|
|
432
550
|
else
|
|
433
|
-
print
|
|
551
|
+
print content_prefix.green
|
|
434
552
|
end
|
|
435
553
|
printed_content_prefix = true
|
|
436
554
|
end
|
|
437
555
|
|
|
438
556
|
# Prevent infinite loops/repetitive garbage
|
|
439
|
-
if full_content.length + content_chunk.length >
|
|
557
|
+
if full_content.length + content_chunk.length > max_content_chars
|
|
440
558
|
stop_stream = true
|
|
441
559
|
break
|
|
442
560
|
end
|
|
@@ -460,7 +578,7 @@ module CommitGpt
|
|
|
460
578
|
end
|
|
461
579
|
|
|
462
580
|
# Close the quote
|
|
463
|
-
puts(chunk_label ? '' :
|
|
581
|
+
puts(chunk_label ? '' : content_suffix.green) if printed_content_prefix
|
|
464
582
|
|
|
465
583
|
# Post-processing Logic (Retry if empty content)
|
|
466
584
|
if (full_content.nil? || full_content.strip.empty?) && (full_reasoning && !full_reasoning.strip.empty?)
|
|
@@ -511,9 +629,7 @@ module CommitGpt
|
|
|
511
629
|
# Reset retrying flag
|
|
512
630
|
@is_retrying = false
|
|
513
631
|
|
|
514
|
-
|
|
515
|
-
first_line = full_content.split("\n").map(&:strip).reject(&:empty?).first
|
|
516
|
-
first_line&.gsub(/\A["']|["']\z/, '') || ''
|
|
632
|
+
finalize_message(full_content)
|
|
517
633
|
end
|
|
518
634
|
# rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity, Metrics/BlockLength
|
|
519
635
|
|
|
@@ -530,14 +646,31 @@ module CommitGpt
|
|
|
530
646
|
''
|
|
531
647
|
end
|
|
532
648
|
|
|
649
|
+
rules = if detailed?
|
|
650
|
+
[
|
|
651
|
+
'- The first line is the subject: a maximum of 100 characters, no trailing period.',
|
|
652
|
+
'- Then one blank line, then a body paragraph of 1-3 sentences explaining what changed and why.',
|
|
653
|
+
'- Then one blank line, then a bullet list where each bullet starts with "- " and describes one notable change.',
|
|
654
|
+
'- Wrap body and bullet text at 72 characters.',
|
|
655
|
+
'- Present tense.',
|
|
656
|
+
'- Be specific: include concrete details rather than generic statements.',
|
|
657
|
+
'- Do not use markdown headings, bold, or code fences. Plain text only.',
|
|
658
|
+
'- Return ONLY the commit message, nothing else. No quotes, no explanations.'
|
|
659
|
+
]
|
|
660
|
+
else
|
|
661
|
+
[
|
|
662
|
+
'- Maximum 100 characters.',
|
|
663
|
+
'- Present tense.',
|
|
664
|
+
'- Be specific: include concrete details rather than generic statements.',
|
|
665
|
+
'- Return ONLY the commit message, nothing else. No quotes, no explanations.'
|
|
666
|
+
]
|
|
667
|
+
end
|
|
668
|
+
|
|
533
669
|
system_content = [
|
|
534
670
|
'You are given multiple commit messages generated from different segments of a single large git diff.',
|
|
535
|
-
|
|
671
|
+
"Synthesize them into ONE unified git commit message that captures the overall change.#{' Keep it concise.' unless detailed?}",
|
|
536
672
|
'Rules:',
|
|
537
|
-
|
|
538
|
-
'- Present tense.',
|
|
539
|
-
'- Be specific: include concrete details rather than generic statements.',
|
|
540
|
-
'- Return ONLY the commit message, nothing else. No quotes, no explanations.',
|
|
673
|
+
*rules,
|
|
541
674
|
format_instruction
|
|
542
675
|
].reject(&:empty?).join("\n")
|
|
543
676
|
|
|
@@ -558,7 +691,9 @@ module CommitGpt
|
|
|
558
691
|
|
|
559
692
|
if can_disable_reasoning
|
|
560
693
|
payload[:disable_reasoning] = true
|
|
561
|
-
|
|
694
|
+
# SGLang/vLLM back ends honour this where disable_reasoning is ignored
|
|
695
|
+
payload[:chat_template_kwargs] = { enable_thinking: false }
|
|
696
|
+
payload[:max_tokens] = reasoning_disabled_max_tokens
|
|
562
697
|
else
|
|
563
698
|
payload[:max_tokens] = provider_config['max_tokens'] || 2000
|
|
564
699
|
end
|
|
@@ -606,11 +741,11 @@ module CommitGpt
|
|
|
606
741
|
content_chunk = delta['content']
|
|
607
742
|
if content_chunk && !content_chunk.empty?
|
|
608
743
|
unless printed_content_prefix
|
|
609
|
-
print "\n
|
|
744
|
+
print "\n#{content_prefix}".green
|
|
610
745
|
printed_content_prefix = true
|
|
611
746
|
end
|
|
612
747
|
|
|
613
|
-
if full_content.length + content_chunk.length >
|
|
748
|
+
if full_content.length + content_chunk.length > max_content_chars
|
|
614
749
|
stop_stream = true
|
|
615
750
|
break
|
|
616
751
|
end
|
|
@@ -630,15 +765,14 @@ module CommitGpt
|
|
|
630
765
|
return nil
|
|
631
766
|
end
|
|
632
767
|
|
|
633
|
-
puts
|
|
768
|
+
puts content_suffix.green if printed_content_prefix
|
|
634
769
|
|
|
635
770
|
if full_content.strip.empty?
|
|
636
771
|
puts 'ā No response from AI during synthesis.'.red
|
|
637
772
|
return nil
|
|
638
773
|
end
|
|
639
774
|
|
|
640
|
-
|
|
641
|
-
first_line&.gsub(/\A["']|["']\z/, '') || ''
|
|
775
|
+
finalize_message(full_content)
|
|
642
776
|
end
|
|
643
777
|
end
|
|
644
778
|
end
|
|
@@ -11,6 +11,11 @@ module CommitGpt
|
|
|
11
11
|
# Seconds to wait when fetching a provider's model list
|
|
12
12
|
DEFAULT_MODEL_FETCH_TIMEOUT = 30
|
|
13
13
|
|
|
14
|
+
# How much the commit message says: a subject line only, or a subject
|
|
15
|
+
# followed by an explanatory body
|
|
16
|
+
COMMIT_DETAILS = %w[concise detailed].freeze
|
|
17
|
+
DEFAULT_COMMIT_DETAIL = 'concise'
|
|
18
|
+
|
|
14
19
|
class << self
|
|
15
20
|
# Get the config directory path
|
|
16
21
|
def config_dir
|
|
@@ -168,6 +173,23 @@ module CommitGpt
|
|
|
168
173
|
save_main_config(main_config)
|
|
169
174
|
end
|
|
170
175
|
|
|
176
|
+
# Get commit detail level ('concise' or 'detailed')
|
|
177
|
+
def get_commit_detail
|
|
178
|
+
return DEFAULT_COMMIT_DETAIL unless config_exists?
|
|
179
|
+
|
|
180
|
+
main_config = YAML.load_file(main_config_path)
|
|
181
|
+
detail = main_config['commit_detail']
|
|
182
|
+
COMMIT_DETAILS.include?(detail) ? detail : DEFAULT_COMMIT_DETAIL
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
# Set commit detail level
|
|
186
|
+
def set_commit_detail(detail)
|
|
187
|
+
ensure_config_dir
|
|
188
|
+
main_config = config_exists? ? YAML.load_file(main_config_path) : { 'providers' => [], 'active_provider' => '' }
|
|
189
|
+
main_config['commit_detail'] = detail
|
|
190
|
+
save_main_config(main_config)
|
|
191
|
+
end
|
|
192
|
+
|
|
171
193
|
# Get the model list fetch timeout (seconds)
|
|
172
194
|
def get_model_fetch_timeout
|
|
173
195
|
return DEFAULT_MODEL_FETCH_TIMEOUT unless config_exists?
|
|
@@ -116,8 +116,26 @@ module CommitGpt
|
|
|
116
116
|
puts "\nā Commit format set to: #{format}".green
|
|
117
117
|
end
|
|
118
118
|
|
|
119
|
+
# Choose how much the commit message says
|
|
120
|
+
def choose_detail
|
|
121
|
+
puts "\nā Choose git commit message detail level:\n".green
|
|
122
|
+
|
|
123
|
+
detail = @prompt.select('Select detail level:', default: default_detail_index) do |menu|
|
|
124
|
+
menu.choice 'Concise - Subject line only', 'concise'
|
|
125
|
+
menu.choice 'Detailed - Subject line, explanatory paragraph, and bullet list of changes', 'detailed'
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
ConfigManager.set_commit_detail(detail)
|
|
129
|
+
puts "\nā Commit detail set to: #{detail}".green
|
|
130
|
+
end
|
|
131
|
+
|
|
119
132
|
private
|
|
120
133
|
|
|
134
|
+
# Menu position of the currently configured detail level (1-based)
|
|
135
|
+
def default_detail_index
|
|
136
|
+
ConfigManager::COMMIT_DETAILS.index(ConfigManager.get_commit_detail).to_i + 1
|
|
137
|
+
end
|
|
138
|
+
|
|
121
139
|
# Select provider from list
|
|
122
140
|
def select_provider
|
|
123
141
|
config = ConfigManager.load_config
|
data/lib/commitgpt/version.rb
CHANGED