evilution 0.23.0 → 0.25.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 (106) hide show
  1. checksums.yaml +4 -4
  2. data/.beads/interactions.jsonl +210 -0
  3. data/CHANGELOG.md +51 -0
  4. data/README.md +81 -4
  5. data/exe/evil +6 -0
  6. data/lib/evilution/ast/source_surgeon.rb +15 -1
  7. data/lib/evilution/cli/commands/compare.rb +68 -0
  8. data/lib/evilution/cli/parser/command_extractor.rb +78 -0
  9. data/lib/evilution/cli/parser/file_args.rb +41 -0
  10. data/lib/evilution/cli/parser/options_builder.rb +123 -0
  11. data/lib/evilution/cli/parser/stdin_reader.rb +28 -0
  12. data/lib/evilution/cli/parser.rb +27 -196
  13. data/lib/evilution/cli/printers/compare.rb +159 -0
  14. data/lib/evilution/cli.rb +1 -0
  15. data/lib/evilution/compare/categorizer.rb +109 -0
  16. data/lib/evilution/compare/detector.rb +21 -0
  17. data/lib/evilution/compare/fingerprint.rb +83 -0
  18. data/lib/evilution/compare/normalizer.rb +106 -0
  19. data/lib/evilution/compare/record.rb +16 -0
  20. data/lib/evilution/compare.rb +15 -0
  21. data/lib/evilution/config.rb +178 -3
  22. data/lib/evilution/example_filter.rb +143 -0
  23. data/lib/evilution/integration/base.rb +11 -57
  24. data/lib/evilution/integration/crash_detector.rb +5 -2
  25. data/lib/evilution/integration/minitest.rb +25 -7
  26. data/lib/evilution/integration/minitest_crash_detector.rb +5 -2
  27. data/lib/evilution/integration/rspec.rb +99 -12
  28. data/lib/evilution/isolation/fork.rb +26 -0
  29. data/lib/evilution/isolation/in_process.rb +1 -0
  30. data/lib/evilution/mcp/info_tool.rb +77 -5
  31. data/lib/evilution/mcp/mutate_tool/config_builder.rb +20 -0
  32. data/lib/evilution/mcp/mutate_tool/error_payload.rb +17 -0
  33. data/lib/evilution/mcp/mutate_tool/option_parser.rb +54 -0
  34. data/lib/evilution/mcp/mutate_tool/progress_streamer.rb +37 -0
  35. data/lib/evilution/mcp/mutate_tool/report_trimmer.rb +31 -0
  36. data/lib/evilution/mcp/mutate_tool/survived_enricher.rb +52 -0
  37. data/lib/evilution/mcp/mutate_tool.rb +34 -186
  38. data/lib/evilution/mutation.rb +43 -3
  39. data/lib/evilution/mutator/base.rb +39 -1
  40. data/lib/evilution/mutator/operator/argument_nil_substitution.rb +5 -1
  41. data/lib/evilution/mutator/operator/argument_removal.rb +5 -1
  42. data/lib/evilution/parallel/work_queue.rb +149 -31
  43. data/lib/evilution/parallel_db_warning.rb +68 -0
  44. data/lib/evilution/reporter/cli.rb +38 -11
  45. data/lib/evilution/reporter/html/assets/style.css +85 -0
  46. data/lib/evilution/reporter/html/baseline_keys.rb +28 -0
  47. data/lib/evilution/reporter/html/diff_formatter.rb +27 -0
  48. data/lib/evilution/reporter/html/escape.rb +12 -0
  49. data/lib/evilution/reporter/html/namespace.rb +11 -0
  50. data/lib/evilution/reporter/html/report.rb +68 -0
  51. data/lib/evilution/reporter/html/section.rb +21 -0
  52. data/lib/evilution/reporter/html/sections/baseline_comparison.rb +46 -0
  53. data/lib/evilution/reporter/html/sections/error_details.rb +30 -0
  54. data/lib/evilution/reporter/html/sections/error_entry.rb +22 -0
  55. data/lib/evilution/reporter/html/sections/file_section.rb +62 -0
  56. data/lib/evilution/reporter/html/sections/header.rb +29 -0
  57. data/lib/evilution/reporter/html/sections/mutation_map.rb +32 -0
  58. data/lib/evilution/reporter/html/sections/neutral_details.rb +25 -0
  59. data/lib/evilution/reporter/html/sections/summary_cards.rb +11 -0
  60. data/lib/evilution/reporter/html/sections/survived_details.rb +35 -0
  61. data/lib/evilution/reporter/html/sections/survived_entry.rb +36 -0
  62. data/lib/evilution/reporter/html/sections/truncation_notice.rb +17 -0
  63. data/lib/evilution/reporter/html/sections/unparseable_details.rb +25 -0
  64. data/lib/evilution/reporter/html/sections/unresolved_details.rb +25 -0
  65. data/lib/evilution/reporter/html/sections.rb +4 -0
  66. data/lib/evilution/reporter/html/stylesheet.rb +14 -0
  67. data/lib/evilution/reporter/html/templates/baseline_comparison.html.erb +8 -0
  68. data/lib/evilution/reporter/html/templates/error_details.html.erb +6 -0
  69. data/lib/evilution/reporter/html/templates/error_entry.html.erb +10 -0
  70. data/lib/evilution/reporter/html/templates/file_section.html.erb +12 -0
  71. data/lib/evilution/reporter/html/templates/header.html.erb +4 -0
  72. data/lib/evilution/reporter/html/templates/mutation_map.html.erb +6 -0
  73. data/lib/evilution/reporter/html/templates/neutral_details.html.erb +14 -0
  74. data/lib/evilution/reporter/html/templates/report.html.erb +17 -0
  75. data/lib/evilution/reporter/html/templates/summary_cards.html.erb +26 -0
  76. data/lib/evilution/reporter/html/templates/survived_details.html.erb +21 -0
  77. data/lib/evilution/reporter/html/templates/survived_entry.html.erb +8 -0
  78. data/lib/evilution/reporter/html/templates/truncation_notice.html.erb +1 -0
  79. data/lib/evilution/reporter/html/templates/unparseable_details.html.erb +11 -0
  80. data/lib/evilution/reporter/html/templates/unresolved_details.html.erb +11 -0
  81. data/lib/evilution/reporter/html.rb +11 -390
  82. data/lib/evilution/reporter/json.rb +19 -9
  83. data/lib/evilution/reporter/suggestion/diff_helpers.rb +28 -0
  84. data/lib/evilution/reporter/suggestion/registry.rb +64 -0
  85. data/lib/evilution/reporter/suggestion/templates/generic.rb +55 -0
  86. data/lib/evilution/reporter/suggestion/templates/minitest.rb +659 -0
  87. data/lib/evilution/reporter/suggestion/templates/rspec.rb +613 -0
  88. data/lib/evilution/reporter/suggestion.rb +8 -1327
  89. data/lib/evilution/result/mutation_result.rb +9 -1
  90. data/lib/evilution/result/summary.rb +21 -1
  91. data/lib/evilution/runner/baseline_runner.rb +92 -0
  92. data/lib/evilution/runner/diagnostics.rb +105 -0
  93. data/lib/evilution/runner/isolation_resolver.rb +134 -0
  94. data/lib/evilution/runner/mutation_executor.rb +325 -0
  95. data/lib/evilution/runner/mutation_planner.rb +126 -0
  96. data/lib/evilution/runner/report_publisher.rb +60 -0
  97. data/lib/evilution/runner/subject_pipeline.rb +121 -0
  98. data/lib/evilution/runner.rb +61 -692
  99. data/lib/evilution/source_ast_cache.rb +39 -0
  100. data/lib/evilution/spec_ast_cache.rb +166 -0
  101. data/lib/evilution/spec_resolver.rb +6 -1
  102. data/lib/evilution/spec_selector.rb +39 -0
  103. data/lib/evilution/temp_dir_tracker.rb +23 -3
  104. data/lib/evilution/version.rb +1 -1
  105. data/script/memory_check +7 -5
  106. metadata +75 -2
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "digest"
4
+ require "prism"
5
+ require_relative "../evilution"
6
+
7
+ # Content-hash-keyed LRU of Prism::ParseResult. Different source bytes always
8
+ # yield a different key, so the cache is valid for the lifetime of the process.
9
+ class Evilution::SourceAstCache
10
+ DEFAULT_MAX_ENTRIES = 50
11
+ private_constant :DEFAULT_MAX_ENTRIES
12
+
13
+ def initialize(max_entries: DEFAULT_MAX_ENTRIES)
14
+ @max_entries = max_entries
15
+ @entries = {}
16
+ end
17
+
18
+ def fetch(source)
19
+ return Prism.parse(source) if @max_entries <= 0
20
+
21
+ key = Digest::SHA256.digest(source)
22
+ if @entries.key?(key)
23
+ result = @entries.delete(key)
24
+ @entries[key] = result
25
+ return result
26
+ end
27
+
28
+ result = Prism.parse(source)
29
+ @entries[key] = result
30
+ evict_until_within_bounds
31
+ result
32
+ end
33
+
34
+ private
35
+
36
+ def evict_until_within_bounds
37
+ @entries.shift while @entries.length > @max_entries
38
+ end
39
+ end
@@ -0,0 +1,166 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "prism"
4
+ require_relative "../evilution"
5
+
6
+ class Evilution::SpecAstCache
7
+ Block = Struct.new(:kind, :line, :end_line, :body_text)
8
+
9
+ BLOCK_METHODS = %i[
10
+ describe context fcontext xcontext
11
+ it fit xit specify
12
+ before after
13
+ ].freeze
14
+ private_constant :BLOCK_METHODS
15
+
16
+ DEFAULT_MAX_FILES = 50
17
+ DEFAULT_MAX_BLOCKS = 10_000
18
+ private_constant :DEFAULT_MAX_FILES, :DEFAULT_MAX_BLOCKS
19
+
20
+ def initialize(max_files: DEFAULT_MAX_FILES, max_blocks: DEFAULT_MAX_BLOCKS)
21
+ @max_files = max_files
22
+ @max_blocks = max_blocks
23
+ @entries = {}
24
+ @total_blocks = 0
25
+ end
26
+
27
+ def fetch(path)
28
+ if @entries.key?(path)
29
+ blocks = @entries.delete(path)
30
+ @entries[path] = blocks
31
+ return blocks
32
+ end
33
+
34
+ blocks = parse(path)
35
+ insert(path, blocks)
36
+ blocks
37
+ end
38
+
39
+ def cached?(path)
40
+ @entries.key?(path)
41
+ end
42
+
43
+ private
44
+
45
+ def insert(path, blocks)
46
+ @entries[path] = blocks
47
+ @total_blocks += blocks.length
48
+ evict_until_within_bounds
49
+ end
50
+
51
+ def evict_until_within_bounds
52
+ while @entries.length > @max_files || @total_blocks > @max_blocks
53
+ break if @entries.empty?
54
+
55
+ oldest_path = @entries.keys.first
56
+ evicted = @entries.delete(oldest_path)
57
+ @total_blocks -= evicted.length
58
+ end
59
+ end
60
+
61
+ def parse(path)
62
+ raise Evilution::ParseError.new("file not found: #{path}", file: path) unless File.exist?(path)
63
+
64
+ source = read_source(path)
65
+ result = Prism.parse(source)
66
+
67
+ if result.failure?
68
+ raise Evilution::ParseError.new(
69
+ "failed to parse #{path}: #{result.errors.map(&:message).join(", ")}",
70
+ file: path
71
+ )
72
+ end
73
+
74
+ comment_ranges = result.comments
75
+ .map { |c| c.location.start_offset...c.location.end_offset }
76
+ .sort_by(&:begin)
77
+ collector = BlockCollector.new(source, comment_ranges)
78
+ collector.visit(result.value)
79
+ collector.blocks
80
+ end
81
+
82
+ def read_source(path)
83
+ File.read(path)
84
+ rescue SystemCallError => e
85
+ raise Evilution::ParseError.new("cannot read #{path}: #{e.message}", file: path)
86
+ end
87
+
88
+ class BlockCollector < Prism::Visitor
89
+ attr_reader :blocks
90
+
91
+ def initialize(source, comment_ranges)
92
+ @source = source
93
+ @comment_ranges = comment_ranges
94
+ @blocks = []
95
+ super()
96
+ end
97
+
98
+ def visit_call_node(node)
99
+ @blocks << build_block(node) if block_method?(node)
100
+ super
101
+ end
102
+
103
+ private
104
+
105
+ def block_method?(node)
106
+ BLOCK_METHODS.include?(node.name) && node.block
107
+ end
108
+
109
+ def build_block(node)
110
+ location = node.location
111
+ Block.new(
112
+ node.name,
113
+ location.start_line,
114
+ location.end_line,
115
+ extract_body_text(node)
116
+ )
117
+ end
118
+
119
+ def extract_body_text(node)
120
+ block = node.block
121
+ return "" unless block
122
+
123
+ body = block.body
124
+ return "" unless body
125
+
126
+ start_off = body.location.start_offset
127
+ end_off = body.location.end_offset
128
+ slice = @source.byteslice(start_off, end_off - start_off) || ""
129
+ stripped = strip_comments(slice, start_off)
130
+ stripped.downcase
131
+ end
132
+
133
+ def strip_comments(slice, base_offset)
134
+ return slice if @comment_ranges.empty?
135
+
136
+ ranges = comment_ranges_within(base_offset, base_offset + slice.bytesize)
137
+ return slice if ranges.empty?
138
+
139
+ result = +""
140
+ cursor = base_offset
141
+ ranges.each do |range|
142
+ result << @source.byteslice(cursor, range.begin - cursor)
143
+ cursor = range.end
144
+ end
145
+ result << @source.byteslice(cursor, base_offset + slice.bytesize - cursor)
146
+ result
147
+ end
148
+
149
+ def comment_ranges_within(start_off, end_off)
150
+ lower = @comment_ranges.bsearch_index { |r| r.begin >= start_off }
151
+ return [] unless lower
152
+
153
+ result = []
154
+ idx = lower
155
+ while idx < @comment_ranges.length
156
+ range = @comment_ranges[idx]
157
+ break if range.begin >= end_off
158
+
159
+ result << range if range.end <= end_off
160
+ idx += 1
161
+ end
162
+ result
163
+ end
164
+ end
165
+ private_constant :BlockCollector
166
+ end
@@ -10,11 +10,12 @@ class Evilution::SpecResolver
10
10
  @request_dir = request_dir
