henitai 0.5.1 → 0.6.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 (122) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +296 -1
  3. data/README.md +109 -16
  4. data/assets/schema/henitai.schema.json +20 -0
  5. data/lib/henitai/arid_node_filter.rb +4 -19
  6. data/lib/henitai/bootstrap_error_message.rb +20 -0
  7. data/lib/henitai/canonical_report_merger.rb +23 -2
  8. data/lib/henitai/cli/run_command.rb +25 -7
  9. data/lib/henitai/cli/run_options.rb +1 -1
  10. data/lib/henitai/cli.rb +4 -1
  11. data/lib/henitai/configuration.rb +4 -1
  12. data/lib/henitai/configuration_validator/rules.rb +8 -0
  13. data/lib/henitai/configuration_validator/scalars.rb +9 -0
  14. data/lib/henitai/configuration_validator.rb +2 -1
  15. data/lib/henitai/constant_path.rb +48 -0
  16. data/lib/henitai/control_run.rb +73 -0
  17. data/lib/henitai/coverage_bootstrapper.rb +57 -15
  18. data/lib/henitai/coverage_formatter.rb +26 -3
  19. data/lib/henitai/coverage_report_reader.rb +21 -0
  20. data/lib/henitai/equivalence_detector/operand_predicates.rb +16 -0
  21. data/lib/henitai/equivalence_detector.rb +46 -38
  22. data/lib/henitai/example_summary.rb +24 -0
  23. data/lib/henitai/execution_engine/fork_server_scope.rb +57 -0
  24. data/lib/henitai/execution_engine.rb +75 -60
  25. data/lib/henitai/fidelity_check.rb +173 -0
  26. data/lib/henitai/flaky_detection_baseline.rb +61 -0
  27. data/lib/henitai/flaky_mutant_retrier.rb +43 -0
  28. data/lib/henitai/flaky_retry_decision.rb +23 -0
  29. data/lib/henitai/flaky_test_registry.rb +63 -0
  30. data/lib/henitai/fork_server/client.rb +231 -0
  31. data/lib/henitai/fork_server/group_killer.rb +48 -0
  32. data/lib/henitai/fork_server/group_terminator.rb +47 -0
  33. data/lib/henitai/fork_server/launcher.rb +38 -0
  34. data/lib/henitai/fork_server/mutant_runner.rb +141 -0
  35. data/lib/henitai/fork_server/protocol.rb +73 -0
  36. data/lib/henitai/fork_server/reaper.rb +24 -0
  37. data/lib/henitai/fork_server/runtime.rb +109 -0
  38. data/lib/henitai/fork_server/server.rb +108 -0
  39. data/lib/henitai/fork_server/server_process.rb +73 -0
  40. data/lib/henitai/fork_server/status.rb +52 -0
  41. data/lib/henitai/fork_server.rb +43 -0
  42. data/lib/henitai/generated_artifacts.rb +3 -0
  43. data/lib/henitai/git_diff_analyzer.rb +28 -11
  44. data/lib/henitai/incremental_filter.rb +20 -10
  45. data/lib/henitai/integration/base.rb +72 -5
  46. data/lib/henitai/integration/capped_log_sink.rb +34 -0
  47. data/lib/henitai/integration/child_bootstrap.rb +7 -0
  48. data/lib/henitai/integration/child_report_store.rb +36 -0
  49. data/lib/henitai/integration/minitest.rb +17 -7
  50. data/lib/henitai/integration/minitest_autorun.rb +20 -0
  51. data/lib/henitai/integration/minitest_outcome_reporter.rb +28 -0
  52. data/lib/henitai/integration/minitest_preload.rb +30 -0
  53. data/lib/henitai/integration/minitest_runnable_filter.rb +61 -0
  54. data/lib/henitai/integration/minitest_test_runner.rb +32 -15
  55. data/lib/henitai/integration/mutant_run_support.rb +73 -13
  56. data/lib/henitai/integration/rspec_child_runner.rb +44 -9
  57. data/lib/henitai/integration/rspec_preload.rb +35 -0
  58. data/lib/henitai/integration/rspec_process_runner.rb +4 -1
  59. data/lib/henitai/integration/scenario_log_support.rb +31 -18
  60. data/lib/henitai/integration.rb +16 -3
  61. data/lib/henitai/minitest_coverage_reporter.rb +22 -6
  62. data/lib/henitai/minitest_result_file.rb +62 -0
  63. data/lib/henitai/mutant/activation.rb +32 -0
  64. data/lib/henitai/mutant/activator.rb +75 -55
  65. data/lib/henitai/mutant/def_injection.rb +136 -0
  66. data/lib/henitai/mutant/lexical_scope_check.rb +120 -0
  67. data/lib/henitai/mutant/lexical_target.rb +105 -0
  68. data/lib/henitai/mutant/replacement.rb +52 -0
  69. data/lib/henitai/mutant.rb +27 -8
  70. data/lib/henitai/mutant_generator.rb +62 -18
  71. data/lib/henitai/mutant_history_store/verdict_cache.rb +12 -11
  72. data/lib/henitai/mutant_history_store.rb +14 -4
  73. data/lib/henitai/mutant_identity.rb +5 -1
  74. data/lib/henitai/mutant_verdict.rb +71 -0
  75. data/lib/henitai/operators/array_declaration.rb +4 -0
  76. data/lib/henitai/operators/assignment_expression.rb +2 -0
  77. data/lib/henitai/operators/conditional_expression.rb +21 -1
  78. data/lib/henitai/operators/hash_literal.rb +7 -4
  79. data/lib/henitai/operators/method_expression.rb +14 -3
  80. data/lib/henitai/operators/regex_metacharacters.rb +89 -0
  81. data/lib/henitai/operators/regex_mutator.rb +23 -28
  82. data/lib/henitai/operators/return_value.rb +1 -1
  83. data/lib/henitai/operators.rb +1 -0
  84. data/lib/henitai/per_test_coverage.rb +11 -7
  85. data/lib/henitai/per_test_coverage_collector.rb +30 -31
  86. data/lib/henitai/per_test_coverage_delta.rb +60 -0
  87. data/lib/henitai/per_test_coverage_selector.rb +26 -13
  88. data/lib/henitai/process_wakeup.rb +8 -3
  89. data/lib/henitai/process_worker_runner.rb +19 -5
  90. data/lib/henitai/reporter.rb +15 -8
  91. data/lib/henitai/result.rb +32 -16
  92. data/lib/henitai/reuse_context.rb +50 -0
  93. data/lib/henitai/rspec_coverage_formatter.rb +4 -0
  94. data/lib/henitai/rspec_outcomes.rb +72 -0
  95. data/lib/henitai/runner.rb +58 -19
  96. data/lib/henitai/runner_dependencies.rb +22 -1
  97. data/lib/henitai/sampling_strategy.rb +36 -6
  98. data/lib/henitai/scenario_execution_result.rb +28 -5
  99. data/lib/henitai/signature_line.rb +21 -0
  100. data/lib/henitai/slot_scheduler/draining.rb +12 -2
  101. data/lib/henitai/slot_scheduler/process_control.rb +33 -0
  102. data/lib/henitai/slot_scheduler/retry_policy.rb +8 -6
  103. data/lib/henitai/slot_scheduler.rb +47 -8
  104. data/lib/henitai/source_file_selection.rb +1 -1
  105. data/lib/henitai/source_parser.rb +29 -9
  106. data/lib/henitai/static_filter.rb +45 -14
  107. data/lib/henitai/stillborn_filter.rb +70 -2
  108. data/lib/henitai/subject.rb +8 -3
  109. data/lib/henitai/subject_resolver.rb +65 -31
  110. data/lib/henitai/suite_boot_clock.rb +48 -0
  111. data/lib/henitai/survivor_activation_cache.rb +18 -11
  112. data/lib/henitai/survivor_rerun_strategy.rb +47 -43
  113. data/lib/henitai/test_file_outcomes.rb +79 -0
  114. data/lib/henitai/timeout_calibrator.rb +31 -9
  115. data/lib/henitai/timeout_ceiling.rb +37 -0
  116. data/lib/henitai/verdict_fingerprint.rb +29 -30
  117. data/lib/henitai/version.rb +1 -1
  118. data/lib/henitai.rb +17 -1
  119. data/sig/configuration_validator.rbs +1 -0
  120. data/sig/henitai.rbs +227 -45
  121. metadata +46 -3
  122. data/lib/henitai/survivor_test_filter.rb +0 -72
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 321426b14e3d03dae68885665b6abee8e11ea48d7a00036664dd0b6a10af2659
4
- data.tar.gz: 262e708c7393a11e57f8fe25b9588b8b6aa04c026bebef1dec0aa77fedd782ba
3
+ metadata.gz: 4c9ef6e2d806e8d12acfd36adda062192c5cf4ba700ee140ce7d6ad848eff0c8
4
+ data.tar.gz: 800ba49ba72ce0ab6090e1ab9b40ea5146559738e59af5a81ceeb80923bc974e
5
5
  SHA512:
