henitai 0.1.3 → 0.1.5

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: 748dc8e07fefafa1e15d8f84b6e047896d20615362c1edc4d69e6321acd3db4d
4
- data.tar.gz: d61c9559adc952e63417d3edffc9826093ea5e0e9b3298761badf85080e11a12
3
+ metadata.gz: 28b31872e8c6d86e806caa179862bb2af3eda92425e2c9f0690fd4b5d287d573
4
+ data.tar.gz: d7b0d36289b8fbbe11c0a690f1b501f857e738fb997790626282846df5450724
5
5
  SHA512:
6
- metadata.gz: 3172df8aa6dac29dd8498e0c2b697e0a1698a049ce86db1b976a5d93163206e146b16b2a641dd77088e0cc58e17238ffa657f704732a0e574d93a331faa80be0
7
- data.tar.gz: 6458897e5a6c53321362b5f65cfd9018c8ca415ccad74a0ba8303c56336e182407b50cd3ac02d976bb55d2eb284444676de137719194c7bbb04f20717903f082
6
+ metadata.gz: ed8eab942e02c4723a2a7b77f0d4b713698e61a914e1d9ab8bb3e4b1429514737dfef8160cc279a17d5d6428f6b02910f0d0258cd7e0a61342982d4a2f18e8cc
7
+ data.tar.gz: 4e4994d17183eecf751848d6877a300352f0e366fd715f557bc27fbdeede4e4f9164d51861bf3d1cca475fe2e789d2c80c9e2ff25331f8c34a2e957a59fbe5b9
data/CHANGELOG.md CHANGED
@@ -7,6 +7,43 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.1.5] - 2026-04-14
11
+
12
+ ### Fixed
13
+ - Steep type errors in `Runner` after removing targeted-run bootstrap scoping:
14
+ updated `bootstrap_mutants` RBS signature to match the new single-argument
15
+ form and removed stale signatures for `refresh_coverage_for_targeted_run`,
16
+ `scoped_bootstrap_test_files`, `targeted_run?`, and `retry_full_bootstrap?`
17
+ - `RSpec/ExampleLength` offense in `coverage_bootstrapper_spec.rb` — extracted
18
+ workspace setup and report writing into helper methods
19
+
20
+ ### Changed
21
+ - Targeted-run coverage bootstrap no longer scopes the initial run to the
22
+ subject's test files; the full suite is always used for the baseline,
23
+ trading a minor performance optimisation for reliability
24
+
25
+ ## [0.1.4] - 2026-04-14
26
+
27
+ ### Fixed
28
+ - `StringLiteral` operator no longer generates no-op mutations where the
29
+ replacement equals the original value (e.g. the spurious `"" → ""` mutant
30
+ that was emitted for methods already returning an empty string literal)
31
+ - Terminal diff output now uses `display_unparse` for string literal nodes,
32
+ making whitespace-only mutations unambiguous in the report
33
+ (e.g. `""`, `" "`, and `"\n"` are now visually distinct)
34
+ - Targeted coverage bootstrap (`--since` / explicit subjects) now correctly
35
+ retriggers a full suite run when the scoped bootstrap does not produce
36
+ coverage for all configured source files; previously the run could raise
37
+ `CoverageError` even though a fallback was available
38
+ - Coverage formatter specs now honor `HENITAI_REPORTS_DIR`, so the baseline
39
+ coverage bootstrap no longer fails when the suite runs under the mutation
40
+ runner's configured reports directory
41
+
42
+ ### Changed
43
+ - `ScenarioExecutionResult.build` factory method consolidates status and
44
+ exit-status derivation that was previously spread across `Integration`,
45
+ reducing the mutation surface of the value object
46
+
10
47
  ## [0.1.3] - 2026-04-13
11
48
 
12
49
  ### Added
@@ -122,7 +159,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
122
159
  - CLI critical path: `henitai run` now executes the full pipeline, supports `--since`, returns CI-friendly exit codes, and `henitai version` prints `Henitai::VERSION`