11
11
  end
12
12
 
13
- def call(source_path)
13
+ def call(source_path, spec_pattern: nil)
14
14
  return nil if source_path.nil? || source_path.empty?
15
15
 
16
16
  normalized = normalize_path(source_path)
17
17
  candidates = candidate_test_paths(normalized)
18
+ candidates = filter_by_pattern(candidates, spec_pattern) if spec_pattern
18
19
  candidates.find { |path| File.exist?(path) }
19
20
  end
20
21
 
@@ -24,6 +25,10 @@ class Evilution::SpecResolver
24
25
 
25
26
  private
26
27
 
28
+ def filter_by_pattern(candidates, pattern)
29
+ candidates.select { |path| File.fnmatch?(pattern, path, File::FNM_PATHNAME | File::FNM_EXTGLOB) }
30
+ end
31
+
27
32
  def normalize_path(path)
28
33
  path = path.delete_prefix("./")
29
34
  path = path.delete_prefix("#{Dir.pwd}/") if path.start_with?("/")
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "spec_resolver"
4
+
5
+ class Evilution::SpecSelector
6
+ def initialize(spec_files: [], spec_mappings: {}, spec_pattern: nil, spec_resolver: Evilution::SpecResolver.new)
7
+ @spec_files = Array(spec_files)
8
+ @spec_mappings = spec_mappings || {}
9
+ @spec_pattern = spec_pattern
10
+ @spec_resolver = spec_resolver
11
+ end
12
+
13
+ def call(source_path)
14
+ return @spec_files unless @spec_files.empty?
15
+
16
+ mapped = mapping_for(source_path)
17
+ if mapped
18
+ existing = mapped.select { |path| File.exist?(path) }
19
+ return existing unless existing.empty?
20
+ end
21
+
22
+ resolved = @spec_resolver.call(source_path, spec_pattern: @spec_pattern)
23
+ resolved ? [resolved] : nil
24
+ end
25
+
26
+ private
27
+
28
+ def mapping_for(source_path)
29
+ @spec_mappings[normalize(source_path)]
30
+ end
31
+
32
+ def normalize(path)
33
+ return path if path.nil?
34
+
35
+ normalized = path.to_s
36
+ normalized = normalized.delete_prefix("#{Dir.pwd}/") if normalized.start_with?("/")
37
+ normalized.delete_prefix("./")
38
+ end
39
+ end
@@ -21,9 +21,15 @@ module Evilution::TempDirTracker
21
21
  end
