henitai 0.1.7 → 0.1.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e0cdead9488bf4134ad58d6d920152df41ea0bd7de9a5073ed71975bba2da8d6
4
- data.tar.gz: c57e3419807bdc599ddb741803895da2228a59df82bab8a03b15698de246d98f
3
+ metadata.gz: 93624e520ff6014d8b5e69ee8b3ac0939d490697130e4acb996cce369c331b78
4
+ data.tar.gz: 02f3116c9e5031df714e08b8d394cd0b90c914c57f1ffe9639540ca4251b4013
5
5
  SHA512:
6
- metadata.gz: b8dac0f228eb54df57b00ff8a51e8147548fbd41bc0c2d09972868efef45ea30be5269f16fcc83f9616e71b8e011b676cc5914eb407f5ac3651455e481162c8e
7
- data.tar.gz: 3bb1af3759153a8c9d806a3799180bfc69fffc80f555376f205d87a8c5073c0fd94dc12b40ceba0283f0ae227251b6ef11836e51259a372be8475bfcb95ba522
6
+ metadata.gz: 8823368e6bdb4fd73ce08253983a63088037a488f8e8a423d36ba495be44f157bb5e1d0a77d3ee1105986eb7c965c670552a489d9b50c3657a8e6935d1774003
7
+ data.tar.gz: b49c42e37777ca6519436ab327a58fcc0a9ab012ad6ad9ffee226c9cacfe156612653bb6a3e794cdf9255d1cf84fa36c6b221ef12dfafc0523ddd884a7ff6e50
data/CHANGELOG.md CHANGED
@@ -7,6 +7,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.1.8] - 2026-04-16
11
+
12
+ ### Added
13
+ - Minitest integration now supports per-test coverage: a `MinitestCoverageReporter`
14
+ snapshots `Coverage.peek_result` after each test and writes `henitai_per_test.json`,
15
+ enabling targeted mutation runs that only execute the tests covering the mutated lines —
16
+ the same efficiency that was previously available only to RSpec projects
17
+
18
+ ### Fixed
19
+ - RSpec integration now respects `--exclude-pattern` entries in `.rspec` so excluded
20
+ spec files are not passed to the runner during mutation runs
21
+ - Per-test source file filter corrected to check only the project-relative path prefix
22
+ rather than scanning the full absolute path, preventing false exclusions when the
23
+ project lives under a directory whose path contains `/spec/` or `/test/`
24
+
10
25
  ## [0.1.7] - 2026-04-14
11
26
 
12
27
  ### Added
@@ -181,7 +196,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
181
196
  - CLI critical path: `henitai run` now executes the full pipeline, supports `--since`, returns CI-friendly exit codes, and `henitai version` prints `Henitai::VERSION`
182
197
  - RSpec per-test coverage output: `henitai/coverage_formatter` now writes `coverage/henitai_per_test.json`
183
198
 
184
- [Unreleased]: https://github.com/martinotten/henitai/compare/v0.1.7...HEAD
199
+ [Unreleased]: https://github.com/martinotten/henitai/compare/v0.1.8...HEAD
200
+ [0.1.8]: https://github.com/martinotten/henitai/compare/v0.1.7...v0.1.8
185
201
  [0.1.7]: https://github.com/martinotten/henitai/compare/v0.1.6...v0.1.7
186
202
  [0.1.6]: https://github.com/martinotten/henitai/compare/v0.1.5...v0.1.6
187
203
  [0.1.5]: https://github.com/martinotten/henitai/compare/v0.1.4...v0.1.5
@@ -1,112 +1,23 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "coverage"
4
- require "fileutils"
5
- require "json"
3
+ require "henitai/per_test_coverage_collector"
6
4
 
7
5
  module Henitai
8
6
  # Collects per-test coverage data for static filtering heuristics.
9
7
  class CoverageFormatter
10
- REPORT_DIR_ENV = "HENITAI_REPORTS_DIR"
11
- REPORT_FILE_NAME = "henitai_per_test.json"
8
+ REPORT_DIR_ENV = PerTestCoverageCollector::REPORT_DIR_ENV
9
+ REPORT_FILE_NAME = PerTestCoverageCollector::REPORT_FILE_NAME
12
10
 
13
11
  def initialize(_output)