123
160
  - RSpec per-test coverage output: `henitai/coverage_formatter` now writes `coverage/henitai_per_test.json`
124
161
 
125
- [Unreleased]: https://github.com/martinotten/henitai/compare/v0.1.3...HEAD
162
+ [Unreleased]: https://github.com/martinotten/henitai/compare/v0.1.4...HEAD
163
+ [0.1.4]: https://github.com/martinotten/henitai/compare/v0.1.3...v0.1.4
126
164
  [0.1.3]: https://github.com/martinotten/henitai/compare/v0.1.2...v0.1.3
127
165
  [0.1.2]: https://github.com/martinotten/henitai/compare/v0.1.1...v0.1.2
128
166
  [0.1.1]: https://github.com/martinotten/henitai/compare/v0.1.0...v0.1.1
data/README.md CHANGED
@@ -163,10 +163,13 @@ cd henitai
163
163
  bundle install
164
164
  bundle exec rspec # run tests
165
165
  bundle exec rubocop # lint
166
+ bundle exec henitai clean # remove stale generated report artifacts
166
167
  bundle exec henitai run # dogfood
167
168
  ```
168
169
 
169
- A Dev Container configuration is included (`.devcontainer/`) for VS Code with the official `ruby:4.0.2-alpine` image and the Codex CLI preinstalled.
170
+ A Dev Container configuration is included (`.devcontainer/`) for VS Code with the official `ruby:4`
171
+ image, the Codex CLI, and RTK preinstalled. Codex support is bootstrapped with
172
+ `rtk init -g --codex --auto-patch` during container creation.
170
173
 
171
174
  ## Architecture
172
175
 
data/lib/henitai/cli.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "fileutils"
3
4
  require "optparse"
4
5
  module Henitai
5
6
  # Command-line interface entry point.
@@ -35,6 +36,15 @@ module Henitai
35
36
  " low: 60"
36
37
  ].freeze
37
38
 
39
+ REPORT_CLEANUP_PATHS = [
40
+ %w[mutation-logs baseline.log],
41
+ %w[mutation-logs baseline.stdout.log],
42
+ %w[mutation-logs baseline.stderr.log],
43
+ %w[coverage .resultset.json],
44
+ %w[coverage .last_run.json],
45
+ ["henitai_per_test.json"]
46
+ ].freeze
47
+
38
48
  OPERATOR_METADATA = {
39
49
  "ArithmeticOperator" => ["Arithmetic operators", "a + b -> a - b"],
40
50
  "EqualityOperator" => ["Comparison operators", "a == b -> a != b"],
@@ -69,6 +79,7 @@ module Henitai
69
79
  command = @argv.shift
70
80
  case command
71
81
  when "run" then run_command
82
+ when "clean" then clean_command
72
83
  when "version" then puts Henitai::VERSION
73
84
  when "init" then init_command
74
85
  when "operator" then operator_command
@@ -94,6 +105,18 @@ module Henitai
94
105
  handle_run_error(e)
95
106
  end
96
107
 
108
+ def clean_command
109
+ @command_halted = false
110
+ options = parse_clean_options
111
+ return if @command_halted
112
+
113
+ config = load_config(options)
114
+ removed_paths = cleanup_report_artifacts(config)
115
+ puts clean_summary(removed_paths)
116
+ rescue StandardError => e
117
+ handle_run_error(e)
118
+ end
119
+
97
120
  def parse_run_options
98
121
  options = {}
99
122
  build_run_option_parser(options).parse!(@argv)
@@ -142,6 +165,21 @@ module Henitai
142
165
  end
143
166
  end
144
167
 
168
+ def parse_clean_options
169
+ options = {}
170
+ build_clean_option_parser(options).parse!(@argv)
171
+ options
172
+ end
173
+
174
+ def build_clean_option_parser(options)
175
+ OptionParser.new do |opts|
176
+ opts.banner = "Usage: henitai clean [options]"
177
+ add_config_option(opts, options)
178
+ add_help_option(opts)
179
+ add_version_option(opts)
180
+ end
181
+ end
182
+
145
183
  def add_since_option(opts, options)
146
184
  opts.on("--since GIT_REF", "Only mutate subjects changed since GIT_REF") do |ref|
147
185
  options[:since] = ref
@@ -198,6 +236,7 @@ module Henitai
198
236
 
199
237
  Usage:
200
238
  henitai run [options] [SUBJECT_PATTERN...]
239
+ henitai clean [options]
201
240
  henitai version
202
241
  henitai init [PATH]
203
242
  henitai operator list
@@ -207,6 +246,7 @@ module Henitai
207
246
  bundle exec henitai run --since origin/main
208
247
  bundle exec henitai run 'Foo::Bar#my_method'
209
248
  bundle exec henitai run 'MyNamespace*' --operators full
249
+ bundle exec henitai clean
210
250
  bundle exec henitai init
211
251
  bundle exec henitai operator list
212
252
 
@@ -239,6 +279,28 @@ module Henitai
239
279
  exit 2
240
280
  end
241
281
 
282
+ def clean_summary(removed_paths)
283
+ return "No generated report artifacts to clean" if removed_paths.empty?
284
+
285
+ format(
286
+ "Removed %<count>s generated report artifact%<plural>s",
287
+ count: removed_paths.length,
288
+ plural: removed_paths.length == 1 ? "" : "s"
289
+ )
290
+ end
291
+
292
+ def cleanup_report_artifacts(config)
293
+ removed_paths = report_cleanup_paths(config).select { |path| File.exist?(path) }
294
+ removed_paths.each { |path| FileUtils.rm_f(path) }
295
+ removed_paths
296
+ end
297
+
298
+ def report_cleanup_paths(config)
299
+ REPORT_CLEANUP_PATHS.map do |relative_path|
300
+ File.join(config.reports_dir, *relative_path)
301
+ end
302
+ end
303
+
242
304
  def exit_status_for(result, config)
243
305
  result.mutation_score.to_i >= config.thresholds.fetch(:low, 60) ? 0 : 1
244
306
  end
@@ -27,7 +27,7 @@ module Henitai
27
27
  bootstrap_coverage(integration, config, test_files)
28
28
  end
29
29
 
30
- return if coverage_available?(source_files, config)
30
+ return if coverage_available?(source_files, config, test_files)
31
31
 
32
32
  raise CoverageError,
33
33
  "Coverage data is unavailable for the configured source files"
@@ -37,24 +37,37 @@ module Henitai
37
37
 
38
38
  attr_reader :static_filter
39
39
 
40
- def coverage_available?(source_files, config)
40
+ def coverage_available?(source_files, config, test_files)
41
41
  coverage_lines = static_filter.coverage_lines_for(config)
42
+ covered_sources = covered_source_files(source_files, coverage_lines)
43
+ existing_sources = existing_source_file_paths(source_files)
42
44
 
43
- source_file_paths(source_files).any? do |path|
44
- Array(coverage_lines[path]).any?
45
- end
45
+ return covered_sources.any? if existing_sources.empty?
46
+ return covered_sources.any? if test_files
47
+
48
+ covered_sources.any?
46
49
  end
47
50
 
48
51
  def coverage_ready?(source_files, config, integration, test_files)
49
52
  coverage_fresh?(source_files, config, integration, test_files) &&
50
- coverage_available?(source_files, config) &&
53
+ coverage_available?(source_files, config, test_files) &&
51
54
  per_test_coverage_ready?(source_files, config, integration, test_files)
52
55
  end
53
56
 
57
+ def covered_source_files(source_files, coverage_lines)
58
+ source_file_paths(source_files).select do |path|
59
+ Array(coverage_lines[path]).any?
60
+ end
61
+ end
62
+
54
63
  def source_file_paths(source_files)
55
64
  Array(source_files).map { |path| File.expand_path(path) }
56
65
  end
57
66
 
67
+ def existing_source_file_paths(source_files)
68
+ source_file_paths(source_files).select { |path| File.exist?(path) }
69
+ end
70
+
58
71
  # Returns true when a coverage report already exists and is newer than
59
72
  # every watched source and test file. Stale or absent reports return false.
60
73
  def coverage_fresh?(source_files, config, integration, test_files)
@@ -19,7 +19,9 @@ module Henitai
19
19
  private
20
20
 
21
21
  def equivalent_mutation?(mutant)
22
- equivalent_arithmetic_mutation?(mutant) || equivalent_logical_mutation?(mutant)
22
+ equivalent_arithmetic_mutation?(mutant) ||
23
+ equivalent_logical_mutation?(mutant) ||
24
+ equivalent_singleton_equality_mutation?(mutant)
23
25
  end
24
26
 
25
27
  def equivalent_arithmetic_mutation?(mutant)
@@ -137,6 +139,62 @@ module Henitai
137
139
  end
138
140
  end
139
141
 
142
+ # Detects `lhs == <singleton>` mutated to `lhs.equal?(<singleton>)` (or the
143
+ # reverse), but only when the receiver is itself a singleton literal.
144
+ #
145
+ # Both sides must be singleton literals so that we can prove, without
146
+ # runtime type information, that `==` reduces to identity comparison.
147
+ # A variable or arbitrary expression as the receiver is unsafe: for example
148
+ # `1.0 == 1` is true while `1.0.equal?(1)` is false, and any object with a
149
+ # custom `#==` can exhibit the same divergence.
150
+ #
151
+ # Singleton types accepted on both receiver and RHS:
152
+ # Symbol – interned; only one instance of :foo ever exists in a process.
153
+ # nil/true/false – singletons by language specification.
154
+ def equivalent_singleton_equality_mutation?(mutant)
155
+ original = mutant.original_node
156
+ mutated = mutant.mutated_node
157
+
158
+ equality_send?(original) && equality_send?(mutated) &&
159
+ same_receiver?(original, mutated) &&
160
+ singleton_literal?(original.children[0]) &&
161
+ singleton_rhs_match?(original, mutated) &&
162
+ equality_operators?(original.children[1], mutated.children[1])
163
+ end
164
+
165
+ def singleton_rhs_match?(original, mutated)
166
+ rhs = original.children[2]
167
+ singleton_literal?(rhs) && same_node?(rhs, mutated.children[2])
168
+ end
169
+
170
+ def equality_send?(node)
171
+ node.is_a?(Parser::AST::Node) &&
172
+ node.type == :send &&
173
+ node.children.size == 3 &&
174
+ equality_operator?(node.children[1])
175
+ end
176
+
177
+ def equality_operator?(operator)
178
+ %i[== equal?].include?(operator)
179
+ end
180
+
181
+ def equality_operators?(op_a, op_b)
182
+ equality_operator?(op_a) && equality_operator?(op_b) && op_a != op_b
183
+ end
184
+
185
+ # Returns true for AST nodes that represent Ruby singleton values:
186
+ # symbols, nil, true, and false.
187
+ def singleton_literal?(node)
188
+ return false unless node.is_a?(Parser::AST::Node)
189
+
190
+ # rubocop:disable Lint/BooleanSymbol
191
+ case node.type
192
+ when :sym, :nil, :true, :false then true
193
+ else false
194
+ end
195
+ # rubocop:enable Lint/BooleanSymbol
196
+ end
197
+
140
198
  def same_node?(left, right)