22
22
 
23
23
  def self.cleanup_all
24
- @monitor.synchronize do
25
- @dirs.each { |d| FileUtils.rm_rf(d) }
26
- @dirs.clear
24
+ # Trap-safe: Signal.trap handlers forbid Monitor#synchronize, so both the
25
+ # snapshot and the per-dir tracking removal fall back to a lock-free path
26
+ # when ThreadError is raised. Successful removals drop the entry from
27
+ # @dirs; failures stay tracked so a later cleanup can retry.
28
+ snapshot_tracked_dirs.each do |d|
29
+ FileUtils.rm_rf(d)
30
+ remove_from_tracking(d)
31
+ rescue StandardError
32
+ nil
27
33
  end
28
34
  end
29
35
 
@@ -36,4 +42,18 @@ module Evilution::TempDirTracker
36
42
  @at_exit_registered = true
37
43
  end
38
44
  private_class_method :register_at_exit
45
+
46
+ def self.snapshot_tracked_dirs
47
+ @monitor.synchronize { @dirs.to_a }
48
+ rescue ThreadError
49
+ @dirs.to_a
50
+ end
51
+ private_class_method :snapshot_tracked_dirs
52
+
53
+ def self.remove_from_tracking(dir)
54
+ @monitor.synchronize { @dirs.delete(dir) }
55
+ rescue ThreadError
56
+ @dirs.delete(dir)
57
+ end
58
+ private_class_method :remove_from_tracking
39
59
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Evilution
4
- VERSION = "0.23.0"
4
+ VERSION = "0.25.0"
5
5
  end