14
- @coverage_by_test = Hash.new do |hash, test_file|
15
- hash[test_file] = Hash.new { |nested, source_file| nested[source_file] = [] }
16
- end
17
- @previous_snapshot = {}
18
- @warned_missing_coverage = false
12
+ @collector = PerTestCoverageCollector.new
19
13
  end
20
14
 
21
15
  def example_finished(notification)
22
- snapshot = current_snapshot
23
- return warn_missing_coverage unless snapshot
24
-
25
- test_file = notification.example.metadata[:file_path]
26
- new_lines(snapshot).each do |source_file, lines|
27
- @coverage_by_test[test_file][source_file].concat(lines)
28
- @coverage_by_test[test_file][source_file].uniq!
29
- @coverage_by_test[test_file][source_file].sort!
30
- end
31
- @previous_snapshot = snapshot
16
+ @collector.record_test(notification.example.metadata[:file_path])
32
17
  end
33
18
 
34
19
  def dump_summary(_summary)
35
- return if @coverage_by_test.empty?
36
-
37
- FileUtils.mkdir_p(File.dirname(report_path))
38
- File.write(report_path, JSON.pretty_generate(serializable_report))
39
- end
40
-
41
- private
42
-
43
- def report_path
44
- File.join(reports_dir, REPORT_FILE_NAME)
45
- end
46
-
47
- def reports_dir
48
- ENV.fetch(REPORT_DIR_ENV, "coverage")
49
- end
50
-
51
- def current_snapshot
52
- Coverage.peek_result
53
- rescue StandardError
54
- nil
55
- end
56
-
57
- def warn_missing_coverage
58
- return if @warned_missing_coverage
59
-
60
- warn "Per-test coverage unavailable; skipping coverage formatter output"
61
- @warned_missing_coverage = true
62
- end
63
-
64
- def new_lines(snapshot)
65
- snapshot.each_with_object({}) do |(source_file, file_coverage), result|
66
- next unless source_file?(source_file)
67
-
68
- lines = new_line_numbers(
69
- file_coverage,
70
- previous_line_counts(source_file)
71
- )
72
- result[source_file] = lines unless lines.empty?
73
- end
74
- end
75
-
76
- def new_line_numbers(file_coverage, previous_counts)
77
- line_counts_for(file_coverage).each_with_index.filter_map do |count, index|
78
- next unless count.to_i.positive?
79
- next if previous_counts.fetch(index, 0).to_i.positive?
80
-
81
- index + 1
82
- end
83
- end
84
-
85
- def previous_line_counts(source_file)
86
- line_counts_for(@previous_snapshot.fetch(source_file, []))
87
- end
88
-
89
- def line_counts_for(file_coverage)
90
- case file_coverage
91
- when Hash
92
- Array(file_coverage[:lines] || file_coverage["lines"])
93
- else
94
- Array(file_coverage)
95
- end
96
- end
97
-
98
- def source_file?(path)
99
- expanded = File.expand_path(path)
100
- expanded.start_with?(Dir.pwd) &&
101
- !expanded.include?("#{File::SEPARATOR}spec#{File::SEPARATOR}")
102
- end
103
-
104
- def serializable_report
105
- @coverage_by_test.transform_values do |source_map|
106
- source_map.to_h do |source_file, lines|
107
- [File.expand_path(source_file), lines.uniq.sort]
108
- end
109
- end
20
+ @collector.write_report
110
21
  end
111
22
  end
112
23
  end
@@ -4,7 +4,7 @@ require "henitai"
4
4
 
5
5
  # Force all autoloaded constants to load so mutation testing tools
6
6
  # (e.g. mutant) can discover subjects via ObjectSpace.
7
- SIDE_EFFECT_FILES = %w[minitest_simplecov.rb rspec_coverage_formatter.rb].freeze
7
+ SIDE_EFFECT_FILES = %w[minitest_simplecov.rb minitest_coverage_hook.rb rspec_coverage_formatter.rb].freeze
8
8
 
9
9
  Dir[File.join(__dir__, "**/*.rb")].each do |f|
10
10
  require f unless SIDE_EFFECT_FILES.any? { |name| f.end_with?(name) }
@@ -21,7 +21,8 @@ module Henitai
21
21
  def equivalent_mutation?(mutant)
