ai_refactor 0.5.2 → 0.5.4

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9ebe9a5f356fd432e2e0fdba9c96bc78ea6e5d9b8f2b98357f7a75bd1a53be35
4
- data.tar.gz: 035fb030362ff96600eee4a57939077ae6b1d2c1c0faf2b6f2ec5cfd5c3b8570
3
+ metadata.gz: ca299458e2af2bc7ba495ce6473e1e6b1824e679893815616a209697178aefbd
4
+ data.tar.gz: b13da64462ef3f5572a9afebb0020ecdb5adc538b92e7c80b3b6bbd756473f9d
5
5
  SHA512:
6
- metadata.gz: 764bde690ee5d65522a91e11642e830cb60a1704a20811d00ee522b33b358c36ad7a4193bf437373ce735434fad4f824ddf378ad55c83cce205eeac3311da1f5
7
- data.tar.gz: a19722433c2ed1778a45779383a51dbeb21d0f421efc9b41510c581c96318f7b04b5914bfbea4ca155155742a573b9d3c6a3e0e75b1d0eed43b25b19d6081c62
6
+ metadata.gz: 5714f5535a9a6436beba5a6b33a79a0eb02c095f4ca93a5cdfd794fc231cdcab6341a87565fc8c9f32ef24e1edf35957d5268b48f259c2046f867d722520760a
7
+ data.tar.gz: d986828235c4f26ba78a443ca267455be12d2b9647b672f7cb8a4f4ce53eed5f02c426f626531a9d567058ad8f3e4a34f0c43072529820ca803719542ea0c9e0
data/CHANGELOG.md CHANGED
@@ -1,5 +1,35 @@
1
1
  # AI Refactor Changelog
2
2
 
3
+
4
+ ## [0.5.4] - 2024-02-07
5
+
6
+ ### Added
7
+ - Support for built-in command YML files to make it easy to add new refactors
8
+ - Support for specifying context files from gems with `context_file_paths_from_gems:` key in command templates
9
+ - Command to convert Minitest tests to Quickdraw tests
10
+
11
+ ### Changes
12
+ - Default openAI model is now `gpt-4-turbo-preview`
13
+
14
+ ## [0.5.3] - 2024-02-06
15
+
16
+ ### Added
17
+ - Add runner to run steep on inputs after generating RBS
18
+ - Add refactor to write RBS
19
+
20
+ ### Fixed
21
+ - Removed dependency on `dotenv` gems
22
+ - Update openai dependency
23
+ - Improve prompt handling to allow having custom text prompts from commands that can append to build in prompt templates
24
+ - Custom refactor should allow prompt to come from prompt text option
25
+
26
+ ## [0.5.2] - 2023-09-21
27
+
28
+ ### Added
29
+
30
+ ### Fixed
31
+ - Removed `puts`
32
+
3
33
  ## [0.5.1] - 2023-09-21
4
34
 
5
35
  ### Added
data/Gemfile CHANGED
@@ -11,7 +11,7 @@ gem "minitest", "~> 5.0"
11
11
 
12
12
  gem "standard", "~> 1.3"
13
13
 
14
- gem "dotenv"
14
+ gem "steep"
15
15
 
16
16
  # for the examples
17
17
 
data/README.md CHANGED
@@ -49,8 +49,7 @@ And find the file `examples/ex1_input_test.rb` has been created. Note the proces
49
49
 
50
50
  If you see an error, then try to run it again, or use a different GPT model.
51
51
 
52
-
53
- ## Available refactors
52
+ ## Available refactors & commands
54
53
 
55
54
  Write your own prompt:
56
55
 
@@ -107,6 +106,14 @@ For example, if the input file is `app/stuff/my_thing.rb` the output will be wri
107
106
  This refactor can benefit from being passed related files as context, for example, if the class under test inherits from another class,
108
107
  then context can be used to provide the parent class.
109
108
 