6
- metadata.gz: 4ef8ed1d823903bceca334c81a895a1734745480e0966ab55e100ea95b8479201824ebbd048ea86f0465c0a98fd39dd561a62876614e20bed7b1f64460d10e20
7
- data.tar.gz: b152279d4854b7aeb55afb5696a77054184947f0ea16570dd9e3f90a62388295b6f4f6499f101d323a8fa0071c9d83d8847e3348c878061c980587ab6ddfd53f
6
+ metadata.gz: 60acc4fc6df82c4d273c110302af6a73b7c4f7d6f3ebb2ae9fdcea06ca69e5cef2e61945c396b38810f978b8ace3db2b988d224b637d0f8f11c02ccb03eee368
7
+ data.tar.gz: 9fe40adf6368041348247af329f3153a0027e89bbbe60e9dcdef27a4a666512446ab7569188e891ec7eb2f5fe8419c33eeee2ed1778a5b034c97901ca94a095c
data/CHANGELOG.md CHANGED
@@ -7,9 +7,262 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.6.0] - 2026-10-20
11
+
12
+ Minor release with a deliberate score break: false kills and harness
13
+ failures no longer count as detected mutants, so most projects, including
14
+ Henitai's own dogfood run, will report lower Mutation Scores. Re-measure
15
+ before adjusting thresholds.
16
+
17
+ ### Changed
18
+
19
+ - **Scores will drop, by design.** A mutant now counts as detected only when a
20
+ test failed because of its mutation (ADR-14). Harness failures that used to
21
+ be credited as kills are now reported as `CompileError` or `RuntimeError`,
22
+ with the reason in `statusReason`. On an assertion-free test suite, 0.5.3
23
+ reported 130 of 137 mutants as killed; this release reports none. Projects
24
+ should expect lower Mutation Scores. Re-measure before adjusting thresholds.
25
+ - **Flaky-test handling.** Survivors are no longer retried: a retry could only
26
+ turn a survivor into a kill and inflate the score. Kills are re-checked, up
27
+ to `max_flaky_retries`, only when every failing test file is known to be
28
+ flaky, as found by the new opt-in `mutation.flaky_detection` (baseline suite
29
+ runs twice; flaky files go to `henitai_flaky_tests.json`, removed by
30
+ `henitai clean`). Without it nothing is retried, so runtime on survivor-heavy
31
+ suites drops. The ">5% of mutants needed a retry" warning is replaced by a
32
+ report of flaky tests and re-checked kills. Child reports now carry
33
+ `failed_tests`. Failures outside an example (an `after(:context)` hook error,
34
+ a suite-level hook, a Minitest result without a file) are attributed to their
35
+ spec file when RSpec names it; otherwise the run is flagged
36
+ `unattributed_failures`, which makes a kill genuine (never retried) and makes
37
+ the flaky baseline reject the run. Baseline runs that fail on a file the two
38
+ runs cannot call flaky (for example one seen in a single run) now abort
39
+ instead of being accepted. Minitest outcomes are keyed to the file defining
40
+ the test class, not to a shared module that supplies the test method.
41
+ - Mutant runs are classified from an explicit report written by the child
42
+ process instead of from its exit status. An exception while activating the
43
+ mutant (for example a `NameError` from a superclass defined in another file)
44
+ is `CompileError`, not `Killed`. A child killed by a signal, or one that
45
+ exits without reporting, is `RuntimeError`, not `Killed`. A `SystemExit`
46
+ raised by the code under test is `RuntimeError`, not `Survived`.
47
+ - Mutants are activated only after the spec or test files have loaded, so a
48
+ subject whose superclass or constants live in other files can be tested.
49
+ Previously the child loaded the subject file on its own and failed with a
50
+ `NameError` (every `ApplicationRecord` model, for example).
51
+ - Mutants are activated by re-evaluating the subject's own `def` inside its
52
+ original module and class openers (ADR-13), not through `define_method`.
53
+ Constant lookup through enclosing modules, implicit `super`, `yield`,
54
+ `block_given?`, argument forwarding, default values, `__FILE__`/`__LINE__`,
55
+ `frozen_string_literal` and visibility now behave as in the original method.
56
+ Mutations in default values take effect, and splices no longer shift after
57
+ multibyte characters or leave heredoc bodies behind. Activation recipes from
58
+ earlier versions are ignored and regenerated.
59
+
60
+ ### Added
61
+
62
+ - `mutation.sampling.seed` (integer >= 0, default `0`) and real stratified
63
+ sampling: per subject `max(1, ceil(size * ratio))` mutants, strata by
64
+ operator ordered by `SHA256(seed, stable_id)`, visited round-robin, original
65
+ order kept. The report records `"sampling": {strategy, ratio, seed}`.
66
+ - Report fields: top-level `completeFiles` (non-authoritative runs) and
67
+ per-file `sessionId` and `gitSha` provenance.
68
+ - `rake oracle:reuse` (run in CI): on a scratch copy of the oracle corpus, the
69
+ `--incremental` verdicts must equal a fresh full run after scripted edits to
70
+ source, tests, a helper and the configuration.
71
+ - **Fork server for Minitest and parallel runs.** `mutation.fork_server`
72
+ (opt-in, ADR-15) now supports Minitest and `jobs > 1`: the environment is
73
+ booted once and each mutant process is forked from the server. Falls back to
74
+ per-mutant boot with a warning if the server is unavailable.
75
+ - A fidelity check rejects, before execution, every mutant whose activated
76
+ code differs from the original method by more than the reported mutation.
77
+ Examples are a negated condition that lost its parentheses, keyword
78
+ arguments re-rendered as a positional hash, `|x|` re-rendered as `|x,|`, a
79
+ splice shifted by a multibyte character, and a removed assignment that turns
80
+ a later local-variable read into a method call. Such mutants are reported as
81
+ `CompileError` with the reason.
82
+ - `mutation.control_runs` (default `true`): after execution, every subject
83
+ with a detected verdict runs once more with its unmutated method injected.
84
+ If its tests fail, the subject's detected verdicts are reclassified as
85
+ `CompileError`, because the injection itself broke them.
86
+ - Methods defined in a constructor block assigned to a constant
87
+ (`Point = Struct.new(:x) do ... end`, likewise `Data.define` and
88
+ `Class.new`) and in `class << CONST` are now subjects. Top-level
89
+ `def Const.name` methods are subjects as well.
90
+ - `rake oracle` runs a corpus whose verdicts follow from its construction and
91
+ fails on false kills or false survivors. CI runs it.
92
+
93
+ ### Fixed
94
+
95
+ - **Verdict reuse is bound to a reuse context.** `--incremental` reused stale
96
+ `Killed` verdicts after a spec helper, the configuration or the henitai
97
+ version changed. Every cached `Killed` and `Survived` verdict now stores a
98
+ `ReuseContext` fingerprint (test dependency files, henitai version,
99
+ integration, operators, `test_excludes`, `coverage_criteria`) and is reused
100
+ only under an identical one; rows recorded without it are never reused.
101
+ `rake oracle:reuse` reproduced 121 mismatches against fresh runs after a
102
+ helper edit before the fix and none after it.
103
+ - **`--survivors-from` no longer skips survivors on a git heuristic.** The
104
+ selected survivors go through the same content-fingerprint reuse rules as
105
+ `--incremental`, always: new or edited tests and helpers invalidate them,
106
+ reused survivors are reported with `fromCache`, and recipe stubs without a
107
+ source range are always re-executed. `SurvivorTestFilter` is removed.
108
+ The recipe fast path now refreshes coverage (Gate 0) so new or edited tests
109
+ are selected, recipe stubs also run their name-matched tests, and survivor
110
+ reruns never sample (sampling had dropped prior survivors before matching).
111
+ `--force` now also bypasses this reuse. Duplicate programs are collapsed
112
+ before sampling, so a sampled run keeps the same representative as an
113
+ unsampled survivor rerun and its survivors are found again.
114
+ - A survived verdict whose covering-test fingerprint cannot be recomputed keeps
115
+ stored fingerprints only from a survived row of the same subject source,
116
+ never from a killed row or an edited method.
117
+ - Gate 1 source selection has its own per-test coverage instance, so reuse and
118
+ history read the post-bootstrap per-test map.
119
+ - Recipe stubs keep their precomputed stable ID and persist the `replacement`
120
+ source in the history store.
121
+ - **Trend history only records authoritative runs.** `--since`,
122
+ subject-scoped and survivor-rerun results no longer add a `runs` row.
123
+ - **Canonical report merge.** A scoped run that mutated every subject of a file
124
+ replaces that file's prior entry wholesale (and removes it when it has no
125
+ mutants), so stale mutants of edited or deleted code no longer linger.
126
+ `--survivors-from <canonical report>` redirects to the session snapshot only
127
+ when every file entry carries the report's `sessionId`; a file whose every
128
+ prior mutant was re-run takes the new run's provenance.
129
+ - `--since` diffs against the merge base (`REF...HEAD`) instead of the ref's
130
+ tip, so commits that landed on the base branch after the fork point are not
131
+ treated as changes.
132
+ - The dashboard reporter uploads the merged canonical report for scoped runs
133
+ instead of the partial one.
134
+ - `Pending` mutants are excluded from the MS and MSI denominators; MSI is
135
+ `killed / (total - pending)`.
136
+ - **Equivalence detection only flags sound cases.** `false || x` and
137
+ `true && x` (left literal only); `n + 0`, `n * 1` and siblings only for
138
+ Integer/Float literal receivers (not `-0.0`); `==` <-> `eql?` only when both
139
+ operands are string literals; `==` <-> `equal?` for singleton literals.
140
+ Unsound rules (`x || false`, `x && true`, unknown receivers, a single string
141
+ literal) no longer mark mutants `Equivalent`.
142
+ - **`mutation.ignore_patterns` is line-scoped.** A pattern is matched against
143
+ the source line where the mutant's node starts, in `StaticFilter`; matching
144
+ mutants are reported as `Ignored` with an `ignore_reason` instead of being
145
+ dropped at generation time. An enclosing construct whose body matches is no
146
+ longer suppressed. The line is decoded honouring a magic encoding comment,
147
+ so a non-UTF-8 source file no longer aborts filtering.
148
+ - **Review follow-ups.** The slot scheduler no longer signals the process
149
+ group of a reaped pid that owns no mutant slot. Minitest results for tests
150
+ added by reopening a class in another file are attributed to the file that
151
+ defines the method, so coverage-first selection no longer misses them. With
152
+ the fork server active, calibrated timeouts keep the measured suite boot
153
+ (served mutants still run `before(:suite)` hooks). The `fork_server` schema
154
+ description and the remediation plan now state the real scope (RSpec and
155
+ Minitest, any `jobs`).
156
+ - **`--incremental` no longer reuses stale survived verdicts for def-line
157
+ mutants.** Their test selection is widened with name-matched tests that the
158
+ survivor fingerprint does not cover, so survived verdicts for mutants on a
159
+ method's def line always re-execute (killed reuse is unaffected: a kill by
160
+ an unchanged covering test remains valid).
161
+ - **Output capture no longer delays or races on lingering grandchildren.**
162
+ Closing a mutant child's captured stdout/stderr no longer waits up to 1 s
163
+ per stream for EOF and no longer kills the drain thread mid-write (which
164
+ could exceed the cap or write the truncation marker twice). The drain
165
+ thread is stopped cooperatively through a stop pipe and takes what is
166
+ already readable.
167
+ - **Fork-server lifecycle.** A server lost or stalled mid-run no longer turns
168
+ its in-flight mutants into `runtime_error`: they are rerun through the
169
+ per-mutant-boot fallback. Fallback runs use boot-inclusive timeouts. Mutant
170
+ processes forked by the server are cleaned up on timeout, loss and teardown
171
+ even before they lead a process group, and the connection and wake-pipe fds
172
+ are no longer inherited by mutant processes. The parallel event loop no
173
+ longer stalls on an exit event already buffered in the client, and the
174
+ SIGTERM drain window always runs its full length.
175
+ - The "negated condition" mutant now negates the whole condition
176
+ (`!(a > b)`), not just its left operand (`!a > b`).
177
+ - Source files are read as UTF-8 regardless of the process locale. With `LANG`
178
+ unset or `C`, any non-ASCII source file used to abort subject resolution.
179
+ - Per-test coverage now narrows RSpec test selection. RSpec records test files
180
+ as `./spec/x_spec.rb` while candidates arrive as `spec/x_spec.rb`, so the
181
+ lookup never matched and every mutant ran all candidate files.
182
+ - `--since` and the dirty-source and survivor checks now work for a project in
183
+ a sub-directory of its git repository and for non-ASCII file names.
184
+ - A corrupt or locked `mutation-history.sqlite3` no longer discards the run's
185
+ reports. History recording and export now warn instead.
186
+ - Forked mutant children reset the INT, TERM, HUP and CHLD handlers inherited
187
+ from the parent, so a draining SIGTERM is no longer ignored.
188
+ - A zero-example check no longer matches `10 examples, 0 failures`, nor a
189
+ `No examples found.` or `0 examples, 0 failures` quoted in failure output;
190
+ only the run's last summary line counts. Such kills were reported as
191
+ `CompileError`.
192
+ - A run in which harness errors (`CompileError`) left no mutant to score now
193
+ exits `1` with a warning instead of passing its threshold with nothing
194
+ tested.
195
+ - Mutating an `elsif` condition no longer rewrites the whole `if` chain; only
196
+ the condition changes.
197
+ - String parts of interpolated strings, symbols, backticks and regexps, and
198
+ elements of `%w` arrays, are no longer mutated as standalone strings. Their
199
+ replacement text changed the literal's syntax rather than its value.
200
+ - Operator replacements are parenthesised when spliced, so `a && b && c`
201
+ mutated to `a || b` no longer reads as `a || (b && c)`.
202
+ - Two mutants at the same position with the same description no longer share
203
+ one stable identity and history entry.
204
+ - `ReturnValue` mutates only the method's final expression, not every
205
+ expression that happens to equal it.
206
+ - Subject expressions for predicate, bang, setter and operator methods, such
207
+ as `Foo#empty?`, `Foo#save!`, `Foo#name=` and `Foo#==`, select their method
208
+ instead of nothing.
209
+ - Mutants from different operators that produce the same program run once.
210
+ - The implicit value of a shorthand hash pair (`build(config:)`) is no longer
211
+ mutated. Its replacement rewrote the key to `build(nil:)`.
212
+ - A mutant that removes the only assignment of a local variable read later in
213
+ the method is no longer generated. Ruby parses the later read as a method
214
+ call, so every such mutant failed with a `NameError` wherever it ran.
215
+ - Subjects are attributed to the right namespace. A `class ::Top` inside
216
+ `module Outer` is `Top`, not `Outer::Top`. A `def Other.name` inside `Outer`
217
+ belongs to `Other`, not `Outer`. Methods of `class << obj` or `def obj.name`
218
+ for a non-constant `obj` are no longer reported as class methods of the
219
+ enclosing class.
220
+ - "replaced method call with nil" on a call with a block replaces the call
221
+ together with its block. Replacing only the call left `nil { ... }`, which
222
+ does not parse, so these sites were never tested. Blocks attached to
223
+ logger, output and debug calls stay arid.
224
+ - `RegexMutator` no longer drops the interpolations and later lines of a
225
+ dynamic or multi-line regexp, which it now leaves unmutated. It skips
226
+ escaped characters and the contents of character classes, so `[^0-9]` is
227
+ no longer reported as a removed `^` anchor and `[+-]` no longer as a
228
+ quantifier swap.
229
+
230
+ ## [0.5.3] - 2026-08-26
231
+
232
+ ### Fixed
233
+
234
+ - A timed-out Minitest baseline suite is now actually killed. Its child was
235
+ spawned without `pgroup: true`, so the parent's group-wide `SIGTERM` named a
236
+ process group that never existed, raised `ESRCH`, and left the suite running;
237
+ the parent then blocked in `Process.wait` until the suite finished on its own
238
+ — minutes past the timeout it had already reported, and long after the log
239
+ tail quoted in the error had stopped describing reality. The suite child now
240
+ leads its own group, as the RSpec baseline child already did, and post-run
241
+ cleanup signals that group instead of the single pid, so a Rails test server
242
+ or browser driver started by the suite is torn down with it.
243
+
244
+ ## [0.5.2] - 2026-08-26
245
+
246
+ ### Changed
247
+
248
+ - `test_excludes` now also applies to the coverage-bootstrap suite, not only to
249
+ per-mutant test selection. Slow end-to-end or integration suites could be kept
250
+ away from mutant children but still had to run in full during the baseline,
251
+ where the fixed suite timeout aborts the run with
252
+ `Henitai::CoverageError: Configured test suite failed while generating
253
+ coverage` even though the suite itself was green. Excluded files are also
254
+ dropped from the coverage freshness watch list, so a test that no longer
255
+ contributes to the baseline no longer invalidates it. Lines covered only by
256
+ excluded tests now count as uncovered and are not mutated — matching what
257
+ already happened at execution time, where such mutants could never be killed.
258
+ A `test_excludes` set that leaves no test file now aborts with
259
+ `Henitai::ConfigurationError` instead of running an empty suite and then
260
+ reporting missing coverage.
261
+
10
262
  ## [0.5.1] - 2026-08-25
