committer 0.1.1 → 0.2.2
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 +32 -12
- data/bin/committer +84 -59
- data/lib/clients/claude_client.rb +29 -16
- data/lib/committer/config.rb +9 -3
- data/lib/committer/prompt_templates.rb +83 -0
- data/lib/committer/version.rb +1 -1
- metadata +5 -5
- data/bin/git-smart-commit +0 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 19704907042388baa2b8ede51ebe80710b9cbe5f6095fd09b0fb7cb997496382
|
4
|
+
data.tar.gz: ecaab24d0b58301c41a9677cf09fd1ae5ef23c3930c911c6cdf9820abacc52f9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1a6eb3eaabe0723082df95e113c7cbeb47521a16839ae04cdf6bfa146a3747b68b77aeac2462954220eeefd2cf709d73a8e2df9ab63b6b265d3c9f4304706357
|
7
|
+
data.tar.gz: 1c98170b2c2b194bb739c96278e3150e005d1f00df9c69c1c21219b26e7f2511d710894eb1001dfe973103e23efa22e91aabaa0f27fd06e53387aeca1b10e626
|
data/README.md
CHANGED
@@ -4,8 +4,25 @@ An AI-powered git commit message generator using Claude.
|
|
4
4
|
|
5
5
|
## Overview
|
6
6
|
|
7
|
+
The goal of committer is to make it easier to write beautiful commits.
|
8
|
+
|
7
9
|
Committer uses Claude AI to analyze your staged git changes and generate conventional commit messages for you. It detects the type of changes (feature, fix, refactor, etc.) and creates a well-formatted commit message that follows best practices.
|
8
10
|
|
11
|
+
## What Makes a Good Commit
|
12
|
+
|
13
|
+
A good commit should:
|
14
|
+
- Have a summary that clearly describes the change
|
15
|
+
- Explain WHY the change was made, not just what changed
|
16
|
+
|
17
|
+
When a future developer uses git blame to understand a line of code, they should immediately understand why the change was made. This context is invaluable for maintaining and evolving the codebase effectively.
|
18
|
+
|
19
|
+
## How Committer Helps
|
20
|
+
|
21
|
+
Committer analyzes your code changes and generates commit messages that:
|
22
|
+
1. Provide a clean, descriptive summary of the change
|
23
|
+
2. Include context about why the change was necessary
|
24
|
+
3. Follow conventional commit format for consistency
|
25
|
+
|
9
26
|
## Installation
|
10
27
|
|
11
28
|
### Install from RubyGems
|
@@ -23,6 +40,7 @@ gem install committer
|
|
23
40
|
```
|
24
41
|
|
25
42
|
2. Install the project dependencies:
|
43
|
+
|
26
44
|
```sh
|
27
45
|
bundle install
|
28
46
|
```
|
@@ -43,13 +61,19 @@ committer setup
|
|
43
61
|
|
44
62
|
This will create a template config file at `~/.committer/config.yml`.
|
45
63
|
|
46
|
-
Next, edit this file to add your Anthropic API key and optionally change the model:
|
64
|
+
Next, edit this file to add your Anthropic API key and optionally change the model or configure commit scopes:
|
47
65
|
|
48
66
|
```yaml
|
49
67
|
api_key: your_anthropic_api_key_here
|
50
|
-
model: claude-3-sonnet-
|
68
|
+
model: claude-3-7-sonnet-20250219
|
69
|
+
scopes:
|
70
|
+
- feature
|
71
|
+
- api
|
72
|
+
- ui
|
51
73
|
```
|
52
74
|
|
75
|
+
The `scopes` configuration is optional. When provided, Committer will generate conventional commit messages with scopes (like `feat(api): add new endpoint`). If left as `null` or omitted, commit messages will be generated without scopes (like `feat: add new endpoint`).
|
76
|
+
|
53
77
|
You only need to do this setup once.
|
54
78
|
|
55
79
|
## Usage
|
@@ -61,11 +85,13 @@ committer
|
|
61
85
|
```
|
62
86
|
|
63
87
|
This will:
|
88
|
+
|
64
89
|
1. Get the diff of your staged changes
|
65
|
-
2.
|
66
|
-
3.
|
67
|
-
4.
|
68
|
-
5.
|
90
|
+
2. Ask you for optional context about why you're making the change
|
91
|
+
3. Send the diff and context to Claude for analysis
|
92
|
+
4. Generate a commit message in conventional format (with scope if configured)
|
93
|
+
5. Open your default git editor with the suggested message
|
94
|
+
6. Allow you to edit the message if needed or simply save to confirm
|
69
95
|
|
70
96
|
## Commands
|
71
97
|
|
@@ -73,12 +99,6 @@ This will:
|
|
73
99
|
- `committer setup` - Create the config file template
|
74
100
|
- `committer help` - Display help information
|
75
101
|
|
76
|
-
You can also run it directly through git:
|
77
|
-
|
78
|
-
```bash
|
79
|
-
git smart-commit
|
80
|
-
```
|
81
|
-
|
82
102
|
## License
|
83
103
|
|
84
104
|
MIT
|
data/bin/committer
CHANGED
@@ -6,6 +6,7 @@ require 'open3'
|
|
6
6
|
require 'httparty'
|
7
7
|
require 'yaml'
|
8
8
|
require_relative '../lib/committer/config'
|
9
|
+
require_relative '../lib/committer/prompt_templates'
|
9
10
|
require_relative '../lib/clients/claude_client'
|
10
11
|
|
11
12
|
# Handle command line arguments
|
@@ -27,71 +28,95 @@ when 'help', '--help', '-h'
|
|
27
28
|
end
|
28
29
|
|
29
30
|
# Default behavior: generate commit message
|
30
|
-
def
|
31
|
-
|
31
|
+
def build_commit_prompt(diff, commit_context = nil)
|
32
|
+
scopes = Committer::Config.load
|
33
|
+
scope_section = scopes.empty? ? '' : "\nScopes:\n#{scopes.map { |s| "- #{s}" }.join("\n")}"
|
34
|
+
scope_instruction = if scopes.empty?
|
35
|
+
'- DO NOT include a scope in your commit message'
|
36
|
+
else
|
37
|
+
'- Choose an appropriate scope from the list above if relevant to the change'
|
38
|
+
end
|
39
|
+
format(template(commit_context),
|
40
|
+
diff: diff,
|
41
|
+
scopes_section: scope_section,
|
42
|
+
scope_instruction: scope_instruction,
|
43
|
+
commit_context: commit_context)
|
44
|
+
end
|
32
45
|
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
exit 0
|
37
|
-
else
|
38
|
-
begin
|
39
|
-
client = Clients::ClaudeClient.new
|
40
|
-
puts 'Sending diff to Claude...'
|
41
|
-
|
42
|
-
prompt = <<~PROMPT
|
43
|
-
Below is a git diff of staged changes. Please analyze it and create a commit message following the Conventional Commits format:
|
44
|
-
|
45
|
-
Format: <type>(<optional scope>): <description>
|
46
|
-
|
47
|
-
Types:
|
48
|
-
- feat: A new feature
|
49
|
-
- fix: A bug fix
|
50
|
-
- docs: Documentation only changes
|
51
|
-
- style: Changes that do not affect the meaning of the code
|
52
|
-
- refactor: A code change that neither fixes a bug nor adds a feature
|
53
|
-
- perf: A code change that improves performance
|
54
|
-
- test: Adding missing tests or correcting existing tests
|
55
|
-
- chore: Changes to the build process or auxiliary tools
|
56
|
-
|
57
|
-
Guidelines:
|
58
|
-
- Keep the first line under 70 characters
|
59
|
-
- Use imperative, present tense (e.g., "add" not "added" or "adds")
|
60
|
-
- Do not end with a period
|
61
|
-
- Be concise but descriptive
|
62
|
-
|
63
|
-
Git Diff:
|
64
|
-
```
|
65
|
-
#{stdout}
|
66
|
-
```
|
67
|
-
|
68
|
-
Respond ONLY with the commit message text, nothing else.
|
69
|
-
PROMPT
|
70
|
-
|
71
|
-
response = client.post(prompt)
|
72
|
-
commit_message = begin
|
73
|
-
response.dig('content', 0, 'text')
|
74
|
-
rescue StandardError
|
75
|
-
response.inspect
|
76
|
-
end
|
77
|
-
|
78
|
-
puts "\nOpening git commit with the suggested message..."
|
79
|
-
|
80
|
-
# Create git commit with the suggested message and open in editor
|
81
|
-
system('git', 'commit', '-m', commit_message, '-e')
|
82
|
-
rescue Clients::ClaudeClient::ConfigError => e
|
83
|
-
puts "Error: #{e.message}"
|
84
|
-
exit 1
|
85
|
-
rescue StandardError => e
|
86
|
-
puts "Error: #{e.message}"
|
87
|
-
exit 1
|
88
|
-
end
|
89
|
-
end
|
46
|
+
def template(commit_context)
|
47
|
+
if commit_context.nil? || commit_context.empty?
|
48
|
+
Committer::PromptTemplates::SUMMARY_ONLY
|
90
49
|
else
|
50
|
+
Committer::PromptTemplates::SUMMARY_AND_BODY
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def check_git_status
|
55
|
+
stdout, stderr, status = Open3.capture3('git diff --staged')
|
56
|
+
|
57
|
+
unless status.success?
|
91
58
|
puts 'Error executing git diff --staged:'
|
92
59
|
puts stderr
|
93
60
|
exit 1
|
94
61
|
end
|
62
|
+
|
63
|
+
if stdout.empty?
|
64
|
+
puts 'No changes are staged for commit.'
|
65
|
+
exit 0
|
66
|
+
end
|
67
|
+
|
68
|
+
stdout
|
69
|
+
end
|
70
|
+
|
71
|
+
def parse_response(response, commit_context)
|
72
|
+
text = response.dig('content', 0, 'text')
|
73
|
+
|
74
|
+
# If user didn't provide context, response should only be a summary line
|
75
|
+
if commit_context.nil? || commit_context.empty?
|
76
|
+
{ summary: text.strip, body: nil }
|
77
|
+
else
|
78
|
+
# Split the response into summary and body
|
79
|
+
message_parts = text.split("\n\n", 2)
|
80
|
+
summary = message_parts[0].strip
|
81
|
+
body = message_parts[1]&.strip
|
82
|
+
|
83
|
+
# Wrap body text at 80 characters
|
84
|
+
body = body.gsub(/(.{1,80})(\s+|$)/, "\\1\n").strip if body
|
85
|
+
|
86
|
+
{ summary: summary, body: body }
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def prepare_commit_message(diff, commit_context = nil)
|
91
|
+
client = Clients::ClaudeClient.new
|
92
|
+
puts 'Sending diff to Claude...'
|
93
|
+
|
94
|
+
prompt = build_commit_prompt(diff, commit_context)
|
95
|
+
response = client.post(prompt)
|
96
|
+
parse_response(response, commit_context)
|
97
|
+
end
|
98
|
+
|
99
|
+
def execute_git_diff_staged
|
100
|
+
diff = check_git_status
|
101
|
+
|
102
|
+
# Prompt user for commit context
|
103
|
+
puts 'Why are you making this change? (Press Enter to skip)'
|
104
|
+
commit_context = gets.chomp
|
105
|
+
|
106
|
+
commit_message = prepare_commit_message(diff, commit_context)
|
107
|
+
|
108
|
+
summary = commit_message[:summary]
|
109
|
+
body = commit_message[:body]
|
110
|
+
|
111
|
+
# Create git commit with the suggested message and open in editor
|
112
|
+
if body
|
113
|
+
system('git', 'commit', '-m', summary, '-m', body, '-e')
|
114
|
+
else
|
115
|
+
system('git', 'commit', '-m', summary, '-e')
|
116
|
+
end
|
117
|
+
rescue Clients::ClaudeClient::ConfigError, StandardError => e
|
118
|
+
puts "Error: #{e.message}"
|
119
|
+
exit 1
|
95
120
|
end
|
96
121
|
|
97
122
|
# Execute the function if no specific command was given
|
@@ -5,6 +5,7 @@ require 'httparty'
|
|
5
5
|
require_relative '../committer/config'
|
6
6
|
|
7
7
|
module Clients
|
8
|
+
# Claude API client for communicating with Anthropic's Claude model
|
8
9
|
class ClaudeClient
|
9
10
|
class OverloadError < StandardError; end
|
10
11
|
class UnknownError < StandardError; end
|
@@ -21,13 +22,28 @@ module Clients
|
|
21
22
|
|
22
23
|
def post(message)
|
23
24
|
body = {
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
{
|
25
|
+
model: @config['model'],
|
26
|
+
max_tokens: 4096,
|
27
|
+
messages: [
|
28
|
+
{ role: 'user', content: message }
|
28
29
|
]
|
29
30
|
}
|
30
|
-
|
31
|
+
|
32
|
+
response = send_request(body)
|
33
|
+
handle_error_response(response) if response['type'] == 'error'
|
34
|
+
|
35
|
+
response
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def send_request(body)
|
41
|
+
options = build_request_options(body)
|
42
|
+
HTTParty.post('https://api.anthropic.com/v1/messages', options)
|
43
|
+
end
|
44
|
+
|
45
|
+
def build_request_options(body)
|
46
|
+
{
|
31
47
|
headers: {
|
32
48
|
'anthropic-version': '2023-06-01',
|
33
49
|
'content-type': 'application/json',
|
@@ -35,17 +51,14 @@ module Clients
|
|
35
51
|
},
|
36
52
|
body: body.to_json
|
37
53
|
}
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
raise UnknownError, "Claude API error: #{response.inspect}"
|
47
|
-
end
|
48
|
-
response
|
54
|
+
end
|
55
|
+
|
56
|
+
def handle_error_response(response)
|
57
|
+
error = response['error']
|
58
|
+
raise OverloadError if error['type'] == 'overloaded_error'
|
59
|
+
|
60
|
+
puts response
|
61
|
+
raise UnknownError, "Claude API error: #{response.inspect}"
|
49
62
|
end
|
50
63
|
end
|
51
64
|
end
|
data/lib/committer/config.rb
CHANGED
@@ -4,12 +4,14 @@ require 'yaml'
|
|
4
4
|
require 'fileutils'
|
5
5
|
|
6
6
|
module Committer
|
7
|
+
# Configuration management for the Committer gem
|
7
8
|
class Config
|
8
9
|
CONFIG_DIR = File.join(Dir.home, '.committer')
|
9
10
|
CONFIG_FILE = File.join(CONFIG_DIR, 'config.yml')
|
10
11
|
DEFAULT_CONFIG = {
|
11
12
|
'api_key' => nil,
|
12
|
-
'model' => 'claude-3-sonnet-
|
13
|
+
'model' => 'claude-3-7-sonnet-20250219',
|
14
|
+
'scopes' => nil
|
13
15
|
}.freeze
|
14
16
|
|
15
17
|
def self.load
|
@@ -23,7 +25,7 @@ module Committer
|
|
23
25
|
end
|
24
26
|
|
25
27
|
def self.create_default_config
|
26
|
-
FileUtils.mkdir_p(CONFIG_DIR)
|
28
|
+
FileUtils.mkdir_p(CONFIG_DIR)
|
27
29
|
File.write(CONFIG_FILE, DEFAULT_CONFIG.to_yaml)
|
28
30
|
end
|
29
31
|
|
@@ -35,7 +37,11 @@ module Committer
|
|
35
37
|
puts 'Example config format:'
|
36
38
|
puts '---'
|
37
39
|
puts 'api_key: your_api_key_here'
|
38
|
-
puts 'model: claude-3-sonnet-
|
40
|
+
puts 'model: claude-3-7-sonnet-20250219'
|
41
|
+
puts 'scopes:'
|
42
|
+
puts ' - feature'
|
43
|
+
puts ' - api'
|
44
|
+
puts ' - ui'
|
39
45
|
end
|
40
46
|
end
|
41
47
|
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Committer
|
4
|
+
module PromptTemplates
|
5
|
+
SUMMARY_ONLY = <<~PROMPT
|
6
|
+
Below is a git diff of staged changes. Please analyze it and create a commit message following the Conventional Commits format with ONLY a summary line (NO body):
|
7
|
+
|
8
|
+
Format when scopes are available: <type>(<scope>): <description>
|
9
|
+
Format when no scopes are available: <type>: <description>
|
10
|
+
|
11
|
+
Types:
|
12
|
+
- feat: A new feature
|
13
|
+
- fix: A bug fix
|
14
|
+
- docs: Documentation only changes
|
15
|
+
- style: Changes that do not affect the meaning of the code
|
16
|
+
- refactor: A code change that neither fixes a bug nor adds a feature
|
17
|
+
- perf: A code change that improves performance
|
18
|
+
- test: Adding missing tests or correcting existing tests
|
19
|
+
- chore: Changes to the build process or auxiliary tools
|
20
|
+
%<scopes_section>s
|
21
|
+
Guidelines:
|
22
|
+
- Keep the summary under 70 characters
|
23
|
+
- Use imperative, present tense (e.g., "add" not "added" or "adds")
|
24
|
+
- Do not end the summary with a period
|
25
|
+
- Be concise but descriptive in the summary
|
26
|
+
%<scope_instruction>s
|
27
|
+
|
28
|
+
Git Diff:
|
29
|
+
```
|
30
|
+
%<diff>s
|
31
|
+
```
|
32
|
+
|
33
|
+
Respond ONLY with the commit message summary line, nothing else.
|
34
|
+
PROMPT
|
35
|
+
|
36
|
+
SUMMARY_AND_BODY = <<~PROMPT
|
37
|
+
Below is a git diff of staged changes. Please analyze it and create a commit message following the Conventional Commits format with a summary line and a detailed body:
|
38
|
+
|
39
|
+
Format when scopes are available:
|
40
|
+
<type>(<scope>): <description>
|
41
|
+
|
42
|
+
<blank line>
|
43
|
+
<body with more detailed explanation>
|
44
|
+
|
45
|
+
Format when no scopes are available:
|
46
|
+
<type>: <description>
|
47
|
+
|
48
|
+
<blank line>
|
49
|
+
<body with more detailed explanation>
|
50
|
+
|
51
|
+
|
52
|
+
Types:
|
53
|
+
- feat: A new feature
|
54
|
+
- fix: A bug fix
|
55
|
+
- docs: Documentation only changes
|
56
|
+
- style: Changes that do not affect the meaning of the code
|
57
|
+
- refactor: A code change that neither fixes a bug nor adds a feature
|
58
|
+
- perf: A code change that improves performance
|
59
|
+
- test: Adding missing tests or correcting existing tests
|
60
|
+
- chore: Changes to the build process or auxiliary tools
|
61
|
+
%<scopes_section>s
|
62
|
+
Guidelines:
|
63
|
+
- Keep the first line (summary) under 70 characters
|
64
|
+
- Use imperative, present tense (e.g., "add" not "added" or "adds")
|
65
|
+
- Do not end the summary with a period
|
66
|
+
- Be concise but descriptive in the summary
|
67
|
+
- Add a blank line between summary and body
|
68
|
+
- Use the body to explain why the change was made, incorporating the user's context
|
69
|
+
- Wrap each line in the body at 80 characters maximum
|
70
|
+
- Break the body into multiple paragraphs if needed
|
71
|
+
%<scope_instruction>s
|
72
|
+
|
73
|
+
User's context for this change: %<commit_context>s
|
74
|
+
|
75
|
+
Git Diff:
|
76
|
+
```
|
77
|
+
%<diff>s
|
78
|
+
```
|
79
|
+
|
80
|
+
Respond ONLY with the commit message text (summary and body), nothing else.
|
81
|
+
PROMPT
|
82
|
+
end
|
83
|
+
end
|
data/lib/committer/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: committer
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sebastien Stettler
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2025-03-
|
11
|
+
date: 2025-03-06 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: httparty
|
@@ -29,21 +29,21 @@ description: A tool that uses Claude API to generate conventional commit message
|
|
29
29
|
email:
|
30
30
|
- sebastien@managerbot.dev
|
31
31
|
executables:
|
32
|
-
- git-smart-commit
|
33
32
|
- committer
|
34
33
|
extensions: []
|
35
34
|
extra_rdoc_files: []
|
36
35
|
files:
|
37
36
|
- README.md
|
38
37
|
- bin/committer
|
39
|
-
- bin/git-smart-commit
|
40
38
|
- lib/clients/claude_client.rb
|
41
39
|
- lib/committer/config.rb
|
40
|
+
- lib/committer/prompt_templates.rb
|
42
41
|
- lib/committer/version.rb
|
43
42
|
homepage: https://github.com/Hyper-Unearthing/committer
|
44
43
|
licenses:
|
45
44
|
- MIT
|
46
|
-
metadata:
|
45
|
+
metadata:
|
46
|
+
rubygems_mfa_required: 'true'
|
47
47
|
post_install_message:
|
48
48
|
rdoc_options: []
|
49
49
|
require_paths:
|