ai_refactor 0.5.3 → 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: 5f916993ff53967b44cbbc81138c87d518518618d3edc932cefa6aea9f0a4c4c
4
- data.tar.gz: 5d3bd3b9cea6d7bd39e0028caa5ccf24527dd03fcd98d643498a65743a2cac5e
3
+ metadata.gz: ca299458e2af2bc7ba495ce6473e1e6b1824e679893815616a209697178aefbd
4
+ data.tar.gz: b13da64462ef3f5572a9afebb0020ecdb5adc538b92e7c80b3b6bbd756473f9d
5
5
  SHA512:
6
- metadata.gz: 41cb1f31ceb6e503cdbeeb972f17a91e1826eccb4fb66e8a73b3b54bb3899ff9572d9cb4783c67fe7e7fee8999f7f25f2b19ae7ab9fde6c766e895e4fb4a1b32
7
- data.tar.gz: 57626f38b266521d820bb411dbf517dd3273ed6e4bd44412d4c4e4b0306a3347252a1b87d0214c148f5f5e35c20cd81c6325648f878dd34d67fe58226156118d
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/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)
@@ -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:
data/exe/ai_refactor CHANGED
@@ -6,8 +6,9 @@ require "openai"
6
6
  require "shellwords"
7
7
  require_relative "../lib/ai_refactor"
8
8
 
9
- supported_refactors = AIRefactor::Refactors.all
10
- 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)
11
12
 
12
13
  arguments = ARGV.dup
13
14
 
@@ -52,7 +53,7 @@ option_parser = OptionParser.new do |parser|
52
53
  run_config.ai_max_attempts = c
53
54
  end
54
55
 
55
- 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|
56
57
  run_config.ai_model = m
57
58
  end
58
59
 
@@ -137,10 +138,9 @@ if arguments.empty? || arguments.all? { |arg| arg.start_with?("-") && !(arg == "
137
138
  # For each option that is required but not provided, prompt for it
138
139
  # Put the option in arguments to parse with option_parser
139
140
  interactive_log.info "Interactive mode started. You can use tab to autocomplete:"
140
- predefined_commands = AIRefactor::Refactors.names
141
141
 
142
- interactive_log.info "Available refactors: #{predefined_commands.join(", ")}\n"
143
- 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)
144
144
  exit_with_option_error("No refactor name provided.", option_parser) if command.nil? || command.empty?
145
145
  initial = [command]
146
146
 
@@ -180,10 +180,12 @@ logger = AIRefactor::Logger.new(verbose: run_config.verbose, debug: run_config.d
180
180
  logger.info "Also loaded options from '.ai_refactor' file..." if options_from_config_file&.size&.positive?
181
181
 
182
182
  command_or_file = arguments.shift
183
- if AIRefactor::CommandFileParser.command_file?(command_or_file)
184
- 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}'..."
185
186
  begin
186
- 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)
187
189
  rescue => e
188
190
  exit_with_option_error(e.message, option_parser, logger)
189
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
@@ -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)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module AIRefactor
4
- VERSION = "0.5.3"
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.3
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: 2024-02-06 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
@@ -89,6 +89,7 @@ files:
89
89
  - Rakefile
90
90
  - Steepfile
91
91
  - ai_refactor.gemspec
92
+ - commands/quickdraw/0.1.0/convert_minitest.yml
92
93
  - examples/ex1_convert_a_rspec_test_to_minitest.yml
93
94
  - examples/ex1_input_spec.rb
94
95
  - examples/ex2_input.rb
@@ -99,6 +100,7 @@ files:
99
100
  - lib/ai_refactor.rb
100
101
  - lib/ai_refactor/cli.rb
101
102
  - lib/ai_refactor/command_file_parser.rb
103
+ - lib/ai_refactor/commands.rb
102
104
  - lib/ai_refactor/context.rb
103
105
  - lib/ai_refactor/file_processor.rb
104
106
  - lib/ai_refactor/logger.rb