aigcm 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 +7 -0
- data/.envrc +4 -0
- data/CHANGELOG.md +11 -0
- data/LICENSE.txt +21 -0
- data/README.md +128 -0
- data/Rakefile +8 -0
- data/bin/aigcm +5 -0
- data/lib/aigcm/commit_message_generator.rb +113 -0
- data/lib/aigcm/git_diff.rb +44 -0
- data/lib/aigcm/style_guide.rb +50 -0
- data/lib/aigcm/version.rb +5 -0
- data/lib/aigcm.rb +146 -0
- data/sig/aicommit.rbs +4 -0
- metadata +136 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 1f0e2c18c6c1c820563cc9f640a319709b1cbb752fdee4464617759cd2e6f963
|
4
|
+
data.tar.gz: 3a74e5f33183449a690f5ae8892bd55d438cff57193eb90af1b358458f215c77
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: e203e403e07b9865c879d6bee4cedb9545fc460a8cbaa990d9257e00e49a7bf735ac5ee4bd403f2e7dd1ad4ee399734e194d49e55c723d7f184684cee8c5a548
|
7
|
+
data.tar.gz: 22ff6406f1aba1ab8defeac098bc1dab5b0aece00c04801c3aa4b888399a23db974ed89b0387e373a5f05e77a0a99b7834c1da8ffb95c240f926429330d6b1ba
|
data/.envrc
ADDED
data/CHANGELOG.md
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2025 Dewayne VanHoozer
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,128 @@
|
|
1
|
+
# aigcm
|
2
|
+
|
3
|
+
The **AI git commit message** generator.
|
4
|
+
ƒ
|
5
|
+
## Overview
|
6
|
+
|
7
|
+
**aigcm** is a Ruby gem designed to generate high-quality commit messages for git diffs. It leverages AI to analyze changes in your codebase and create concise, meaningful commit messages following best practices.
|
8
|
+
|
9
|
+
## Features
|
10
|
+
|
11
|
+
- **Automatic Commit Message Generation**: Automatically generate commit messages based on code diffs.
|
12
|
+
- **Security-Aware Provider Selection**: Detects execution in a private repository and ensures non-local providers are not used for security reasons. This means that if you are working within a private repository, the gem will default to local providers unless explicitly forced otherwise.
|
13
|
+
- **Configurable Style Guide**: Allows using a specific style guide for commit messages, either from a default location or specified by the user.
|
14
|
+
- **AI Model Integration**: Integration with various AI models for enhanced message generation.
|
15
|
+
|
16
|
+
## Installation
|
17
|
+
|
18
|
+
Add this line to your application's Gemfile:
|
19
|
+
|
20
|
+
```ruby
|
21
|
+
gem 'aigcm'
|
22
|
+
```
|
23
|
+
|
24
|
+
And then execute:
|
25
|
+
|
26
|
+
```shell
|
27
|
+
bundle install
|
28
|
+
```
|
29
|
+
|
30
|
+
Or install it yourself as:
|
31
|
+
|
32
|
+
```shell
|
33
|
+
gem install aigcm
|
34
|
+
```
|
35
|
+
|
36
|
+
## Usage
|
37
|
+
|
38
|
+
To generate a commit message:
|
39
|
+
|
40
|
+
```shell
|
41
|
+
aigcm [options] [ref]
|
42
|
+
```
|
43
|
+
|
44
|
+
### Options
|
45
|
+
|
46
|
+
- `-a, --amend`: Amend the last commit.
|
47
|
+
- `-c, --context=CONTEXT`: Extra context beyond the diff.
|
48
|
+
- `-d, --dry`: Dry run the command without making any changes.
|
49
|
+
- `-m, --model=MODEL`: Specify the AI model to use.
|
50
|
+
- `--provider=PROVIDER`: Specify the provider (ollama, openai, anthropic, etc). Note: This only needs to be used when the specified model is available from multiple providers; otherwise, the owner of the model is used by default.
|
51
|
+
- `--force-external`: Force using external AI provider even for private repos.
|
52
|
+
- `-s, --style=STYLE`: Path to the style guide file. If not provided, the system looks for `COMMITS.md` in the repo root or uses the default style guide.
|
53
|
+
- `--default`: Print the default style guide and exit the application.
|
54
|
+
- `--version`: Show version.
|
55
|
+
|
56
|
+
### Examples
|
57
|
+
|
58
|
+
If your commit involves refactoring a function to improve its performance, you might provide context like:
|
59
|
+
```shell
|
60
|
+
aigcm -m MODEL -c "Refactored to improve performance by using algorithm X"
|
61
|
+
```
|
62
|
+
|
63
|
+
This context helps the AI craft a more informative commit message.
|
64
|
+
|
65
|
+
When your commit is related to a specific JIRA ticket:
|
66
|
+
```shell
|
67
|
+
aigcm -m MODEL -c "Resolved issues as per JIRA ticket JIRA-1234"
|
68
|
+
```
|
69
|
+
|
70
|
+
Including the JIRA ticket helps relate the commit to external tracking systems.
|
71
|
+
|
72
|
+
Including multiple context strings:
|
73
|
+
```shell
|
74
|
+
aigcm -m MODEL -c "Refactored for performance" -c "JIRA-1234"
|
75
|
+
```
|
76
|
+
|
77
|
+
Multiple context strings can be added by repeating the `-c` option.
|
78
|
+
|
79
|
+
Using environment variables in context:
|
80
|
+
```shell
|
81
|
+
aigcm -c "Put the work ticket as the first entry on the subject line" -c "Ticket: $TICKET"
|
82
|
+
```
|
83
|
+
|
84
|
+
This allows you to dynamically include environment variables in your commit message.
|
85
|
+
|
86
|
+
### Style Guide Example
|
87
|
+
|
88
|
+
The style guide is used as part of the generative AI prompt that instructs the large language model (LLM) how to craft its summary of the `git diff` results. The see the default style guide use the `--default` option.
|
89
|
+
|
90
|
+
You can create your own style guide named `COMMITS.md` in the root directory of your repository. You can also use the `--style` option to point `aigcm` to your style guide if you choose to keep it in a different place. This is handy when you want to have consistent commit messages across several different projects.
|
91
|
+
|
92
|
+
This would be a simple style guide:
|
93
|
+
|
94
|
+
```
|
95
|
+
- Use conventional commits format (type: description)
|
96
|
+
- Keep first line under 72 characters
|
97
|
+
- Use present tense ("add" not "added")
|
98
|
+
- Be descriptive but concise
|
99
|
+
- Have fun. Be creative. Add ASCII art if you feel like it.
|
100
|
+
```
|
101
|
+
|
102
|
+
## Last Thoughts
|
103
|
+
|
104
|
+
This gem saves its commit message in the file `.aigcm_msg` at the root directory of the repository. Its there even if you do a `--dry` run. This could be handy if you want to incorporate `aigcm` into some larger workflow.
|
105
|
+
|
106
|
+
Remember that the style guide can be extended using one or more `--context` strings. For example you could create a shell alias like this:
|
107
|
+
|
108
|
+
```
|
109
|
+
alias gc='aigcm -c "JIRA $JIRA_TICKET"'
|
110
|
+
```
|
111
|
+
|
112
|
+
## Development
|
113
|
+
|
114
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests.
|
115
|
+
|
116
|
+
To install this gem onto your local machine, run `bundle exec rake install`.
|
117
|
+
|
118
|
+
## Contributing
|
119
|
+
|
120
|
+
1. Fork it (<https://github.com/your_username/aigcm/fork>)
|
121
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
122
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
123
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
124
|
+
5. Create a new Pull Request
|
125
|
+
|
126
|
+
## License
|
127
|
+
|
128
|
+
The gem is available as open-source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
data/bin/aigcm
ADDED
@@ -0,0 +1,113 @@
|
|
1
|
+
|
2
|
+
require "net/http"
|
3
|
+
require "json"
|
4
|
+
require "open3"
|
5
|
+
require "ai_client"
|
6
|
+
|
7
|
+
module Aigcm
|
8
|
+
class CommitMessageGenerator
|
9
|
+
class Error < StandardError; end
|
10
|
+
|
11
|
+
MAX_DIFF_SIZE = 4000 # Characters
|
12
|
+
|
13
|
+
def initialize(model:, provider:, max_tokens:, force_external:)
|
14
|
+
@force_external = force_external
|
15
|
+
validate_model_provider_combination(model)
|
16
|
+
check_provider_availability
|
17
|
+
|
18
|
+
@client = AiClient.new(model, provider: provider)
|
19
|
+
rescue StandardError => e
|
20
|
+
raise Error, "Failed to initialize AI client: #{e.message}"
|
21
|
+
end
|
22
|
+
|
23
|
+
def generate(diff, style_guide, context = [])
|
24
|
+
return "No changes to commit" if diff.strip.empty?
|
25
|
+
|
26
|
+
check_repository_privacy unless @force_external
|
27
|
+
|
28
|
+
# Truncate diff if too large
|
29
|
+
if diff.length > MAX_DIFF_SIZE
|
30
|
+
diff = diff[0...MAX_DIFF_SIZE] + "\n...[diff truncated]"
|
31
|
+
end
|
32
|
+
|
33
|
+
processed_context = process_context(context)
|
34
|
+
prompt = build_prompt(diff, style_guide, processed_context)
|
35
|
+
|
36
|
+
begin
|
37
|
+
response = @client.chat(prompt)
|
38
|
+
response.to_s.strip
|
39
|
+
rescue StandardError => e
|
40
|
+
"Error generating commit message: #{e.message}"
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
def validate_model_provider_combination(model)
|
47
|
+
client = AiClient.new(model)
|
48
|
+
@provider = client.provider
|
49
|
+
rescue ArgumentError => e
|
50
|
+
raise Error, "Invalid model/provider combination: #{e.message}"
|
51
|
+
end
|
52
|
+
|
53
|
+
def check_provider_availability
|
54
|
+
case @provider
|
55
|
+
when :ollama
|
56
|
+
check_ollama_running
|
57
|
+
when :localai
|
58
|
+
check_localai_running
|
59
|
+
end
|
60
|
+
rescue StandardError => e
|
61
|
+
raise Error, "Provider not available: #{e.message}"
|
62
|
+
end
|
63
|
+
|
64
|
+
def check_ollama_running
|
65
|
+
Net::HTTP.get(URI("http://localhost:11434/api/version"))
|
66
|
+
rescue StandardError
|
67
|
+
raise Error, "Ollama is not running. Please start ollama first."
|
68
|
+
end
|
69
|
+
|
70
|
+
def check_localai_running
|
71
|
+
Net::HTTP.get(URI("http://localhost:8080/v1/models"))
|
72
|
+
rescue StandardError
|
73
|
+
raise Error, "LocalAI is not running. Please start localai first."
|
74
|
+
end
|
75
|
+
|
76
|
+
def process_context(context_array)
|
77
|
+
context_array.map do |ctx|
|
78
|
+
if ctx.start_with?("@")
|
79
|
+
filename = ctx[1..]
|
80
|
+
File.read(filename) rescue "Could not read #{filename}"
|
81
|
+
else
|
82
|
+
ctx
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def check_repository_privacy
|
88
|
+
return if @provider == :ollama # Local provider is always safe
|
89
|
+
|
90
|
+
stdout, _, status = Open3.capture3("gh repo view --json isPrivate -q '.isPrivate'")
|
91
|
+
|
92
|
+
if status.success? && stdout.strip == "true"
|
93
|
+
raise Error, "This is a private repository. Use a local model (ollama) or run with --force-external flag"
|
94
|
+
end
|
95
|
+
rescue Errno::ENOENT
|
96
|
+
puts "Warning: Unable to check repository privacy status (gh command not found)"
|
97
|
+
end
|
98
|
+
|
99
|
+
def build_prompt(diff, style_guide, context)
|
100
|
+
<<~PROMPT
|
101
|
+
Generate a commit message for the git diff that follows these instructions.
|
102
|
+
Do not wrap your response in a code block.
|
103
|
+
Follow these style guidelines when constructing your response:
|
104
|
+
#{style_guide}
|
105
|
+
|
106
|
+
#{context.join("\n") unless context.empty?}
|
107
|
+
|
108
|
+
Git diff:
|
109
|
+
#{diff}
|
110
|
+
PROMPT
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require "open3"
|
2
|
+
|
3
|
+
module Aigcm
|
4
|
+
class GitDiff
|
5
|
+
class Error < StandardError; end
|
6
|
+
|
7
|
+
def initialize(dir:, commit_hash: nil, amend: false)
|
8
|
+
@dir = dir
|
9
|
+
@commit_hash = commit_hash
|
10
|
+
@amend = amend
|
11
|
+
validate_git_repo
|
12
|
+
end
|
13
|
+
|
14
|
+
def generate_diff
|
15
|
+
Dir.chdir(@dir) do
|
16
|
+
cmd = if @amend
|
17
|
+
"git diff --cached HEAD^ 2>/dev/null || git diff --cached"
|
18
|
+
elsif @commit_hash
|
19
|
+
"git diff #{@commit_hash}^..#{@commit_hash}"
|
20
|
+
else
|
21
|
+
"git diff --cached"
|
22
|
+
end
|
23
|
+
|
24
|
+
stdout, _, status = Open3.capture3(cmd)
|
25
|
+
|
26
|
+
raise Error, "Git command failed" unless status.success?
|
27
|
+
raise Error, "No changes detected" if stdout.strip.empty?
|
28
|
+
|
29
|
+
stdout
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def validate_git_repo
|
36
|
+
Dir.chdir(@dir) do
|
37
|
+
_, _, status = Open3.capture3("git rev-parse --git-dir")
|
38
|
+
raise Error, "Not a git repository" unless status.success?
|
39
|
+
end
|
40
|
+
rescue Errno::ENOENT
|
41
|
+
raise Error, "Directory not found: #{@dir}"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'erb'
|
2
|
+
|
3
|
+
module Aigcm
|
4
|
+
class StyleGuide
|
5
|
+
LINE_MAX = 72
|
6
|
+
DEFAULT_GUIDE = ERB.new(
|
7
|
+
File.read(__FILE__)
|
8
|
+
.split("__END__")
|
9
|
+
.last
|
10
|
+
.strip
|
11
|
+
).result
|
12
|
+
|
13
|
+
def self.load(dir, custom_path = nil)
|
14
|
+
# If a custom path is provided, use it
|
15
|
+
if custom_path
|
16
|
+
return load_from_file(custom_path)
|
17
|
+
end
|
18
|
+
|
19
|
+
# Check for COMMITS.md in the repository root
|
20
|
+
config_file = File.join(dir, "COMMITS.md")
|
21
|
+
return load_from_file(config_file) if File.exist?(config_file)
|
22
|
+
|
23
|
+
# Fallback to the default style guide
|
24
|
+
DEFAULT_GUIDE
|
25
|
+
rescue StandardError => e
|
26
|
+
puts "Warning: Error reading style guide: #{e.message}"
|
27
|
+
DEFAULT_GUIDE
|
28
|
+
end
|
29
|
+
|
30
|
+
private_class_method def self.load_from_file(path)
|
31
|
+
File.read(path)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
__END__
|
37
|
+
|
38
|
+
1. Craft a Clear Subject Line:
|
39
|
+
• Summarize Concisely: Begin with a brief summary (<%= Aigcm::StyleGuide::LINE_MAX %> characters max).
|
40
|
+
• Capitalize the Subject: Start the subject line with a capital letter.
|
41
|
+
• Omit Periods in Subject Line: Avoid ending with a period to save space.
|
42
|
+
• Use Imperative Mood: Phrase commands as direct actions (e.g., "Add feature" instead of "Added feature").
|
43
|
+
|
44
|
+
2. Provide a Detailed Body:
|
45
|
+
• Seperate the body from the subject line with a blank line.
|
46
|
+
• Explain the Reason: Clearly articulate the rationale for the change rather than just summarizing the modification.
|
47
|
+
• Wrap Body Text at <%= Aigcm::StyleGuide::LINE_MAX %> Characters: Ensure that the body text wraps at <%= Aigcm::StyleGuide::LINE_MAX %> characters per line.
|
48
|
+
|
49
|
+
3. Reference Issues/Tickets:
|
50
|
+
• Include relevant issue numbers, ticket IDs and/or references when they are provided. Dp not invent your own reference. Use what has been provided.
|
data/lib/aigcm.rb
ADDED
@@ -0,0 +1,146 @@
|
|
1
|
+
# lib/aigcm.rb
|
2
|
+
|
3
|
+
require 'debug_me'
|
4
|
+
include DebugMe
|
5
|
+
|
6
|
+
require 'optparse'
|
7
|
+
require 'ai_client'
|
8
|
+
require 'time'
|
9
|
+
|
10
|
+
require_relative 'aigcm/version'
|
11
|
+
require_relative 'aigcm/git_diff'
|
12
|
+
require_relative 'aigcm/commit_message_generator'
|
13
|
+
require_relative 'aigcm/style_guide'
|
14
|
+
|
15
|
+
module Aigcm
|
16
|
+
COMMIT_MESSAGE_FILE = '.aigcm_msg'
|
17
|
+
RECENT_THRESHOLD = 60 # seconds (1 minute)
|
18
|
+
|
19
|
+
class Error < StandardError; end
|
20
|
+
|
21
|
+
def self.run(test_mode: false)
|
22
|
+
dir = Dir.pwd
|
23
|
+
options = parse_options
|
24
|
+
|
25
|
+
if options[:amend]
|
26
|
+
system('git commit --amend')
|
27
|
+
return
|
28
|
+
end
|
29
|
+
|
30
|
+
commit_message = check_recent_commit(dir)
|
31
|
+
|
32
|
+
# Generate a new commit message if not reusing an existing one
|
33
|
+
commit_message ||= generate_commit_message(dir, options)
|
34
|
+
|
35
|
+
perform_commit(dir, commit_message, options)
|
36
|
+
|
37
|
+
rescue OptionParser::InvalidOption => e
|
38
|
+
STDERR.puts "Error: '#{e.message}'"
|
39
|
+
exit 1
|
40
|
+
rescue GitDiff::Error => e
|
41
|
+
puts "Git error: #{e.message}"
|
42
|
+
exit 1
|
43
|
+
rescue StandardError => e
|
44
|
+
puts "Error: #{e.message}"
|
45
|
+
exit 1
|
46
|
+
end
|
47
|
+
|
48
|
+
private_class_method def self.parse_options
|
49
|
+
options = {
|
50
|
+
amend: false,
|
51
|
+
context: [],
|
52
|
+
dry: false,
|
53
|
+
model: 'gpt-4o-mini',
|
54
|
+
provider: nil,
|
55
|
+
force_external: false,
|
56
|
+
style: nil
|
57
|
+
}
|
58
|
+
|
59
|
+
OptionParser.new do |opts|
|
60
|
+
opts.banner = "Usage: aigcm [options] [ref]"
|
61
|
+
|
62
|
+
opts.on("-a", "--amend", "Amend the last commit") { options[:amend] = true }
|
63
|
+
|
64
|
+
opts.on("-cCONTEXT", "--context=CONTEXT", "Extra context beyond the diff") do |context|
|
65
|
+
options[:context] << context
|
66
|
+
end
|
67
|
+
|
68
|
+
opts.on("-d", "--dry", "Dry run the command") { options[:dry] = true }
|
69
|
+
|
70
|
+
opts.on("-mMODEL", "--model=MODEL", "The model to use") { |model| options[:model] = model }
|
71
|
+
|
72
|
+
opts.on("--provider=PROVIDER", "Specify the provider (ollama, openai, anthropic, etc)") do |provider|
|
73
|
+
provider = provider.to_sym
|
74
|
+
unless [:ollama, :openai, :anthropic, :google, :mistral].include?(provider)
|
75
|
+
puts "Invalid provider specified. Valid providers are: ollama, openai, anthropic, google, mistral"
|
76
|
+
exit 1
|
77
|
+
end
|
78
|
+
options[:provider] = provider
|
79
|
+
end
|
80
|
+
|
81
|
+
opts.on("--force-external", "Force using external AI provider even for private repos") {
|
82
|
+
options[:force_external] = true
|
83
|
+
}
|
84
|
+
|
85
|
+
opts.on("-sSTYLE", "--style=STYLE", "Path to the style guide file") { |style| options[:style] = style }
|
86
|
+
|
87
|
+
opts.on("--default", "Print the default style guide and exit") do
|
88
|
+
puts "\nDefault Style Guide:"
|
89
|
+
puts "-------------------"
|
90
|
+
puts StyleGuide::DEFAULT_GUIDE
|
91
|
+
exit
|
92
|
+
end
|
93
|
+
|
94
|
+
opts.on("--version", "Show version") { puts Aigcm::VERSION; exit }
|
95
|
+
|
96
|
+
end.parse!
|
97
|
+
|
98
|
+
options
|
99
|
+
end
|
100
|
+
|
101
|
+
private_class_method def self.check_recent_commit(dir)
|
102
|
+
commit_file_path = File.join(dir, COMMIT_MESSAGE_FILE)
|
103
|
+
|
104
|
+
if File.exist?(commit_file_path)
|
105
|
+
file_mod_time = File.mtime(commit_file_path)
|
106
|
+
current_time = Time.now
|
107
|
+
if (current_time - file_mod_time).to_i < RECENT_THRESHOLD
|
108
|
+
return File.read(commit_file_path)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
nil
|
113
|
+
end
|
114
|
+
|
115
|
+
private_class_method def self.generate_commit_message(dir, options)
|
116
|
+
diff_generator = GitDiff.new(dir: dir, commit_hash: ARGV.shift, amend: options[:amend])
|
117
|
+
diff = diff_generator.generate_diff
|
118
|
+
|
119
|
+
style_guide = StyleGuide.load(dir, options[:style])
|
120
|
+
generator = CommitMessageGenerator.new(
|
121
|
+
model: options[:model],
|
122
|
+
provider: options[:provider],
|
123
|
+
max_tokens: 1000,
|
124
|
+
force_external: options[:force_external]
|
125
|
+
)
|
126
|
+
|
127
|
+
commit_message = generator.generate(diff, style_guide, options[:context])
|
128
|
+
File.write(File.join(dir, COMMIT_MESSAGE_FILE), commit_message)
|
129
|
+
commit_message
|
130
|
+
end
|
131
|
+
|
132
|
+
private_class_method def self.perform_commit(dir, commit_message, options)
|
133
|
+
commit_file_path = File.join(dir, COMMIT_MESSAGE_FILE)
|
134
|
+
|
135
|
+
if options[:dry]
|
136
|
+
puts "\nDry run - would generate commit message:"
|
137
|
+
puts "-"*StyleGuide::LINE_MAX
|
138
|
+
puts commit_message
|
139
|
+
puts "-"*StyleGuide::LINE_MAX
|
140
|
+
puts
|
141
|
+
else
|
142
|
+
File.write(commit_file_path, commit_message)
|
143
|
+
system("git commit --edit -F #{commit_file_path}")
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
data/sig/aicommit.rbs
ADDED
metadata
ADDED
@@ -0,0 +1,136 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: aigcm
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.2.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Dewayne VanHoozer
|
8
|
+
bindir: bin
|
9
|
+
cert_chain: []
|
10
|
+
date: 2025-02-09 00:00:00.000000000 Z
|
11
|
+
dependencies:
|
12
|
+
- !ruby/object:Gem::Dependency
|
13
|
+
name: ai_client
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
15
|
+
requirements:
|
16
|
+
- - "~>"
|
17
|
+
- !ruby/object:Gem::Version
|
18
|
+
version: 0.4.0
|
19
|
+
type: :runtime
|
20
|
+
prerelease: false
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
22
|
+
requirements:
|
23
|
+
- - "~>"
|
24
|
+
- !ruby/object:Gem::Version
|
25
|
+
version: 0.4.0
|
26
|
+
- !ruby/object:Gem::Dependency
|
27
|
+
name: minitest
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
29
|
+
requirements:
|
30
|
+
- - "~>"
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '5.16'
|
33
|
+
type: :development
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - "~>"
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: '5.16'
|
40
|
+
- !ruby/object:Gem::Dependency
|
41
|
+
name: mocha
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
43
|
+
requirements:
|
44
|
+
- - ">="
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: '0'
|
47
|
+
type: :development
|
48
|
+
prerelease: false
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - ">="
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
- !ruby/object:Gem::Dependency
|
55
|
+
name: rake
|
56
|
+
requirement: !ruby/object:Gem::Requirement
|
57
|
+
requirements:
|
58
|
+
- - "~>"
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: '13.0'
|
61
|
+
type: :development
|
62
|
+
prerelease: false
|
63
|
+
version_requirements: !ruby/object:Gem::Requirement
|
64
|
+
requirements:
|
65
|
+
- - "~>"
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: '13.0'
|
68
|
+
- !ruby/object:Gem::Dependency
|
69
|
+
name: rubocop
|
70
|
+
requirement: !ruby/object:Gem::Requirement
|
71
|
+
requirements:
|
72
|
+
- - "~>"
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: '1.21'
|
75
|
+
type: :development
|
76
|
+
prerelease: false
|
77
|
+
version_requirements: !ruby/object:Gem::Requirement
|
78
|
+
requirements:
|
79
|
+
- - "~>"
|
80
|
+
- !ruby/object:Gem::Version
|
81
|
+
version: '1.21'
|
82
|
+
description: |
|
83
|
+
`aigcm` generates meaningful git commit messages using artificial
|
84
|
+
intelligence. It supports multiple AI providers including OpenAI,
|
85
|
+
Anthropic, Google, and local models via Ollama. The gem
|
86
|
+
automatically detects private repositories and defaults to using
|
87
|
+
local models for security. It integrates seamlessly with git
|
88
|
+
workflows and supports various commit scenarios including amending
|
89
|
+
commits and handling staged changes. The gem follows conventional
|
90
|
+
commit message formats and allows customization of commit message
|
91
|
+
styles through configuration.
|
92
|
+
email:
|
93
|
+
- dvanhoozer@gmail.com
|
94
|
+
executables:
|
95
|
+
- aigcm
|
96
|
+
extensions: []
|
97
|
+
extra_rdoc_files: []
|
98
|
+
files:
|
99
|
+
- ".envrc"
|
100
|
+
- CHANGELOG.md
|
101
|
+
- LICENSE.txt
|
102
|
+
- README.md
|
103
|
+
- Rakefile
|
104
|
+
- bin/aigcm
|
105
|
+
- lib/aigcm.rb
|
106
|
+
- lib/aigcm/commit_message_generator.rb
|
107
|
+
- lib/aigcm/git_diff.rb
|
108
|
+
- lib/aigcm/style_guide.rb
|
109
|
+
- lib/aigcm/version.rb
|
110
|
+
- sig/aicommit.rbs
|
111
|
+
homepage: https://github.com/MadBomber/aigcm
|
112
|
+
licenses:
|
113
|
+
- MIT
|
114
|
+
metadata:
|
115
|
+
allowed_push_host: https://rubygems.org
|
116
|
+
homepage_uri: https://github.com/MadBomber/aigcm
|
117
|
+
source_code_uri: https://github.com/MadBomber/aigcm
|
118
|
+
changelog_uri: https://github.com/MadBomber/aigcm/blob/main/CHANGELOG.md
|
119
|
+
rdoc_options: []
|
120
|
+
require_paths:
|
121
|
+
- lib
|
122
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
123
|
+
requirements:
|
124
|
+
- - ">="
|
125
|
+
- !ruby/object:Gem::Version
|
126
|
+
version: 3.1.0
|
127
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - ">="
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '0'
|
132
|
+
requirements: []
|
133
|
+
rubygems_version: 3.6.3
|
134
|
+
specification_version: 4
|
135
|
+
summary: AI-powered git commit message generator
|
136
|
+
test_files: []
|