11
263
 
12
264
  ### Changed
265
+
13
266
  - `minitest` runtime dependency widened from `~> 5.25` to `>= 5.25, < 7`.
14
267
  The floor is unchanged; only the ceiling moved. `~> 5.25` capped the
15
268
  range below 6.0, so bundles resolving minitest 6.x could not install
@@ -20,6 +273,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
20
273
  ## [0.5.0] - 2026-08-21
21
274
 
22
275
  ### Security
276
+
23
277
  - `sqlite3` requirement raised from `~> 1.7` to `>= 2.9.5, < 3`, excluding the
24
278
  vulnerable releases up to and including 2.9.4. This is a runtime major bump:
25
279
  applications pinned to `sqlite3` 1.x will need to upgrade before installing
@@ -28,6 +282,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
28
282
  floors so a regression fails the suite rather than shipping quietly
29
283
 
30
284
  ### Changed
285
+
31
286
  - `coverage_criteria` now actually affects scoring. The block was documented,
32
287
  defaulted and validated since it was introduced, but nothing ever read it —
33
288
  `Result` hardcoded the `MS` numerator as `killed + timeout + runtime_error`,
@@ -50,6 +305,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
50
305
  4.0-only form), and CI runs the floor alongside 4.0.2
51
306
 
52
307
  ### Fixed