109
+ ### `quickdraw/0.1.0/convert_minitest`
110
+
111
+ Convert Minitest or Test::Unit test suite files to [Quickdraw](https://github.com/joeldrapper/quickdraw) test suite files.
112
+
113
+ Files, by default, are output to the same directory as the input file but with .test.rb extension (and _test removed).
114
+
115
+ Note: Quickdraw is still missing some features, so some minitest methods are not converted, for example, Quickdraw does not support setup/teardown just yet.
116
+
110
117
  ## Installation
111
118
 
112
119
  Install the gem and add to the application's Gemfile by executing:
@@ -134,7 +141,7 @@ Where REFACTOR_TYPE_OR_COMMAND_FILE is either the path to a command YML file, or
134
141
  -p, --prompt PROMPT_FILE Specify path to a text file that contains the ChatGPT 'system' prompt.
135
142
  -f, --diffs Request AI generate diffs of changes rather than writing out the whole file.
136
143
  -C, --continue [MAX_MESSAGES] If ChatGPT stops generating due to the maximum token count being reached, continue to generate more messages, until a stop condition or MAX_MESSAGES. MAX_MESSAGES defaults to 3
137
- -m, --model MODEL_NAME Specify a ChatGPT model to use (default gpt-4).
144
+ -m, --model MODEL_NAME Specify a ChatGPT model to use (default gpt-4-turbo-preview).
138
145
  --temperature TEMP Specify the temperature parameter for ChatGPT (default 0.7).
139
146
  --max-tokens MAX_TOKENS Specify the max number of tokens of output ChatGPT can generate. Max will depend on the size of the prompt (default 1500)
140
147
  -t, --timeout SECONDS Specify the max wait time for ChatGPT response.
@@ -180,7 +187,7 @@ context_text: |
180
187
  Some extra info to prepend to the prompt
181
188
  diff: true/false (default false)
182
189
  ai_max_attempts: max times to generate more if AI does not complete generating (default 3)
183
- ai_model: ChatGPT model name (default gpt-4)
190
+ ai_model: ChatGPT model name (default gpt-4-turbo-preview)
184
191
  ai_temperature: ChatGPT temperature (default 0.7)
185
192
  ai_max_tokens: ChatGPT max tokens (default 1500)
186
193
  ai_timeout: ChatGPT timeout (default 60)
data/Steepfile ADDED
@@ -0,0 +1,27 @@
1
+ # D = Steep::Diagnostic
2
+ #
3
+ # target :lib do
4
+ # signature "sig"
5
+ #
6
+ # check "lib" # Directory name
7
+ # check "Gemfile" # File name
8
+ # check "app/models/**/*.rb" # Glob
9
+ # # ignore "lib/templates/*.rb"
10
+ #
11
+ # # library "pathname" # Standard libraries
12
+ # # library "strong_json" # Gems
13
+ #
14
+ # # configure_code_diagnostics(D::Ruby.default) # `default` diagnostics setting (applies by default)
15
+ # # configure_code_diagnostics(D::Ruby.strict) # `strict` diagnostics setting
16
+ # # configure_code_diagnostics(D::Ruby.lenient) # `lenient` diagnostics setting
17
+ # # configure_code_diagnostics(D::Ruby.silent) # `silent` diagnostics setting
18
+ # # configure_code_diagnostics do |hash| # You can setup everything yourself
19
+ # # hash[D::Ruby::NoMethod] = :information
20
+ # # end
21
+ # end
22
+
23
+ # A steep target to check our example
24
+ target :examples do
25
+ signature "examples/outputs"
26
+ check "examples/ex2_input.rb"
27
+ end
data/ai_refactor.gemspec CHANGED
@@ -31,6 +31,6 @@ Gem::Specification.new do |spec|
31
31
  # Uncomment to register a new dependency of your gem
32
32
  spec.add_dependency "colorize", "< 2.0"
33
33
  spec.add_dependency "open3", "< 2.0"
34
- spec.add_dependency "ruby-openai", ">= 3.4.0", "< 5.0"
34
+ spec.add_dependency "ruby-openai", ">= 3.4.0", "< 6.0"
35
35
  spec.add_dependency "zeitwerk", "~> 2.6"
36
36
  end
@@ -0,0 +1,194 @@
1
+ # Convert Minitest or Test::Unit test suite files to Quickdraw test suite files.
2
+ #
3
+ # Files are output to the same directory as the input file but with .test.rb extension (and _test removed).
4
+ # Quickdraw is still missing some features, so some minitest methods are not converted. Also
5
+ # Quickdraw does not support setup/teardown just yet.
6
+
7
+ refactor: ruby/refactor_ruby
8
+ description: Convert Minitest or Test::Unit test suite files to Quickdraw test suite files.
9
+ output_template_path: "[DIR]/[NAME|_test|].test[EXT]"
10
+ context_file_paths_from_gems:
11
+ quickdraw:
12
+ - "lib/quickdraw/matchers/boolean.rb"
13
+ - "lib/quickdraw/matchers/case_equality.rb"
14
+ - "lib/quickdraw/matchers/change.rb"
15
+ - "lib/quickdraw/matchers/equality.rb"
16
+ - "lib/quickdraw/matchers/include.rb"
17
+ - "lib/quickdraw/matchers/predicate.rb"
18
+ - "lib/quickdraw/matchers/respond_to.rb"
19
+ - "lib/quickdraw/matchers/to_be_a.rb"
20
+ - "lib/quickdraw/matchers/to_have_attributes.rb"
21
+ prompt: |
22
+ You are an expert Ruby senior software developer. You convert minitest or Test::Unit test suite files to Quickdraw test suite files.
23
+
24
+ Quickdraw is a new test framework for Ruby:
25
+
26
+ - Spec-like DSL, but with just five methods: `describe`, `test` and `expect`, `assert`, `refute`. No `context`, `let`, `subject`, `to`, `it`, `is_expected` or `specify`, and you’ll never need to guess whether the next symbol should be a space, a colon, a dot or an underscore.
27
+ - No chaining on matchers. Rather than chain, the matcher can yield if it wants to allow for more complex matching.
28
+ - Auto-loaded configuration, so you never need to `require "test_helper"`.
29
+ - Scoped execution, so you can define methods and constants at the top level without worrying about collisions.
30
+ - You can define your own matchers, which can be scoped to a specific type of object and they can be overloaded for different types.
31
+ - Designed to take advantage of all your CPU cores — by default it runs one process per CPU core and two threads per process.
32
+ - Optional test names — sometimes the code is so clear, you don’t need names.
33
+ - Make as many expectations as you want in a test. You’ll get a dot for each one to make you feel good about youtself.
34
+
35
+ > [!TIP]
36
+ > Your test files are executed in an anonymous class, so you can define methods and constants at the top level without worrying about collisions. If you’re testing something that references `Class#name`, you may have to define those classes as fixtures somewhere else.
37
+
38
+ ### `.test`
39
+ Use the `test` method to define a test. The description is optional — sometimes you don’t need it.
40
+
41
+ ```ruby
42
+ test { assert true }
43
+ ```
44
+
45
+ You can pass `skip: true` to skip the test. Skipped tests are still run; they pass if they fail and fail they pass.
46
+
47
+ ```ruby
48
+ test(skip: true) { assert false }
49
+ ```
50
+
51
+ ### `.describe`
52
+ You can optionally wrap tests in any number of `describe` blocks, which can take a description as a string or module/class.
53
+
54
+ ```ruby
55
+ describe Thing do
56
+ # your Thing tests here
57
+ end
58
+ ```
59
+
60
+ ### `#assert`
61
+ `assert` takes a value and passes if it’s truthy.
62
+
63
+ ```ruby
64
+ test "something" do
65
+ assert true
66
+ end
67
+ ```
68
+
69
+ You can pass a custom failure message as a block. Using blocks for the failure messages means we don’t waste time constructing them unless the test fails. You don’t need to worry about expensive failure messages slowing down your tests.
70
+
71
+ ```ruby
72
+ test "something" do
73
+ assert(false) { "This is a custom failure message" }
74
+ end
75
+ ```
76
+
77
+ ### `#refute`
78
+ `refute` is just like `assert`, but it passes if the value is falsy.
79
+
80
+ ```ruby
81
+ test "something" do
82
+ refute false
83
+ end
84
+ ```
85
+
86
+ ### `expect` matchers
87
+ `expect` takes either a value or a block and returns an expectation object, which you can call matchers on.
88
+
89
+ #### `==` and `!=`
90
+
91
+ ```ruby
92
+ test "equality" do
93
+ expect(Thing.foo) == "foo"
94
+ expect(Thing.bar) != "foo"
95
+ end
96
+ ```
97
+
98
+ #### `to_raise`
99
+
100
+ ```ruby
101
+ test "raises" do
102
+ expect { Thing.bar! }.to_raise(ArgumentError) do |error|
103
+ expect(error.message) == "Foo bar"
104
+ end
105
+ end
106
+ ```
107
+
108
+ #### `to_receive`
109
+
110
+ ```ruby
111
+ test "mocks and spies" do
112
+ expect(Thing).to_receive(:foo) do |a, b, c|
113
+ # The block receives arguments and can make assertions about them.
114
+ expect(a) == 1
115
+ expect(b) != 1
116
+ assert(c)
117
+
118
+ # Either return a mock response or call the original via `@super`
119
+ @super.call
120
+ end
121
+
122
+ Thing.foo(1, 2, 3)
123
+ end
124
+ ```
125
+
126
+ ### Mappings of minitest assertions/expectations to quickdraw
127
+
128
+ The minitest test class (which inherits from Test::Unit or Minitest::Test) should be removed from the output, as the
129
+ quickdraw test class is anonymous and implicit.
130
+ eg
131
+ ```ruby
132
+ class MyTest < Minitest::Test
133
+ def test_something
134
+ assert true
135
+ end
136
+ end
137
+ ```
138
+ becomes
139
+ ```ruby
140
+ test "something" do
141
+ assert true
142
+ end
143
+ ```
144
+
145
+ `should` in Test::Unit is the same as `describe` in Quickdraw.
146
+
147
+ minitest "assert" and "refute" methods are mapped to quickdraw `assert` and `refute` methods.
148
+
149
+ minitest "expect" methods are mapped to quickdraw `expect` methods.
150
+
151
+ below are the mappings of minitest methods to quickdraw methods:
152
+
153
+ `_(x).must_be`, `expect(x).must_be :==, 0` or `assert_operator x, :==, 0` becomes `expect(x) == 0`
154
+ `_(x).must_be`, `expect(x).must_be :>, 0` or `assert_operator x, :>, 0` becomes `assert(x > 0)`
155
+ `_(x).must_be`, `expect(x).must_be :empty?` `expect(x).must_be_empty` `assert_empty` becomes `assert(x.empty?)`
156
+ `_(x).must_equal`, `expect(x).must_equal b` or `assert_equal b, x` becomes `expect(x) == b`
157
+ `_(x).must_be_close_to`, `expect(x).must_be_close_to 2.99999`, `assert_in_epsilon` or `assert_in_delta` becomes `raise "Not implemented in Quickdraw yet"`
158
+ `_(x).must_be_same_as`, `expect(x).must_be_same_as b` and `assert_same` becomes `expect(x).to_equal(b)`
159
+ `_(x).must_include`, `expect(x).must_include needle`, `assert_includes x, needle` becomes `expect(x).to_include(needle)`
160
+ `_(x).must_be_kind_of`, `expect(x).must_be_kind_of Enumerable` or `assert_kind_of Enumerable, x` becomes `assert(x.kind_of? Enumerable)`
161
+ `_(x).must_be_instance_of`, `expect(x).must_be_instance_of Array` or `assert_instance_of Array, x` becomes `assert(x.instance_of? Array)`
162
+ `_(x).must_be_nil`, `expect(x).must_be_nil` or `assert_nil` becomes `assert(x == nil)`
163
+ `_(x).must_match`, `expect(x).must_match /regex/` , `assert_match x, /regex/` becomes `assert(/regex/ === x)`
164
+ `_(x).must_respond_to`, `expect(x).must_respond_to msg` or `assert_respond_to x, msg` becomes `expect(x).to_respond_to(msg)`
165
+ `_(x).wont_respond_to`, `expect(x).wont_respond_to msg` or `refute_respond_to x, msg` becomes `expect(x).not_to_respond_to(msg)`
166
+ `proc { "no stdout or stderr" }.must_output` or `assert_output {}`, proc { "no stdout or stderr" }.must_be_silent` or `assert_silent {}` becomes `raise "Not implemented in Quickdraw yet"`
167
+ `proc { ... }.must_raise exception` or `assert_raises(exp) {}` becomes `expect {}.to_raise(exp)`
168
+ `proc { ... }.must_throw sym` or `assert_throws(sym) {}` becomes `raise "Not implemented in Quickdraw yet"`
169
+
170
+ note: there are also `refute_*` methods in minitest, which are mapped to either a `refute(...)` or a #not_to* methods in quickdraw.
171
+
172
+ Converting `MiniTest::Spec` to `Quickdraw` as follows (like converting from Spec syntax to Test syntax):
173
+
174
+ `subject {}` becomes
175
+ ```ruby
176
+ def subject
177
+ @subject ||= Thing.new
178
+ end
179
+ ```
180
+
181
+ `let(:x) { 1 }` becomes
182
+ ```ruby
183
+ def x
184
+ @x ||= 1
185
+ end
186
+ ```
187
+
188
+ If any modules are included in the minitest class, then take the contents of the module and add it to the output but remove the wrapping `module`
189
+ Also remove the `include Module` statement from the output.
190
+ Also remove the def self.included(base) method from the output.
191
+
192
+ Only show me the test file code. Do NOT provide any other description of your work. Always enclose the output code in triple backticks (```).
193
+
194
+ The minitest test to convert is as follows:
@@ -2,6 +2,7 @@ refactor: rails/minitest/rspec_to_minitest
2
2
  input_file_paths:
3
3
  - examples/ex1_input_spec.rb
4
4
  # We need to add context here as otherwise to tell the AI to require our local test_helper.rb file so that we can run the tests after
5
- context_text: "In the output test use `require_relative` to include 'test_helper'."
5
+ context_text: "In the output test use `require_relative '../test_helper'` to include 'test_helper'."
6
6
  # By default, ai_refactor runs "bundle exec rails test" but this isn't going to work here as we are not actually in a Rails app context in the examples
7
7
  minitest_run_command: ruby __FILE__
8
+ output_file_path: examples/outputs/ex1_input_test.rb
@@ -0,0 +1,17 @@
1
+ # example from https://blog.kiprosh.com/type-checking-in-ruby-3-using-rbs/
2
+ # basic_math.rb
3
+
4
+ class BasicMath
5
+ def initialize(num1, num2)
6
+ @num1 = num1
7
+ @num2 = num2
8
+ end
9
+
10
+ def first_less_than_second?
11
+ @num1 < @num2
12
+ end
13
+
14
+ def add
15
+ @num1 + @num2
16
+ end
17
+ end
@@ -0,0 +1,7 @@
1
+ refactor: ruby/write_rbs
2
+ input_file_paths:
3
+ - examples/ex2_input.rb
4
+ # We need to add context here as our class doesnt actually give any context.
5
+ context_text: "Assume that the inputs can be any numeric type."
6
+ # By default this refactor writes to `sig/...` but here we just put the result in `examples/...`
7
+ output_file_path: examples/outputs/ex2_input.rbs
data/exe/ai_refactor CHANGED
@@ -6,10 +6,9 @@ require "openai"
6
6
  require "shellwords"
7
7
  require_relative "../lib/ai_refactor"
8
8
 