141
199
  left == right
142
200
  end
@@ -314,35 +314,18 @@ module Henitai
314
314
  end
315
315
 
316
316
  def build_result(wait_result, log_paths)
317
- status = scenario_status(wait_result)
318
317
  stdout = read_log_file(log_paths[:stdout_path])
319
318
  stderr = read_log_file(log_paths[:stderr_path])
320
319
  write_combined_log(log_paths[:log_path], stdout, stderr)
321
320
 
322
- ScenarioExecutionResult.new(
323
- status:,
321
+ ScenarioExecutionResult.build(
322
+ wait_result:,
324
323
  stdout:,
325
324
  stderr:,
326
- log_path: log_paths[:log_path],
327
- exit_status: exit_status_for(wait_result)
325
+ log_path: log_paths[:log_path]
328
326
  )
329
327
  end
330
328
 
331
- def scenario_status(wait_result)
332
- return :timeout if wait_result == :timeout
333
- return :compile_error if exit_status_for(wait_result) == 2
334
- return :survived if wait_result.respond_to?(:success?) && wait_result.success?
335
-
336
- :killed
337
- end
338
-
339
- def exit_status_for(wait_result)
340
- return nil if wait_result == :timeout
341
- return nil unless wait_result.respond_to?(:exitstatus)
342
-
343
- wait_result.exitstatus
344
- end
345
-
346
329
  def spec_files
347
330
  Dir.glob("spec/**/*_spec.rb")
348
331
  end
@@ -494,6 +477,10 @@ module Henitai
494
477
  # runner. Minitest shares selection and execution semantics, but per-test
495
478
  # coverage collection is not yet wired into this path.
496
479
  class Minitest < Rspec
480
+ def per_test_coverage_supported?
481
+ false
482
+ end
483
+
497
484
  def run_mutant(mutant:, test_files:, timeout:)
498
485
  setup_load_path
499
486
  super
@@ -27,7 +27,8 @@ module Henitai
27
27
  private
28
28
 
29
29
  def mutate_plain_string(node, subject:)
30
- REPLACEMENTS.map do |replacement|
30
+ original_value = node.children.first
31
+ REPLACEMENTS.reject { |r| r == original_value }.map do |replacement|
31
32
  build_mutant(
32
33
  subject:,
33
34
  original_node: node,
@@ -126,11 +126,23 @@ module Henitai
126
126
  end
127
127
 
128
128
  def original_line(mutant)
129
- format("- %s", safe_unparse(mutant.original_node))
129
+ format("- %s", display_unparse(mutant.original_node))
130
130
  end
131
131
 
132
132
  def mutated_line(mutant)
133
- format("+ %s", safe_unparse(mutant.mutated_node))
133
+ format("+ %s", display_unparse(mutant.mutated_node))
134
+ end
135
+
136
+ # Like safe_unparse but makes invisible characters visible in terminal
137
+ # output. For string literal nodes the inner value is shown via #inspect
138
+ # so that e.g. "" vs " " vs "\n" are unambiguous. Other nodes unparse
139
+ # normally.
140
+ def display_unparse(node)
141
+ if node.respond_to?(:type) && node.respond_to?(:children) && node.type == :str
142
+ node.children.first.inspect
143
+ else
144
+ safe_unparse(node)
145
+ end
134
146
  end
135
147
 
136
148
  def score_line(result)
@@ -41,10 +41,6 @@ module Henitai
41
41
  # The thread is joined before Gate 3 (static filtering), which is the first
42
42
  # phase that requires coverage data.
43
43
  #
44
- # For targeted runs (`subjects:` provided), the bootstrap is further scoped
45
- # to the spec files that cover the requested subjects rather than the full
46
- # suite, reducing the baseline run time proportionally.
47
- #
48
44
  # @return [Result]
49
45
  def run
50
46
  started_at = Time.now
@@ -76,26 +72,15 @@ module Henitai
76
72
  end
77
73
 
78
74
  def mutants_for(subjects, source_files)
79
- bootstrap_thread = bootstrap_mutants(source_files, subjects)
75
+ bootstrap_thread = bootstrap_mutants(source_files)
80
76
  mutants = generate_mutants(subjects)
81
77
  bootstrap_thread.value
82
78
 
83
- filtered_mutants = filter_mutants(mutants)
84
- return filtered_mutants unless targeted_run?
85
-
86
- refresh_coverage_for_targeted_run(filtered_mutants, source_files)
87
- end
88
-
89
- def refresh_coverage_for_targeted_run(mutants, source_files)
90
- return mutants unless retry_full_bootstrap?(mutants)
91
-
92
- bootstrap_coverage(source_files)
93
79
  filter_mutants(mutants)
94
80
  end
95
81
 
96
- def bootstrap_mutants(source_files, subjects)
97
- scoped_tests = scoped_bootstrap_test_files(subjects)
98
- Thread.new { bootstrap_coverage(source_files, scoped_tests) }
82
+ def bootstrap_mutants(source_files)
83
+ Thread.new { bootstrap_coverage(source_files) }
99
84
  end
100
85
 
101
86
  def execute_mutants(mutants)
@@ -131,21 +116,6 @@ module Henitai
131
116
  @result
132
117
  end
133
118
 
134
- # Returns the spec files to use for the coverage bootstrap.
135
- #
136
- # For full runs (no subject pattern given), returns nil so the bootstrapper
137
- # falls back to the integration's full test-file list.
138
- #
139
- # For targeted runs, returns the union of test files selected for each
140
- # resolved subject. Falls back to nil (all tests) if the selection is empty,
141
- # so the bootstrapper always has a non-empty file list.
142
- def scoped_bootstrap_test_files(subjects)
143
- return nil if pattern_subjects.empty?
144
-
145
- files = subjects.flat_map { |subject| integration.select_tests(subject) }.uniq
146
- files.empty? ? nil : files
147
- end
148
-
149
119
  def bootstrap_coverage(source_files, test_files = nil)
150
120
  coverage_bootstrapper.ensure!(source_files:, config:, integration:, test_files:)
151
121
  end
@@ -217,19 +187,6 @@ module Henitai
217
187
  Array(@subjects)
218
188
  end
219
189
 
220
- def targeted_run?
221
- !pattern_subjects.empty?
222
- end
223
-
224
- def retry_full_bootstrap?(mutants)
225
- executable_mutants = Array(mutants).reject do |mutant|
226
- %i[ignored compile_error equivalent].include?(mutant.status)
227
- end
228
- return false if executable_mutants.empty?
229
-
230
- executable_mutants.all? { |mutant| mutant.status == :no_coverage }
231
- end
232
-
233
190
  def unique_subjects(subjects)
234
191
  subjects.uniq { |subject| [subject.expression, subject.source_file] }
235
192
  end
@@ -5,6 +5,16 @@ module Henitai
5
5
  class ScenarioExecutionResult
6
6
  attr_reader :status, :stdout, :stderr, :exit_status, :log_path
7
7
 
8
+ def self.build(wait_result:, stdout:, stderr:, log_path:)
9
+ new(
10
+ status: status_for(wait_result),
11
+ stdout: stdout,
12
+ stderr: stderr,
13
+ log_path: log_path,
14
+ exit_status: exit_status_for(wait_result)
15
+ )
16
+ end
17
+
8
18
  def initialize(status:, stdout:, stderr:, log_path:, exit_status: nil)
9
19
  @status = status
10
20
  @stdout = stdout.to_s
@@ -51,11 +61,11 @@ module Henitai
51
61
  log_text.lines.last(lines).join
52
62
  end
53
63
 
54
- def should_show_logs?(all_logs: false)
64
+ def should_show_logs?(all_logs: nil)
55
65
  all_logs || timeout?
56
66
  end
57
67
 
58
- def failure_tail(all_logs: false, lines: 12)
68
+ def failure_tail(all_logs: nil, lines: 12)
59
69
  return combined_output if all_logs
60
70
  return "" unless should_show_logs?(all_logs:)
61
71
 
@@ -64,6 +74,25 @@ module Henitai
64
74
 
65
75
  private
66
76
 
77
+ class << self
78
+ private
79
+
80
+ def status_for(wait_result)
81
+ return :timeout if wait_result == :timeout
82
+ return :compile_error if exit_status_for(wait_result) == 2
83
+ return :survived if wait_result.respond_to?(:success?) && wait_result.success?
84
+
85
+ :killed
86
+ end
87
+
88
+ def exit_status_for(wait_result)
89
+ return nil if wait_result == :timeout
90
+ return nil unless wait_result.respond_to?(:exitstatus)
91
+
92
+ wait_result.exitstatus
93
+ end
94
+ end
95
+
67
96
  def stream_section(name, content)
68
97
  "#{name}:\n#{content}"
69
98
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Henitai
4
- VERSION = "0.1.3"
4
+ VERSION = "0.1.5"
5
5
  end
data/sig/henitai.rbs CHANGED
@@ -112,6 +112,7 @@ module Henitai
112
112
  attr_reader exit_status: Integer?
113
113
  attr_reader log_path: String
114
114
 
115
+ def self.build: (wait_result: untyped, stdout: String?, stderr: String?, log_path: String) -> ScenarioExecutionResult
115
116
  def initialize: (status: Symbol, stdout: String?, stderr: String?, log_path: String, ?exit_status: Integer?) -> void
116
117
  def survived?: () -> bool
117
118
  def killed?: () -> bool
@@ -121,6 +122,11 @@ module Henitai
121
122
  def tail: (?Integer) -> String
122
123
  def should_show_logs?: (?all_logs: bool) -> bool
123
124
  def failure_tail: (?all_logs: bool, ?lines: Integer) -> String
125
+
126
+ private
127
+
128
+ def self.status_for: (untyped) -> Symbol
129
+ def self.exit_status_for: (untyped) -> Integer?
124
130
  end
125
131
 
126
132
  class Subject
@@ -474,6 +480,7 @@ module Henitai
474
480
  def survived_mutant_header: (Mutant) -> String
475
481
  def original_line: (Mutant) -> String
476
482
  def mutated_line: (Mutant) -> String
483
+ def display_unparse: (untyped) -> String
477
484
  def score_line: (Result) -> String
478
485
  def format_row: (String, untyped) -> String
479
486
  def count_status: (Result, Symbol) -> Integer
@@ -606,13 +613,9 @@ module Henitai
606
613
 
607
614
  def build_result: (Array[Mutant], Time, Time) -> Result
608
615
  def persist_history: (Result, Time) -> void
609
- def refresh_coverage_for_targeted_run: (Array[Mutant], Array[String]) -> Array[Mutant]
610
616
  def bootstrap_coverage: (Array[String], ?Array[String]?) -> void
611
- def bootstrap_mutants: (Array[String], Array[Subject]) -> Thread
617
+ def bootstrap_mutants: (Array[String]) -> Thread
612
618
  def mutants_for: (Array[Subject], Array[String]) -> Array[Mutant]
613
- def scoped_bootstrap_test_files: (Array[Subject]) -> Array[String]?
614
- def targeted_run?: () -> bool
615
- def retry_full_bootstrap?: (Array[Mutant]) -> bool
616
619
  def with_reports_dir: () { () -> untyped } -> untyped
617
620
  def result_thresholds: () -> Hash[Symbol, Integer]?
618
621
  end
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: henitai
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.3
4
+ version: 0.1.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Martin Otten
8
- autorequire:
9
8
  bindir: exe
10
9
  cert_chain: []
11
- date: 2026-04-13 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: prism
@@ -158,9 +157,8 @@ metadata:
158
157
  changelog_uri: https://github.com/martinotten/henitai/blob/main/CHANGELOG.md
159
158
  documentation_uri: https://github.com/martinotten/henitai/blob/main/README.md
160
159
  homepage_uri: https://github.com/martinotten/henitai
161
- source_code_uri: https://github.com/martinotten/henitai/tree/v0.1.2
160
+ source_code_uri: https://github.com/martinotten/henitai/tree/v0.1.4
162
161
  rubygems_mfa_required: 'true'
163
- post_install_message:
164
162
  rdoc_options: []
165
163
  require_paths:
166
164
  - lib
@@ -175,8 +173,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
175
173
  - !ruby/object:Gem::Version
176
174
  version: '0'
177
175
  requirements: []
178
- rubygems_version: 3.3.5
179
- signing_key:
176
+ rubygems_version: 4.0.6
180
177
  specification_version: 4
181
178
  summary: Mutation testing for Ruby
182
179
  test_files: []