aigc 0.6.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 731000c9272bd0e49dccf72a563483eaf7181ad8625b700681fc19ef20920f99
4
+ data.tar.gz: 9abc3262a28663be3bf20e3b3f49f053cec6db3c31f41cbfc8305407f5c4b86d
5
+ SHA512:
6
+ metadata.gz: 5756382d91f21ac0e15e538fe129a794dc97df50be3de1fa0f4d5be8024f473cf5bfe1e2580e4cc192eb0a79e5cd125a69bbf2444c0a537915f5a2ef137fc304
7
+ data.tar.gz: fb8967522a40b62a442875a34ec40a78e73fa7d82d60ba704cec33de61615c6ccdb515e9a2297b985885275a82c81d7a0b741f6dff34d5480bf09c15d1a53310
data/.commit-prompts ADDED
@@ -0,0 +1,12 @@
1
+ # Custom commit message prompts
2
+ # Add your project-specific context and preferences here
3
+ # Lines starting with # are ignored
4
+
5
+ # Example prompts:
6
+ # - Always include the JIRA ticket number in the scope
7
+ # - Use 'feat' for new features, 'fix' for bug fixes
8
+ # - Keep descriptions under 50 characters
9
+ # - Prefer active voice and present tense
10
+ # - Include breaking change indicators when applicable
11
+
12
+ # Your custom prompts go here:
data/.rubocop.yml ADDED
@@ -0,0 +1,8 @@
1
+ AllCops:
2
+ TargetRubyVersion: 3.1
3
+
4
+ Style/StringLiterals:
5
+ EnforcedStyle: double_quotes
6
+
7
+ Style/StringLiteralsInInterpolation:
8
+ EnforcedStyle: double_quotes
data/CHANGELOG.md ADDED
@@ -0,0 +1,42 @@
1
+ ## [Unreleased]
2
+
3
+ ## [0.6.0] - 2025-01-27
4
+
5
+ ### Changed
6
+ - Made `aigc` the default command (no subcommand needed)
7
+ - Users can now run just `aigc` instead of `aigc commit`
8
+ - Updated all error messages to reference `aigc` instead of `smart-commit`
9
+
10
+ ## [0.5.0] - 2025-01-27
11
+
12
+ ### Changed
13
+ - Renamed gem from `smart-commit` to `aigc` for shorter, more convenient usage
14
+ - Updated executable name from `smart-commit` to `aigc`
15
+
16
+ ## [0.4.0] - 2025-01-27
17
+
18
+ ### Fixed
19
+ - Made regenerate functionality truly recursive with accumulated feedback
20
+ - Limited regenerations to maximum of 3 attempts
21
+ - Each regeneration now builds upon all previous feedback
22
+ - Improved prompt to address all accumulated feedback points
23
+
24
+ ## [0.3.0] - 2025-01-27
25
+
26
+ ### Added
27
+ - Regenerate functionality for commit messages with user feedback
28
+ - Users can now provide specific feedback to improve AI-generated commit messages
29
+ - Recursive regeneration - users can regenerate multiple times with different feedback
30
+ - Enhanced prompt that incorporates user feedback for better message generation
31
+
32
+ ## [0.2.0] - 2025-01-27
33
+
34
+ ### Added
35
+ - Edit functionality for commit messages in the `commit` command
36
+ - Users can now modify AI-generated commit messages before committing
37
+ - Support for custom editor via `$EDITOR` or `$VISUAL` environment variables
38
+ - Fallback to `nano` editor if no default editor is configured
39
+
40
+ ## [0.1.0] - 2025-08-25
41
+
42
+ - Initial release
@@ -0,0 +1,132 @@
1
+ # Contributor Covenant Code of Conduct
2
+
3
+ ## Our Pledge
4
+
5
+ We as members, contributors, and leaders pledge to make participation in our
6
+ community a harassment-free experience for everyone, regardless of age, body
7
+ size, visible or invisible disability, ethnicity, sex characteristics, gender
8
+ identity and expression, level of experience, education, socio-economic status,
9
+ nationality, personal appearance, race, caste, color, religion, or sexual
10
+ identity and orientation.
11
+
12
+ We pledge to act and interact in ways that contribute to an open, welcoming,
13
+ diverse, inclusive, and healthy community.
14
+
15
+ ## Our Standards
16
+
17
+ Examples of behavior that contributes to a positive environment for our
18
+ community include:
19
+
20
+ * Demonstrating empathy and kindness toward other people
21
+ * Being respectful of differing opinions, viewpoints, and experiences
22
+ * Giving and gracefully accepting constructive feedback
23
+ * Accepting responsibility and apologizing to those affected by our mistakes,
24
+ and learning from the experience
25
+ * Focusing on what is best not just for us as individuals, but for the overall
26
+ community
27
+
28
+ Examples of unacceptable behavior include:
29
+
30
+ * The use of sexualized language or imagery, and sexual attention or advances of
31
+ any kind
32
+ * Trolling, insulting or derogatory comments, and personal or political attacks
33
+ * Public or private harassment
34
+ * Publishing others' private information, such as a physical or email address,
35
+ without their explicit permission
36
+ * Other conduct which could reasonably be considered inappropriate in a
37
+ professional setting
38
+
39
+ ## Enforcement Responsibilities
40
+
41
+ Community leaders are responsible for clarifying and enforcing our standards of
42
+ acceptable behavior and will take appropriate and fair corrective action in
43
+ response to any behavior that they deem inappropriate, threatening, offensive,
44
+ or harmful.
45
+
46
+ Community leaders have the right and responsibility to remove, edit, or reject
47
+ comments, commits, code, wiki edits, issues, and other contributions that are
48
+ not aligned to this Code of Conduct, and will communicate reasons for moderation
49
+ decisions when appropriate.
50
+
51
+ ## Scope
52
+
53
+ This Code of Conduct applies within all community spaces, and also applies when
54
+ an individual is officially representing the community in public spaces.
55
+ Examples of representing our community include using an official email address,
56
+ posting via an official social media account, or acting as an appointed
57
+ representative at an online or offline event.
58
+
59
+ ## Enforcement
60
+
61
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be
62
+ reported to the community leaders responsible for enforcement at
63
+ [INSERT CONTACT METHOD].
64
+ All complaints will be reviewed and investigated promptly and fairly.
65
+
66
+ All community leaders are obligated to respect the privacy and security of the
67
+ reporter of any incident.
68
+
69
+ ## Enforcement Guidelines
70
+
71
+ Community leaders will follow these Community Impact Guidelines in determining
72
+ the consequences for any action they deem in violation of this Code of Conduct:
73
+
74
+ ### 1. Correction
75
+
76
+ **Community Impact**: Use of inappropriate language or other behavior deemed
77
+ unprofessional or unwelcome in the community.
78
+
79
+ **Consequence**: A private, written warning from community leaders, providing
80
+ clarity around the nature of the violation and an explanation of why the
81
+ behavior was inappropriate. A public apology may be requested.
82
+
83
+ ### 2. Warning
84
+
85
+ **Community Impact**: A violation through a single incident or series of
86
+ actions.
87
+
88
+ **Consequence**: A warning with consequences for continued behavior. No
89
+ interaction with the people involved, including unsolicited interaction with
90
+ those enforcing the Code of Conduct, for a specified period of time. This
91
+ includes avoiding interactions in community spaces as well as external channels
92
+ like social media. Violating these terms may lead to a temporary or permanent
93
+ ban.
94
+
95
+ ### 3. Temporary Ban
96
+
97
+ **Community Impact**: A serious violation of community standards, including
98
+ sustained inappropriate behavior.
99
+
100
+ **Consequence**: A temporary ban from any sort of interaction or public
101
+ communication with the community for a specified period of time. No public or
102
+ private interaction with the people involved, including unsolicited interaction
103
+ with those enforcing the Code of Conduct, is allowed during this period.
104
+ Violating these terms may lead to a permanent ban.
105
+
106
+ ### 4. Permanent Ban
107
+
108
+ **Community Impact**: Demonstrating a pattern of violation of community
109
+ standards, including sustained inappropriate behavior, harassment of an
110
+ individual, or aggression toward or disparagement of classes of individuals.
111
+
112
+ **Consequence**: A permanent ban from any sort of public interaction within the
113
+ community.
114
+
115
+ ## Attribution
116
+
117
+ This Code of Conduct is adapted from the [Contributor Covenant][homepage],
118
+ version 2.1, available at
119
+ [https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].
120
+
121
+ Community Impact Guidelines were inspired by
122
+ [Mozilla's code of conduct enforcement ladder][Mozilla CoC].
123
+
124
+ For answers to common questions about this code of conduct, see the FAQ at
125
+ [https://www.contributor-covenant.org/faq][FAQ]. Translations are available at
126
+ [https://www.contributor-covenant.org/translations][translations].
127
+
128
+ [homepage]: https://www.contributor-covenant.org
129
+ [v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
130
+ [Mozilla CoC]: https://github.com/mozilla/diversity
131
+ [FAQ]: https://www.contributor-covenant.org/faq
132
+ [translations]: https://www.contributor-covenant.org/translations
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2025 Behrang Mirzamani
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,137 @@
1
+ # AI Commit 🤖
2
+
3
+ Generate intelligent, conventional commit messages using Claude AI. No more struggling with commit message writer's block!
4
+
5
+ ## Features
6
+
7
+ - ✨ **AI-Powered**: Uses Claude AI to analyze your git diff and generate meaningful commit messages
8
+ - 📝 **Conventional Commits**: Follows conventional commit format (feat, fix, docs, etc.)
9
+ - 🔒 **Secure**: API keys stored securely with proper file permissions
10
+ - 🚀 **Fast**: Quick generation with Claude 3 Haiku
11
+ - 🎯 **Smart**: Contextual analysis of your actual code changes
12
+ - 💎 **Ruby Gem**: Simple installation and usage
13
+
14
+ ## Installation
15
+
16
+ Install the gem:
17
+
18
+ ```bash
19
+ gem install smart-commit
20
+ ```
21
+
22
+ Or add to your Gemfile:
23
+
24
+ ```ruby
25
+ gem 'smart-commit'
26
+ ```
27
+
28
+ ## Setup
29
+
30
+ First, configure your Anthropic API key:
31
+
32
+ ```bash
33
+ smart-commit setup
34
+ ```
35
+
36
+ You'll be prompted to enter your API key. Get one at [Anthropic Console](https://console.anthropic.com/).
37
+
38
+ ## Usage
39
+
40
+ ### Basic Usage
41
+
42
+ 1. Stage your changes:
43
+ ```bash
44
+ git add .
45
+ ```
46
+
47
+ 2. Generate a commit message:
48
+ ```bash
49
+ smart-commit generate
50
+ ```
51
+
52
+ 3. The tool will analyze your diff and suggest a commit message:
53
+ ```
54
+ ✨ Generated commit message:
55
+ feat: add user authentication with JWT tokens
56
+
57
+ To commit with this message, run:
58
+ git commit -m "feat: add user authentication with JWT tokens"
59
+ ```
60
+
61
+ ### Commands
62
+
63
+ - `smart-commit setup` - Configure your API key
64
+ - `smart-commit generate` - Generate commit message from staged changes
65
+ - `smart-commit config` - Show current configuration
66
+ - `smart-commit help` - Show help information
67
+
68
+ ## Examples
69
+
70
+ The AI generates contextual commit messages based on your actual changes:
71
+
72
+ ```bash
73
+ # Adding a new feature
74
+ git add lib/user_auth.rb
75
+ smart-commit generate
76
+ # → "feat: add JWT-based user authentication system"
77
+
78
+ # Fixing a bug
79
+ git add lib/payment_processor.rb
80
+ smart-commit generate
81
+ # → "fix: handle null payment amounts in processor"
82
+
83
+ # Documentation updates
84
+ git add README.md
85
+ smart-commit generate
86
+ # → "docs: update installation instructions"
87
+
88
+ # Refactoring code
89
+ git add lib/user_model.rb
90
+ smart-commit generate
91
+ # → "refactor: extract user validation into separate method"
92
+ ```
93
+
94
+ ## Configuration
95
+
96
+ ### API Key Storage
97
+ - Config stored in: `~/.smart-commit/config.yml`
98
+ - File permissions: `600` (secure)
99
+ - Environment variable: `ANTHROPIC_API_KEY` (fallback)
100
+
101
+ ### Customizing the Prompt
102
+ The commit message generation prompt can be customized by editing:
103
+ `lib/ai/commit/claude_client.rb` in the `build_prompt` method.
104
+
105
+ ## Requirements
106
+
107
+ - Ruby 3.1.0+
108
+ - Git repository
109
+ - Anthropic API key
110
+ - Staged changes in git
111
+
112
+ ## Development
113
+
114
+ After checking out the repo:
115
+
116
+ ```bash
117
+ bin/setup # Install dependencies
118
+ rake test # Run tests
119
+ bin/console # Interactive prompt
120
+ bundle exec exe/smart-commit # Run locally
121
+ ```
122
+
123
+ ## Contributing
124
+
125
+ Bug reports and pull requests are welcome on GitHub at https://github.com/bmirzamani/smart-commit.
126
+
127
+ ## License
128
+
129
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
130
+
131
+ ## Changelog
132
+
133
+ See [CHANGELOG.md](CHANGELOG.md) for version history.
134
+
135
+ ---
136
+
137
+ Made with ❤️ by [Behrang Mirzamani](https://github.com/bmirzamani)
data/Rakefile ADDED
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "minitest/test_task"
5
+
6
+ Minitest::TestTask.create
7
+
8
+ require "rubocop/rake_task"
9
+
10
+ RuboCop::RakeTask.new
11
+
12
+ task default: %i[test rubocop]
data/exe/aigc ADDED
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require_relative '../lib/ai/commit'
5
+
6
+ Ai::Commit::CLI.start(ARGV)
@@ -0,0 +1,139 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'net/http'
4
+ require 'json'
5
+ require 'uri'
6
+
7
+ module Ai
8
+ module Commit
9
+ class ClaudeClient
10
+ API_URL = 'https://api.anthropic.com/v1/messages'
11
+
12
+ def initialize(api_key)
13
+ @api_key = api_key
14
+ end
15
+
16
+ def generate_commit_message(diff)
17
+ prompt = build_prompt(diff)
18
+
19
+ response = make_request(prompt)
20
+ extract_message(response)
21
+ end
22
+
23
+ def generate_commit_message_with_feedback(diff, feedback_array)
24
+ prompt = build_prompt_with_feedback(diff, feedback_array)
25
+
26
+ response = make_request(prompt)
27
+ extract_message(response)
28
+ end
29
+
30
+ private
31
+
32
+ def build_prompt(diff)
33
+ custom_prompts = Config.custom_prompts
34
+ custom_prompt_text = custom_prompts.empty? ? '' : "\n\nAdditional context and preferences:\n#{custom_prompts.join("\n")}"
35
+
36
+ <<~PROMPT
37
+ You are a helpful assistant that generates conventional commit messages.
38
+
39
+ Based on the following git diff, generate a conventional commit message that:
40
+ 1. Use conventional commit format: type(scope): description
41
+ 2. Keep subject line under 50 characters
42
+ 3. Use imperative mood ("add" not "added")
43
+ 4. Separate subject from body with blank line
44
+ 5. Wrap body at 72 characters
45
+ 6. Use 'feat' for new features, 'fix' for bug fixes, 'docs' for documentation, 'style' for formatting, 'refactor' for code refactoring, 'test' for tests, 'chore' for build/tooling
46
+ 7. Be clear and specific about what changed, not just "update"
47
+ 8. Explain the impact/benefit when relevant
48
+ 9. Include scope based on file structure (e.g., auth, bank, api, components)
49
+ 10. Reference any related components or services affected
50
+ 11. For breaking changes, start with 'BREAKING CHANGE:'
51
+ 12. Use bullet points in body for multiple changes
52
+ 13. Reference ticket numbers when applicable
53
+ 14. Match existing project commit style
54
+
55
+ #{custom_prompt_text}
56
+
57
+ Git diff:
58
+ #{diff}
59
+
60
+ Respond with the complete commit message including subject and body, preserving line breaks.
61
+ PROMPT
62
+ end
63
+
64
+ def build_prompt_with_feedback(diff, feedback_array)
65
+ custom_prompts = Config.custom_prompts
66
+ custom_prompt_text = custom_prompts.empty? ? '' : "\n\nAdditional context and preferences:\n#{custom_prompts.join("\n")}"
67
+
68
+ feedback_text = feedback_array.map.with_index { |feedback, i| "#{i + 1}. #{feedback}" }.join("\n")
69
+
70
+ <<~PROMPT
71
+ You are a helpful assistant that generates conventional commit messages.
72
+
73
+ Based on the following git diff, generate a conventional commit message that:
74
+ 1. Use conventional commit format: type(scope): description
75
+ 2. Keep subject line under 50 characters
76
+ 3. Use imperative mood ("add" not "added")
77
+ 4. Separate subject from body with blank line
78
+ 5. Wrap body at 72 characters
79
+ 6. Use 'feat' for new features, 'fix' for bug fixes, 'docs' for documentation, 'style' for formatting, 'refactor' for code refactoring, 'test' for tests, 'chore' for build/tooling
80
+ 7. Be clear and specific about what changed, not just "update"
81
+ 8. Explain the impact/benefit when relevant
82
+ 9. Include scope based on file structure (e.g., auth, bank, api, components)
83
+ 10. Reference any related components or services affected
84
+ 11. For breaking changes, start with 'BREAKING CHANGE:'
85
+ 12. Use bullet points in body for multiple changes
86
+ 13. Reference ticket numbers when applicable
87
+ 14. Match existing project commit style
88
+
89
+ #{custom_prompt_text}
90
+
91
+ IMPORTANT: The user has provided feedback on previous attempts. Please address ALL of the following feedback:
92
+ #{feedback_text}
93
+
94
+ Git diff:
95
+ #{diff}
96
+
97
+ Respond with the complete commit message including subject and body, preserving line breaks.
98
+ PROMPT
99
+ end
100
+
101
+ def make_request(prompt)
102
+ uri = URI(API_URL)
103
+ http = Net::HTTP.new(uri.host, uri.port)
104
+ http.use_ssl = true
105
+
106
+ request = Net::HTTP::Post.new(uri)
107
+ request['Content-Type'] = 'application/json'
108
+ request['x-api-key'] = @api_key
109
+ request['anthropic-version'] = '2023-06-01'
110
+
111
+ request.body = {
112
+ model: 'claude-3-haiku-20240307',
113
+ max_tokens: 300,
114
+ messages: [
115
+ {
116
+ role: 'user',
117
+ content: prompt
118
+ }
119
+ ]
120
+ }.to_json
121
+
122
+ response = http.request(request)
123
+
124
+ unless response.code == '200'
125
+ raise Error, "API request failed: #{response.code} - #{response.body}"
126
+ end
127
+
128
+ JSON.parse(response.body)
129
+ end
130
+
131
+ def extract_message(response)
132
+ content = response.dig('content', 0, 'text')
133
+ return content.strip if content
134
+
135
+ raise Error, "Unexpected response format: #{response}"
136
+ end
137
+ end
138
+ end
139
+ end
@@ -0,0 +1,300 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'thor'
4
+ require 'io/console'
5
+ require_relative 'config'
6
+
7
+ module Ai
8
+ module Commit
9
+ class CLI < Thor
10
+ def self.exit_on_failure?
11
+ true
12
+ end
13
+
14
+ default_command :commit
15
+
16
+ desc "commit", "Generate a commit message and commit with confirmation (default command)"
17
+ desc "setup", "Configure AI Commit with your Anthropic API key"
18
+ def setup
19
+ puts "Welcome to AI Commit setup!"
20
+ print "Enter your Anthropic API key: "
21
+ api_key = STDIN.noecho(&:gets).chomp
22
+ puts
23
+
24
+ if api_key.empty?
25
+ puts "No API key provided. Setup cancelled."
26
+ exit 1
27
+ end
28
+
29
+ Config.set_api_key(api_key)
30
+ puts "✓ API key saved successfully!"
31
+ puts "You can now use 'aigc' to create commit messages."
32
+ end
33
+
34
+ desc "generate", "Generate a commit message based on staged changes"
35
+ def generate
36
+ unless Config.api_key_configured?
37
+ puts "❌ No API key configured. Please run 'aigc setup' first."
38
+ exit 1
39
+ end
40
+
41
+ unless Git.in_git_repo?
42
+ puts "❌ Not in a git repository"
43
+ exit 1
44
+ end
45
+
46
+ unless Git.has_staged_changes?
47
+ puts "❌ No staged changes found. Stage your changes with 'git add' first."
48
+ exit 1
49
+ end
50
+
51
+ puts "🤖 Generating commit message..."
52
+
53
+ begin
54
+ diff = Git.staged_diff
55
+ client = ClaudeClient.new(Config.api_key)
56
+ message = client.generate_commit_message(diff)
57
+
58
+ puts "\n✨ Generated commit message:"
59
+ puts "#{message}"
60
+ puts "\nTo commit with this message, run:"
61
+ puts "git commit -m \"#{message}\""
62
+
63
+ rescue Error => e
64
+ puts "❌ Error: #{e.message}"
65
+ exit 1
66
+ rescue => e
67
+ puts "❌ Unexpected error: #{e.message}"
68
+ exit 1
69
+ end
70
+ end
71
+
72
+ desc "commit", "Generate a commit message and commit with confirmation"
73
+ def commit
74
+ unless Config.api_key_configured?
75
+ puts "❌ No API key configured. Please run 'aigc setup' first."
76
+ exit 1
77
+ end
78
+
79
+ unless Git.in_git_repo?
80
+ puts "❌ Not in a git repository"
81
+ exit 1
82
+ end
83
+
84
+ unless Git.has_staged_changes?
85
+ puts "❌ No staged changes found. Stage your changes with 'git add' first."
86
+ exit 1
87
+ end
88
+
89
+ puts "🤖 Generating commit message..."
90
+
91
+ begin
92
+ diff = Git.staged_diff
93
+ client = ClaudeClient.new(Config.api_key)
94
+ message = client.generate_commit_message(diff)
95
+
96
+ puts "\n✨ Generated commit message:"
97
+ puts "=" * 50
98
+ puts message
99
+ puts "=" * 50
100
+
101
+ print "\nCommit with this message? (y/N/e to edit/r to regenerate): "
102
+ response = STDIN.gets.chomp.downcase
103
+
104
+ if response == 'y' || response == 'yes'
105
+ puts "\n🚀 Committing..."
106
+ result = system("git", "commit", "-m", message)
107
+
108
+ if result
109
+ puts "✅ Successfully committed!"
110
+ else
111
+ puts "❌ Failed to commit"
112
+ exit 1
113
+ end
114
+ elsif response == 'e' || response == 'edit'
115
+ edited_message = edit_message(message)
116
+ if edited_message && !edited_message.strip.empty?
117
+ puts "\n🚀 Committing with edited message..."
118
+ result = system("git", "commit", "-m", edited_message)
119
+
120
+ if result
121
+ puts "✅ Successfully committed!"
122
+ else
123
+ puts "❌ Failed to commit"
124
+ exit 1
125
+ end
126
+ else
127
+ puts "❌ Commit cancelled."
128
+ exit 0
129
+ end
130
+ elsif response == 'r' || response == 'regenerate'
131
+ regenerate_with_feedback(diff, client, 1, [])
132
+ else
133
+ puts "❌ Commit cancelled."
134
+ exit 0
135
+ end
136
+
137
+ rescue Error => e
138
+ puts "❌ Error: #{e.message}"
139
+ exit 1
140
+ rescue => e
141
+ puts "❌ Unexpected error: #{e.message}"
142
+ exit 1
143
+ end
144
+ end
145
+
146
+ desc "config", "Show current configuration"
147
+ def config
148
+ if Config.api_key_configured?
149
+ masked_key = Config.api_key[0..10] + "..." if Config.api_key
150
+ puts "✓ API key configured: #{masked_key}"
151
+ else
152
+ puts "❌ No API key configured"
153
+ end
154
+
155
+ if Config.has_custom_prompts?
156
+ puts "✓ Custom prompts found: #{Config.custom_prompts.length} prompt(s)"
157
+ Config.custom_prompts.each_with_index do |prompt, i|
158
+ puts " #{i + 1}. #{prompt}"
159
+ end
160
+ else
161
+ puts "ℹ️ No custom prompts found"
162
+ puts " Create a .commit-prompts file in your project root to add custom context"
163
+ end
164
+ end
165
+
166
+ desc "init-prompts", "Create a sample .commit-prompts file"
167
+ def init_prompts
168
+ prompts_file = Config::PROMPTS_FILE
169
+
170
+ if File.exist?(prompts_file)
171
+ puts "❌ .commit-prompts file already exists"
172
+ exit 1
173
+ end
174
+
175
+ sample_content = <<~PROMPTS
176
+ # Custom commit message prompts
177
+ # Add your project-specific context and preferences here
178
+ # Lines starting with # are ignored
179
+
180
+ # Example prompts:
181
+ # - Always include the JIRA ticket number in the scope
182
+ # - Use 'feat' for new features, 'fix' for bug fixes
183
+ # - Keep descriptions under 50 characters
184
+ # - Prefer active voice and present tense
185
+ # - Include breaking change indicators when applicable
186
+
187
+ # Your custom prompts go here:
188
+
189
+ PROMPTS
190
+
191
+ File.write(prompts_file, sample_content)
192
+ puts "✅ Created #{prompts_file} with sample content"
193
+ puts "📝 Edit the file to add your custom prompts"
194
+ end
195
+
196
+ private
197
+
198
+ def edit_message(original_message)
199
+ puts "\n📝 Opening editor to modify commit message..."
200
+ puts "Original message will be loaded in your default editor."
201
+ puts "Save and close the editor to commit, or close without saving to cancel."
202
+
203
+ # Create a temporary file with the original message
204
+ require 'tempfile'
205
+ temp_file = Tempfile.new(['commit_message', '.txt'])
206
+ temp_file.write(original_message)
207
+ temp_file.close
208
+
209
+ # Get the default editor
210
+ editor = ENV['EDITOR'] || ENV['VISUAL'] || 'nano'
211
+
212
+ # Open the file in the editor
213
+ system("#{editor} #{temp_file.path}")
214
+
215
+ # Read the edited content
216
+ edited_content = File.read(temp_file.path)
217
+ temp_file.unlink
218
+
219
+ edited_content
220
+ end
221
+
222
+ def regenerate_with_feedback(diff, client, attempt = 1, accumulated_feedback = [])
223
+ if attempt > 3
224
+ puts "\n⚠️ Maximum regenerations (3) reached. Please commit with the current message or edit it manually."
225
+ return
226
+ end
227
+
228
+ puts "\n💬 What would you like to change about the commit message? (Attempt #{attempt}/3)"
229
+ puts "Examples: 'make it more concise', 'add more detail about the bug fix', 'change the type to feat'"
230
+ if !accumulated_feedback.empty?
231
+ puts "Previous feedback: #{accumulated_feedback.join(', ')}"
232
+ end
233
+ print "Feedback: "
234
+ feedback = STDIN.gets.chomp.strip
235
+
236
+ if feedback.empty?
237
+ puts "❌ No feedback provided. Cancelling regeneration."
238
+ return
239
+ end
240
+
241
+ # Add current feedback to accumulated list
242
+ all_feedback = accumulated_feedback + [feedback]
243
+
244
+ puts "\n🤖 Regenerating commit message with your feedback..."
245
+
246
+ begin
247
+ new_message = client.generate_commit_message_with_feedback(diff, all_feedback)
248
+
249
+ puts "\n✨ New commit message:"
250
+ puts "=" * 50
251
+ puts new_message
252
+ puts "=" * 50
253
+
254
+ print "\nCommit with this message? (y/N/e to edit/r to regenerate again): "
255
+ response = STDIN.gets.chomp.downcase
256
+
257
+ if response == 'y' || response == 'yes'
258
+ puts "\n🚀 Committing..."
259
+ result = system("git", "commit", "-m", new_message)
260
+
261
+ if result
262
+ puts "✅ Successfully committed!"
263
+ else
264
+ puts "❌ Failed to commit"
265
+ exit 1
266
+ end
267
+ elsif response == 'e' || response == 'edit'
268
+ edited_message = edit_message(new_message)
269
+ if edited_message && !edited_message.strip.empty?
270
+ puts "\n🚀 Committing with edited message..."
271
+ result = system("git", "commit", "-m", edited_message)
272
+
273
+ if result
274
+ puts "✅ Successfully committed!"
275
+ else
276
+ puts "❌ Failed to commit"
277
+ exit 1
278
+ end
279
+ else
280
+ puts "❌ Commit cancelled."
281
+ exit 0
282
+ end
283
+ elsif response == 'r' || response == 'regenerate'
284
+ regenerate_with_feedback(diff, client, attempt + 1, all_feedback)
285
+ else
286
+ puts "❌ Commit cancelled."
287
+ exit 0
288
+ end
289
+
290
+ rescue Error => e
291
+ puts "❌ Error regenerating message: #{e.message}"
292
+ exit 1
293
+ rescue => e
294
+ puts "❌ Unexpected error: #{e.message}"
295
+ exit 1
296
+ end
297
+ end
298
+ end
299
+ end
300
+ end
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'fileutils'
4
+ require 'yaml'
5
+
6
+ module Ai
7
+ module Commit
8
+ class Config
9
+ CONFIG_FILE = File.expand_path('~/.smart-commit/config.yml').freeze
10
+ PROMPTS_FILE = '.commit-prompts'.freeze
11
+
12
+ def self.api_key
13
+ config = load_config
14
+ config['api_key'] || ENV['ANTHROPIC_API_KEY']
15
+ end
16
+
17
+ def self.set_api_key(key)
18
+ config = load_config
19
+ config['api_key'] = key
20
+ save_config(config)
21
+ end
22
+
23
+ def self.api_key_configured?
24
+ !api_key.nil? && !api_key.empty?
25
+ end
26
+
27
+ def self.custom_prompts
28
+ prompts_file = find_prompts_file
29
+ return [] unless prompts_file && File.exist?(prompts_file)
30
+
31
+ File.readlines(prompts_file)
32
+ .map(&:strip)
33
+ .reject(&:empty?)
34
+ .reject { |line| line.start_with?('#') }
35
+ end
36
+
37
+ def self.find_prompts_file
38
+ # Look for .commit-prompts in current directory or parent directories
39
+ current_dir = Dir.pwd
40
+ while current_dir != File.dirname(current_dir)
41
+ prompts_file = File.join(current_dir, PROMPTS_FILE)
42
+ return prompts_file if File.exist?(prompts_file)
43
+ current_dir = File.dirname(current_dir)
44
+ end
45
+ nil
46
+ end
47
+
48
+ def self.has_custom_prompts?
49
+ !custom_prompts.empty?
50
+ end
51
+
52
+ private
53
+
54
+ def self.load_config
55
+ return {} unless File.exist?(CONFIG_FILE)
56
+
57
+ YAML.load_file(CONFIG_FILE) || {}
58
+ rescue Psych::SyntaxError
59
+ {}
60
+ end
61
+
62
+ def self.save_config(config)
63
+ FileUtils.mkdir_p(File.dirname(CONFIG_FILE))
64
+ File.write(CONFIG_FILE, YAML.dump(config))
65
+ File.chmod(0o600, CONFIG_FILE) # Secure permissions
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ai
4
+ module Commit
5
+ class Git
6
+ def self.staged_diff
7
+ result = `git diff --cached`
8
+
9
+ if $?.exitstatus != 0
10
+ raise Error, "Failed to get git diff. Are you in a git repository?"
11
+ end
12
+
13
+ if result.empty?
14
+ raise Error, "No staged changes found. Stage your changes with 'git add' first."
15
+ end
16
+
17
+ result
18
+ end
19
+
20
+ def self.in_git_repo?
21
+ system('git rev-parse --git-dir', out: File::NULL, err: File::NULL)
22
+ end
23
+
24
+ def self.has_staged_changes?
25
+ !`git diff --cached --name-only`.strip.empty?
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ai
4
+ module Commit
5
+ VERSION = "0.6.0"
6
+ end
7
+ end
data/lib/ai/commit.rb ADDED
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "commit/version"
4
+ require_relative "commit/config"
5
+ require_relative "commit/cli"
6
+ require_relative "commit/claude_client"
7
+ require_relative "commit/git"
8
+
9
+ module Ai
10
+ module Commit
11
+ class Error < StandardError; end
12
+ end
13
+ end
data/sig/ai/commit.rbs ADDED
@@ -0,0 +1,6 @@
1
+ module Ai
2
+ module Commit
3
+ VERSION: String
4
+ # See the writing guide of rbs: https://github.com/ruby/rbs#guides
5
+ end
6
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "lib/ai/commit/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "aigc"
7
+ spec.version = Ai::Commit::VERSION
8
+ spec.authors = ["Behrang Mirzamani"]
9
+ spec.email = ["behraaang@gmail.com"]
10
+
11
+ spec.summary = "Generate intelligent conventional commit messages using Claude AI"
12
+ spec.description = "AI-powered commit message generator that analyzes your git diff and creates meaningful, conventional commit messages using Claude AI. No more commit message writer's block!"
13
+ spec.homepage = "https://github.com/behraaang/smart-commit"
14
+ spec.license = "MIT"
15
+ spec.required_ruby_version = ">= 3.1.0"
16
+
17
+ spec.metadata["allowed_push_host"] = "https://rubygems.org"
18
+
19
+ spec.metadata["homepage_uri"] = spec.homepage
20
+ spec.metadata["source_code_uri"] = "https://github.com/behraaang/smart-commit"
21
+ spec.metadata["changelog_uri"] = "https://github.com/behraaang/smart-commit/blob/main/CHANGELOG.md"
22
+
23
+ # Specify which files should be added to the gem when it is released.
24
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
25
+ gemspec = File.basename(__FILE__)
26
+ spec.files = IO.popen(%w[git ls-files -z], chdir: __dir__, err: IO::NULL) do |ls|
27
+ ls.readlines("\x0", chomp: true).reject do |f|
28
+ (f == gemspec) ||
29
+ f.start_with?(*%w[bin/ test/ spec/ features/ .git .github appveyor Gemfile Gemfile.lock]) ||
30
+ f.end_with?('.gem')
31
+ end
32
+ end
33
+ spec.bindir = "exe"
34
+ spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
35
+ spec.require_paths = ["lib"]
36
+
37
+ spec.add_dependency "thor", "~> 1.0"
38
+ spec.add_dependency "net-http", "~> 0.3"
39
+ spec.add_dependency "json", "~> 2.0"
40
+
41
+ # For more information and examples about making a new gem, check out our
42
+ # guide at: https://bundler.io/guides/creating_gem.html
43
+ end
metadata ADDED
@@ -0,0 +1,105 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: aigc
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.6.0
5
+ platform: ruby
6
+ authors:
7
+ - Behrang Mirzamani
8
+ bindir: exe
9
+ cert_chain: []
10
+ date: 2025-08-30 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: thor
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: '1.0'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - "~>"
24
+ - !ruby/object:Gem::Version
25
+ version: '1.0'
26
+ - !ruby/object:Gem::Dependency
27
+ name: net-http
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - "~>"
31
+ - !ruby/object:Gem::Version
32
+ version: '0.3'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '0.3'
40
+ - !ruby/object:Gem::Dependency
41
+ name: json
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: '2.0'
47
+ type: :runtime
48
+ prerelease: false
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: '2.0'
54
+ description: AI-powered commit message generator that analyzes your git diff and creates
55
+ meaningful, conventional commit messages using Claude AI. No more commit message
56
+ writer's block!
57
+ email:
58
+ - behraaang@gmail.com
59
+ executables:
60
+ - aigc
61
+ extensions: []
62
+ extra_rdoc_files: []
63
+ files:
64
+ - ".commit-prompts"
65
+ - ".rubocop.yml"
66
+ - CHANGELOG.md
67
+ - CODE_OF_CONDUCT.md
68
+ - LICENSE.txt
69
+ - README.md
70
+ - Rakefile
71
+ - exe/aigc
72
+ - lib/ai/commit.rb
73
+ - lib/ai/commit/claude_client.rb
74
+ - lib/ai/commit/cli.rb
75
+ - lib/ai/commit/config.rb
76
+ - lib/ai/commit/git.rb
77
+ - lib/ai/commit/version.rb
78
+ - sig/ai/commit.rbs
79
+ - smart-commit.gemspec
80
+ homepage: https://github.com/behraaang/smart-commit
81
+ licenses:
82
+ - MIT
83
+ metadata:
84
+ allowed_push_host: https://rubygems.org
85
+ homepage_uri: https://github.com/behraaang/smart-commit
86
+ source_code_uri: https://github.com/behraaang/smart-commit
87
+ changelog_uri: https://github.com/behraaang/smart-commit/blob/main/CHANGELOG.md
88
+ rdoc_options: []
89
+ require_paths:
90
+ - lib
91
+ required_ruby_version: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - ">="
94
+ - !ruby/object:Gem::Version
95
+ version: 3.1.0
96
+ required_rubygems_version: !ruby/object:Gem::Requirement
97
+ requirements:
98
+ - - ">="
99
+ - !ruby/object:Gem::Version
100
+ version: '0'
101
+ requirements: []
102
+ rubygems_version: 3.6.5
103
+ specification_version: 4
104
+ summary: Generate intelligent conventional commit messages using Claude AI
105
+ test_files: []