committer 0.5.0 → 0.5.1

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: d6586bef8883555a10ed5f9f1e5a5b80100356a1aba0d94209ab2174898406f7
4
- data.tar.gz: d2a292a436612443581b1259ad4133b6e6cadf08611a8a40a07f125647744a3a
3
+ metadata.gz: 15d7ba36e033d0d50db617ab0e02673cead10eea1fa8a86e95180bed40ea3734
4
+ data.tar.gz: b7e3e8ddb14cf500b1bce8bec83ad1f4ce9f345bf950945365b400550e6a6851
5
5
  SHA512:
6
- metadata.gz: fab0c91147d5cde773a423d789c35e2eebb224de868ae958294cfa1a06db1ac2e444ab608c51c4ed1c13edd0e0ed289229668b5f61305ba6e1b798c267aaf7a7
7
- data.tar.gz: 74a49ce5edf20dc730a2bcaafb5b31d5a78cb55bcf220b8581c14a8dc98e68aff5650c11d0322a0749df9bb1e16826124ef658b68636d408a0fc766afad84d72
6
+ metadata.gz: 5a180861a3641814d2f517194639f57178c8b4470331a21c1b8a7e404ea437c1c2b4b1ab750c8b1588aee56e0bc2b5837ee6a95e92c72088c84d385268046c2c
7
+ data.tar.gz: 3a9efb4acca99255a025eea8caa25f9d4583b8a75583f82db363769f64d250f6b195dc83a9fc74428e81c93aa17ae9256c46e66c4f24394fff9d8ae87d71e5e4
data/README.md CHANGED
@@ -96,6 +96,7 @@ committer setup-git-hook
96
96
  ```
97
97
 
98
98
  This command will:
99
+
99
100
  1. Verify you're in the root of a git repository
100
101
  2. Install the prepare-commit-msg hook in your `.git/hooks` directory
101
102
  3. Make the hook executable
@@ -103,6 +104,7 @@ This command will:
103
104
  ### How the Git Hook Works
104
105
 
105
106
  Once installed, the git hook will:
107
+
106
108
  1. Automatically run whenever you execute `git commit`
107
109
  2. Generate an AI-powered commit message based on your staged changes
108
110
  3. Pre-fill your commit message editor with the generated message
@@ -127,6 +129,70 @@ bundle install
127
129
  bundle exec rake spec
128
130
  ```
129
131
 
132
+ ## Evaluation Tools
133
+
134
+ The `eval` directory contains tools for benchmarking and testing commit message generation across different AI models. These tools help evaluate the quality and consistency of generated commit messages.
135
+
136
+ ### Usage
137
+
138
+ 1. **Install dependencies**:
139
+
140
+ ```bash
141
+ npm install
142
+ npm install -g promptfoo
143
+ ```
144
+
145
+ 2. **Set environment variables**:
146
+
147
+ ```bash
148
+ export ANTHROPIC_API_KEY=your_anthropic_api_key_here
149
+ export OPENAI_API_KEY=your_openai_api_key_here
150
+ ```
151
+
152
+ 3. **Dump commits** from a repository to a database:
153
+
154
+ ```bash
155
+ node eval.mjs dump-commits --eval-data-dir ./path --repo ./repo-path
156
+ ```
157
+
158
+ 4. **Process commits** for evaluation:
159
+
160
+ ```bash
161
+ node eval.mjs process-commits --eval-data-dir ./path
162
+ ```
163
+
164
+ 5. **Run evaluations** against different AI models:
165
+ ```bash
166
+ node eval.mjs run-evaluation --eval-data-dir ./path [--sha commit_sha] [--limit number]
167
+ ```
168
+
169
+ The system compares multiple AI models (Claude 3.7 Sonnet, Claude 3.5 Haiku, GPT-4o, GPT-4o Mini) and tests their ability to generate properly formatted conventional commit messages based on commit diffs.
170
+
171
+ ### Viewing Evaluation Results prompt versions
172
+
173
+ select your verison from https://drive.google.com/drive/folders/1xHpOkNSss2PM-cnLKGaCHLNfWvxl9hAX
174
+
175
+ download .promptfoo file and move it to ~/.promptfoo promptfoo.sqlite
176
+
177
+ ```bash
178
+ npm install -g promptfoo
179
+ promptfoo view -y
180
+ ```
181
+
182
+ ### Running Evaluation for prompt versions
183
+
184
+ select your verison from https://drive.google.com/drive/folders/1xHpOkNSss2PM-cnLKGaCHLNfWvxl9hAX
185
+
186
+ download .electron-eval folder and move it the root fo this project
187
+
188
+ ```bash
189
+ npm install
190
+ npm install -g promptfoo
191
+ export ANTHROPIC_API_KEY=your_anthropic_api_key_here
192
+ export OPENAI_API_KEY=your_openai_api_key_here
193
+ node eval.mjs run-evaluation --eval-data-dir ./electron-eval
194
+ ```
195
+
130
196
  ## License