308
+
53
309
  - Forked mutant children no longer outlive a parent that dies without warning.
54
310
  Children run in their own process group, and all parent-side cleanup —
55
311
  timeout kills, graceful drain, signal traps — only runs while the parent's
@@ -84,6 +340,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
84
340
  0.3.1; this restores it
85
341
 
86
342
  ### Internal
343
+
87
344
  - Test-suite coupling to private implementation details is now guarded by
88
345
  `spec/infra/private_method_reach_spec.rb`, a ratchet: every spec that reaches
89
346
  a private method through `send` or an instance-variable poke carries a
@@ -104,6 +361,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
104
361
  ## [0.4.0] - 2026-07-14
105
362
 
106
363
  ### Added
364
+
107
365
  - `hard` operator set (`mutation.operators: hard` / `--operators hard`), a
108
366
  strict superset of `full` for usually-unkillable mutations (ADR-12):
109
367
  currently `EqualityIdentityOperator` and the new `HashKeyType`
@@ -113,6 +371,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
113
371
  single-pair hashes and double-splat entries are skipped
114
372
 
115
373
  ### Changed
374
+
116
375
  - `full` now means "usually killable": the symbol-key -> string-key mutation
117
376
  moved from `HashLiteral` into the new hard-set `HashKeyType`, and
