ai-commit-message 0.1.2 → 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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 829bf4eb533e768334fd7f8ff083f2ec00d1c113a4b394b55e2802f4a30eb9db
4
- data.tar.gz: 962c1f333ecde972d3710884f5c9cbfcd9afb72755af66bdf6de0d6e44eb8a88
3
+ metadata.gz: cf7ae3da6684c33dd9b0518bda5c3d0f85398132e1089a51a442bf88892e4b7d
4
+ data.tar.gz: d3d27482ca167d55bffb77c25ebd0553eea74fc93fdf5554bd6a434e80926aeb
5
5
  SHA512:
6
- metadata.gz: 65391923463e9a944a0974962acec6510a2615437b83394f19c7418565a25ab0ceb3f2e5058928df14352f7dd92950367b5529f05bb74fc756364f220cace722
7
- data.tar.gz: c6e7f071038a300f68316dc8e9c31a66c8dba1a4e4060f9c06950e0094f42b90d5db23a1fdfdbc0d72718da8a791a57fac18a9746cccb113abc627089b95cb2f
6
+ metadata.gz: 07d58b95afcdde4e24d7f85e15354d4d437cecbb92ddfc16b68e8a8d13bf2a10a576674b43000e163b0465021947cc6c5a8210894a5583bdb00e278e7600e641
7
+ data.tar.gz: b22d685bb2687103cb3b414f6fc0b002dd92007b377b76ee9a89a7d99e50a052184d641277ac636d46e56780cc8265282350cd40dbb5e3515ba8418a0f8aaca1
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Afonso Neto
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 all
13
+ 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 THE
21
+ SOFTWARE.
data/README.md CHANGED
@@ -1,6 +1,8 @@
1
1
  # AI git commit message
2
2
 