131
197
 
132
198
  MIT
@@ -16,9 +16,8 @@ module Committer
16
16
  end
17
17
 
18
18
  def build_commit_prompt
19
- format(template,
20
- diff: @diff,
21
- commit_context: @commit_context)
19
+ scopes = Committer::Config::Accessor.instance[:scopes] || []
20
+ Committer::PromptTemplates.build_prompt(diff, scopes, commit_context)
22
21
  end
23
22
 
24
23
  def template
@@ -55,8 +54,8 @@ module Committer
55
54
  end
56
55
  end
57
56
 
58
- def prepare_commit_message
59
- client = Clients::ClaudeClient.new
57
+ def prepare_commit_message(client_class = Clients::ClaudeClient)
58
+ client = client_class.new
60
59
 
61
60
  prompt = build_commit_prompt
62
61
  response = client.post(prompt)
@@ -28,60 +28,52 @@ module Committer
28
28
 
29
29
  def load_config
30
30
  # Load configs from both locations and merge them
31
- home_config = load_config_from_path(Committer::Config::Constants::CONFIG_FILE)
32
- git_root_config = load_config_from_git_root
31
+
32
+ home_config = load_yml_file(read_file_from_home(Committer::Config::Constants::CONFIG_FILE_NAME))
33
+ git_root_config = load_yml_file(read_file_from_git_root(Committer::Config::Constants::CONFIG_FILE_NAME))
33
34
  raise Committer::ConfigErrors::NotSetup if home_config.empty? && git_root_config.empty?
34
35
 
36
+ unless home_config.is_a?(Hash) && git_root_config.is_a?(Hash)
37
+ raise Committer::ConfigErrors::FormatError,
38
+ 'Config file must be a YAML hash'
39
+ end
40
+
35
41
  # Merge configs with git root taking precedence
36
42
  home_config.merge(git_root_config)
37
43
  end
38
44
 
39
- def load_config_from_path(path)
40
- return {} unless File.exist?(path)
41
-
42
- result = YAML.load_file(path)
43
- raise Committer::ConfigErrors::FormatError, 'Config file must be a YAML hash' unless result.is_a?(Hash)
44
-
45
- result
45
+ def load_yml_file(contents)
46
+ YAML.safe_load(contents, permitted_classes: [Symbol, NilClass, String, Array]) || {}
46
47
  end
47
48
 
48
- def load_file_from_path(path)
49
+ def read_file_from_path(path)
49
50
  return '' unless File.exist?(path)
50
51
 
51
52
  File.read(path)
52
53
  end
53
54
 
54
- def load_formatting_rules
55
- git_root = Committer::GitHelper.repo_root
56
- unless git_root.empty?
57
- formatting_rules_git_path = File.join(git_root, '.committer',
58
- Committer::Config::Constants::FORMATTING_RULES_FILE_NAME)
59
- end
60
-
61
- git_path_contents = load_file_from_path(formatting_rules_git_path) if formatting_rules_git_path
55
+ def read_file_from_git_root(file_name)
56
+ read_file_from_path(File.join(Committer::GitHelper.repo_root, '.committer', file_name))
57
+ end
62
58
 
63
- return git_path_contents unless git_path_contents.empty?
59
+ def read_file_from_home(file_name)
60
+ read_file_from_path(File.join(Committer::Config::Constants::CONFIG_DIR, file_name))
61
+ end
64
62
 