118
377
  `EqualityIdentityOperator` moved from `full` to `hard` — `full` runs emit
@@ -121,6 +380,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
121
380
  ## [0.3.1] - 2026-07-13
122
381
 
123
382
  ### Fixed
383
+
124
384
  - Per-mutant log capture no longer crashes with
125
385
  `Encoding::UndefinedConversionError` when the host app sets
126
386
  `Encoding.default_internal` (as Rails test environments do) and a child
@@ -130,6 +390,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
130
390
  from corrupting the file.
131
391
 
132
392
  ### Changed
393
+
133
394
  - `--since REF` now includes the working tree: tracked files with
134
395
  uncommitted changes and untracked files count as changed, matching what
135
396
  actually gets tested. Previously only committed changes in `REF..HEAD`
@@ -146,6 +407,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
146
407
  ## [0.3.0] - 2026-07-13
147
408
 
148
409
  ### Added
410
+
149
411
  - `henitai run --incremental`: verdict reuse from the history store. Killed
150
412
  verdicts are reused when the subject's source and every covering test file
151
413
  are byte-identical to what was recorded; Survived verdicts are additionally
@@ -181,6 +443,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
181
443
  moved out of the default light set into its own operator (ADR-10)
182
444
 
183
445
  ### Fixed
446
+
184
447
  - Mutants in `module_function` modules were unkillable: `module_function`
185
448
  copies the method onto the module's singleton and activation only replaced
186
449
  the instance side, so callers kept running original code and every such
@@ -200,6 +463,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
200
463
  - Flaky-retry counter no longer inflates when a respawned child fails again
201
464
 
202
465
  ### Changed
466
+
203
467
  - Terminal reporter renders original nodes from their source slice and reuses
204
468
  a memoized unparse of the mutated node shared with the JSON reporter
205
469
  (a dogfood profile spent 27% of wall time re-unparsing survivors)
@@ -213,6 +477,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
213
477
  redefinition warnings into their logs
214
478
 
215
479
  ### Internal
480
+
216
481
  - Architecture documentation gained §8.11 (incremental verdict reuse) and
217
482
  ADR-11 (content-fingerprint verdict reuse, not git scoping); new backlog
218
483
  tickets for CI warm verdict cache, child null formatter, and auto-`--since`
@@ -224,12 +489,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
224
489
  ## [0.2.1] - 2026-06-25
225
490
 
226
491
  ### Fixed
492
+
227
493
  - `henitai run` now exits 0 when a run evaluates no valid mutants, instead of
228
494
  failing a CI gate on a vacuous result
229
495
  - Flaky-retry counts are now recorded on the parallel execution path (they were
230
496
  always reported as 0 regardless of actual retries)
231
497
 
232
498
  ### Changed
499
+
233
500
  - `Result` and `Reporter::Json` take their IO as an injected dependency,
234
501
  restoring the domain/infrastructure boundary (no public API change)
235
502
  - Decomposed the monolithic `integration.rb` into single-responsibility files
@@ -238,6 +505,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
238
505
  - Narrowed broad rescues in `safe_unparse` and per-test coverage snapshotting
239
506
 
240
507
  ### Internal
508
+
241
509
  - De-mocked runner specs, added `process_wakeup` and helper coverage, and
242
510
  removed sleep- and chdir-based flakiness from the suite
243
511
  - Enabled full-operator dogfooding configuration and isolated the
@@ -247,6 +515,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
247
515
  ## [0.2.0] - 2026-04-30
248
516
 
249
517
  ### Added
518
+
250
519
  - `--survivors-from <path>` flag on `henitai run` for survivor-only reruns:
251
520
  re-execute only the mutants that survived a prior full run without re-running
252
521
  the whole suite
@@ -281,6 +550,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
281
550
  - x86_64 platform added to gem platform list
282
551
 
283
552
  ### Changed
553
+
284
554
  - JSON mutation report vendor extension now always includes `sessionId`
285
555
  (and `gitSha` when available) to support survivor-only reruns
286
556
  - Terminal reporter labels partial reruns as "Partial survivor rerun" and
@@ -295,6 +565,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
295
565
  `max_flaky_retries: 3` added
296
566
 
297
567
  ### Fixed
568
+
298
569
  - Minitest autorun hook suppressed in mutation child processes to prevent
299
570
  spurious re-runs of the full suite inside each fork
300
571
  - Coverage bootstrap RSpec subprocess ARGV leakage resolved — child processes
@@ -307,6 +578,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
307
578
  preventing stale cache hits after edits
308
579
 
309
580
  ### Performance
581
+
310
582
  - File discovery cached in the integration layer; repeated calls within one
311
583
  run no longer re-scan the filesystem
312
584
  - Polling sleep removed from the scheduler hot loop; slots are refilled
@@ -315,6 +587,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
315
587
  ## [0.1.10] - 2026-04-16
316
588
 
317
589
  ### Fixed
590
+
318
591
  - RuboCop `RSpec/MultipleExpectations` offenses in `coverage_formatter_spec` and
319
592
  `per_test_coverage_collector_spec` resolved by splitting each two-assertion
320
593
  example into focused single-expectation examples
@@ -322,6 +595,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
322
595
  ## [0.1.9] - 2026-04-16
323
596
 
324
597
  ### Fixed
598
+
325
599
  - SimpleCov is now suppressed during Minitest mutant child runs: `SimpleCov.start`
326
600
  is turned into a no-op before test files are required, eliminating the
327
601
  "Stopped processing SimpleCov as a previous error has been detected" warning
@@ -330,12 +604,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
330
604
  ## [0.1.8] - 2026-04-16
331
605
 
332
606
  ### Added
607
+
333
608
  - Minitest integration now supports per-test coverage: a `MinitestCoverageReporter`
334
609
  snapshots `Coverage.peek_result` after each test and writes `henitai_per_test.json`,
335
610
  enabling targeted mutation runs that only execute the tests covering the mutated lines —
336
611
  the same efficiency that was previously available only to RSpec projects
337
612
 
338
613
  ### Fixed
614
+
339
615
  - RSpec integration now respects `--exclude-pattern` entries in `.rspec` so excluded
340
616
  spec files are not passed to the runner during mutation runs
341
617
  - Per-test source file filter corrected to check only the project-relative path prefix
@@ -345,11 +621,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
345
621
  ## [0.1.7] - 2026-04-14
346
622
 
347
623
  ### Added
624
+
348
625
  - Committed integration smoke projects for RSpec and Minitest, runnable via
349
626
  `bundle exec rake smoke:integration:all`, to exercise `henitai` against
350
627
  small real projects that depend on the local source checkout
351
628
 
352
629
  ### Fixed
630
+
353
631
  - Child-process stdout/stderr restoration after captured test runs now keeps
354
632
  the standard streams usable, preventing passing mutant executions from being
355
633
  misclassified because the child exited with a closed stdio handle
@@ -360,6 +638,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
360
638
  ## [0.1.6] - 2026-04-14
361
639
 
362
640
  ### Fixed
641
+
363
642
  - Minitest is no longer required eagerly when `henitai/integration` loads, so
364
643
  projects that do not include Minitest in their bundle can still boot and use
365
644
  non-Minitest integrations without a parent-process `LoadError`
@@ -367,6 +646,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
367
646
  ## [0.1.5] - 2026-04-14
368
647
 
369
648
  ### Fixed
649
+
370
650
  - Steep type errors in `Runner` after removing targeted-run bootstrap scoping:
371
651
  updated `bootstrap_mutants` RBS signature to match the new single-argument
372
652
  form and removed stale signatures for `refresh_coverage_for_targeted_run`,
@@ -375,6 +655,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
375
655
  workspace setup and report writing into helper methods
376
656
 
377
657
  ### Changed