3
- A Ruby gem that automatically generates concise and meaningful git commit messages using AI models via [Ollama](https://github.com/ollama/ollama) or any OpenAI-compatible API.
3
+ A Ruby gem that automatically generates concise and meaningful git commit messages using a local LLM via [Ollama](https://github.com/ollama/ollama), LM Studio, llama.cpp `llama-server`, or any other OpenAI-compatible API.
4
+
5
+ ![Terminal demo](docs/demo.gif)
4
6
 
5
7
  ## Installation
6
8
 
@@ -26,13 +28,13 @@ bundle install
26
28
 
27
29
  - Ruby 3.0 or newer
28
30
  - Git repository
29
- - [Ollama](https://github.com/ollama/ollama) model running locally or other OpenAI-compatible API endpoint
31
+ - A local LLM server: [Ollama](https://github.com/ollama/ollama), [LM Studio](https://lmstudio.ai/), [llama.cpp `llama-server`](https://github.com/ggml-org/llama.cpp), or any other OpenAI-compatible endpoint
30
32
 
31
33
  ## Usage
32
34
 
33
35
  ### Basic Usage
34
36
 
35
- Generate a commit message for your staged changes:
37
+ Generate a commit message for your staged changes and commit with it after a confirmation:
36
38
 
37
39
  ```bash
38
40
  ai-commit-message commit
@@ -43,18 +45,39 @@ This will:
43
45
  2. Review your recent commit history for style consistency
44
46
  3. Consider your current branch name
45
47
  4. Generate an appropriate commit message
48
+ 5. Ask for confirmation and create the commit (answering "no" keeps the printed message so you can edit or copy it)
49
+
50
+ ### Message only
51
+
52
+ If you just want the suggested message without the confirmation prompt — e.g. to pipe it somewhere or review it yourself — use `--message-only`:
53
+
54
+ ```bash
55
+ ai-commit-message commit --message-only
56
+ ```
57
+
58
+ ### Conventional Commits
59
+
60
+ Ask the model to follow the [Conventional Commits](https://www.conventionalcommits.org/) format (`feat:`, `fix:`, `chore:`, ...):
61
+
62
+ ```bash
63
+ ai-commit-message commit --conventional
64
+ ```
46
65
 
47
66
  ### Configuration
48
67
 
49
- You can configure the gem using the built-in configuration command:
68
+ Configure the API URL and model interactively:
50
69
 
51
70
  ```bash
52
71
  ai-commit-message config
53
72
  ```
54
73
 
55
- This interactive prompt allows you to set:
56
- - **API URL**: The endpoint for your AI model (defaults to `http://localhost:11434/api/generate`)
57
- - **Model name**: The AI model to use (defaults to `qwen2.5-coder:7b`, which I personally recommend)
74
+ If your server is reachable, the model prompt becomes a picker populated with the models available on that server.
75
+
76
+ List the models available on your server:
77
+
78
+ ```bash
79
+ ai-commit-message models
80
+ ```
58
81
 
59
82
  ### Command Line Options
60
83
 
@@ -64,44 +87,59 @@ Override configuration settings directly:
64
87
  ai-commit-message commit --url=http://your-api-endpoint --model=your-model-name
65
88
  ```
66
89
 
90
+ All options:
91
+
92
+ | Option | Description | Default |
93
+ | --- | --- | --- |
94
+ | `--url` | Base URL of the OpenAI-compatible API | `http://localhost:11434` |
95
+ | `--model` | Model name | `qwen3:8b` |
96
+ | `--length` | Maximum message length in characters | `72` |
97
+ | `--conventional` | Use Conventional Commits format | off |
98
+ | `--message-only` | Print the message without the commit confirmation prompt | off |
99
+
67
100
  ## How It Works
68
101
 
69
102
  The gem:
70
- 1. Collects your staged changes using `git diff --cached`
103
+ 1. Collects your staged changes using `git diff --cached` (capped at 12,000 characters so large diffs don't overwhelm a local model's context)
71
104
  2. Retrieves your recent commit history for context
72
105
  3. Identifies your current branch name
73
- 4. Sends this information to the specified AI model
74
- 5. Returns a concise, contextual commit message (limited to 250 characters)
106
+ 4. Sends a chat request to `{url}/v1/chat/completions` — the standard OpenAI-compatible endpoint
107
+ 5. Cleans the model output (strips code fences, quotes and reasoning blocks) and prints a single-line commit message (limited to 72 characters by default)
108
+
109
+ The URL accepts base addresses with or without a trailing `/v1` and `/`, so `http://localhost:11434` (Ollama), `http://localhost:1234/v1` (LM Studio) and `http://localhost:8080` (llama.cpp `llama-server`) all work as-is.
75
110
 
76
111
  ## Configuration File
77
112
 
78
113
  The gem stores your configuration in `~/.ai-commit-message.conf`. You can manually edit this file if needed. Example:
79
114
 
80
115
  ```bash
81
- url=http://localhost:11434/api/generate
82
- model=qwen2.5-coder:7b
116
+ url=http://localhost:11434
117
+ model=qwen3:8b
83
118
  ```
84
119
 
85
120
  ## Models
86
121
 
87
- The default configuration uses Ollama with the `qwen2.5-coder:7b` model, but you can use any model available through your API endpoint.
122
+ The default configuration uses Ollama with the `qwen3:8b` model, but you can use any model available through your API endpoint.
88
123
 
89
124
  ### Recommended Models
90
125
 
91
- - **qwen2.5-coder:7b**: Good balance of quality and speed (default)
92
- - **llama3:8b**: Excellent for general commit messages
93
- - **codellama:7b**: Specialized for code-related commits
126
+ - **qwen3:8b**: Good balance of quality and speed (default). Note that it is a thinking model, so expect a few seconds of reasoning before the message appears
127
+ - **qwen2.5-coder:7b**: Faster, non-thinking coding model
128
+ - **qwen3.5:4b**: Current-generation small model for modest hardware
129
+ - **gemma4:e4b**: Compact general-purpose option
130
+ - **llama3.1:8b**: Solid general-purpose choice
94
131
 
95
132
  ## Contributing
96
133
 
97
134
  1. Fork the repository
98
135
  2. Create your feature branch: `git checkout -b my-new-feature`
99
136
  3. Install dependencies: `bundle install`
100
- 4. Make your changes and add tests if applicable
101
- 5. Commit your changes: `git commit -m 'Add some feature'`
102
- 6. Push to the branch: `git push origin my-new-feature`
103
- 7. Submit a pull request
137
+ 4. Run the tests: `bundle exec rake test`
138
+ 5. Make your changes and add tests if applicable
139
+ 6. Commit your changes: `git commit -m 'Add some feature'`
140
+ 7. Push to the branch: `git push origin my-new-feature`
141
+ 8. Submit a pull request
104
142
 
105
143
  ## License
106
144
 
107
- This gem is available as open source free to use/alter without restrictions.
145
+ This gem is available as open source under the terms of the [MIT License](LICENSE).
@@ -5,25 +5,66 @@ require_relative 'config_manager'
5
5
 
6
6
  module AiCommitMessage
7
7
  class CLI < Thor
8
+ def self.exit_on_failure?
9
+ true
10
+ end
11
+
8
12
  DEFAULT_URL = 'http://localhost:11434'
9
- DEFAULT_MODEL_NAME = 'qwen2.5-coder:7b'
13
+ DEFAULT_MODEL_NAME = 'qwen3:8b'
14
+ MAX_DIFF_LENGTH = 12_000
10
15
 
11
- desc "commit", "Generate git commit message"
12
- method_option :url, type: :string, default: DEFAULT_URL
13
- method_option :model, type: :string, default: DEFAULT_MODEL_NAME
16
+ desc "commit", "Generate a commit message and commit with it after confirmation"
17
+ method_option :url, type: :string
18
+ method_option :model, type: :string
19
+ method_option :length, type: :numeric, default: Suggester::DEFAULT_LENGTH
20
+ method_option :conventional, type: :boolean, default: false
21
+ method_option :message_only, type: :boolean, default: false
14
22
 
15
23
  def commit
16
24
  git_diff_output = `git diff --cached --no-color`
25
+ if git_diff_output.strip.empty?
26
+ warn 'Nothing is staged. Stage your changes with `git add` first.'
27
+ exit 1
28
+ end
29
+
17
30
  git_log_output = `git log --format=%s -n 30`
18
31
  git_current_branch = `git branch --show-current`
19
32
 
20
- url = url_to_be_used(options.url)
21
- model = model_to_be_used(options.model)
22
-
23
- suggester = AiCommitMessage::Suggester.new(git_diff_output, git_log_output, git_current_branch)
24
- commit_message = suggester.generate_commit_message(url:, model:)
33
+ suggester = AiCommitMessage::Suggester.new(git_diff_output[0, MAX_DIFF_LENGTH], git_log_output, git_current_branch)
34
+ commit_message = suggester.generate_commit_message(
35
+ url: url_to_be_used(options[:url]),
36
+ model: model_to_be_used(options[:model]),
37
+ length: options[:length],
38
+ conventional: options[:conventional]
39
+ )
25
40
 
26
41
  puts commit_message
42
+ return if options[:message_only]
43
+
44
+ prompt = TTY::Prompt.new
45
+ begin
46
+ # tty-prompt takes the default on EOF, which would silently commit
47
+ # in non-interactive runs; treat a closed stdin as "no".
48
+ confirmed = !$stdin.eof? && prompt.yes?("Commit with this message?", default: true)
49
+ rescue TTY::Reader::InputInterrupt
50
+ confirmed = false
51
+ end
52
+ return unless confirmed
53
+
54
+ system('git', 'commit', '-m', commit_message)
55
+ rescue AiCommitMessage::Suggester::Error => e
56
+ warn e.message
57
+ exit 1
58
+ end
59
+
60
+ desc "models", "List models available on the configured API URL"
61
+ method_option :url, type: :string
62
+
63
+ def models
64
+ puts AiCommitMessage::Suggester.list_models(url_to_be_used(options[:url]))
65
+ rescue AiCommitMessage::Suggester::Error => e
66
+ warn e.message
67
+ exit 1
27
68
  end
28
69
 
29
70
  desc "config", "Set global configs. API URL and Model name"
@@ -31,7 +72,12 @@ module AiCommitMessage
31
72
  prompt = TTY::Prompt.new
32
73
 
33
74
  url = prompt.ask("API URL:", default: ConfigManager.get_url || DEFAULT_URL)
34
- model = prompt.ask("Model name:", default: ConfigManager.get_model || DEFAULT_MODEL_NAME)
75
+ available_models = AiCommitMessage::Suggester.list_models(url) rescue nil
76
+ model = if available_models && !available_models.empty?
77
+ prompt.select("Model name:", available_models)
78
+ else
79
+ prompt.ask("Model name:", default: ConfigManager.get_model || DEFAULT_MODEL_NAME)
80
+ end
35
81
 
36
82
  ConfigManager.set_url(url)
37
83
  ConfigManager.set_model(model)
@@ -42,15 +88,11 @@ module AiCommitMessage
42
88
  private
43
89
 
44
90
  def url_to_be_used(options_url)
45
- return options_url if !options_url.empty? && (options_url != DEFAULT_URL)
46
-
47
- ConfigManager.get_url || DEFAULT_URL
91
+ options_url || ConfigManager.get_url || DEFAULT_URL
48
92
  end
49
93
 
50
94
  def model_to_be_used(options_model)
51
- return options_model if !options_model.empty? && (options_model != DEFAULT_MODEL_NAME)
52
-
53
- ConfigManager.get_model || DEFAULT_MODEL_NAME
95
+ options_model || ConfigManager.get_model || DEFAULT_MODEL_NAME
54
96
  end
55
97
  end
56
98
  end
@@ -34,5 +34,8 @@ class ConfigManager
34
34
  @config
35
35
  end
36
36
 
37
- def self.save_config = File.write(CONFIG_FILE, config.map { |key, value| "#{key}=#{value}" }.join("\n"))
37
+ def self.save_config
38
+ File.write(CONFIG_FILE, config.map { |key, value| "#{key}=#{value}" }.join("\n"))
39
+ FileUtils.chmod(0600, CONFIG_FILE)
40
+ end
38
41
  end
@@ -1,41 +1,144 @@
1
1
  require 'net/http'
2
2
  require 'uri'
3
3
  require 'json'
4
+ require 'openssl'
4
5
 
5
6
  module AiCommitMessage
6
7
  class Suggester
8
+ class Error < StandardError; end
9
+ class ConnectionError < Error; end
10
+ class ApiError < Error; end
11
+
12
+ OPEN_TIMEOUT = 5
13
+ READ_TIMEOUT = 120
14
+ DEFAULT_LENGTH = 72
15
+ # Thinking models (qwen3 and friends) spend tokens reasoning before the
16
+ # answer; the budget must leave room for both or content comes back empty.
17
+ MAX_TOKENS = 2048
18
+
19
+ def self.list_models(url)
20
+ uri = URI("#{normalized_base(url)}/v1/models")
21
+ http = build_http(uri)
22
+
23
+ begin
24
+ response = http.request(Net::HTTP::Get.new(uri))
25
+ rescue Errno::ECONNREFUSED, Errno::EHOSTUNREACH, SocketError
26
+ raise ConnectionError,
27
+ "Could not connect to #{uri.host}:#{uri.port}. Is your local LLM server running?"
28
+ end
29
+
30
+ parse_response(response).fetch('data', []).map { |m| m['id'] }.compact.sort
31
+ end
32
+
33
+ def self.parse_response(response)
34
+ unless response.is_a?(Net::HTTPSuccess)
35
+ raise ApiError, "API returned HTTP #{response.code}: #{response.body.to_s[0, 200]}"
36
+ end
37
+
38
+ JSON.parse(response.body)
39
+ rescue JSON::ParserError
40
+ raise ApiError, 'API response was not valid JSON'
41
+ end
42
+
43
+ def self.build_http(uri)
44
+ http = Net::HTTP.new(uri.host, uri.port)
45
+ http.use_ssl = uri.scheme == 'https'
46
+ http.verify_mode = OpenSSL::SSL::VERIFY_PEER
47
+ http.open_timeout = OPEN_TIMEOUT
48
+ http.read_timeout = READ_TIMEOUT
49
+ http
50
+ end
51
+
52
+ def self.normalized_base(url)
53
+ url.chomp('/').sub(%r{/v1$}, '')
54
+ end
55
+
7
56
  def initialize(git_diff_output, git_log_output, git_current_branch)
8
57
  @git_diff_output = git_diff_output
9
58
  @git_log_output = git_log_output
10
59
  @git_current_branch = git_current_branch
11
60
  end
12
61
 
13
- def generate_commit_message(url:, model:)
14
- url = URI(url + '/api/generate')
15
- body = {
62
+ def generate_commit_message(url:, model:, length: DEFAULT_LENGTH, conventional: false)
63
+ response = post_json(chat_completions_uri(url), request_body(model, length, conventional))
64
+ json = self.class.parse_response(response)
65
+ message = clean_commit_message(json.dig('choices', 0, 'message', 'content'), length)
66
+ return message unless message.empty?
67
+
68
+ raise ApiError,
69
+ 'Model returned no commit message content. Try a non-thinking model or a different model.'
70
+ end
71
+
72
+ private
73
+
74
+ def request_body(model, length, conventional)
75
+ {
16
76
  model: model,
17
- prompt: "Create a concise git commit message with no more than 250 characters. Exclude anything unnecessary such as translation, backticks characters or multiple suggestions, since your entire response will be passed directly into git commit. Consider the following messages as example to follow: #{@git_log_output}.
18
- The First [#XXX] option is the branch name. The current branch name is: #{@git_current_branch}. Now Do it for the following git diff: #{@git_diff_output}",
19
- stream: false,
20
- options: { temperature: 0.8 }
77
+ messages: [
78
+ { role: 'system', content: system_prompt(conventional) },
79
+ { role: 'user', content: user_prompt(length) }
80
+ ],
81
+ temperature: 0.3,
82
+ max_tokens: MAX_TOKENS,
83
+ stream: false
21
84
  }
22
- json_body = body.to_json
23
- headers = { 'Content-Type' => 'application/json' }
85
+ end
86
+
87
+ def system_prompt(conventional)
88
+ prompt = 'You write git commit messages. Respond with a single line containing ' \
89
+ 'only the commit message: no quotes, no backticks, no markdown, no explanations.'
90
+ prompt += ' Use the Conventional Commits format (type: description).' if conventional
91
+ prompt
92
+ end
24
93
 
25
- http = Net::HTTP.new(url.host, url.port)
26
- request = Net::HTTP::Post.new(url)
27
- request.body = json_body
28
- headers.each { |key, value| request[key] = value }
29
- response = http.request(request)
94
+ def user_prompt(length)
95
+ <<~PROMPT
96
+ Write a concise git commit message with no more than #{length} characters for the staged changes below.
97
+ Follow the style of these recent commit messages: #{@git_log_output}
98
+ #{branch_line}
99
+ Git diff:
100
+ #{@git_diff_output}
101
+ PROMPT
102
+ end
103
+
104
+ # git branch --show-current returns nothing on a detached HEAD
105
+ def branch_line
106
+ @git_current_branch.strip.empty? ? '' : "The current branch name is: #{@git_current_branch.strip}"
107
+ end
108
+ # Models tend to wrap answers in code fences or quotes, inline their
109
+ # reasoning in <think> blocks, or offer alternatives; reduce everything to
110
+ # a single clean subject line. Control characters are stripped so that a
111
+ # prompt-injected model cannot smuggle terminal escapes into git history.
112
+ def clean_commit_message(raw, length)
113
+ line = raw.to_s
114
+ .gsub(%r{<think>.*?</think>}m, '')
115
+ .gsub(/\e\][^\x07\e]*(?:\x07|\e\\)/, '')
116
+ .gsub(/\e\[[0-9;:]*[A-Za-z]/, '')
117
+ .gsub(/[\e\a\b\v\f\r]/, '')
118
+ .gsub('`', '')
119
+ .each_line.map(&:strip).reject(&:empty?).first.to_s
120
+ line = line.sub(/\A["'](.*)["']\z/, '\1')
121
+ line.gsub(/\s+/, ' ')[0, length].to_s.strip
122
+ end
123
+
124
+ # Accepts base URLs with or without a trailing /v1 and with or without a
125
+ # trailing slash, e.g. http://localhost:11434, http://localhost:1234/v1/
126
+ def chat_completions_uri(url)
127
+ URI("#{self.class.normalized_base(url)}/v1/chat/completions")
128
+ end
129
+
130
+ def post_json(uri, body)
131
+ http = self.class.build_http(uri)
132
+ request = Net::HTTP::Post.new(uri)
133
+ request['Content-Type'] = 'application/json'
134
+ request.body = body.to_json
30
135
 
31
136
  begin
32
- json = JSON.parse(response.body)
33
- return json['response'] if json
34
- rescue JSON::ParserError => e
35
- puts "Failed to parse API response as JSON: #{e.message}"
137
+ http.request(request)
138
+ rescue Errno::ECONNREFUSED, Errno::EHOSTUNREACH, SocketError => e
139
+ raise ConnectionError,
140
+ "Could not connect to #{uri.host}:#{uri.port}. Is your local LLM server running?"
36
141
  end
37
-
38
- nil
39
142
  end
40
143
  end
41
- end
144
+ end
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ai-commit-message
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.2
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Afonso Neto
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2025-03-17 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
@@ -39,13 +38,13 @@ dependencies:
39
38
  - !ruby/object:Gem::Version
40
39
  version: '0'
41
40
  - !ruby/object:Gem::Dependency
42
- name: net-http
41
+ name: rake
43
42
  requirement: !ruby/object:Gem::Requirement
44
43
  requirements:
45
44
  - - ">="
46
45
  - !ruby/object:Gem::Version
47
46
  version: '0'
48
- type: :runtime
47
+ type: :development
49
48
  prerelease: false
50
49
  version_requirements: !ruby/object:Gem::Requirement
51
50
  requirements:
@@ -53,20 +52,21 @@ dependencies:
53
52
  - !ruby/object:Gem::Version
54
53
  version: '0'
55
54
  - !ruby/object:Gem::Dependency
56
- name: json
55
+ name: minitest
57
56
  requirement: !ruby/object:Gem::Requirement
58
57
  requirements:
59
58
  - - ">="
60
59
  - !ruby/object:Gem::Version
61
60
  version: '0'
62
- type: :runtime
61
+ type: :development
63
62
  prerelease: false
64
63
  version_requirements: !ruby/object:Gem::Requirement
65
64
  requirements:
66
65
  - - ">="
67
66
  - !ruby/object:Gem::Version
68
67
  version: '0'
69
- description: Git commit messages suggested by AI
68
+ description: Git commit messages suggested by AI using a local LLM via any OpenAI-compatible
69
+ API
70
70
  email:
71
71
  - afonso.pontesneto@gmail.com
72
72
  executables:
@@ -74,6 +74,7 @@ executables:
74
74
  extensions: []
75
75
  extra_rdoc_files: []
76
76
  files:
77
+ - LICENSE
77
78
  - README.md
78
79
  - bin/ai-commit-message
79
80
  - lib/ai-commit-message/cli.rb
@@ -81,9 +82,9 @@ files:
81
82
  - lib/ai-commit-message/suggester.rb
82
83
  homepage: https://github.com/AfonsoNeto/ai-commit-message
83
84
  licenses:
84
- -
85
- metadata: {}
86
- post_install_message:
85
+ - MIT
86
+ metadata:
87
+ rubygems_mfa_required: 'true'
87
88
  rdoc_options: []
88
89
  require_paths:
89
90
  - lib
@@ -98,8 +99,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
98
99
  - !ruby/object:Gem::Version
99
100
  version: '0'
100
101
  requirements: []
101
- rubygems_version: 3.5.11
102
- signing_key:
102
+ rubygems_version: 4.0.12
103
103
  specification_version: 4
104
104
  summary: Git commit messages suggested by AI
105
105
  test_files: []