data/script/memory_check CHANGED
@@ -7,8 +7,8 @@ require_relative "../lib/evilution/integration/rspec"
7
7
 
8
8
  FIXTURE = File.expand_path("../spec/support/fixtures/simple_class.rb", __dir__)
9
9
  FIXTURE_SPEC = File.expand_path("../spec/support/fixtures/simple_class_spec.rb", __dir__)
10
- COMPLEX_FIXTURE = File.expand_path("../lib/evilution/config.rb", __dir__)
11
- COMPLEX_FIXTURE_SPEC = File.expand_path("../spec/evilution/config_spec.rb", __dir__)
10
+ COMPLEX_FIXTURE = File.expand_path("../spec/support/fixtures/memory_check/target.rb", __dir__)
11
+ COMPLEX_FIXTURE_SPEC = File.expand_path("../spec/support/fixtures/memory_check/target_examples.rb", __dir__)
12
12
  ITERATIONS = Integer(ENV.fetch("MEMORY_CHECK_ITERATIONS", 50))
13
13
  MAX_GROWTH_KB = Integer(ENV.fetch("MEMORY_CHECK_MAX_GROWTH_KB", 10_240))
14
14
 
@@ -95,8 +95,10 @@ if mutations.size >= 2
95
95
  end
96
96
 