658
+
378
659
  - Targeted-run coverage bootstrap no longer scopes the initial run to the
379
660
  subject's test files; the full suite is always used for the baseline,
380
661
  trading a minor performance optimisation for reliability
@@ -382,6 +663,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
382
663
  ## [0.1.4] - 2026-04-14
383
664
 
384
665
  ### Fixed
666
+
385
667
  - `StringLiteral` operator no longer generates no-op mutations where the
386
668
  replacement equals the original value (e.g. the spurious `"" → ""` mutant
387
669
  that was emitted for methods already returning an empty string literal)
@@ -397,6 +679,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
397
679
  runner's configured reports directory
398
680
 
399
681
  ### Changed
682
+
400
683
  - `ScenarioExecutionResult.build` factory method consolidates status and
401
684
  exit-status derivation that was previously spread across `Integration`,
402
685
  reducing the mutation surface of the value object
@@ -404,6 +687,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
404
687
  ## [0.1.3] - 2026-04-13
405
688
 
406
689
  ### Added
690
+
407
691
  - Four new mutation operators: `UnaryOperator` (negates boolean and numeric
408
692
  unary expressions), `UpdateOperator` (swaps `+=`/`-=`/`*=` and targets
409
693
  compound-assignment nodes), `RegexMutator` (replaces regex literals with
@@ -422,6 +706,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
422
706
  `x || false`, `true && x`, `x && true` are suppressed as equivalent mutants
423
707
 
424
708
  ### Changed
709
+
425
710
  - Per-line mutation cap (`max_mutants_per_line`) removed from the generator,
426
711
  configuration schema, and validator — see ADR-08. All syntactically valid
427
712
  mutations on a line are now generated unconditionally
@@ -436,6 +721,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
436
721
  without reflection
437
722
 
438
723
  ### Performance
724
+
439
725
  - Coverage bootstrap freshness check: the baseline RSpec run is skipped when
440
726
  `.resultset.json` is newer than every watched source and test file,
441
727
  eliminating ~83 % of bootstrap wall time on repeated runs within a session
@@ -457,6 +743,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
457
743
  AST node
458
744
 
459
745
  ### Fixed
746
+
460
747
  - Mutant child processes now run in isolated process groups (`setpgid`);
461
748
  `cleanup_process_group` sends `SIGTERM` to the entire group on timeout or
462
749
  error, preventing orphaned subprocesses
@@ -472,10 +759,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
472
759
  ## [0.1.2] - 2026-04-07
473
760
 
474
761
  ### Added
762
+
475
763
  - Method coverage is now enabled in both RSpec and Minitest bootstraps, and
476
764
  the static filter merges method-level coverage into the line map
477
765
 
478
766
  ### Fixed
767
+
479
768
  - Coverage baseline regeneration now happens on every `henitai run`, so stale
480
769
  coverage state does not leak between runs
481
770
  - Coverage handling now accepts symbol-keyed `Coverage.peek_result` output and
@@ -492,6 +781,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
492
781
  ## [0.1.1] - 2026-04-03
493
782
 
494
783
  ### Added
784
+
495
785
  - Minitest integration for Rails projects: injects SimpleCov for coverage
496
786
  collection, sets `RAILS_ENV=test` and `PARALLEL_WORKERS=1` in the baseline
497
787
  subprocess, preloads `config/environment.rb` before mutant activation, adds
@@ -499,6 +789,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
499
789
  - `simplecov` runtime dependency (required by the Minitest integration)
500
790
 
501
791
  ### Fixed
792
+
502
793
  - `rspec/core` was unconditionally required at load time, causing a `LoadError`
503
794
  in projects that do not have RSpec installed — now loaded lazily only when the
504
795
  RSpec integration is used
@@ -508,6 +799,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
508
799
  ## [0.1.0] - 2026-03-01
509
800
 
510
801
  ### Added
802
+
511
803
  - Initial gem scaffold with Ruby 4.0.2 support
512
804
  - Dev Container configuration (official `ruby:4.0.2-alpine` base image, Codex CLI preinstalled)
513
805
  - CI pipeline (RuboCop + RSpec + incremental mutation testing on PRs)
@@ -516,7 +808,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
516
808
  - CLI critical path: `henitai run` now executes the full pipeline, supports `--since`, returns CI-friendly exit codes, and `henitai version` prints `Henitai::VERSION`
517
809
  - RSpec per-test coverage output: `henitai/coverage_formatter` now writes `coverage/henitai_per_test.json`
518
810
 
519
- [Unreleased]: https://github.com/martinotten/henitai/compare/v0.5.1...HEAD
811
+ [Unreleased]: https://github.com/martinotten/henitai/compare/v0.6.0...HEAD
812
+ [0.6.0]: https://github.com/martinotten/henitai/compare/v0.5.3...v0.6.0
813
+ [0.5.3]: https://github.com/martinotten/henitai/compare/v0.5.2...v0.5.3
814
+ [0.5.2]: https://github.com/martinotten/henitai/compare/v0.5.1...v0.5.2
520
815
  [0.5.1]: https://github.com/martinotten/henitai/compare/v0.5.0...v0.5.1
521
816
  [0.5.0]: https://github.com/martinotten/henitai/compare/v0.4.0...v0.5.0
522
817
  [0.4.0]: https://github.com/martinotten/henitai/compare/v0.3.1...v0.4.0