22
22
  equivalent_arithmetic_mutation?(mutant) ||
23
23
  equivalent_logical_mutation?(mutant) ||
24
- equivalent_singleton_equality_mutation?(mutant)
24
+ equivalent_singleton_equality_mutation?(mutant) ||
25
+ equivalent_string_eql_mutation?(mutant)
25
26
  end
26
27
 
27
28
  def equivalent_arithmetic_mutation?(mutant)
@@ -162,6 +163,59 @@ module Henitai
162
163
  equality_operators?(original.children[1], mutated.children[1])
163
164
  end
164
165
 
166
+ # Detects `lhs == rhs` mutated to `lhs.eql?(rhs)` (or the reverse) when at
167
+ # least one operand is a string literal.
168
+ #
169
+ # String#eql? is documented to compare both type and value. Since String#==
170
+ # also compares type and value (it returns false for any non-String argument
171
+ # without invoking the other object's #==), the two methods are equivalent
172
+ # for all possible inputs whenever at least one operand is statically known
173
+ # to be a String — proven here by the presence of a :str literal on the
174
+ # receiver or the argument side.
175
+ #
176
+ # When no operand is a string literal we conservatively leave the mutant
177
+ # pending: the receiver could be any object whose custom #== diverges from
178
+ # its #eql?.
179
+ def equivalent_string_eql_mutation?(mutant)
180
+ original = mutant.original_node
181
+ mutated = mutant.mutated_node
182
+
183
+ string_eql_send?(original) && string_eql_send?(mutated) &&
184
+ same_receiver?(original, mutated) &&
185
+ string_eql_operators?(original.children[1], mutated.children[1]) &&
186
+ same_rhs?(original, mutated) &&
187
+ string_operand?(original)
188
+ end
189
+
190
+ def string_eql_send?(node)
191
+ node.is_a?(Parser::AST::Node) &&
192
+ node.type == :send &&
193
+ node.children.size == 3 &&
194
+ string_eql_operator?(node.children[1])
195
+ end
196
+
197
+ def string_eql_operator?(operator)
198
+ %i[== eql?].include?(operator)
199
+ end
200
+
201
+ def string_eql_operators?(op_a, op_b)
202
+ string_eql_operator?(op_a) && string_eql_operator?(op_b) && op_a != op_b
203
+ end
204
+
205
+ def same_rhs?(original, mutated)
206
+ same_node?(original.children[2], mutated.children[2])
207
+ end
208
+
209
+ # Returns true when at least one operand is a string literal, giving static
210
+ # proof that the comparison is string-typed on at least one side.
211
+ def string_operand?(node)
212
+ string_literal?(node.children[0]) || string_literal?(node.children[2])
213
+ end
214
+
215
+ def string_literal?(node)
216
+ node.is_a?(Parser::AST::Node) && node.type == :str
217
+ end
218
+
165
219
  def singleton_rhs_match?(original, mutated)
166
220
  rhs = original.children[2]
167
221
  singleton_literal?(rhs) && same_node?(rhs, mutated.children[2])
@@ -326,7 +326,8 @@ module Henitai
326
326
  end
327
327
 
328
328
  def spec_files
329
- Dir.glob("spec/**/*_spec.rb")
329
+ paths = Dir.glob("spec/**/*_spec.rb")
330
+ paths - excluded_spec_files
330
331
  end
331
332
 
332
333
  def fallback_spec_files(subject)
@@ -341,6 +342,26 @@ module Henitai
341
342
  matches.empty? ? spec_files : matches
342
343
  end
343
344
 
345
+ def excluded_spec_files
346
+ rspec_exclude_patterns.flat_map { |pattern| Dir.glob(pattern) }.uniq
347
+ end
348
+
349
+ def rspec_exclude_patterns
350
+ rspec_config_lines.filter_map do |line|
351
+ line[/\A--exclude-pattern\s+(.+)\z/, 1]
352
+ end
353
+ end
354
+
355
+ def rspec_config_lines
356
+ return [] unless File.exist?(rspec_config_path)
357
+
358
+ File.readlines(rspec_config_path, chomp: true).map(&:strip)
359
+ end
360
+
361
+ def rspec_config_path
362
+ ".rspec"
363
+ end
364
+
344
365
  def selection_patterns(subject)
