pull_request_ai 0.1.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.
Files changed (38) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +125 -0
  4. data/Rakefile +10 -0
  5. data/app/assets/config/pull_request_ai_manifest.js +1 -0
  6. data/app/assets/javascripts/application.js +248 -0
  7. data/app/assets/javascripts/notifications.js +76 -0
  8. data/app/assets/stylesheets/pull_request_ai/application.css +15 -0
  9. data/app/assets/stylesheets/pull_request_ai/blocs.css +4 -0
  10. data/app/assets/stylesheets/pull_request_ai/helpers.css +26 -0
  11. data/app/assets/stylesheets/pull_request_ai/inputs.css +64 -0
  12. data/app/assets/stylesheets/pull_request_ai/notification.css +71 -0
  13. data/app/assets/stylesheets/pull_request_ai/spinner.css +24 -0
  14. data/app/controllers/pull_request_ai/application_controller.rb +6 -0
  15. data/app/controllers/pull_request_ai/pull_request_ai_controller.rb +85 -0
  16. data/app/helpers/pull_request_ai/application_helper.rb +6 -0
  17. data/app/models/pull_request_ai/application_record.rb +7 -0
  18. data/app/views/layouts/pull_request_ai/application.html.erb +29 -0
  19. data/app/views/pull_request_ai/pull_request_ai/new.html.erb +70 -0
  20. data/config/initializers/rack_attack.rb +30 -0
  21. data/config/routes.rb +13 -0
  22. data/lib/pull_request_ai/bitbucket/client.rb +148 -0
  23. data/lib/pull_request_ai/client.rb +80 -0
  24. data/lib/pull_request_ai/engine.rb +10 -0
  25. data/lib/pull_request_ai/github/client.rb +124 -0
  26. data/lib/pull_request_ai/openAi/client.rb +81 -0
  27. data/lib/pull_request_ai/openAi/interpreter.rb +19 -0
  28. data/lib/pull_request_ai/repo/client.rb +43 -0
  29. data/lib/pull_request_ai/repo/file.rb +20 -0
  30. data/lib/pull_request_ai/repo/prompt.rb +33 -0
  31. data/lib/pull_request_ai/repo/reader.rb +118 -0
  32. data/lib/pull_request_ai/util/configuration.rb +49 -0
  33. data/lib/pull_request_ai/util/error.rb +28 -0
  34. data/lib/pull_request_ai/util/symbol_details.rb +14 -0
  35. data/lib/pull_request_ai/version.rb +5 -0
  36. data/lib/pull_request_ai.rb +52 -0
  37. data/lib/tasks/pull_request_ai_tasks.rake +5 -0
  38. metadata +153 -0
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PullRequestAi
4
+ module OpenAi
5
+ # A client to communicate with the OpenAI API.
6
+ class Client
7
+ attr_accessor :openai_api_key
8
+ attr_accessor :openai_api_endpoint
9
+ attr_accessor :api_version
10
+ attr_accessor :model
11
+ attr_accessor :temperature
12
+ attr_reader :http_timeout
13
+
14
+ ##
15
+ # Initializes the client.
16
+ def initialize(
17
+ openai_api_key: nil,
18
+ openai_api_endpoint: nil,
19
+ api_version: nil,
20
+ model: nil,
21
+ temperature: nil
22
+ )
23
+ @openai_api_key = openai_api_key || PullRequestAi.openai_api_key
24
+ @openai_api_endpoint = openai_api_endpoint || PullRequestAi.openai_api_endpoint
25
+ @api_version = api_version || PullRequestAi.api_version
26
+ @model = model || PullRequestAi.model
27
+ @temperature = temperature || PullRequestAi.temperature
28
+ @http_timeout = PullRequestAi.http_timeout
29
+ end
30
+
31
+ ##
32
+ # Makes the completions request from the OpenAI API.
33
+ # Given a prompt, the model will return one or more predicted completions.
34
+ # https://platform.openai.com/docs/api-reference/chat
35
+ def predicted_completions(content)
36
+ url = build_url
37
+ request(:post, url, body(content))
38
+ end
39
+
40
+ private
41
+
42
+ def request(type, url, body)
43
+ response = HTTParty.send(
44
+ type, url, headers: headers, body: body, timeout: http_timeout
45
+ )
46
+ body = response.parsed_response
47
+
48
+ if response.success?
49
+ if body['choices'].nil? || body['choices'].empty?
50
+ Dry::Monads::Success('')
51
+ else
52
+ Dry::Monads::Success(body['choices'].first.dig('message', 'content'))
53
+ end
54
+ else
55
+ Error.failure(:failed_on_openai_api_endpoint, body.dig('error', 'message'))
56
+ end
57
+ rescue Net::ReadTimeout
58
+ Error.failure(:connection_timeout)
59
+ end
60
+
61
+ def build_url
62
+ "#{openai_api_endpoint}/#{api_version}/chat/completions"
63
+ end
64
+
65
+ def headers
66
+ {
67
+ 'Content-Type' => 'application/json',
68
+ 'Authorization' => "Bearer #{openai_api_key}"
69
+ }
70
+ end
71
+
72
+ def body(content)
73
+ {
74
+ model: model,
75
+ messages: [{ role: :user, content: content }],
76
+ temperature: temperature
77
+ }.to_json
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PullRequestAi
4
+ module OpenAi
5
+ class Interpreter
6
+ def chat_message(feature_type, summary, current_changes)
7
+ result = ''.dup
8
+ unless summary.nil? || summary.strip.empty?
9
+ result << "Given the following summary for the changes made:\n"
10
+ result << "\"#{summary}\""
11
+ result << "\n\n"
12
+ end
13
+ result << "Write a #{feature_type} pull request description based on the following changes:\n"
14
+ result << current_changes
15
+ result.freeze
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PullRequestAi
4
+ module Repo
5
+ class Client
6
+ attr_reader :http_timeout
7
+ attr_accessor :api_endpoint
8
+
9
+ ##
10
+ # Initializes the client.
11
+ def initialize(http_timeout, api_endpoint)
12
+ @http_timeout = http_timeout
13
+ @api_endpoint = api_endpoint
14
+ end
15
+
16
+ def opened_pull_requests(slug, head, base)
17
+ Error.failure(:project_not_configured)
18
+ end
19
+
20
+ def update_pull_request(slug, number, base, title, description)
21
+ Error.failure(:project_not_configured)
22
+ end
23
+
24
+ def open_pull_request(slug, head, base, title, description)
25
+ Error.failure(:project_not_configured)
26
+ end
27
+
28
+ class << self
29
+ def client_from_host(host)
30
+ result = host.success? ? host.success : ''
31
+ case result
32
+ when 'github.com'
33
+ PullRequestAi::GitHub::Client.new
34
+ when 'bitbucket.org'
35
+ PullRequestAi::Bitbucket::Client.new
36
+ else
37
+ PullRequestAi::Repo::Client.new
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PullRequestAi
4
+ module Repo
5
+ class File
6
+ attr_reader :name, :modified_lines
7
+
8
+ def initialize(name, modified_lines)
9
+ @name = name
10
+ @modified_lines = modified_lines
11
+ end
12
+
13
+ def trimmed_modified_lines
14
+ modified_lines.inject(name + "\n") do |result, line|
15
+ result << line.sub(/([+\-])\s*/) { ::Regexp.last_match(1) } + "\n"
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PullRequestAi
4
+ module Repo
5
+ class Prompt
6
+ def configured?
7
+ %x(git rev-parse --is-inside-work-tree > /dev/null 2>&1)
8
+ $CHILD_STATUS.exitstatus == 0
9
+ end
10
+
11
+ def current_branch
12
+ name = %x(git rev-parse --abbrev-ref HEAD).chomp
13
+ name == 'HEAD' ? %x(git rev-parse --short HEAD).chomp : name
14
+ end
15
+
16
+ def remote_name
17
+ %x(git remote).strip
18
+ end
19
+
20
+ def remote_url(name)
21
+ %x(git config --get remote.#{name}.url).strip
22
+ end
23
+
24
+ def remote_branches
25
+ %x(git branch --remotes --no-color).split("\n")
26
+ end
27
+
28
+ def changes_between(branch1, branch2)
29
+ %x(git diff --patch #{branch1}..#{branch2}).strip
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,118 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PullRequestAi
4
+ module Repo
5
+ class Reader
6
+ include Dry::Monads[:result]
7
+
8
+ attr_accessor :prompt
9
+
10
+ def initialize(prompt: Prompt.new)
11
+ @prompt = prompt
12
+ end
13
+
14
+ def current_branch
15
+ prompt.configured? ? Success(prompt.current_branch) : Error.failure(:project_not_configured)
16
+ end
17
+
18
+ def remote_name
19
+ prompt.configured? ? Success(prompt.remote_name) : Error.failure(:project_not_configured)
20
+ end
21
+
22
+ def repository_slug
23
+ remote_uri.bind do |uri|
24
+ regex = %r{\A/?(?<slug>.*?)(?:\.git)?\Z}
25
+ match = regex.match(uri.path)
26
+ match ? Success(match[:slug]) : Error.failure(:invalid_repository_url)
27
+ end
28
+ end
29
+
30
+ def repository_host
31
+ remote_uri.bind do |uri|
32
+ Success(uri.host)
33
+ end
34
+ end
35
+
36
+ def remote_uri
37
+ remote_name.bind do |name|
38
+ url = prompt.remote_url(name)
39
+ uri = GitCloneUrl.parse(url)
40
+ Success(uri)
41
+ end
42
+ rescue URI::InvalidComponentError
43
+ Error.failure(:invalid_repository_url)
44
+ end
45
+
46
+ def remote_branches
47
+ remote_name.bind do |name|
48
+ branches = prompt.remote_branches
49
+ .reject { !_1.strip.start_with?(name) }
50
+ .map { _1.strip.sub(%r{\A#{name}/}, '') }
51
+ .reject(&:empty?)
52
+ Success(branches)
53
+ end
54
+ end
55
+
56
+ def destination_branches
57
+ current_branch.bind do |current|
58
+ remote_branches.bind do |branches|
59
+ if branches.include?(current)
60
+ Success(branches.reject { _1 == current || _1.start_with?('HEAD') })
61
+ else
62
+ Error.failure(:current_branch_not_pushed)
63
+ end
64
+ end
65
+ end
66
+ end
67
+
68
+ def current_changes(to_base)
69
+ current_branch.bind do |current|
70
+ changes_between(to_base, current)
71
+ end
72
+ end
73
+
74
+ def flatten_current_changes(to_base)
75
+ current_changes(to_base).bind do |changes|
76
+ Success(changes.inject(''.dup) { |result, file| result << file.trimmed_modified_lines })
77
+ end
78
+ end
79
+
80
+ private
81
+
82
+ def changes_between(branch1, branch2)
83
+ if prompt.configured? == false
84
+ Error.failure(:project_not_configured)
85
+ else
86
+ diff_output = prompt.changes_between(branch1, branch2)
87
+ changes = []
88
+
89
+ file_name = nil
90
+ modified_lines = []
91
+ diff_output.each_line do |line|
92
+ line = line.chomp
93
+ if line.start_with?('diff --git')
94
+ if file_name && file_name.end_with?('.lock') == false
95
+ changes << PullRequestAi::Repo::File.new(file_name, modified_lines)
96
+ end
97
+ file_name = line.split(' ')[-1].strip
98
+ file_name = file_name.start_with?('b/') ? file_name[2..-1] : file_name
99
+ modified_lines = []
100
+ elsif line.start_with?('--- ') || line.start_with?('+++ ')
101
+ next
102
+ elsif line.start_with?('-') && line.strip != '-'
103
+ modified_lines << line
104
+ elsif line.start_with?('+') && line.strip != '+'
105
+ modified_lines << line
106
+ end
107
+ end
108
+
109
+ if file_name
110
+ changes << PullRequestAi::Repo::File.new(file_name, modified_lines)
111
+ end
112
+
113
+ Success(changes)
114
+ end
115
+ end
116
+ end
117
+ end
118
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PullRequestAi
4
+ module Util
5
+ class Configuration
6
+ attr_accessor :openai_api_key
7
+ attr_accessor :github_access_token
8
+ attr_accessor :bitbucket_app_password
9
+ attr_accessor :bitbucket_username
10
+
11
+ attr_accessor :openai_api_endpoint
12
+ attr_accessor :github_api_endpoint
13
+ attr_accessor :bitbucket_api_endpoint
14
+
15
+ attr_accessor :model
16
+ attr_accessor :temperature
17
+ attr_accessor :http_timeout
18
+
19
+ attr_reader :api_version
20
+ attr_reader :rrtools_grouped_gems
21
+
22
+ def initialize
23
+ @api_version = 'v1'
24
+
25
+ @openai_api_key = ENV['OPENAI_API_KEY']
26
+ @github_access_token = ENV['GITHUB_ACCESS_TOKEN']
27
+ @bitbucket_app_password = ENV['BITBUCKET_APP_PASSWORD']
28
+ @bitbucket_username = ENV['BITBUCKET_USERNAME']
29
+
30
+ @openai_api_endpoint = 'https://api.openai.com'
31
+ @github_api_endpoint = 'https://api.github.com'
32
+ @bitbucket_api_endpoint = 'https://api.bitbucket.org'
33
+
34
+ @model = 'gpt-3.5-turbo'
35
+ @temperature = 0.6
36
+ @http_timeout = 60
37
+
38
+ @rrtools_grouped_gems = Rails.application.routes.routes.select do |prop|
39
+ prop.defaults[:group] == 'RRTools'
40
+ end.collect do |route|
41
+ {
42
+ name: route.name,
43
+ path: route.path.build_formatter.instance_variable_get('@parts').join
44
+ }
45
+ end || []
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PullRequestAi
4
+ class Error
5
+ attr_reader :symbol, :message
6
+
7
+ def initialize(symbol, message = nil)
8
+ @symbol = symbol
9
+ @message = message
10
+ end
11
+
12
+ def description
13
+ error_desc = SYMBOL_DETAILS[symbol]
14
+ if error_desc
15
+ error_desc + (message ? " #{message}" : '')
16
+ else
17
+ message || symbol.to_s
18
+ end
19
+ end
20
+
21
+ class << self
22
+ def failure(symbol, message = nil)
23
+ new_instance = new(symbol, message)
24
+ Dry::Monads::Failure(new_instance)
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PullRequestAi
4
+ SYMBOL_DETAILS = {
5
+ project_not_configured: 'Your project doesn\'t have a GitHub repository configured.',
6
+ invalid_repository_url: 'Couldn\'t read the remote URL from the repository.',
7
+ current_branch_not_pushed: 'The current branch has not yet been pushed into the remote.',
8
+ connection_timeout: 'Connection timeout.',
9
+ failed_on_openai_api_endpoint: 'Failed to communicate with openAI API.',
10
+ failed_on_github_api_endpoint: 'Failed to communicate with GitHub API.',
11
+ failed_on_bitbucket_api_endpoint: 'Failed to communicate with Bitbucket API.',
12
+ no_changes_btween_branches: 'No changes between branches. Please check the destination branch.'
13
+ }
14
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PullRequestAi
4
+ VERSION = '0.1.0'
5
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'forwardable'
4
+ require 'httparty'
5
+ require 'dry/monads'
6
+ require 'rack/attack'
7
+ require 'git_clone_url'
8
+
9
+ require 'pull_request_ai/client'
10
+ require 'pull_request_ai/version'
11
+ require 'pull_request_ai/engine'
12
+
13
+ require 'pull_request_ai/util/configuration'
14
+ require 'pull_request_ai/util/symbol_details'
15
+ require 'pull_request_ai/util/error'
16
+
17
+ require 'pull_request_ai/openAi/client'
18
+ require 'pull_request_ai/openAi/interpreter'
19
+
20
+ require 'pull_request_ai/repo/client'
21
+ require 'pull_request_ai/repo/reader'
22
+ require 'pull_request_ai/repo/prompt'
23
+ require 'pull_request_ai/repo/file'
24
+
25
+ require 'pull_request_ai/bitbucket/client'
26
+ require 'pull_request_ai/github/client'
27
+
28
+ module PullRequestAi
29
+ extend SingleForwardable
30
+ def_delegators :configuration, :github_api_endpoint
31
+ def_delegators :configuration, :github_access_token, :github_access_token=
32
+ def_delegators :configuration, :bitbucket_api_endpoint
33
+ def_delegators :configuration, :bitbucket_app_password, :bitbucket_app_password=
34
+ def_delegators :configuration, :bitbucket_username, :bitbucket_username=
35
+ def_delegators :configuration, :openai_api_key, :openai_api_key=
36
+ def_delegators :configuration, :openai_api_endpoint
37
+ def_delegators :configuration, :api_version
38
+ def_delegators :configuration, :model, :model=
39
+ def_delegators :configuration, :temperature, :temperature=
40
+ def_delegators :configuration, :http_timeout
41
+
42
+ class << self
43
+ def configure(&block)
44
+ yield configuration
45
+ end
46
+
47
+ # Returns an existing configuration object or instantiates a new one
48
+ def configuration
49
+ @configuration ||= Util::Configuration.new
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+ # desc "Explaining what the task does"
3
+ # task :pull_request_ai do
4
+ # # Task goes here
5
+ # end
metadata ADDED
@@ -0,0 +1,153 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: pull_request_ai
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Runtime Revolution
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2023-05-18 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: dry-monads
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.6'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.6'
27
+ - !ruby/object:Gem::Dependency
28
+ name: git_clone_url
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '2.0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '2.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: httparty
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '0.21'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '0.21'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rack-attack
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '6.6'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '6.6'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rails
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '6.1'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '6.1'
83
+ description: Rails Engine that provides pull requests descriptions generated by ChatGPT.
84
+ email:
85
+ - info@runtime-revolution.com
86
+ executables: []
87
+ extensions: []
88
+ extra_rdoc_files: []
89
+ files:
90
+ - MIT-LICENSE
91
+ - README.md
92
+ - Rakefile
93
+ - app/assets/config/pull_request_ai_manifest.js
94
+ - app/assets/javascripts/application.js
95
+ - app/assets/javascripts/notifications.js
96
+ - app/assets/stylesheets/pull_request_ai/application.css
97
+ - app/assets/stylesheets/pull_request_ai/blocs.css
98
+ - app/assets/stylesheets/pull_request_ai/helpers.css
99
+ - app/assets/stylesheets/pull_request_ai/inputs.css
100
+ - app/assets/stylesheets/pull_request_ai/notification.css
101
+ - app/assets/stylesheets/pull_request_ai/spinner.css
102
+ - app/controllers/pull_request_ai/application_controller.rb
103
+ - app/controllers/pull_request_ai/pull_request_ai_controller.rb
104
+ - app/helpers/pull_request_ai/application_helper.rb
105
+ - app/models/pull_request_ai/application_record.rb
106
+ - app/views/layouts/pull_request_ai/application.html.erb
107
+ - app/views/pull_request_ai/pull_request_ai/new.html.erb
108
+ - config/initializers/rack_attack.rb
109
+ - config/routes.rb
110
+ - lib/pull_request_ai.rb
111
+ - lib/pull_request_ai/bitbucket/client.rb
112
+ - lib/pull_request_ai/client.rb
113
+ - lib/pull_request_ai/engine.rb
114
+ - lib/pull_request_ai/github/client.rb
115
+ - lib/pull_request_ai/openAi/client.rb
116
+ - lib/pull_request_ai/openAi/interpreter.rb
117
+ - lib/pull_request_ai/repo/client.rb
118
+ - lib/pull_request_ai/repo/file.rb
119
+ - lib/pull_request_ai/repo/prompt.rb
120
+ - lib/pull_request_ai/repo/reader.rb
121
+ - lib/pull_request_ai/util/configuration.rb
122
+ - lib/pull_request_ai/util/error.rb
123
+ - lib/pull_request_ai/util/symbol_details.rb
124
+ - lib/pull_request_ai/version.rb
125
+ - lib/tasks/pull_request_ai_tasks.rake
126
+ homepage: https://www.runtime-revolution.com
127
+ licenses:
128
+ - MIT
129
+ metadata:
130
+ allowed_push_host: https://rubygems.org
131
+ homepage_uri: https://www.runtime-revolution.com
132
+ source_code_uri: https://github.com/runtimerevolution/pull_request_ai
133
+ changelog_uri: https://github.com/runtimerevolution/pull_request_ai
134
+ post_install_message:
135
+ rdoc_options: []
136
+ require_paths:
137
+ - lib
138
+ required_ruby_version: !ruby/object:Gem::Requirement
139
+ requirements:
140
+ - - ">="
141
+ - !ruby/object:Gem::Version
142
+ version: '0'
143
+ required_rubygems_version: !ruby/object:Gem::Requirement
144
+ requirements:
145
+ - - ">="
146
+ - !ruby/object:Gem::Version
147
+ version: '0'
148
+ requirements: []
149
+ rubygems_version: 3.4.10
150
+ signing_key:
151
+ specification_version: 4
152
+ summary: Rails Engine that provides pull requests descriptions generated by ChatGPT.
153
+ test_files: []