commiti 1.2.3 → 1.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/README.md +7 -1
- data/bin/commiti +6 -1
- data/lib/commiti.rb +1 -0
- data/lib/flows/commit_flow.rb +89 -0
- data/lib/services/flow_context_builder.rb +2 -0
- data/lib/services/git/commit/change_grouping.rb +85 -0
- data/lib/services/git/commit/commit_execution.rb +4 -4
- data/lib/services/git/commit/commit_staging.rb +2 -2
- data/lib/services/git/git_writer.rb +19 -2
- data/lib/services/git/pr/pr_opener.rb +8 -1
- data/lib/services/helpers/config_loader.rb +2 -0
- metadata +3 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 751893cc1501fbd4723682605f17bcc6f0617fafe28997b398affd8e33a20d1e
|
|
4
|
+
data.tar.gz: 6b05cf37e1617655547c5e2de646dd75d07cde47d0883b70ee8eb7114242719b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 777e2d6ddc6e40f0599abbe0ee191b819993926c5e0ca5bed79abb81407071356fa97807de031ba21dc3221a96bfa8b526765ee35a80aeee95cf9410c952214f
|
|
7
|
+
data.tar.gz: 739b300910794916477e8222b1ba2ea4f08698c337b1af10273c126a8fe8f3c619c6ed8171134cf545550e10767746b3b8d0da29f4cd203627ac7e26108edabf
|
data/README.md
CHANGED
|
@@ -51,6 +51,7 @@ GOOGLE_API_KEY=your_google_ai_key
|
|
|
51
51
|
# COMMITI_CANDIDATES=1
|
|
52
52
|
# COMMITI_BASE_BRANCH=main
|
|
53
53
|
# COMMITI_NO_COPY=false
|
|
54
|
+
# COMMITI_AUTO_SPLIT=false
|
|
54
55
|
```
|
|
55
56
|
|
|
56
57
|
`GEMINI_API_KEY` is also accepted as an alias for `GOOGLE_API_KEY`.
|
|
@@ -67,10 +68,14 @@ Never commit `.env` to git.
|
|
|
67
68
|
- `--base BRANCH` base branch for PR diff (default: `main`)
|
|
68
69
|
- `--no-copy` print output only, skip clipboard copy
|
|
69
70
|
- `--candidates N` generate `N` output candidates (`1`-`5`, default: `1`)
|
|
71
|
+
- `--auto-split` auto-group staged changes into multiple connected commits (commit flow only)
|
|
70
72
|
- `-h`, `--help` show help
|
|
71
73
|
|
|
72
74
|
## Commit Flow (`--type commit`)
|
|
73
75
|
|
|
76
|
+
By default, Commiti creates a single commit from staged changes.
|
|
77
|
+
Use `--auto-split` to let Commiti group connected file changes into multiple atomic commits.
|
|
78
|
+
|
|
74
79
|
1. Shows `git status --short`.
|
|
75
80
|
2. Asks for confirmation before staging (`git add -A`).
|
|
76
81
|
3. Ensures there are staged changes.
|
|
@@ -109,7 +114,8 @@ Commit edit mode uses:
|
|
|
109
114
|
- `## Changes Made`
|
|
110
115
|
- `## Testing Notes`
|
|
111
116
|
3. Builds provider compare/MR URL with prefilled title/body using query params.
|
|
112
|
-
- GitHub
|
|
117
|
+
- GitHub: compare URL with `quick_pull=1` (opens the PR form directly)
|
|
118
|
+
- GitBucket: compare URL with `expand=1`
|
|
113
119
|
- GitLab: new merge request URL
|
|
114
120
|
- If the URL would exceed safe browser/provider limits, Commiti drops description prefill automatically and keeps the shortest usable URL.
|
|
115
121
|
4. Asks before opening browser.
|
data/bin/commiti
CHANGED
|
@@ -6,7 +6,8 @@ require 'dotenv/load'
|
|
|
6
6
|
require 'commiti'
|
|
7
7
|
|
|
8
8
|
options = {
|
|
9
|
-
type: :commit
|
|
9
|
+
type: :commit,
|
|
10
|
+
auto_split: false
|
|
10
11
|
}
|
|
11
12
|
|
|
12
13
|
OptionParser.new do |opts|
|
|
@@ -35,6 +36,10 @@ OptionParser.new do |opts|
|
|
|
35
36
|
options[:candidates] = n
|
|
36
37
|
end
|
|
37
38
|
|
|
39
|
+
opts.on('--auto-split', 'Auto-group staged changes into multiple connected commits (commit flow only)') do
|
|
40
|
+
options[:auto_split] = true
|
|
41
|
+
end
|
|
42
|
+
|
|
38
43
|
opts.on('-h', '--help', 'Show this help') do
|
|
39
44
|
puts opts
|
|
40
45
|
exit
|
data/lib/commiti.rb
CHANGED
|
@@ -16,6 +16,7 @@ require_relative 'services/message_generator'
|
|
|
16
16
|
require_relative 'services/message_presenter'
|
|
17
17
|
require_relative 'services/git/commit/commit_staging'
|
|
18
18
|
require_relative 'services/git/commit/commit_execution'
|
|
19
|
+
require_relative 'services/git/commit/change_grouping'
|
|
19
20
|
require_relative 'flows/base_flow'
|
|
20
21
|
require_relative 'flows/commit_flow'
|
|
21
22
|
require_relative 'flows/pr_flow'
|
data/lib/flows/commit_flow.rb
CHANGED
|
@@ -3,6 +3,12 @@
|
|
|
3
3
|
module Commiti
|
|
4
4
|
module Flows
|
|
5
5
|
class CommitFlow < BaseFlow
|
|
6
|
+
def run
|
|
7
|
+
return super unless options[:auto_split]
|
|
8
|
+
|
|
9
|
+
run_auto_split
|
|
10
|
+
end
|
|
11
|
+
|
|
6
12
|
private
|
|
7
13
|
|
|
8
14
|
def flow_type
|
|
@@ -24,6 +30,89 @@ module Commiti
|
|
|
24
30
|
print_message: method(:print_message)
|
|
25
31
|
)
|
|
26
32
|
end
|
|
33
|
+
|
|
34
|
+
def run_auto_split
|
|
35
|
+
prepare!
|
|
36
|
+
diff = collect_diff
|
|
37
|
+
client = Commiti::GoogleClient.new(config: options)
|
|
38
|
+
selected_model = options[:model]
|
|
39
|
+
context = build_context(diff:, client:, model: selected_model)
|
|
40
|
+
|
|
41
|
+
return run_single_group_context(context:, client:, model: selected_model) if single_group?(context)
|
|
42
|
+
|
|
43
|
+
run_grouped_context(context:, client:, model: selected_model)
|
|
44
|
+
rescue StandardError
|
|
45
|
+
run_stage('Restaging uncommitted changes after failure') { Commiti::GitWriter.stage_all! }
|
|
46
|
+
raise
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def single_group?(context)
|
|
50
|
+
context[:change_groups].length <= 1
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def run_single_group_context(context:, client:, model:)
|
|
54
|
+
puts "\nAuto-split found a single connected change group. Falling back to single commit flow."
|
|
55
|
+
Commiti::MessagePresenter.print_summarization_notice(context[:summarized_result])
|
|
56
|
+
|
|
57
|
+
message = generate_message_for_context(context:, client:, model:)
|
|
58
|
+
maybe_copy_to_clipboard(message)
|
|
59
|
+
finalize(message)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def run_grouped_context(context:, client:, model:)
|
|
63
|
+
groups = context[:change_groups]
|
|
64
|
+
run_stage('Unstaging current index for grouped commit execution') { Commiti::GitWriter.unstage_all! }
|
|
65
|
+
|
|
66
|
+
puts "\nAuto-split detected #{groups.length} connected change groups."
|
|
67
|
+
|
|
68
|
+
groups.each_with_index do |group, index|
|
|
69
|
+
break if process_group(group:, index:, total: groups.length, client:, model:) == :stop
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def process_group(group:, index:, total:, client:, model:)
|
|
74
|
+
run_stage("Staging files for group #{index + 1}/#{total}") { Commiti::GitWriter.stage_files!(group[:files]) }
|
|
75
|
+
return :continue unless run_stage('Checking staged changes') { Commiti::GitWriter.staged_changes? }
|
|
76
|
+
|
|
77
|
+
puts "\nGroup #{index + 1}/#{total} files:"
|
|
78
|
+
group[:files].each { |path| puts "- #{path}" }
|
|
79
|
+
|
|
80
|
+
group_context = build_context(diff: group_diff(group), client:, model:)
|
|
81
|
+
Commiti::MessagePresenter.print_summarization_notice(group_context[:summarized_result])
|
|
82
|
+
|
|
83
|
+
message = generate_message_for_context(context: group_context, client:, model:)
|
|
84
|
+
maybe_copy_to_clipboard(message)
|
|
85
|
+
return :continue if finalize(message) == :committed
|
|
86
|
+
|
|
87
|
+
puts "Stopping auto-split flow at group #{index + 1} because commit was skipped."
|
|
88
|
+
run_stage('Restaging remaining uncommitted changes') { Commiti::GitWriter.stage_all! }
|
|
89
|
+
:stop
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def build_context(diff:, client:, model:)
|
|
93
|
+
Commiti::FlowContextBuilder.build(
|
|
94
|
+
flow_type: flow_type,
|
|
95
|
+
diff: diff,
|
|
96
|
+
client: client,
|
|
97
|
+
run_stage: method(:run_stage),
|
|
98
|
+
model: model
|
|
99
|
+
)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def generate_message_for_context(context:, client:, model:)
|
|
103
|
+
candidates = generate_candidates(
|
|
104
|
+
client: client,
|
|
105
|
+
prompt: context[:prompt],
|
|
106
|
+
diff_metadata: context[:diff_metadata],
|
|
107
|
+
model: model
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
select_message(candidates)
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def group_diff(group)
|
|
114
|
+
group[:chunks].map { |chunk| chunk[:lines].join }.join
|
|
115
|
+
end
|
|
27
116
|
end
|
|
28
117
|
end
|
|
29
118
|
end
|
|
@@ -5,6 +5,7 @@ module Commiti
|
|
|
5
5
|
def self.build(flow_type:, diff:, client:, run_stage:, model:)
|
|
6
6
|
line_chunks = Commiti::DiffParser.split_by_file_lines(diff)
|
|
7
7
|
diff_metadata = Commiti::DiffParser.metadata_from_line_chunks(line_chunks)
|
|
8
|
+
change_groups = Commiti::ChangeGrouping.group(line_chunks)
|
|
8
9
|
|
|
9
10
|
summarized_result = run_stage.call('Preparing diff for AI model') do
|
|
10
11
|
Commiti::DiffSummarizer.summarize_if_needed(
|
|
@@ -25,6 +26,7 @@ module Commiti
|
|
|
25
26
|
|
|
26
27
|
{
|
|
27
28
|
diff_metadata: diff_metadata,
|
|
29
|
+
change_groups: change_groups,
|
|
28
30
|
summarized_result: summarized_result,
|
|
29
31
|
prompt: prompt
|
|
30
32
|
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Commiti
|
|
4
|
+
module ChangeGrouping
|
|
5
|
+
NOISE_DIRECTORIES = %w[lib spec test app src docs].freeze
|
|
6
|
+
|
|
7
|
+
def self.group(line_chunks)
|
|
8
|
+
paths = line_chunks.map { |chunk| chunk[:path].to_s.strip }.reject(&:empty?).uniq
|
|
9
|
+
return [] if paths.empty?
|
|
10
|
+
|
|
11
|
+
components = connected_components(paths)
|
|
12
|
+
components.map.with_index(1) do |component, index|
|
|
13
|
+
{
|
|
14
|
+
id: index,
|
|
15
|
+
files: component,
|
|
16
|
+
chunks: line_chunks.select { |chunk| component.include?(chunk[:path]) }
|
|
17
|
+
}
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def self.connected_components(paths)
|
|
22
|
+
visited = {}
|
|
23
|
+
ordered_components = []
|
|
24
|
+
|
|
25
|
+
paths.each do |root|
|
|
26
|
+
next if visited[root]
|
|
27
|
+
|
|
28
|
+
stack = [root]
|
|
29
|
+
visited[root] = true
|
|
30
|
+
component = []
|
|
31
|
+
|
|
32
|
+
until stack.empty?
|
|
33
|
+
current = stack.pop
|
|
34
|
+
component << current
|
|
35
|
+
|
|
36
|
+
paths.each do |candidate|
|
|
37
|
+
next if visited[candidate]
|
|
38
|
+
next unless connected?(current, candidate)
|
|
39
|
+
|
|
40
|
+
visited[candidate] = true
|
|
41
|
+
stack << candidate
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
ordered_components << component.sort_by { |path| paths.index(path) }
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
ordered_components
|
|
49
|
+
end
|
|
50
|
+
private_class_method :connected_components
|
|
51
|
+
|
|
52
|
+
def self.connected?(left, right)
|
|
53
|
+
return true if primary_namespace(left) == primary_namespace(right)
|
|
54
|
+
return true if logical_stem(left) == logical_stem(right)
|
|
55
|
+
|
|
56
|
+
left_segments = normalized_segments(left)
|
|
57
|
+
right_segments = normalized_segments(right)
|
|
58
|
+
shared_depth = [left_segments.length, right_segments.length].min
|
|
59
|
+
|
|
60
|
+
(0...shared_depth).count { |index| left_segments[index] == right_segments[index] } >= 2
|
|
61
|
+
end
|
|
62
|
+
private_class_method :connected?
|
|
63
|
+
|
|
64
|
+
def self.primary_namespace(path)
|
|
65
|
+
segments = normalized_segments(path)
|
|
66
|
+
return nil if segments.empty?
|
|
67
|
+
|
|
68
|
+
segments.first(2).join('/')
|
|
69
|
+
end
|
|
70
|
+
private_class_method :primary_namespace
|
|
71
|
+
|
|
72
|
+
def self.logical_stem(path)
|
|
73
|
+
normalized = path.to_s
|
|
74
|
+
normalized = normalized.sub(%r{\A(?:lib|spec|test|app|src)/}, '') while normalized.match?(%r{\A(?:lib|spec|test|app|src)/})
|
|
75
|
+
normalized = normalized.sub(/_spec\.[^.]+\z/, '')
|
|
76
|
+
normalized.sub(/\.[^.]+\z/, '')
|
|
77
|
+
end
|
|
78
|
+
private_class_method :logical_stem
|
|
79
|
+
|
|
80
|
+
def self.normalized_segments(path)
|
|
81
|
+
path.to_s.split('/').reject { |segment| segment.empty? || NOISE_DIRECTORIES.include?(segment) }
|
|
82
|
+
end
|
|
83
|
+
private_class_method :normalized_segments
|
|
84
|
+
end
|
|
85
|
+
end
|
|
@@ -19,7 +19,7 @@ module Commiti
|
|
|
19
19
|
edited = edit_message_until_valid(working_message)
|
|
20
20
|
if edited.nil?
|
|
21
21
|
puts "\nEditor did not exit successfully. Commit skipped.\n\n"
|
|
22
|
-
return
|
|
22
|
+
return :skipped
|
|
23
23
|
end
|
|
24
24
|
|
|
25
25
|
working_message = edited
|
|
@@ -28,13 +28,13 @@ module Commiti
|
|
|
28
28
|
end
|
|
29
29
|
|
|
30
30
|
puts "\nCommit skipped.\n\n"
|
|
31
|
-
return
|
|
31
|
+
return :skipped
|
|
32
32
|
end
|
|
33
33
|
|
|
34
34
|
output = run_stage.call('Writing commit') { Commiti::GitWriter.commit_with_message_file(working_message) }
|
|
35
35
|
puts output unless output.to_s.strip.empty?
|
|
36
36
|
puts "\nCommit created.\n\n"
|
|
37
|
-
return
|
|
37
|
+
return :committed
|
|
38
38
|
when :edit
|
|
39
39
|
edited = edit_message_until_valid(working_message)
|
|
40
40
|
if edited.nil?
|
|
@@ -46,7 +46,7 @@ module Commiti
|
|
|
46
46
|
print_message.call(working_message)
|
|
47
47
|
else
|
|
48
48
|
puts "\nCommit skipped.\n\n"
|
|
49
|
-
return
|
|
49
|
+
return :skipped
|
|
50
50
|
end
|
|
51
51
|
end
|
|
52
52
|
end
|
|
@@ -14,7 +14,7 @@ module Commiti
|
|
|
14
14
|
puts "\nCurrent git status:\n\n#{status}"
|
|
15
15
|
return unless Commiti::InteractivePrompt.ask_yes_no('Run git add -A now?', default: :no)
|
|
16
16
|
|
|
17
|
-
run_stage.call('Staging changes (git add -A)') { Commiti::GitWriter.stage_all }
|
|
17
|
+
run_stage.call('Staging changes (git add -A)') { Commiti::GitWriter.stage_all! }
|
|
18
18
|
puts "\nStaged changes with git add -A.\n"
|
|
19
19
|
end
|
|
20
20
|
private_class_method :maybe_stage_changes
|
|
@@ -25,7 +25,7 @@ module Commiti
|
|
|
25
25
|
|
|
26
26
|
if Commiti::InteractivePrompt.ask_yes_no('No staged changes found. Stage all changes now with git add -A?',
|
|
27
27
|
default: :yes)
|
|
28
|
-
run_stage.call('Staging changes (git add -A)') { Commiti::GitWriter.stage_all }
|
|
28
|
+
run_stage.call('Staging changes (git add -A)') { Commiti::GitWriter.stage_all! }
|
|
29
29
|
puts "\nStaged changes with git add -A.\n"
|
|
30
30
|
end
|
|
31
31
|
|
|
@@ -19,11 +19,28 @@ module Commiti
|
|
|
19
19
|
!out.strip.empty?
|
|
20
20
|
end
|
|
21
21
|
|
|
22
|
-
def self.stage_all
|
|
22
|
+
def self.stage_all!
|
|
23
23
|
out, err, status = Open3.capture3('git', 'add', '-A')
|
|
24
24
|
raise "git add failed: #{err.strip.empty? ? out.strip : err.strip}" unless status.success?
|
|
25
25
|
|
|
26
|
-
|
|
26
|
+
out
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def self.unstage_all!
|
|
30
|
+
out, err, status = Open3.capture3('git', 'reset')
|
|
31
|
+
raise "git reset failed: #{err.strip.empty? ? out.strip : err.strip}" unless status.success?
|
|
32
|
+
|
|
33
|
+
out
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def self.stage_files!(paths)
|
|
37
|
+
normalized = Array(paths).map(&:to_s).map(&:strip).reject(&:empty?).uniq
|
|
38
|
+
return '' if normalized.empty?
|
|
39
|
+
|
|
40
|
+
out, err, status = Open3.capture3('git', 'add', '--', *normalized)
|
|
41
|
+
raise "git add failed: #{err.strip.empty? ? out.strip : err.strip}" unless status.success?
|
|
42
|
+
|
|
43
|
+
out
|
|
27
44
|
end
|
|
28
45
|
|
|
29
46
|
def self.commit_with_message_file(message)
|
|
@@ -71,7 +71,9 @@ module Commiti
|
|
|
71
71
|
private_class_method :prefilled_url
|
|
72
72
|
|
|
73
73
|
def self.github_like_compare_url(remote:, base_branch:, head_branch:, title:, body:, include_title: true, include_body: true)
|
|
74
|
-
query_params = {
|
|
74
|
+
query_params = {
|
|
75
|
+
github_compare_prefill_param(remote[:provider]) => '1'
|
|
76
|
+
}
|
|
75
77
|
normalized_title = normalize_title(title)
|
|
76
78
|
query_params['title'] = normalized_title if include_title && !normalized_title.empty?
|
|
77
79
|
query_params['body'] = body.to_s if include_body && !body.to_s.empty?
|
|
@@ -83,6 +85,11 @@ module Commiti
|
|
|
83
85
|
"#{base}/#{path}/compare/#{encode_branch_for_path(base_branch)}...#{encode_branch_for_path(head_branch)}?#{query}"
|
|
84
86
|
end
|
|
85
87
|
|
|
88
|
+
def self.github_compare_prefill_param(provider)
|
|
89
|
+
provider == :github ? 'quick_pull' : 'expand'
|
|
90
|
+
end
|
|
91
|
+
private_class_method :github_compare_prefill_param
|
|
92
|
+
|
|
86
93
|
def self.gitlab_mr_url(remote:, base_branch:, head_branch:, title:, body:, include_title: true, include_description: true)
|
|
87
94
|
query_params = {
|
|
88
95
|
'merge_request[source_branch]' => head_branch,
|
|
@@ -8,6 +8,7 @@ module Commiti
|
|
|
8
8
|
candidates: 1,
|
|
9
9
|
base_branch: 'main',
|
|
10
10
|
no_copy: false,
|
|
11
|
+
auto_split: false,
|
|
11
12
|
temperature: Commiti::GoogleClient::DEFAULT_TEMPERATURE,
|
|
12
13
|
timeout_seconds: Commiti::GoogleClient::DEFAULT_TIMEOUT_SECONDS,
|
|
13
14
|
open_timeout_seconds: Commiti::GoogleClient::DEFAULT_OPEN_TIMEOUT_SECONDS
|
|
@@ -22,6 +23,7 @@ module Commiti
|
|
|
22
23
|
candidates: integer_or_default(env.fetch('COMMITI_CANDIDATES', nil), DEFAULT_CONFIG[:candidates]),
|
|
23
24
|
base_branch: present_or_default(env.fetch('COMMITI_BASE_BRANCH', nil), DEFAULT_CONFIG[:base_branch]),
|
|
24
25
|
no_copy: boolean_or_default(env.fetch('COMMITI_NO_COPY', nil), DEFAULT_CONFIG[:no_copy]),
|
|
26
|
+
auto_split: boolean_or_default(env.fetch('COMMITI_AUTO_SPLIT', nil), DEFAULT_CONFIG[:auto_split]),
|
|
25
27
|
temperature: float_or_default(env.fetch('COMMITI_MODEL_TEMPERATURE', nil), DEFAULT_CONFIG[:temperature]),
|
|
26
28
|
timeout_seconds: integer_or_default(env.fetch('COMMITI_MODEL_TIMEOUT_SECONDS', nil), DEFAULT_CONFIG[:timeout_seconds]),
|
|
27
29
|
open_timeout_seconds: integer_or_default(env.fetch('COMMITI_MODEL_OPEN_TIMEOUT_SECONDS', nil),
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: commiti
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Setoju
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-05-
|
|
11
|
+
date: 2026-05-19 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: dotenv
|
|
@@ -58,6 +58,7 @@ files:
|
|
|
58
58
|
- lib/services/diff_summarization/diff_summarizer.rb
|
|
59
59
|
- lib/services/diff_summarization/fallback_builder.rb
|
|
60
60
|
- lib/services/flow_context_builder.rb
|
|
61
|
+
- lib/services/git/commit/change_grouping.rb
|
|
61
62
|
- lib/services/git/commit/commit_execution.rb
|
|
62
63
|
- lib/services/git/commit/commit_staging.rb
|
|
63
64
|
- lib/services/git/diff_parser.rb
|