97
97
  # 5. RSpec integration per-mutation with complex fixture
98
- # Uses Config (227 LOC, 73 specs, 564 mutations) for realistic per-mutation load:
99
- # more ExampleGroup subclasses, deeper spec nesting, heavier metadata.
98
+ # Uses a frozen snapshot under spec/support/fixtures/memory_check/ for
99
+ # realistic per-mutation load: deep spec nesting, heavy metadata. Snapshot
100
+ # is intentional - decoupling the budget from project file growth so this
101
+ # check measures integration plumbing memory, not project size.
100
102
  complex_parser = Evilution::AST::Parser.new
101
103
  complex_registry = Evilution::Mutator::Registry.default
102
104
  complex_subjects = complex_parser.call(COMPLEX_FIXTURE)
@@ -105,7 +107,7 @@ complex_mutations = complex_subjects.flat_map { |s| complex_registry.mutations_f
105
107
  integration = Evilution::Integration::RSpec.new(test_files: [COMPLEX_FIXTURE_SPEC])
106
108
 
107
109
  # Budget is generous: per-mutation require() adds the file to $LOADED_FEATURES and
108
- # accumulates constant-redefinition warnings. Local runs land around 20-25 MB; CI
110
+ # accumulates constant-redefinition warnings. Local runs land around 20 MB; CI
109
111
  # varies up to ~30 MB depending on Ruby build and GC pressure, so we leave headroom.
110
112
  all_passed &= run_check("RSpec integration per-mutation (Config)", iterations: 20, max_growth_kb: 40_960) do
111
113
  mutation = complex_mutations.sample
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: evilution
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.23.0
4
+ version: 0.25.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Denis Kiselev
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-04-14 00:00:00.000000000 Z
11
+ date: 2026-04-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: diff-lcs
@@ -56,6 +56,7 @@ description: Evilution is a mutation testing tool for Ruby. It validates test su
56
56
  email:
57
57
  - denis.kiselyov@gmail.com
58
58
  executables:
59
+ - evil
59
60
  - evilution
60
61
  extensions: []
61
62
  extra_rdoc_files: []
@@ -83,6 +84,7 @@ files:
83
84
  - docs/ast_pattern_syntax.md
84
85
  - docs/isolation.md
85
86
  - docs/mutation_density_benchmark.md
87
+ - exe/evil
86
88
  - exe/evilution
87
89
  - lib/evilution.rb
88
90
  - lib/evilution/ast.rb
@@ -99,6 +101,7 @@ files:
99
101
  - lib/evilution/cli.rb
100
102
  - lib/evilution/cli/command.rb
101
103
  - lib/evilution/cli/commands.rb
104
+ - lib/evilution/cli/commands/compare.rb
102
105
  - lib/evilution/cli/commands/environment_show.rb
103
106
  - lib/evilution/cli/commands/init.rb
104
107
  - lib/evilution/cli/commands/mcp.rb
@@ -114,7 +117,12 @@ files:
114
117
  - lib/evilution/cli/dispatcher.rb
115
118
  - lib/evilution/cli/parsed_args.rb
116
119
  - lib/evilution/cli/parser.rb
120
+ - lib/evilution/cli/parser/command_extractor.rb
121
+ - lib/evilution/cli/parser/file_args.rb
122
+ - lib/evilution/cli/parser/options_builder.rb
123
+ - lib/evilution/cli/parser/stdin_reader.rb
117
124
  - lib/evilution/cli/printers.rb
125
+ - lib/evilution/cli/printers/compare.rb
118
126
  - lib/evilution/cli/printers/environment.rb
119
127
  - lib/evilution/cli/printers/session_detail.rb
120
128
  - lib/evilution/cli/printers/session_diff.rb
@@ -123,6 +131,12 @@ files:
123
131
  - lib/evilution/cli/printers/tests_list.rb
124
132
  - lib/evilution/cli/printers/util_mutation.rb
125
133
  - lib/evilution/cli/result.rb
134
+ - lib/evilution/compare.rb
135
+ - lib/evilution/compare/categorizer.rb
136
+ - lib/evilution/compare/detector.rb
137
+ - lib/evilution/compare/fingerprint.rb
138
+ - lib/evilution/compare/normalizer.rb
139
+ - lib/evilution/compare/record.rb
126
140
  - lib/evilution/config.rb
127
141
  - lib/evilution/disable_comment.rb
128
142
  - lib/evilution/equivalent.rb
@@ -135,6 +149,7 @@ files:
135
149
  - lib/evilution/equivalent/heuristic/method_body_nil.rb
136
150
  - lib/evilution/equivalent/heuristic/noop_source.rb
137
151
  - lib/evilution/equivalent/heuristic/void_context.rb
152
+ - lib/evilution/example_filter.rb
138
153
  - lib/evilution/git.rb
139
154
  - lib/evilution/git/changed_files.rb
140
155
  - lib/evilution/hooks.rb
@@ -152,6 +167,12 @@ files:
152
167
  - lib/evilution/mcp.rb
153
168
  - lib/evilution/mcp/info_tool.rb
154
169
  - lib/evilution/mcp/mutate_tool.rb
170
+ - lib/evilution/mcp/mutate_tool/config_builder.rb
171
+ - lib/evilution/mcp/mutate_tool/error_payload.rb
172
+ - lib/evilution/mcp/mutate_tool/option_parser.rb
173
+ - lib/evilution/mcp/mutate_tool/progress_streamer.rb
174
+ - lib/evilution/mcp/mutate_tool/report_trimmer.rb
175
+ - lib/evilution/mcp/mutate_tool/survived_enricher.rb
155
176
  - lib/evilution/mcp/server.rb
156
177
  - lib/evilution/mcp/session_diff_tool.rb
157
178
  - lib/evilution/mcp/session_list_tool.rb
@@ -239,24 +260,76 @@ files:
239
260
  - lib/evilution/parallel.rb
240
261
  - lib/evilution/parallel/pool.rb
241
262
  - lib/evilution/parallel/work_queue.rb
263
+ - lib/evilution/parallel_db_warning.rb
242
264
  - lib/evilution/rails_detector.rb
243
265
  - lib/evilution/related_spec_heuristic.rb
244
266
  - lib/evilution/reporter.rb
245
267
  - lib/evilution/reporter/cli.rb
246
268
  - lib/evilution/reporter/html.rb
269
+ - lib/evilution/reporter/html/assets/style.css
270
+ - lib/evilution/reporter/html/baseline_keys.rb
271
+ - lib/evilution/reporter/html/diff_formatter.rb
272
+ - lib/evilution/reporter/html/escape.rb
273
+ - lib/evilution/reporter/html/namespace.rb
274
+ - lib/evilution/reporter/html/report.rb
275
+ - lib/evilution/reporter/html/section.rb
276
+ - lib/evilution/reporter/html/sections.rb
277
+ - lib/evilution/reporter/html/sections/baseline_comparison.rb
278
+ - lib/evilution/reporter/html/sections/error_details.rb
279
+ - lib/evilution/reporter/html/sections/error_entry.rb
280
+ - lib/evilution/reporter/html/sections/file_section.rb
281
+ - lib/evilution/reporter/html/sections/header.rb
282
+ - lib/evilution/reporter/html/sections/mutation_map.rb
283
+ - lib/evilution/reporter/html/sections/neutral_details.rb
284
+ - lib/evilution/reporter/html/sections/summary_cards.rb
285
+ - lib/evilution/reporter/html/sections/survived_details.rb
286
+ - lib/evilution/reporter/html/sections/survived_entry.rb
287
+ - lib/evilution/reporter/html/sections/truncation_notice.rb
288
+ - lib/evilution/reporter/html/sections/unparseable_details.rb
289
+ - lib/evilution/reporter/html/sections/unresolved_details.rb
290
+ - lib/evilution/reporter/html/stylesheet.rb
291
+ - lib/evilution/reporter/html/templates/baseline_comparison.html.erb
292
+ - lib/evilution/reporter/html/templates/error_details.html.erb
293
+ - lib/evilution/reporter/html/templates/error_entry.html.erb
294
+ - lib/evilution/reporter/html/templates/file_section.html.erb
295
+ - lib/evilution/reporter/html/templates/header.html.erb
296
+ - lib/evilution/reporter/html/templates/mutation_map.html.erb
297
+ - lib/evilution/reporter/html/templates/neutral_details.html.erb
298
+ - lib/evilution/reporter/html/templates/report.html.erb
299
+ - lib/evilution/reporter/html/templates/summary_cards.html.erb
300
+ - lib/evilution/reporter/html/templates/survived_details.html.erb
301
+ - lib/evilution/reporter/html/templates/survived_entry.html.erb
302
+ - lib/evilution/reporter/html/templates/truncation_notice.html.erb
303
+ - lib/evilution/reporter/html/templates/unparseable_details.html.erb
304
+ - lib/evilution/reporter/html/templates/unresolved_details.html.erb
247
305
  - lib/evilution/reporter/json.rb
248
306
  - lib/evilution/reporter/progress_bar.rb
249
307
  - lib/evilution/reporter/suggestion.rb
308
+ - lib/evilution/reporter/suggestion/diff_helpers.rb
309
+ - lib/evilution/reporter/suggestion/registry.rb
310
+ - lib/evilution/reporter/suggestion/templates/generic.rb
311
+ - lib/evilution/reporter/suggestion/templates/minitest.rb
312
+ - lib/evilution/reporter/suggestion/templates/rspec.rb
250
313
  - lib/evilution/result.rb
251
314
  - lib/evilution/result/coverage_gap.rb
252
315
  - lib/evilution/result/coverage_gap_grouper.rb
253
316
  - lib/evilution/result/mutation_result.rb
254
317
  - lib/evilution/result/summary.rb
255
318
  - lib/evilution/runner.rb
319
+ - lib/evilution/runner/baseline_runner.rb
320
+ - lib/evilution/runner/diagnostics.rb
321
+ - lib/evilution/runner/isolation_resolver.rb
322
+ - lib/evilution/runner/mutation_executor.rb
323
+ - lib/evilution/runner/mutation_planner.rb
324
+ - lib/evilution/runner/report_publisher.rb
325
+ - lib/evilution/runner/subject_pipeline.rb
256
326
  - lib/evilution/session.rb
257
327
  - lib/evilution/session/diff.rb
258
328
  - lib/evilution/session/store.rb
329
+ - lib/evilution/source_ast_cache.rb
330
+ - lib/evilution/spec_ast_cache.rb
259
331
  - lib/evilution/spec_resolver.rb
332
+ - lib/evilution/spec_selector.rb
260
333
  - lib/evilution/subject.rb
261
334
  - lib/evilution/temp_dir_tracker.rb
262
335
  - lib/evilution/version.rb