345
366
  [
346
367
  subject.expression,
@@ -477,7 +498,7 @@ module Henitai
477
498
  # coverage collection is not yet wired into this path.
478
499
  class Minitest < Rspec
479
500
  def per_test_coverage_supported?
480
- false
501
+ true
481
502
  end
482
503
 
483
504
  def run_mutant(mutant:, test_files:, timeout:)
@@ -505,6 +526,7 @@ module Henitai
505
526
  def suite_command(test_files)
506
527
  ["bundle", "exec", "ruby", "-I", "test",
507
528
  "-r", "henitai/minitest_simplecov",
529
+ "-r", "henitai/minitest_coverage_hook",
508
530
  "-e", "ARGV.each { |f| require File.expand_path(f) }",
509
531
  *test_files]
510
532
  end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Injected by henitai into the Minitest baseline subprocess to collect
4
+ # per-test line coverage. Must be required after henitai/minitest_simplecov
5
+ # so that Coverage is already running when the reporter takes snapshots.
6
+
7
+ require "minitest"
8
+ require "henitai/minitest_coverage_reporter"
9
+
10
+ Minitest.extensions << "henitai_coverage"
11
+
12
+ # Henitai per-test coverage plugin for Minitest.
13
+ module Minitest
14
+ def self.plugin_henitai_coverage_init(_options)
15
+ reporter.reporters << Henitai::MinitestCoverageReporter.new
16
+ end
17
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "minitest"
4
+ require "henitai/per_test_coverage_collector"
5
+
6
+ module Henitai
7
+ # Minitest reporter that collects per-test line coverage deltas.
8
+ #
9
+ # Added to Minitest's reporter chain by the henitai_coverage plugin
10
+ # (see minitest_coverage_hook.rb). Delegates accumulation and serialisation
11
+ # to PerTestCoverageCollector so the JSON output format is identical to the
12
+ # RSpec integration.
13
+ class MinitestCoverageReporter < Minitest::Reporter
14
+ def initialize(io = $stdout, options = {})
15
+ super
16
+ @collector = PerTestCoverageCollector.new
17
+ end
18
+
19
+ def record(result)
20
+ super
21
+ @collector.record_test(relative_to_pwd(result.source_location.first))
22
+ end
23
+
24
+ def report
25
+ super
26
+ @collector.write_report
27
+ end
28
+
29
+ private
30
+
31
+ def relative_to_pwd(path)
32
+ prefix = "#{Dir.pwd}#{File::SEPARATOR}"
33
+ path.start_with?(prefix) ? path.sub(prefix, "") : path
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,119 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "coverage"
4
+ require "fileutils"
5
+ require "json"
6
+
7
+ module Henitai
8
+ # Accumulates per-test line coverage deltas across test examples.
9
+ #
10
+ # Framework-agnostic core used by both the RSpec and Minitest adapters.
11
+ # Callers invoke record_test after each test completes and write_report
12
+ # once the full suite has finished.
13
+ class PerTestCoverageCollector
14
+ REPORT_DIR_ENV = "HENITAI_REPORTS_DIR"
15
+ REPORT_FILE_NAME = "henitai_per_test.json"
16
+
17
+ def initialize
18
+ @coverage_by_test = Hash.new do |hash, test_file|
19
+ hash[test_file] = Hash.new { |nested, source_file| nested[source_file] = [] }
20
+ end
21
+ @previous_snapshot = {}
22
+ @warned_missing_coverage = false
23
+ end
24
+
25
+ def record_test(test_file)
26
+ snapshot = current_snapshot
27
+ return warn_missing_coverage unless snapshot
28
+
29
+ new_lines(snapshot).each do |source_file, lines|
30
+ @coverage_by_test[test_file][source_file].concat(lines)
31
+ @coverage_by_test[test_file][source_file].uniq!
32
+ @coverage_by_test[test_file][source_file].sort!
33
+ end
34
+ @previous_snapshot = snapshot
35
+ end
36
+
37
+ def write_report
38
+ return if @coverage_by_test.empty?
39
+
40
+ FileUtils.mkdir_p(File.dirname(report_path))
41
+ File.write(report_path, JSON.pretty_generate(serializable_report))
42
+ end
43
+
44
+ private
45
+
46
+ def report_path
47
+ File.join(reports_dir, REPORT_FILE_NAME)
48
+ end
49
+
50
+ def reports_dir
51
+ ENV.fetch(REPORT_DIR_ENV, "coverage")
52
+ end
53
+
54
+ def current_snapshot
55
+ Coverage.peek_result
56
+ rescue StandardError
57
+ nil
58
+ end
59
+
60
+ def warn_missing_coverage
61
+ return if @warned_missing_coverage
62
+
63
+ warn "Per-test coverage unavailable; skipping coverage formatter output"
64
+ @warned_missing_coverage = true
65
+ end
66
+
67
+ def new_lines(snapshot)
68
+ snapshot.each_with_object({}) do |(source_file, file_coverage), result|
69
+ next unless source_file?(source_file)
70
+
71
+ lines = new_line_numbers(
72
+ file_coverage,
73
+ previous_line_counts(source_file)
74
+ )
75
+ result[source_file] = lines unless lines.empty?
76
+ end
77
+ end
78
+
79
+ def new_line_numbers(file_coverage, previous_counts)
80
+ line_counts_for(file_coverage).each_with_index.filter_map do |count, index|
81
+ next unless count.to_i.positive?
82
+ next if previous_counts.fetch(index, 0).to_i.positive?
83
+
84
+ index + 1
85
+ end
86
+ end
87
+
88
+ def previous_line_counts(source_file)
89
+ line_counts_for(@previous_snapshot.fetch(source_file, []))
90
+ end
91
+
92
+ def line_counts_for(file_coverage)
93
+ case file_coverage
94
+ when Hash
95
+ Array(file_coverage[:lines] || file_coverage["lines"])
96
+ else
97
+ Array(file_coverage)
98
+ end
99
+ end
100
+
101
+ def source_file?(path)
102
+ expanded = File.expand_path(path)
103
+ prefix = "#{Dir.pwd}#{File::SEPARATOR}"
104
+ return false unless expanded.start_with?(prefix)
105
+
106
+ relative = expanded.sub(prefix, "")
107
+ !relative.start_with?("spec#{File::SEPARATOR}") &&
108
+ !relative.start_with?("test#{File::SEPARATOR}")
109
+ end
110
+
111
+ def serializable_report
112
+ @coverage_by_test.transform_values do |source_map|
113
+ source_map.to_h do |source_file, lines|
114
+ [File.expand_path(source_file), lines.uniq.sort]
115
+ end
116
+ end
117
+ end
118
+ end
119
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Henitai
4
- VERSION = "0.1.7"
4
+ VERSION = "0.1.8"
5
5
  end
data/lib/henitai.rb CHANGED
@@ -41,6 +41,8 @@ module Henitai
41
41
  autoload :StillbornFilter, "henitai/stillborn_filter"
42
42
  autoload :ScenarioExecutionResult, "henitai/scenario_execution_result"
43
43
  autoload :CoverageFormatter, "henitai/coverage_formatter"
44
+ autoload :MinitestCoverageReporter, "henitai/minitest_coverage_reporter"
45
+ autoload :PerTestCoverageCollector, "henitai/per_test_coverage_collector"
44
46
  autoload :SyntaxValidator, "henitai/syntax_validator"
45
47
  autoload :SamplingStrategy, "henitai/sampling_strategy"
46
48
  autoload :TestPrioritizer, "henitai/test_prioritizer"
data/sig/henitai.rbs CHANGED
@@ -39,6 +39,15 @@ end
39
39
 
40
40
  module ::Minitest
41
41
  def self.run: (Array[String]) -> bool
42
+ def self.extensions: () -> Array[String]
43
+ def self.reporter: () -> untyped
44
+ def self.plugin_henitai_coverage_init: (untyped) -> void
45
+
46
+ class Reporter
47
+ def initialize: (?IO, ?Hash[untyped, untyped]) -> void
48
+ def record: (untyped) -> void
49
+ def report: () -> void
50
+ end
42
51
  end
43
52
 
44
53
  module ::RSpec::Core::Formatters
@@ -177,25 +186,45 @@ module Henitai
177
186
  def valid?: (Mutant) -> bool
178
187
  end
179
188
 
180
- class CoverageFormatter
189
+ class PerTestCoverageCollector
181
190
  REPORT_DIR_ENV: String
182
191
  REPORT_FILE_NAME: String
183
192
 
184
- def initialize: (untyped) -> void
185
- def example_finished: (untyped) -> void
186
- def dump_summary: (untyped) -> void
193
+ def initialize: () -> void
194
+ def record_test: (String) -> void
195
+ def write_report: () -> void
187
196
 
188
197
  private
189
198
 
190
199
  def report_path: () -> String
191
200
  def reports_dir: () -> String
192
201
  def current_snapshot: () -> untyped
193
- def new_lines: (untyped) -> untyped
194
- def new_line_numbers: (untyped, untyped) -> untyped
195
- def previous_line_counts: (String) -> untyped
202
+ def warn_missing_coverage: () -> void
203
+ def new_lines: (untyped) -> Hash[String, Array[Integer]]
204
+ def new_line_numbers: (untyped, untyped) -> Array[Integer]
205
+ def previous_line_counts: (String) -> Array[untyped]
196
206
  def line_counts_for: (untyped) -> Array[untyped]
197
207
  def source_file?: (String) -> bool
198
- def serializable_report: () -> untyped
208
+ def serializable_report: () -> Hash[String, Hash[String, Array[Integer]]]
209
+ end
210
+
211
+ class CoverageFormatter
212
+ REPORT_DIR_ENV: String
213
+ REPORT_FILE_NAME: String
214
+
215
+ def initialize: (untyped) -> void
216
+ def example_finished: (untyped) -> void
217
+ def dump_summary: (untyped) -> void
218
+ end
219
+
220
+ class MinitestCoverageReporter < ::Minitest::Reporter
221
+ def initialize: (?IO, ?Hash[untyped, untyped]) -> void
222
+ def record: (untyped) -> void
223
+ def report: () -> void
224
+
225
+ private
226
+
227
+ def relative_to_pwd: (String) -> String
199
228
  end
200
229
 
201
230
  class SamplingStrategy
@@ -301,6 +330,10 @@ module Henitai
301
330
  def with_subprocess_env: () { () -> untyped } -> untyped
302
331
  def spec_files: () -> Array[String]
303
332
  def fallback_spec_files: (Subject) -> Array[String]
333
+ def excluded_spec_files: () -> Array[String]
334
+ def rspec_exclude_patterns: () -> Array[String]
335
+ def rspec_config_lines: () -> Array[String]
336
+ def rspec_config_path: () -> String
304
337
  def selection_patterns: (Subject) -> Array[String]
305
338
  def requires_source_file?: (String, String) -> bool
306
339
  def requires_source_file_transitively?: (String, String, ?Array[String]) -> bool
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: henitai
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.7
4
+ version: 0.1.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - Martin Otten
@@ -101,6 +101,8 @@ files:
101
101
  - lib/henitai/git_diff_analyzer.rb
102
102
  - lib/henitai/integration.rb
103
103
  - lib/henitai/integration/rspec_process_runner.rb
104
+ - lib/henitai/minitest_coverage_hook.rb
105
+ - lib/henitai/minitest_coverage_reporter.rb
104
106
  - lib/henitai/minitest_simplecov.rb
105
107
  - lib/henitai/mutant.rb
106
108
  - lib/henitai/mutant/activator.rb
@@ -129,6 +131,7 @@ files:
129
131
  - lib/henitai/operators/update_operator.rb
130
132
  - lib/henitai/parallel_execution_runner.rb
131
133
  - lib/henitai/parser_current.rb
134
+ - lib/henitai/per_test_coverage_collector.rb
132
135
  - lib/henitai/per_test_coverage_selector.rb
133
136
  - lib/henitai/reporter.rb
134
137
  - lib/henitai/result.rb
@@ -157,7 +160,7 @@ metadata:
157
160
  changelog_uri: https://github.com/martinotten/henitai/blob/main/CHANGELOG.md
158
161
  documentation_uri: https://github.com/martinotten/henitai/blob/main/README.md
159
162
  homepage_uri: https://github.com/martinotten/henitai
160
- source_code_uri: https://github.com/martinotten/henitai/tree/v0.1.7
163
+ source_code_uri: https://github.com/martinotten/henitai/tree/v0.1.8
161
164
  rubygems_mfa_required: 'true'
162
165
  rdoc_options: []
163
166
  require_paths: