ai_refactor 0.3.1 → 0.5.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 (47) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +65 -2
  3. data/Gemfile +9 -0
  4. data/Gemfile.lock +169 -1
  5. data/README.md +169 -43
  6. data/Rakefile +1 -1
  7. data/ai_refactor.gemspec +1 -0
  8. data/examples/.gitignore +1 -0
  9. data/examples/ex1_convert_a_rspec_test_to_minitest.yml +7 -0
  10. data/examples/ex1_input_spec.rb +32 -0
  11. data/examples/rails_helper.rb +21 -0
  12. data/examples/test_helper.rb +14 -0
  13. data/exe/ai_refactor +139 -52
  14. data/lib/ai_refactor/cli.rb +138 -0
  15. data/lib/ai_refactor/command_file_parser.rb +27 -0
  16. data/lib/ai_refactor/context.rb +33 -0
  17. data/lib/ai_refactor/file_processor.rb +34 -17
  18. data/lib/ai_refactor/prompt.rb +84 -0
  19. data/lib/ai_refactor/prompts/diff.md +17 -0
  20. data/lib/ai_refactor/prompts/input.md +1 -0
  21. data/lib/ai_refactor/refactors/base_refactor.rb +183 -0
  22. data/lib/ai_refactor/refactors/custom.rb +43 -0
  23. data/lib/ai_refactor/refactors/minitest/write_test_for_class.md +15 -0
  24. data/lib/ai_refactor/refactors/minitest/write_test_for_class.rb +51 -0
  25. data/lib/ai_refactor/refactors/project/write_changelog_from_history.md +35 -0
  26. data/lib/ai_refactor/refactors/project/write_changelog_from_history.rb +50 -0
  27. data/lib/ai_refactor/refactors/{prompts/rspec_to_minitest_rails.md → rails/minitest/rspec_to_minitest.md} +40 -1
  28. data/lib/ai_refactor/refactors/rails/minitest/rspec_to_minitest.rb +77 -0
  29. data/lib/ai_refactor/refactors/rspec/minitest_to_rspec.rb +13 -0
  30. data/lib/ai_refactor/refactors/ruby/refactor_ruby.md +10 -0
  31. data/lib/ai_refactor/refactors/ruby/refactor_ruby.rb +29 -0
  32. data/lib/ai_refactor/refactors/ruby/write_ruby.md +7 -0
  33. data/lib/ai_refactor/refactors/ruby/write_ruby.rb +33 -0
  34. data/lib/ai_refactor/refactors.rb +13 -5
  35. data/lib/ai_refactor/run_configuration.rb +115 -0
  36. data/lib/ai_refactor/{refactors/tests → test_runners}/minitest_runner.rb +2 -2
  37. data/lib/ai_refactor/{refactors/tests → test_runners}/rspec_runner.rb +1 -1
  38. data/lib/ai_refactor/{refactors/tests → test_runners}/test_run_diff_report.rb +1 -1
  39. data/lib/ai_refactor/{refactors/tests → test_runners}/test_run_result.rb +1 -1
  40. data/lib/ai_refactor/version.rb +1 -1
  41. data/lib/ai_refactor.rb +13 -8
  42. metadata +47 -13
  43. data/lib/ai_refactor/base_refactor.rb +0 -66
  44. data/lib/ai_refactor/refactors/generic.rb +0 -113
  45. data/lib/ai_refactor/refactors/minitest_to_rspec.rb +0 -11
  46. data/lib/ai_refactor/refactors/rspec_to_minitest_rails.rb +0 -103
  47. /data/lib/ai_refactor/refactors/{prompts → rspec}/minitest_to_rspec.md +0 -0