65
- home_path = File.join(Committer::Config::Constants::CONFIG_DIR,
66
- Committer::Config::Constants::FORMATTING_RULES_FILE_NAME)
63
+ def load_default_file(file_name)
64
+ read_file_from_path(File.join(Committer::Config::Constants::DEFAULTS_PATH, file_name))
65
+ end
67
66
 
68
- home_path_contents = load_file_from_path(home_path)
67
+ def read_path_prioritized_file(file_name)
68
+ git_path_contents = read_file_from_git_root(file_name)
69
69
 
70
- return home_path_contents unless home_path_contents.empty?
70
+ return git_path_contents unless git_path_contents.empty?
71
71
 
72
- default_path = File.join(Committer::Config::Constants::DEFAULT_PROMPT_PATH,
73
- Committer::Config::Constants::FORMATTING_RULES_FILE_NAME)
74
- load_file_from_path(default_path)
75
- end
72
+ home_path_contents = read_file_from_home(file_name)
76
73
 
77
- def load_config_from_git_root
78
- git_root = Committer::GitHelper.repo_root
79
- return {} if git_root.empty?
74
+ return home_path_contents unless home_path_contents.empty?
80
75
 
81
- git_config_file = File.join(git_root, '.committer', 'config.yml')
82
- load_config_from_path(git_config_file)
83
- rescue StandardError
84
- {}
76
+ load_default_file(file_name)
85
77
  end
86
78
 
87
79
  # Force reload configuration (useful for testing)
@@ -4,10 +4,12 @@ module Committer
4
4
  module Config
5
5
  module Constants
6
6
  CONFIG_DIR = File.join(Dir.home, '.committer')
7
- CONFIG_FILE = File.join(CONFIG_DIR, 'config.yml')
8
- FORMATTING_RULES_FILE_NAME = 'formatting_rules.txt'
9
- DEFAULT_PROMPT_PATH = File.join(File.dirname(__FILE__), '..')
7
+ DEFAULTS_PATH = File.join(File.dirname(__FILE__), './defaults')
8
+
9
+ COMMIT_MESSAGE_ONLY_PROMPT_FILE_NAME = 'commit_message_only.prompt'
10
+ COMMIT_MESSAGE_AND_BODY_PROMPT_FILE_NAME = 'commit_message_and_body.prompt'
10
11
  CONFIG_FILE_NAME = 'config.yml'