9
- require "dotenv/load"
10
-
11
- supported_refactors = AIRefactor::Refactors.all
12
- refactors_descriptions = AIRefactor::Refactors.descriptions
9
+ supported_refactors = AIRefactor::Refactors.all.merge(AIRefactor::Commands.all)
10
+ supported_refactors_names = supported_refactors.keys
11
+ refactors_descriptions = AIRefactor::Refactors.descriptions.merge(AIRefactor::Commands.descriptions)
13
12
 
14
13
  arguments = ARGV.dup
15
14
 
@@ -54,7 +53,7 @@ option_parser = OptionParser.new do |parser|
54
53
  run_config.ai_max_attempts = c
55
54
  end
56
55
 
57
- parser.on("-m", "--model MODEL_NAME", String, "Specify a ChatGPT model to use (default gpt-4).") do |m|
56
+ parser.on("-m", "--model MODEL_NAME", String, "Specify a ChatGPT model to use (default gpt-4-turbo-preview).") do |m|
58
57
  run_config.ai_model = m
59
58
  end
60
59
 
@@ -139,10 +138,9 @@ if arguments.empty? || arguments.all? { |arg| arg.start_with?("-") && !(arg == "
139
138
  # For each option that is required but not provided, prompt for it
140
139
  # Put the option in arguments to parse with option_parser
141
140
  interactive_log.info "Interactive mode started. You can use tab to autocomplete:"
142
- predefined_commands = AIRefactor::Refactors.names
143
141
 
144
- interactive_log.info "Available refactors: #{predefined_commands.join(", ")}\n"
145
- command = AIRefactor::Cli.request_input_with_autocomplete("Enter refactor name: ", predefined_commands)
142
+ interactive_log.info "Available refactors: #{supported_refactors_names.join(", ")}\n"
143
+ command = AIRefactor::Cli.request_input_with_autocomplete("Enter refactor name: ", supported_refactors_names)
146
144
  exit_with_option_error("No refactor name provided.", option_parser) if command.nil? || command.empty?
147
145
  initial = [command]
148
146
 
@@ -182,10 +180,12 @@ logger = AIRefactor::Logger.new(verbose: run_config.verbose, debug: run_config.d
182
180
  logger.info "Also loaded options from '.ai_refactor' file..." if options_from_config_file&.size&.positive?
183
181
 
184
182
  command_or_file = arguments.shift
185
- if AIRefactor::CommandFileParser.command_file?(command_or_file)
186
- logger.info "Loading refactor command file '#{command_or_file}'..."
183
+ is_built_in_command = AIRefactor::Commands.supported?(command_or_file)
184
+ if is_built_in_command || AIRefactor::CommandFileParser.command_file?(command_or_file)
185
+ logger.info "Loading #{is_built_in_command ? "built-in" : "custom"} refactor command file '#{command_or_file}'..."
187
186
  begin
188
- run_config.set!(AIRefactor::CommandFileParser.new(command_or_file).parse)
187
+ command_file_path = is_built_in_command ? Commands.get(command_name).path : command_or_file
188
+ run_config.set!(AIRefactor::CommandFileParser.new(command_file_path).parse)
189
189
  rescue => e
190
190
  exit_with_option_error(e.message, option_parser, logger)
191
191
  end
@@ -85,6 +85,9 @@ module AIRefactor
85
85
 
86
86
  logger.info "AI Refactor #{expanded_inputs.size} files(s)/dir(s) '#{expanded_inputs}' with #{refactorer.refactor_name} refactor\n"
87
87
  logger.info "====================\n"
88
+ if configuration.description
89
+ logger.info "Description: #{configuration.description}\n"
90
+ end
88
91
 
89
92
  return_values = expanded_inputs.map do |file|
90
93
  logger.info "Processing #{file}..."
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AIRefactor
4
+ module Commands
5
+ # TODO: support command_line_options
6
+ BuiltInCommand = Data.define(:name, :description, :path, :command_line_options, :config)
7
+
8
+ def get(name)
9
+ all[name]
10
+ end
11
+ module_function :get
12
+
13
+ def names
14
+ all.keys
15
+ end
16
+ module_function :names
17
+
18
+ def descriptions
19
+ names.map { |n| "\"#{n}\"" }.zip(all.values.map(&:description)).to_h
20
+ end
21
+ module_function :descriptions
22
+
23
+ def supported?(name)
24
+ names.include?(name)
25
+ end
26
+ module_function :supported?
27
+
28
+ def all
29
+ @all ||= begin
30
+ commands = Dir.glob(File.join(__dir__, "../../commands", "**/*.yml")).map do |path|
31
+ path_to_commands = File.join(__dir__, "../../commands/")
32
+ name = File.join(File.dirname(path.gsub(path_to_commands, "")), File.basename(path, ".yml")).to_sym
33
+ config = YAML.safe_load_file(path, permitted_classes: [Symbol], symbolize_names: true, aliases: true)
34
+ BuiltInCommand.new(name: name, path: path, description: config[:description], config: config, command_line_options: [])
35
+ end
36
+ commands.map { |c| [c.name, c] }.to_h
37
+ end
38
+ end
39
+ module_function :all
40
+ end
41
+ end
@@ -62,7 +62,7 @@ module AIRefactor
62
62
 
63
63
  response = @ai_client.chat(
64
64
  parameters: {
65
- model: options[:ai_model] || "gpt-4",
65
+ model: options[:ai_model] || "gpt-4-turbo-preview",
66
66
  messages: messages,
67
67
  temperature: options[:ai_temperature] || 0.7,
68
68
  max_tokens: options[:ai_max_tokens] || 1500
@@ -70,7 +70,7 @@ module AIRefactor
70
70
 
71
71
  def input_to_process
72
72
  return File.read(@input_file_path) if @input_file_path
73
- @input_content
73
+ @input_content || ""
74
74
  end
75
75
 
76
76
  def input_prompt
@@ -39,7 +39,7 @@ module AIRefactor
39
39
 
40
40
  def file_processor
41
41
  context = ::AIRefactor::Context.new(files: options[:context_file_paths], text: options[:context_text], logger: logger)
42
- prompt = ::AIRefactor::Prompt.new(input_content: input_content, input_path: input_file, output_file_path: output_file_path, prompt: prompt_input, context: context, logger: logger, options: options)
42
+ prompt = ::AIRefactor::Prompt.new(input_content: input_content, input_path: input_file, output_file_path: output_file_path, prompt: prompt_input, context: context, logger: logger, prompt_footer: prompt_footer, options: options)
43
43
  AIRefactor::FileProcessor.new(prompt: prompt, ai_client: ai_client, output_path: output_file_path, logger: logger, options: options)
44
44
  end
45
45
 
@@ -102,22 +102,46 @@ module AIRefactor
102
102
  false
103
103
  end
104
104
 
105
- def prompt_input
106
- if options && options[:prompt]&.length&.positive?
107
- return options[:prompt]
108
- end
105
+ def prompt_template_for_refactor
106
+ location = Module.const_source_location(::AIRefactor::Refactors::BaseRefactor.name)
107
+ File.join(File.dirname(location.first), "#{refactor_name}.md")
108
+ end
109
+
110
+ def prompt_template_for_refactor?
111
+ File.exist?(prompt_template_for_refactor)
112
+ end
113
+
114
+ def user_provided_prompt_template?
115
+ options[:prompt_file_path]&.length&.positive?
116
+ end
109
117
 
110
- file = if options && options[:prompt_file_path]&.length&.positive?
111
- options[:prompt_file_path]
118
+ def prompt_from_file?
119
+ user_provided_prompt_template? || prompt_template_for_refactor?
120
+ end
121
+
122
+ def user_provided_prompt?
123
+ options[:prompt]&.length&.positive?
124
+ end
125
+
126
+ def prompt_input
127
+ if user_provided_prompt_template?
128
+ file = options[:prompt_file_path]
129
+ raise "No prompt file '#{file}' found. Check prompt_file_path is valid" unless File.exist?(file)
130
+ File.read(file)
112
131
  else
113
- location = Module.const_source_location(::AIRefactor::Refactors::BaseRefactor.name)
114
- File.join(File.dirname(location.first), "#{refactor_name}.md")
115
- end
116
- file.tap do |prompt|
117
- raise "No prompt file '#{prompt}' found for #{refactor_name}" unless File.exist?(prompt)
132
+ file = prompt_template_for_refactor
133
+ if File.exist?(file)
134
+ File.read(file)
135
+ elsif user_provided_prompt?
136
+ options[:prompt]
137
+ else
138
+ raise "No prompt was provided. Please provide one with the --prompt option or with a command file."
139
+ end
118
140
  end
141
+ end
119
142
 
120
- File.read(file)
143
+ def prompt_footer
144
+ options[:prompt] if user_provided_prompt? && prompt_from_file?
121
145
  end
122
146
 
123
147
  def output_file_path
@@ -4,7 +4,7 @@ module AIRefactor
4
4
  module Refactors
5
5
  class Custom < BaseRefactor
6
6
  def run
7
- logger.verbose "Custom refactor to #{input_file}... (using user supplied prompt #{prompt_file_path})"
7
+ logger.verbose "Custom refactor to #{input_file}... (using user supplied prompt #{prompt_file_path || "from options"})"
8
8
  logger.verbose "Write output to #{output_file_path}..." if output_file_path
9
9
 
10
10
  begin
@@ -26,6 +26,7 @@ module AIRefactor
26
26
  private
27
27
 
28
28
  def prompt_file_path
29
+ return if options[:prompt]&.length&.positive?
29
30
  specified_prompt_path = options[:prompt_file_path]
30
31
  if specified_prompt_path&.length&.positive?
31
32
  if File.exist?(specified_prompt_path)
@@ -5,6 +5,4 @@ __{{prompt_header}}__
5
5
  The input file is: __{{input_file_path}}__
6
6
  The output file path is: __{{output_file_path}}__
7
7
 
8
- __{{content}}__
9
-
10
8
  __{{prompt_footer}}__
@@ -0,0 +1,118 @@
1
+ You are an expert Ruby senior software developer.
2
+
3
+ You will be writing the type signatures for the Ruby code below, in RBS format.
4
+
5
+ RBS is a language to describe the structure of Ruby programs. You can write down the definition of a class or module: methods defined in the class, instance variables and their types, and inheritance/mix-in relations. It also allows declaring constants and global variables.
6
+
7
+ The following is a small example of RBS for a chat app.
8
+
9
+ Given the Ruby
10
+ ```ruby
11
+ module ChatApp
12
+ VERSION = "1.0.0"
13
+ class User
14
+ attr_reader :login, :email
15
+
16
+ def initialize(login:, email:); end
17
+
18
+ def my_method(String arg1, Integer arg2); end
19
+ end
20
+
21
+ class Bot
22
+ attr_reader :name
23
+ attr_reader :email
24
+ attr_reader :owner
25
+
26
+ def initialize(name:, owner:); end
27
+ end
28
+
29
+ class Message
30
+ attr_reader :id, :string, :from, :reply_to
31
+
32
+ def initialize(from:, string:); end
33
+
34
+ def reply(from:, string:)
35
+ Message.new(from, string)
36
+ end
37
+ end
38
+
39
+ class Channel
40
+ attr_reader :name, :messages, :users, :bots
41
+
42
+ def initialize(name)
43
+ @name = name
44
+ @messages = []
45
+ @users = []
46
+ @bots = []
47
+ end
48
+
49
+ def each_member(&block)
50
+ members = users + bots
51
+ block? ? members.each(&block) : members.each
52
+ end
53
+ end
54
+ end
55
+ ```
56
+
57
+ We can write the RBS as follows:
58
+ ```
59
+ module ChatApp
60
+ VERSION: String
61
+
62
+ class User
63
+ attr_reader login: String
64
+ attr_reader email: String
65
+
66
+ # If a method takes keyword arguments then use `key: Type` syntax.
67
+ def initialize: (login: String, email: String) -> void
68
+
69
+ # If a method takes positional arguments then put the type before the argument name.
70
+ def my_method: (String arg1, Integer arg2) -> String
71
+ end
72
+
73
+ class Bot
74
+ attr_reader name: String
75
+ attr_reader email: String
76
+ attr_reader owner: User
77
+
78
+ def initialize: (name: String, owner: User) -> void
79
+ end
80
+
81
+ class Message
82
+ attr_reader id: String
83
+ attr_reader string: String
84
+ attr_reader from: User | Bot # `|` means union types: `#from` can be `User` or `Bot`
85
+ attr_reader reply_to: Message? # `?` means optional type: `#reply_to` can be `nil`
86
+
87
+ def initialize: (from: User | Bot, string: String) -> void
88
+
89
+ def reply: (from: User | Bot, string: String) -> Message
90
+ end
91
+
92
+ class Channel
93
+ attr_reader name: String
94
+ attr_reader messages: Array[Message]
95
+ attr_reader users: Array[User]
96
+ attr_reader bots: Array[Bot]
97
+
98
+ def initialize: (String name) -> void
99
+
100
+ def each_member: () { (User | Bot) -> void } -> void # `{` and `}` means block.
101
+ | () -> Enumerator[User | Bot, void] # Method can be overloaded.
102
+ end
103
+ end
104
+ ```
105
+
106
+ Note, when writing the RBS for a class method, eg one in a `class << self` block, use `def self.method` in the
107
+ type definition.
108
+
109
+ Do not include comments in your RBS code or start the file with 'rbs' or '.rbs'.
110
+
111
+ __{{context}}__
112
+
113
+ __{{prompt_header}}__
114
+
115
+ The input file is: __{{input_file_path}}__
116
+ The output file path is: __{{output_file_path}}__
117
+
118
+ __{{prompt_footer}}__
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AIRefactor
4
+ module Refactors
5
+ module Ruby
6
+ class WriteRbs < BaseRefactor
7
+ def run
8
+ logger.verbose "Write some RBS for #{input_file}..."
9
+ logger.verbose "Write output to #{output_file_path}..." if output_file_path
10
+
11
+ begin
12
+ output_content = process!(strip_ticks: true)
13
+ rescue => e
14
+ logger.error "Failed to process #{input_file}: #{e.message}"
15
+ return false
16
+ end
17
+
18
+ return false unless output_content
19
+
20
+ if output_file_path
21
+ steep_runner = AIRefactor::TestRunners::SteepRunner.new(input_file, command_template: options.minitest_run_command)
22
+
23
+ logger.verbose "Run steep against generated RBS file #{output_file_path} (`#{steep_runner.command}`)..."
24
+ test_run = steep_runner.run
25
+
26
+ if test_run.failed?
27
+ logger.warn "#{input_file} was translated to #{output_file_path} but the resulting RBS fails to pass a steep check..."
28
+ logger.error "Failed to run test, exited with status #{test_run.exitstatus}. Stdout: #{test_run.stdout}\n\nStderr: #{test_run.stderr}\n\n"
29
+ logger.error "New RBS failed!", bold: true
30
+ self.failed_message = "Generated RBS file failed to pass checks"
31
+ return false
32
+ end
33
+
34
+ logger.verbose "\nNew RBS file passed checks"
35
+ end
36
+
37
+ output_file_path ? true : output_content
38
+ end
39
+
40
+ def self.description
41
+ "User supplied prompt to write RBS for some Ruby"
42
+ end
43
+
44
+ def default_output_path
45
+ File.join("sig", input_file.gsub(/\.rb$/, ".rbs"))
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
@@ -2,6 +2,4 @@ __{{context}}__
2
2
 
3
3
  __{{prompt_header}}__
4
4
 
5
- __{{content}}__
6
-
7
5
  __{{prompt_footer}}__
@@ -8,10 +8,12 @@ module AIRefactor
8
8
  end
9
9
 
10
10
  attr_reader :refactor,
11
+ :description,
11
12
  :input_file_paths,
12
13
  :output_file_path,
13
14
  :output_template_path,
14
15
  :context_file_paths,
16
+ :context_file_paths_from_gems,
15
17
  :context_text,
16
18
  :review_prompt,
17
19
  :prompt,
@@ -33,7 +35,7 @@ module AIRefactor
33
35
  end
34
36
  end
35
37
 
36
- attr_writer :refactor
38
+ attr_writer :refactor, :description
37
39
 
38
40
  # @deprecated
39
41
  def [](key)
@@ -56,6 +58,25 @@ module AIRefactor
56
58
  @context_file_paths.concat(paths)
57
59
  end
58
60
 
61
+ # A hash is passed in, where the keys are gem names that should be in the bundle and the path is a path inside the gem
62
+ # install location. We resolve the absolute path of each and then add to @context_file_paths
63
+ def context_file_paths_from_gems=(paths)
64
+ @context_file_paths ||= []
65
+ @context_file_paths_from_gems ||= {}
66
+ @context_file_paths_from_gems.merge!(paths)
67
+
68
+ paths.each do |gem_name, paths|
69
+ paths = [paths] unless paths.is_a?(Array)
70
+ paths.each do |path|
71
+ gem_spec = Gem::Specification.find_by_name(gem_name.to_s)
72
+ raise "Gem #{gem_name} not found" unless gem_spec
73
+ gem_path = gem_spec.gem_dir
74
+ full_path = File.join(gem_path, path)
75
+ @context_file_paths << full_path
76
+ end
77
+ end
78
+ end
79
+
59
80
  def context_text=(text)
60
81
  @context_text ||= ""
61
82
  @context_text += text
@@ -81,7 +102,7 @@ module AIRefactor
81
102
  end
82
103
 
83
104
  def ai_model=(value)
84
- @ai_model = value || "gpt-4"
105
+ @ai_model = value || "gpt-4-turbo-preview"
85
106
  end
86
107
 
87
108
  def ai_temperature=(value)
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "open3"
4
+
5
+ module AIRefactor
6
+ module TestRunners
7
+ class SteepRunner
8
+ def initialize(file_path, command_template: "bundle exec steep __FILE__")
9
+ @file_path = file_path
10
+ @command_template = command_template
11
+ end
12
+
13
+ def command
14
+ @command_template.gsub("__FILE__", @file_path)
15
+ end
16
+
17
+ def run
18
+ stdout, stderr, status = Open3.capture3(command)
19
+ # TODO: parse output
20
+ # look initally for Cannot find a configuration at Steepfile
21
+ #
22
+ # _matched, runs, _assertions, failures, errors, skips = stdout.match(/(\d+) runs, (\d+) assertions, (\d+) failures, (\d+) errors, (\d+) skips/).to_a
23
+ TestRunResult.new(stdout, stderr, status) # , runs, failures, skips, errors)
24
+ end
25
+ end
26
+ end
27
+ end
@@ -5,7 +5,7 @@ module AIRefactor
5
5
  class TestRunResult
6
6
  attr_reader :stdout, :stderr, :example_count, :failure_count, :pending_count
7
7
 
8
- def initialize(stdout, stderr, status, example_count, failure_count, pending_count, errored)
8
+ def initialize(stdout, stderr, status, example_count = 0, failure_count = 0, pending_count = 0, errored = 0)
9
9
  @stdout = stdout
10
10
  @stderr = stderr
11
11
  @status = status
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module AIRefactor
4
- VERSION = "0.5.2"
4
+ VERSION = "0.5.4"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ai_refactor
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.2
4
+ version: 0.5.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Stephen Ierodiaconou
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2023-09-21 00:00:00.000000000 Z
11
+ date: 2024-02-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: colorize
@@ -47,7 +47,7 @@ dependencies:
47
47
  version: 3.4.0
48
48
  - - "<"
49
49
  - !ruby/object:Gem::Version
50
- version: '5.0'
50
+ version: '6.0'
51
51
  type: :runtime
52
52
  prerelease: false
53
53
  version_requirements: !ruby/object:Gem::Requirement
@@ -57,7 +57,7 @@ dependencies:
57
57
  version: 3.4.0
58
58
  - - "<"
59
59
  - !ruby/object:Gem::Version
60
- version: '5.0'
60
+ version: '6.0'
61
61
  - !ruby/object:Gem::Dependency
62
62
  name: zeitwerk
63
63
  requirement: !ruby/object:Gem::Requirement
@@ -84,20 +84,23 @@ files:
84
84
  - ".standard.yml"
85
85
  - CHANGELOG.md
86
86
  - Gemfile
87
- - Gemfile.lock
88
87
  - LICENSE.txt
89
88
  - README.md
90
89
  - Rakefile
90
+ - Steepfile
91
91
  - ai_refactor.gemspec
92
- - examples/.gitignore
92
+ - commands/quickdraw/0.1.0/convert_minitest.yml
93
93
  - examples/ex1_convert_a_rspec_test_to_minitest.yml
94
94
  - examples/ex1_input_spec.rb
95
+ - examples/ex2_input.rb
96
+ - examples/ex2_write_rbs.yml
95
97
  - examples/rails_helper.rb
96
98
  - examples/test_helper.rb
97
99
  - exe/ai_refactor
98
100
  - lib/ai_refactor.rb
99
101
  - lib/ai_refactor/cli.rb
100
102
  - lib/ai_refactor/command_file_parser.rb
103
+ - lib/ai_refactor/commands.rb
101
104
  - lib/ai_refactor/context.rb
102
105
  - lib/ai_refactor/file_processor.rb
103
106
  - lib/ai_refactor/logger.rb
@@ -117,12 +120,15 @@ files:
117
120
  - lib/ai_refactor/refactors/rspec/minitest_to_rspec.rb
118
121
  - lib/ai_refactor/refactors/ruby/refactor_ruby.md
119
122
  - lib/ai_refactor/refactors/ruby/refactor_ruby.rb
123
+ - lib/ai_refactor/refactors/ruby/write_rbs.md
124
+ - lib/ai_refactor/refactors/ruby/write_rbs.rb
120
125
  - lib/ai_refactor/refactors/ruby/write_ruby.md
121
126
  - lib/ai_refactor/refactors/ruby/write_ruby.rb
122
127
  - lib/ai_refactor/run_configuration.rb
123
128
  - lib/ai_refactor/templated_path.rb
124
129
  - lib/ai_refactor/test_runners/minitest_runner.rb
125
130
  - lib/ai_refactor/test_runners/rspec_runner.rb
131
+ - lib/ai_refactor/test_runners/steep_runner.rb
126
132
  - lib/ai_refactor/test_runners/test_run_diff_report.rb
127
133
  - lib/ai_refactor/test_runners/test_run_result.rb
128
134
  - lib/ai_refactor/version.rb
@@ -147,7 +153,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
147
153
  - !ruby/object:Gem::Version
148
154
  version: '0'
149
155
  requirements: []
150
- rubygems_version: 3.4.19
156
+ rubygems_version: 3.4.20
151
157
  signing_key:
152
158
  specification_version: 4
153
159
  summary: Use AI to convert a Rails RSpec test suite to minitest.
data/Gemfile.lock DELETED
@@ -1,244 +0,0 @@
1
- PATH
2
- remote: .
3
- specs:
4
- ai_refactor (0.5.2)
5
- colorize (< 2.0)
6
- open3 (< 2.0)
7
- ruby-openai (>= 3.4.0, < 5.0)
8
- zeitwerk (~> 2.6)
9
-
10
- GEM
11
- remote: https://rubygems.org/
12
- specs:
13
- actioncable (7.0.8)
14
- actionpack (= 7.0.8)
15
- activesupport (= 7.0.8)
16
- nio4r (~> 2.0)
17
- websocket-driver (>= 0.6.1)
18
- actionmailbox (7.0.8)
19
- actionpack (= 7.0.8)
20
- activejob (= 7.0.8)
21
- activerecord (= 7.0.8)
22
- activestorage (= 7.0.8)
23
- activesupport (= 7.0.8)
24
- mail (>= 2.7.1)
25
- net-imap
26
- net-pop
27
- net-smtp
28
- actionmailer (7.0.8)
29
- actionpack (= 7.0.8)
30
- actionview (= 7.0.8)
31
- activejob (= 7.0.8)
32
- activesupport (= 7.0.8)
33
- mail (~> 2.5, >= 2.5.4)
34
- net-imap
35
- net-pop
36
- net-smtp
37
- rails-dom-testing (~> 2.0)
38
- actionpack (7.0.8)
39
- actionview (= 7.0.8)
40
- activesupport (= 7.0.8)
41
- rack (~> 2.0, >= 2.2.4)
42
- rack-test (>= 0.6.3)
43
- rails-dom-testing (~> 2.0)
44
- rails-html-sanitizer (~> 1.0, >= 1.2.0)
45
- actiontext (7.0.8)
46
- actionpack (= 7.0.8)
47
- activerecord (= 7.0.8)
48
- activestorage (= 7.0.8)
49
- activesupport (= 7.0.8)
50
- globalid (>= 0.6.0)
51
- nokogiri (>= 1.8.5)
52
- actionview (7.0.8)
53
- activesupport (= 7.0.8)
54
- builder (~> 3.1)
55
- erubi (~> 1.4)
56
- rails-dom-testing (~> 2.0)
57
- rails-html-sanitizer (~> 1.1, >= 1.2.0)
58
- activejob (7.0.8)
59
- activesupport (= 7.0.8)
60
- globalid (>= 0.3.6)
61
- activemodel (7.0.8)
62
- activesupport (= 7.0.8)
63
- activerecord (7.0.8)
64
- activemodel (= 7.0.8)
65
- activesupport (= 7.0.8)
66
- activestorage (7.0.8)
67
- actionpack (= 7.0.8)
68
- activejob (= 7.0.8)
69
- activerecord (= 7.0.8)
70
- activesupport (= 7.0.8)
71
- marcel (~> 1.0)
72
- mini_mime (>= 1.1.0)
73
- activesupport (7.0.8)
74
- concurrent-ruby (~> 1.0, >= 1.0.2)
75
- i18n (>= 1.6, < 2)
76
- minitest (>= 5.1)
77
- tzinfo (~> 2.0)
78
- ast (2.4.2)
79
- builder (3.2.4)
80
- colorize (0.8.1)
81
- concurrent-ruby (1.2.2)
82
- crass (1.0.6)
83
- date (3.3.3)
84
- diff-lcs (1.5.0)
85
- dotenv (2.8.1)
86
- erubi (1.12.0)
87
- faraday (2.7.4)
88
- faraday-net_http (>= 2.0, < 3.1)
89
- ruby2_keywords (>= 0.0.4)
90
- faraday-multipart (1.0.4)
91
- multipart-post (~> 2)
92
- faraday-net_http (3.0.2)
93
- globalid (1.2.1)
94
- activesupport (>= 6.1)
95
- i18n (1.14.1)
96
- concurrent-ruby (~> 1.0)
97
- json (2.6.3)
98
- language_server-protocol (3.17.0.3)
99
- lint_roller (1.0.0)
100
- loofah (2.21.3)
101
- crass (~> 1.0.2)
102
- nokogiri (>= 1.12.0)
103
- mail (2.8.1)
104
- mini_mime (>= 0.1.1)
105
- net-imap
106
- net-pop
107
- net-smtp
108
- marcel (1.0.2)
109
- method_source (1.0.0)
110
- mini_mime (1.1.5)
111
- minitest (5.18.0)
112
- multipart-post (2.3.0)
113
- net-imap (0.3.7)
114
- date
115
- net-protocol
116
- net-pop (0.1.2)
117
- net-protocol
118
- net-protocol (0.2.1)
119
- timeout
120
- net-smtp (0.3.3)
121
- net-protocol
122
- nio4r (2.5.9)
123
- nokogiri (1.15.4-arm64-darwin)
124
- racc (~> 1.4)
125
- open3 (0.1.2)
126
- parallel (1.23.0)
127
- parser (3.2.2.1)
128
- ast (~> 2.4.1)
129
- racc (1.7.1)
130
- rack (2.2.8)
131
- rack-test (2.1.0)
132
- rack (>= 1.3)
133
- rails (7.0.8)
134
- actioncable (= 7.0.8)
135
- actionmailbox (= 7.0.8)
136
- actionmailer (= 7.0.8)
137
- actionpack (= 7.0.8)
138
- actiontext (= 7.0.8)
139
- actionview (= 7.0.8)
140
- activejob (= 7.0.8)
141
- activemodel (= 7.0.8)
142
- activerecord (= 7.0.8)
143
- activestorage (= 7.0.8)
144
- activesupport (= 7.0.8)
145
- bundler (>= 1.15.0)
146
- railties (= 7.0.8)
147
- rails-dom-testing (2.2.0)
148
- activesupport (>= 5.0.0)
149
- minitest
150
- nokogiri (>= 1.6)
151
- rails-html-sanitizer (1.6.0)
152
- loofah (~> 2.21)
153
- nokogiri (~> 1.14)
154
- railties (7.0.8)
155
- actionpack (= 7.0.8)
156
- activesupport (= 7.0.8)
157
- method_source
158
- rake (>= 12.2)
159
- thor (~> 1.0)
160
- zeitwerk (~> 2.5)
161
- rainbow (3.1.1)
162
- rake (13.0.6)
163
- regexp_parser (2.8.0)
164
- rexml (3.2.5)
165
- rspec (3.12.0)
166
- rspec-core (~> 3.12.0)
167
- rspec-expectations (~> 3.12.0)
168
- rspec-mocks (~> 3.12.0)
169
- rspec-core (3.12.2)
170
- rspec-support (~> 3.12.0)
171
- rspec-expectations (3.12.3)
172
- diff-lcs (>= 1.2.0, < 2.0)
173
- rspec-support (~> 3.12.0)
174
- rspec-mocks (3.12.6)
175
- diff-lcs (>= 1.2.0, < 2.0)
176
- rspec-support (~> 3.12.0)
177
- rspec-rails (6.0.3)
178
- actionpack (>= 6.1)
179
- activesupport (>= 6.1)
180
- railties (>= 6.1)
181
- rspec-core (~> 3.12)
182
- rspec-expectations (~> 3.12)
183
- rspec-mocks (~> 3.12)
184
- rspec-support (~> 3.12)
185
- rspec-support (3.12.1)
186
- rubocop (1.50.2)
187
- json (~> 2.3)
188
- parallel (~> 1.10)
189
- parser (>= 3.2.0.0)
190
- rainbow (>= 2.2.2, < 4.0)
191
- regexp_parser (>= 1.8, < 3.0)
192
- rexml (>= 3.2.5, < 4.0)
193
- rubocop-ast (>= 1.28.0, < 2.0)
194
- ruby-progressbar (~> 1.7)
195
- unicode-display_width (>= 2.4.0, < 3.0)
196
- rubocop-ast (1.28.1)
197
- parser (>= 3.2.1.0)
198
- rubocop-performance (1.16.0)
199
- rubocop (>= 1.7.0, < 2.0)
200
- rubocop-ast (>= 0.4.0)
201
- ruby-openai (4.1.0)
202
- faraday (>= 1)
203
- faraday-multipart (>= 1)
204
- ruby-progressbar (1.13.0)
205
- ruby2_keywords (0.0.5)
206
- shoulda-matchers (5.3.0)
207
- activesupport (>= 5.2.0)
208
- standard (1.28.2)
209
- language_server-protocol (~> 3.17.0.2)
210
- lint_roller (~> 1.0)
211
- rubocop (~> 1.50.2)
212
- standard-custom (~> 1.0.0)
213
- standard-performance (~> 1.0.1)
214
- standard-custom (1.0.0)
215
- lint_roller (~> 1.0)
216
- standard-performance (1.0.1)
217
- lint_roller (~> 1.0)
218
- rubocop-performance (~> 1.16.0)
219
- thor (1.2.2)
220
- timeout (0.4.0)
221
- tzinfo (2.0.6)
222
- concurrent-ruby (~> 1.0)
223
- unicode-display_width (2.4.2)
224
- websocket-driver (0.7.6)
225
- websocket-extensions (>= 0.1.0)
226
- websocket-extensions (0.1.5)
227
- zeitwerk (2.6.8)
228
-
229
- PLATFORMS
230
- arm64-darwin-22
231
-
232
- DEPENDENCIES
233
- ai_refactor!
234
- dotenv
235
- minitest (~> 5.0)
236
- rails
237
- rake (~> 13.0)
238
- rspec
239
- rspec-rails
240
- shoulda-matchers
241
- standard (~> 1.3)
242
-
243
- BUNDLED WITH
244
- 2.4.10
data/examples/.gitignore DELETED
@@ -1 +0,0 @@
1
- ex1_input_test.rb