@@ -0,0 +1,183 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AIRefactor
4
+ module Refactors
5
+ class BaseRefactor
6
+ # All subclasses must register themselves with the Registry
7
+ def self.inherited(subclass)
8
+ super
9
+ Refactors.register(subclass)
10
+ end
11
+
12
+ def self.description
13
+ "(No description provided)"
14
+ end
15
+
16
+ def self.takes_input_files?
17
+ true
18
+ end
19
+
20
+ attr_reader :input_file, :options, :logger
21
+ attr_accessor :input_content
22
+ attr_writer :failed_message
23
+
24
+ def initialize(input_file, options, logger)
25
+ @input_file = input_file
26
+ @options = options
27
+ @logger = logger
28
+ end
29
+
30
+ def run
31
+ raise NotImplementedError
32
+ end
33
+
34
+ def failed_message
35
+ @failed_message || "Reason not specified"
36
+ end
37
+
38
+ private
39
+
40
+ def file_processor
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)
43
+ AIRefactor::FileProcessor.new(prompt: prompt, ai_client: ai_client, output_path: output_file_path, logger: logger, options: options)
44
+ end
45
+
46
+ def process!(strip_ticks: true)
47
+ processor = file_processor
48
+
49
+ if processor.output_exists?
50
+ return false unless overwrite_existing_output?(output_file_path)
51
+ end
52
+
53
+ logger.verbose "Processing #{input_file}..."
54
+
55
+ begin
56
+ output_content, finished_reason, usage = processor.process! do |content|
57
+ if block_given?
58
+ yield content
59
+ elsif strip_ticks
60
+ content.gsub("```ruby", "").gsub("```", "")
61
+ else
62
+ content
63
+ end
64
+ end
65
+
66
+ logger.verbose "AI finished, with reason '#{finished_reason}'..."
67
+ logger.verbose "Used tokens: #{usage["total_tokens"]}".colorize(:light_black) if usage
68
+ if finished_reason == "length"
69
+ logger.warn "Translation may contain an incomplete output as the max token length was reached. You can try using the '--continue' option next time to increase the length of generated output."
70
+ end
71
+
72
+ if !output_content || output_content.length == 0
73
+ logger.warn "Skipping #{input_file}, no translated output..."
74
+ logger.error "Failed to translate #{input_file}, finished reason #{finished_reason}"
75
+ self.failed_message = "AI conversion failed, no output was generated"
76
+ raise NoOutputError, "No output"
77
+ end
78
+
79
+ output_content
80
+ rescue => e
81
+ logger.error "Request to AI failed: #{e.message}"
82
+ puts e.backtrace
83
+ logger.warn "Skipping #{input_file}..."
84
+ self.failed_message = "Request to OpenAI failed"
85
+ raise e
86
+ end
87
+ end
88
+
89
+ def overwrite_existing_output?(output_path)
90
+ overwrite = options && options[:overwrite]&.downcase
91
+ answer = if ["y", "n"].include? overwrite
92
+ overwrite
93
+ else
94
+ logger.info "Do you wish to overwrite #{output_path}? (y/n)"
95
+ $stdin.gets.chomp.downcase
96
+ end
97
+ if answer == "y"
98
+ logger.verbose "Overwriting #{output_path}..."
99
+ return true
100
+ end
101
+ logger.warn "Skipping #{input_file}..."
102
+ self.failed_message = "Skipped as output file already exists"
103
+ false
104
+ end
105
+
106
+ def prompt_input
107
+ if options && options[:prompt]&.length&.positive?
108
+ return options[:prompt]
109
+ end
110
+
111
+ file = if options && options[:prompt_file_path]&.length&.positive?
112
+ options[:prompt_file_path]
113
+ else
114
+ location = Module.const_source_location(::AIRefactor::Refactors::BaseRefactor.name)
115
+ File.join(File.dirname(location.first), "#{refactor_name}.md")
116
+ end
117
+ file.tap do |prompt|
118
+ raise "No prompt file '#{prompt}' found for #{refactor_name}" unless File.exist?(prompt)
119
+ end
120
+
121
+ File.read(file)
122
+ end
123
+
124
+ def output_file_path
125
+ @output_file_path ||= determine_output_file_path
126
+ end
127
+
128
+ def determine_output_file_path
129
+ return output_file_path_from_template if output_template_path
130
+
131
+ path = options[:output_file_path]
132
+ return default_output_path unless path
133
+
134
+ if path == true
135
+ input_file
136
+ else
137
+ path
138
+ end
139
+ end
140
+
141
+ def default_output_path
142
+ nil
143
+ end
144
+
145
+ def output_template_path
146
+ options[:output_template_path]
147
+ end
148
+
149
+ def output_file_path_from_template
150
+ path = output_template_path.gsub("[FILE]", File.basename(input_file))
151
+ .gsub("[NAME]", File.basename(input_file, ".*"))
152
+ .gsub("[DIR]", File.dirname(input_file))
153
+ .gsub("[REFACTOR]", self.class.refactor_name)
154
+ .gsub("[EXT]", File.extname(input_file))
155
+ raise "Output template could not be used" unless path.length.positive?
156
+ path
157
+ end
158
+
159
+ def ai_client
160
+ @ai_client ||= OpenAI::Client.new
161
+ end
162
+
163
+ def refactor_name
164
+ self.class.refactor_name
165
+ end
166
+
167
+ class << self
168
+ def command_line_options
169
+ []
170
+ end
171
+
172
+ def refactor_name
173
+ name.gsub("AIRefactor::Refactors::", "")
174
+ .gsub(/::/, "/")
175
+ .gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
176
+ .gsub(/([a-z\d])([A-Z])/, '\1_\2')
177
+ .tr("-", "_")
178
+ .downcase
179
+ end
180
+ end
181
+ end
182
+ end
183
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AIRefactor
4
+ module Refactors
5
+ class Custom < BaseRefactor
6
+ def run
7
+ logger.verbose "Custom refactor to #{input_file}... (using user supplied prompt #{prompt_file_path})"
8
+ logger.verbose "Write output to #{output_file_path}..." if output_file_path
9
+
10
+ begin
11
+ output_content = process!(strip_ticks: false)
12
+ rescue => e
13
+ logger.error "Failed to process #{input_file}: #{e.message}"
14
+ return false
15
+ end
16
+
17
+ return false unless output_content
18
+
19
+ output_file_path ? true : output_content
20
+ end
21
+
22
+ def self.description
23
+ "Generic refactor using user supplied prompt"
24
+ end
25
+
26
+ private
27
+
28
+ def prompt_file_path
29
+ specified_prompt_path = options[:prompt_file_path]
30
+ if specified_prompt_path&.length&.positive?
31
+ if File.exist?(specified_prompt_path)
32
+ return specified_prompt_path
33
+ else
34
+ logger.error "No prompt file '#{specified_prompt_path}' found"
35
+ end
36
+ else
37
+ logger.error "No prompt file was specified!"
38
+ end
39
+ exit 1
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,15 @@
1
+ You are an expert Ruby senior software developer. Write a unit test for the class show below, using minitest and minitest/mock if necessary.
2
+ Test 100% of the code.
3
+
4
+ The path to the file to test is: __{{input_file_path}}__
5
+ The output file path is: __{{output_file_path}}__
6
+
7
+ __{{prompt_header}}__
8
+
9
+ Only show me the test file code. Do NOT provide any other description of your work. Always enclose the output code in triple backticks (```).
10
+
11
+ __{{context}}__
12
+
13
+ __{{prompt_footer}}__
14
+
15
+ The class to test is:
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AIRefactor
4
+ module Refactors
5
+ module Minitest
6
+ class WriteTestForClass < BaseRefactor
7
+ def run
8
+ logger.verbose "'Write minitest test' refactor for #{input_file}..."
9
+ logger.verbose "Write output to #{output_file_path}..." if output_file_path
10
+
11
+ begin
12
+ output_content = process!
13
+ rescue => e
14
+ logger.error "Failed to process #{input_file}: #{e.message}"
15
+ return false
16
+ end
17
+
18
+ return false unless output_file_path
19
+
20
+ logger.verbose "Generated #{output_file_path} from #{input_file} ..." if output_content
21
+
22
+ minitest_runner = AIRefactor::TestRunners::MinitestRunner.new(output_file_path, command_template: "bundle exec ruby __FILE__")
23
+
24
+ logger.verbose "Run generated test file #{output_file_path} (#{minitest_runner.command})..."
25
+ test_run = minitest_runner.run
26
+
27
+ if test_run.failed?
28
+ logger.warn "#{input_file} was translated to #{output_file_path} but the resulting test is failing..."
29
+ logger.error "Failed to run test, exited with status #{test_run.exitstatus}. Stdout: #{test_run.stdout}\n\nStderr: #{test_run.stderr}\n\n"
30
+ logger.error "New test failed!", bold: true
31
+ self.failed_message = "Generated test file failed to run correctly"
32
+ return false
33
+ end
34
+
35
+ logger.verbose "\nNew test file ran and returned the following results:"
36
+ logger.verbose ">> Runs: #{test_run.example_count}, Failures: #{test_run.failure_count}, Skips: #{test_run.pending_count}\n"
37
+
38
+ output_file_path ? true : output_content
39
+ end
40
+
41
+ def self.description
42
+ "Write a minitest test for a class"
43
+ end
44
+
45
+ def default_output_path
46
+ File.join("test", input_file.gsub(/\.rb$/, "_test.rb"))
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,35 @@
1
+ You are a senior Ruby software developer. You are diligent about writing clear changelogs for your users.
2
+
3
+ You have a git history of changes to your project. You want to write a changelog from this history.
4
+
5
+ The format of the changelog you will create entries for is from keepachangelog.com version 1.0.0.
6
+
7
+ Here is an example of a set of changelog entries:
8
+
9
+ ```
10
+ ## [0.2.0] - 2015-10-06
11
+
12
+ ### Changed
13
+
14
+ - Remove exclusionary mentions of "open source" since this project can
15
+ benefit both "open" and "closed" source projects equally.
16
+
17
+ ## [0.1.0] - 2015-10-06
18
+
19
+ ### Added
20
+
21
+ - Answer "Should you ever rewrite a change log?".
22
+
23
+ ### Changed
24
+
25
+ - Improve argument against commit logs.
26
+ - Start following [SemVer](https://semver.org) properly.
27
+
28
+ ### Fixed
29
+
30
+ - Fix typos in recent CHANGELOG entries.
31
+ ```
32
+
33
+ If the history contains any mention of changes of version (such as "Bump version to 1.0.0"), then you should use that version number in a new changelog entry.
34
+
35
+ Now write changelog entries from the following git history.
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AIRefactor
4
+ module Refactors
5
+ module Project
6
+ class WriteChangelogFromHistory < BaseRefactor
7
+ def self.description
8
+ "Write changelog entries from the git history"
9
+ end
10
+
11
+ def self.takes_input_files?
12
+ false
13
+ end
14
+
15
+ def run
16
+ logger.verbose "Creating changelog entries for project from #{options[:git_commit_count] || 3} commits..."
17
+ logger.verbose "Write output to #{output_file_path}..." if output_file_path
18
+
19
+ self.input_content = `git log -#{options[:git_commit_count] || 3} --pretty=format:"%ci %d %s"`.split("\n").map { |line| "- #{line}" }.join("\n")
20
+ logger.debug "\nInput messages: \n#{input_content}\n\n"
21
+ begin
22
+ output_content = process!(strip_ticks: false)
23
+ rescue => e
24
+ logger.error "Failed to process: #{e.message}"
25
+ return false
26
+ end
27
+
28
+ return false unless output_content
29
+
30
+ output_file_path ? true : output_content
31
+ end
32
+
33
+ def default_output_path
34
+ nil
35
+ end
36
+
37
+ def self.command_line_options
38
+ [
39
+ {
40
+ key: :git_commit_count,
41
+ long: "--commits N",
42
+ type: Integer,
43
+ help: "The number of commits to analyse when creating the changelog entries (defaults to 3)"
44
+ }
45
+ ]
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
@@ -1,8 +1,8 @@
1
+ You are an expert software developer.
1
2
  You convert RSpec tests to ActiveSupport::TestCase tests for Ruby on Rails.
2
3
  ActiveSupport::TestCase uses MiniTest under the hood.
3
4
  Remember that MiniTest does not support `context` blocks, instead these should be removed and the context
4
5
  specified in them should be moved directly into the relevant tests.
5
- Always enclose the output code in triple backticks (```).
6
6
 
7
7
  Here are some examples to use as a guide:
8
8
 
@@ -42,6 +42,10 @@ subject(:model) { create(:order_state) }
42
42
  context "when rejected" do
43
43
  before { model.rejected_at = 1.day.ago }
44
44
 
45
+ it "should be not valid" do
46
+ expect(model).not_to be_valid
47
+ end
48
+
45
49
  context "with reason and message" do
46
50
  before do
47
51
  model.rejected_message = reason
@@ -68,6 +72,11 @@ setup do
68
72
  @reason = "my reason"
69
73
  end
70
74
 
75
+ test "when rejected, model should be not valid" do
76
+ @model.rejected_at = 1.day.ago
77
+ refute @model.valid?
78
+ end
79
+
71
80
  test "when rejected, with reason and message, model should be valid" do
72
81
  @model.rejected_at = 1.day.ago
73
82
  @model.rejected_message = @reason
@@ -231,3 +240,33 @@ test "stubs any instance" do
231
240
  end
232
241
  end
233
242
  ```
243
+
244
+ Example 9) RSpec:
245
+ ```
246
+ assert_association @model, :message_thread, :belongs_to
247
+ ```
248
+
249
+ Result 9) minitest:
250
+
251
+ ```
252
+ assert_instance_of MessageThread, @model.message_thread
253
+ ```
254
+
255
+ Example 10) RSpec:
256
+ ```
257
+ assert_association @model, :message_thread, :belongs_to, optional: true
258
+ ```
259
+
260
+ Result 10) minitest:
261
+ ```
262
+ assoc = @model.reflect_on_association(:message_thread)
263
+ refute assoc.nil?, "no association :message_thread"
264
+ assert_equal :belongs_to, assoc.macro
265
+ assert assoc.options[:optional]
266
+ ```
267
+
268
+ __{{context}}__
269
+
270
+ Only output the refactored class. Do NOT provide any other description of your work. Always enclose the output code in triple backticks (```).
271
+
272
+ Convert this Rspec file to minitest:
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AIRefactor
4
+ module Refactors
5
+ module Rails
6
+ module Minitest
7
+ class RspecToMinitest < BaseRefactor
8
+ def run
9
+ spec_runner = AIRefactor::TestRunners::RSpecRunner.new(input_file, command_template: options.rspec_run_command)
10
+ logger.verbose "Run spec #{input_file}... (#{spec_runner.command})"
11
+
12
+ spec_run = spec_runner.run
13
+
14
+ if spec_run.failed?
15
+ logger.warn "Skipping #{input_file}..."
16
+ logger.error "Failed to run #{input_file}, exited with status #{spec_run.exitstatus}. Stdout: #{spec_run.stdout}\n\nStderr: #{spec_run.stderr}\n\n"
17
+ self.failed_message = "Failed to run RSpec file, has errors"
18
+ return false
19
+ end
20
+
21
+ logger.debug "\nOriginal test run results:"
22
+ logger.debug ">> Examples: #{spec_run.example_count}, Failures: #{spec_run.failure_count}, Pendings: #{spec_run.pending_count}\n"
23
+
24
+ begin
25
+ result = process!
26
+ rescue AIRefactor::NoOutputError => _e
27
+ return false
28
+ rescue => e
29
+ logger.error "Failed to convert #{input_file} to Minitest, error: #{e.message}"
30
+ return false
31
+ end
32
+
33
+ logger.verbose "Converted #{input_file} to #{output_file_path}..." if result
34
+
35
+ minitest_runner = AIRefactor::TestRunners::MinitestRunner.new(output_file_path, command_template: options.minitest_run_command)
36
+
37
+ logger.verbose "Run generated test file #{output_file_path} (#{minitest_runner.command})..."
38
+ test_run = minitest_runner.run
39
+
40
+ if test_run.failed?
41
+ logger.warn "Skipping #{input_file}..."
42
+ logger.error "Failed to run translated #{output_file_path}, exited with status #{test_run.exitstatus}. Stdout: #{test_run.stdout}\n\nStderr: #{test_run.stderr}\n\n"
43
+ logger.error "Conversion failed!", bold: true
44
+ self.failed_message = "Generated test file failed to run correctly"
45
+ return false
46
+ end
47
+
48
+ logger.debug "\nTranslated test file results:"
49
+ logger.debug ">> Runs: #{test_run.example_count}, Failures: #{test_run.failure_count}, Skips: #{test_run.pending_count}\n"
50
+
51
+ report = AIRefactor::TestRunners::TestRunDiffReport.new(spec_run, test_run)
52
+
53
+ if report.no_differences?
54
+ logger.verbose "Done converting #{input_file} to #{output_file_path}..."
55
+ logger.success "\nNo differences found! Conversion worked!"
56
+ true
57
+ else
58
+ logger.warn report.diff.colorize(:yellow)
59
+ logger.verbose "Done converting #{input_file} to #{output_file_path}..."
60
+ logger.error "\nDifferences found! Conversion failed!", bold: true
61
+ self.failed_message = "Generated test file run output did not match original RSpec spec run output"
62
+ false
63
+ end
64
+ end
65
+
66
+ def self.description
67
+ "Convert RSpec file to Minitest (for Rails apps)"
68
+ end
69
+
70
+ def default_output_path
71
+ input_file.gsub("_spec.rb", "_test.rb").gsub("spec/", "test/")
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AIRefactor
4
+ module Refactors
5
+ module Rspec
6
+ class MinitestToRspec < BaseRefactor
7
+ def run
8
+ raise "Not implemented"
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,10 @@
1
+ __{{context}}__
2
+
3
+ __{{prompt_header}}__
4
+
5
+ The input file is: __{{input_file_path}}__
6
+ The output file path is: __{{output_file_path}}__
7
+
8
+ __{{content}}__
9
+
10
+ __{{prompt_footer}}__
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AIRefactor
4
+ module Refactors
5
+ module Ruby
6
+ class RefactorRuby < Custom
7
+ def run
8
+ logger.verbose "Custom refactor to #{input_file}... (using user supplied prompt #{prompt_file_path})"
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
+ output_file_path ? true : output_content
21
+ end
22
+
23
+ def self.description
24
+ "Generic refactor using user supplied prompt (assumes Ruby code generation)"
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,7 @@
1
+ __{{context}}__
2
+
3
+ __{{prompt_header}}__
4
+
5
+ __{{content}}__
6
+
7
+ __{{prompt_footer}}__
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AIRefactor
4
+ module Refactors
5
+ module Ruby
6
+ class WriteRuby < Custom
7
+ def run
8
+ logger.verbose "Write some ruby code... (using user supplied prompt #{prompt_file_path})"
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
+ output_file_path ? true : output_content
21
+ end
22
+
23
+ def self.takes_input_files?
24
+ false
25
+ end
26
+
27
+ def self.description
28
+ "User supplied prompt to write Ruby code"
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -2,6 +2,11 @@
2
2
 
3
3
  module AIRefactor
4
4
  module Refactors
5
+ def register(klass)
6
+ all[klass.refactor_name] = klass
7
+ end
8
+ module_function :register
9
+
5
10
  def get(name)
6
11
  all[name]
7
12
  end
@@ -12,16 +17,19 @@ module AIRefactor
12
17
  end
13
18
  module_function :names
14
19
 
15
- def all
16
- @all ||= constants.map { |n| const_get(n) }.select { |c| c.is_a? Class }.each_with_object({}) do |klass, hash|
17
- hash[klass.refactor_name] = klass
18
- end
20
+ def descriptions
21
+ names.map { |n| "\"#{n}\"" }.zip(all.values.map(&:description)).to_h
19
22
  end
20
- module_function :all
23
+ module_function :descriptions
21
24
 
22
25
  def supported?(name)
23
26
  names.include?(name)
24
27
  end
25
28
  module_function :supported?
29
+
30
+ def all
31
+ @all ||= {}
32
+ end
33
+ module_function :all
26
34
  end
27
35
  end