12
+
11
13
  DEFAULT_CONFIG = {
12
14
  'api_key' => nil,
13
15
  'model' => 'claude-3-7-sonnet-20250219',
@@ -0,0 +1,52 @@
1
+ You are an experienced software developer tasked with creating a commit message based on a git diff. Your goal is to produce a clear, concise, and informative commit message.
2
+
3
+ First, carefully analyze the following git diff:
4
+
5
+ <git_diff>
6
+ {{DIFF}}
7
+ </git_diff>
8
+
9
+ Here are the available scopes (if any):
10
+
11
+ <scopes>
12
+ {{SCOPES}}
13
+ </scopes>
14
+
15
+ <user_context>
16
+ {{CONTEXT}}
17
+ </user_context>
18
+
19
+ Please follow these instructions to generate the commit message:
20
+
21
+ 1. Analyze the git diff and determine the most appropriate commit type from the following options:
22
+ - feat: A new feature
23
+ - fix: A bug fix
24
+ - docs: Documentation only changes
25
+ - style: Changes that do not affect the meaning of the code
26
+ - refactor: A code change that neither fixes a bug nor adds a feature
27
+ - perf: A code change that improves performance
28
+ - test: Adding missing tests or correcting existing tests
29
+ - chore: Changes to the build process or auxiliary tools and libraries
30
+
31
+
32
+ 2. Adhere to these message guidelines:
33
+ - Keep the summary under 70 characters
34
+ - Use imperative, present tense (e.g., "add" not "added" or "adds")
35
+ - Do not end the summary with a period
36
+ - Be concise but descriptive
37
+
38
+ 3. Format the commit message as follows:
39
+ - If a scope is available: <type>(<scope>): <description>
40
+ - If no scope is available: <type>: <description>
41
+
42
+ 4 Body Guidelines:
43
+ - Add a blank line between summary and body
44
+ - Use the body to explain why the change was made, incorporating the user's context, defined in <user_context>
45
+ - Wrap each line in the body at 80 characters maximum
46
+ - Break the body into multiple paragraphs if needed
47
+
48
+ [Your concise commit message in the specified format]
49
+ [blank line]
50
+ [Your detailed commit message body]
51
+
52
+ Respond ONLY with the commit message text (message and body), nothing else.
@@ -0,0 +1,49 @@
1
+ You are an experienced software developer tasked with creating a commit message based on a git diff. Your goal is to produce a clear, concise, and informative commit message.
2
+
3
+ First, carefully analyze the following git diff:
4
+
5
+ <git_diff>
6
+ {{DIFF}}
7
+ </git_diff>
8
+
9
+ Here are the available scopes (if any):
10
+
11
+ <scopes>
12
+ {{SCOPES}}
13
+ </scopes>
14
+
15
+
16
+
17
+ 2. Adhere to these message guidelines:
18
+ - Keep the summary under 70 characters
19
+ - Use imperative, present tense (e.g., "add" not "added" or "adds")
20
+ - Do not end the summary with a period
21
+ - Be concise but descriptive
22
+
23
+ 3. Format the commit message as follows:
24
+ - If a scope is available: <type>(<scope>): <description>
25
+ - If no scope is available: <type>: <description>
26
+
27
+
28
+ Please follow these instructions to generate the commit message:
29
+
30
+ if such and such changes its docs b ut if commit also has such then its a feat
31
+
32
+ 1. Analyze the git diff and determine the most appropriate commit type from the following options:
33
+ - feat: A new feature these changes ARE NOT in the docs directory
34
+ - fix: A bug fix these changes ARE NOT in the docs directory
35
+ - docs: Documentation only changes for example changes that happen in docs directory
36
+ - style: Changes that do not affect the code execution (e.g., white-space, formatting, missing semi-colons, etc.) usually done by linters
37
+ - refactor:
38
+ - A code change that does not change the behaviour of the code at all, just how the code is written executed,
39
+ - modifing error messages is not a refactor
40
+ - if parts of the code is removed and no alternative in place IT IS NOT A REFACTOR
41
+ - perf: A code change that improves performance
42
+ - test: Adding missing tests or correcting existing tests
43
+ - build: Changes that affect the build system or external dependencies
44
+ - ci:
45
+ - Changes to the CI configuration files and scripts
46
+ - these changes should not change the way that the code is built otherwise its build
47
+
48
+
49
+ Respond ONLY with the commit message line, nothing else.
@@ -20,7 +20,6 @@ module Committer
20
20
 
21
21
  def setup
22
22
  create_default_config
23
- create_sample_formatting_rules
24
23
  end
25
24
 
26
25
  def write_config_file(file_path, contents)
@@ -34,15 +33,6 @@ module Committer
34
33
  end
35
34
  end
36
35
 
37
- def create_sample_formatting_rules
38
- default_formatting_rules = File.read(File.join(Committer::Config::Constants::DEFAULT_PROMPT_PATH,
39
- Committer::Config::Constants::FORMATTING_RULES_FILE_NAME))
40
- formatting_rules_file = File.join(@config_dir,
41
- "#{Committer::Config::Constants::FORMATTING_RULES_FILE_NAME}.sample")
42
- wrote_file = write_config_file(formatting_rules_file, default_formatting_rules)
43
- nil unless wrote_file
44
- end
45
-
46
36
  def create_default_config
47
37
  wrote_file = write_config_file(config_file, Committer::Config::Constants::DEFAULT_CONFIG.to_yaml)
48
38
  return unless wrote_file
@@ -22,7 +22,7 @@ module Committer
22
22
  stdout, stderr, status = Open3.capture3('git diff --staged')
23
23
  raise Committer::Error, "Failed to get git diff: #{stderr}" unless status.success?
24
24
 
25
- stdout
25
+ stdout.dup.force_encoding('UTF-8').scrub
26
26
  end
27
27
  end
28
28
  end
@@ -2,12 +2,19 @@
2
2
 
3
3
  module Committer
4
4
  module PromptTemplates
5
- def self.load_formatting_rules
6
- Committer::Config::Accessor.instance.load_formatting_rules
5
+ def self.build_prompt(diff, scopes, commit_context)
6
+ prompt_template = if commit_context.nil? || commit_context.empty?
7
+ Committer::PromptTemplates.build_prompt_summary_only
8
+ else
9
+ Committer::PromptTemplates.build_prompt_summary_and_body
10
+ end
11
+ prompt_template
12
+ .gsub('{{DIFF}}', diff)
13
+ .gsub('{{SCOPES}}', build_scopes_list(scopes))
14
+ .gsub('{{CONTEXT}}', commit_context || '')
7
15
  end
8
16
 
9
- def self.load_scopes
10
- scopes = Committer::Config::Accessor.instance[:scopes] || []
17
+ def self.build_scopes_list(scopes)
11
18
  return 'DO NOT include a scope in your commit message' if scopes.empty?
12
19
 
13
20
  scope_list = "\nScopes:\n#{scopes.map { |s| "- #{s}" }.join("\n")}"
@@ -15,56 +22,16 @@ module Committer
15
22
  "- Choose an appropriate scope from the list above if relevant to the change \n#{scope_list}"
16
23
  end
17
24
 
18
- def self.commit_message_guidelines
19
- <<~PROMPT
20
- #{load_formatting_rules}
21
-
22
- # Formatting rules with body:
23
- <message>
24
-
25
- <blank line>
26
- <body with more detailed explanation>
27
-
28
- #{load_scopes}
29
-
30
- # Message Guidelines:
31
- - Keep the summary under 70 characters
32
- - Use imperative, present tense (e.g., "add" not "added" or "adds")
33
- - Do not end the summary with a period
34
- - Be concise but descriptive in the summary
35
-
36
- # Body Guidelines:
37
- - Add a blank line between summary and body
38
- - Use the body to explain why the change was made, incorporating the user's context
39
- - Wrap each line in the body at 80 characters maximum
40
- - Break the body into multiple paragraphs if needed
41
-
42
- Git Diff:
43
- ```
44
- %<diff>s
45
- ```
46
- PROMPT
47
- end
48
-
49
25
  def self.build_prompt_summary_only
50
- <<~PROMPT
51
- Below is a git diff of staged changes. Please analyze it and create a commit message following the formatting rules format with ONLY a message line (NO body):
52
-
53
- #{commit_message_guidelines}
54
-
55
- Respond ONLY with the commit message line, nothing else.
56
- PROMPT
26
+ load_prompt(Committer::Config::Constants::COMMIT_MESSAGE_ONLY_PROMPT_FILE_NAME)
57
27
  end
58
28
 
59
29
  def self.build_prompt_summary_and_body
60
- <<~PROMPT
61
- Below is a git diff of staged changes. Please analyze it and create a commit message following the formatting rules format with a summary line and a detailed body:
62
-
63
- #{commit_message_guidelines}
64
- User's context for this change: %<commit_context>s
30
+ load_prompt(Committer::Config::Constants::COMMIT_MESSAGE_AND_BODY_PROMPT_FILE_NAME)
31
+ end
65
32
 
66
- Respond ONLY with the commit message text (message and body), nothing else.
67
- PROMPT
33
+ def self.load_prompt(file_name)
34
+ Committer::Config::Accessor.instance.read_path_prioritized_file(file_name)
68
35
  end
69
36
  end
70
37
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Committer
4
- VERSION = '0.5.0'
4
+ VERSION = '0.5.1'
5
5
  end
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.5.0
4
+ version: 0.5.1
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-08 00:00:00.000000000 Z
11
+ date: 2025-03-26 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: A tool that uses Claude API to generate conventional commit messages
14
14
  based on staged changes
@@ -32,8 +32,10 @@ files:
32
32
  - lib/committer/committer_errors.rb
33
33
  - lib/committer/config/accessor.rb
34
34
  - lib/committer/config/constants.rb
35
+ - lib/committer/config/defaults/commit_message_and_body.prompt
36
+ - lib/committer/config/defaults/commit_message_only.prompt
37
+ - lib/committer/config/defaults/formatting_rules.txt
35
38
  - lib/committer/config/writer.rb
36
- - lib/committer/formatting_rules.txt
37
39
  - lib/committer/git_helper.rb
38
40
  - lib/committer/prepare-commit-msg
39
41
  - lib/committer/prompt_templates.rb