evilution 0.10.0 → 0.11.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.
- checksums.yaml +4 -4
- data/.beads/issues.jsonl +10 -10
- data/CHANGELOG.md +16 -0
- data/README.md +12 -4
- data/lib/evilution/mcp/mutate_tool.rb +37 -7
- data/lib/evilution/mutator/operator/arithmetic_replacement.rb +3 -1
- data/lib/evilution/mutator/operator/array_literal.rb +7 -0
- data/lib/evilution/mutator/operator/boolean_literal_replacement.rb +15 -0
- data/lib/evilution/mutator/operator/collection_replacement.rb +9 -1
- data/lib/evilution/mutator/operator/comparison_replacement.rb +4 -4
- data/lib/evilution/mutator/operator/float_literal.rb +7 -0
- data/lib/evilution/mutator/operator/hash_literal.rb +7 -0
- data/lib/evilution/mutator/operator/integer_literal.rb +7 -0
- data/lib/evilution/mutator/operator/nil_replacement.rb +10 -6
- data/lib/evilution/mutator/operator/regexp_mutation.rb +11 -6
- data/lib/evilution/mutator/operator/string_literal.rb +7 -0
- data/lib/evilution/mutator/operator/symbol_literal.rb +7 -0
- data/lib/evilution/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 75b54000885c712ef99cba9fe62d841b66e24a77730c84c2b1887390bda48210
|
|
4
|
+
data.tar.gz: 8c9d01ab7e64a0d00863d2868da561c39f4b9e99a6f7922039260c384f31c20d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 703e6bd316ba86d75f0e472adce7de078d5249f563cfbc110537c000b1d44e1904a07ccfabc7633e2df8bc8cf2401f55dbdb30d62b182eab861f93b8a9e215f2
|
|
7
|
+
data.tar.gz: 752b81e0d695029e586e5459d1bfa2a73535e37c65fe0e6ee21262d8db241d1d33d0c1b76348d8e0fa0fdafb515cf01cac64f7eac1b4fd69ff0e8bc06b26ba66
|
data/.beads/issues.jsonl
CHANGED
|
@@ -83,10 +83,10 @@
|
|
|
83
83
|
{"id":"EV-4.9","title":"Integrate diff-based targeting into Runner","description":"Update Runner to optionally use Diff::Parser + FileFilter when --diff flag is set. Filter subjects before mutation generation. File: lib/evilution/runner.rb (edit).","status":"closed","priority":2,"issue_type":"task","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-02T00:06:45.851982561+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-02T11:54:25.596280246+07:00","closed_at":"2026-03-02T11:54:25.596280246+07:00","close_reason":"Closed","dependencies":[{"issue_id":"EV-4.9","depends_on_id":"EV-4","type":"parent-child","created_at":"0001-01-01T00:00:00Z"},{"issue_id":"EV-4.9","depends_on_id":"EV-4.6","type":"blocks","created_at":"0001-01-01T00:00:00Z"},{"issue_id":"EV-4.9","depends_on_id":"EV-2.12","type":"blocks","created_at":"0001-01-01T00:00:00Z"}]}
|
|
84
84
|
{"id":"EV-40","title":"Separate rspec noise from evilution output (stdout/stderr)","description":"RSpec warnings flood stdout and corrupt JSON output. When using --format json, the JSON gets buried in hundreds of lines of rspec warnings. Fix: redirect rspec subprocess output to stderr (or /dev/null), keep evilution results on stdout. Critical for piping JSON output.","status":"closed","priority":1,"issue_type":"bug","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-13T09:56:58.604909176+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-16T11:15:15.02691418+07:00","closed_at":"2026-03-16T11:15:15.02691418+07:00","close_reason":"Fixed and merged via PR #86"}
|
|
85
85
|
{"id":"EV-41","title":"Progress indicator during mutation runs","description":"Zero output during multi-minute runs makes it look stuck. Add a simple 'mutation 3/19 killed...' progress line to stderr so users know work is happening. Only show in text mode or when stderr is a TTY.","status":"closed","priority":2,"issue_type":"feature","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-13T09:57:01.023293323+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-16T11:38:40.674664201+07:00","closed_at":"2026-03-16T11:38:40.674664201+07:00","close_reason":"Fixed and merged via PR #87"}
|
|
86
|
-
{"id":"EV-42","title":"Expand mutation operators (method call removal, receiver replacement, etc.)","description":"Currently 18 operators generating ~19 mutations vs mutant's 78 for the same method. Missing operators: method call removal, self receiver replacement, additional boolean logic mutations, nil substitutions. Higher operator count catches more subtle bugs and produces a more meaningful mutation score.","status":"
|
|
86
|
+
{"id":"EV-42","title":"Expand mutation operators (method call removal, receiver replacement, etc.)","description":"Currently 18 operators generating ~19 mutations vs mutant's 78 for the same method. Missing operators: method call removal, self receiver replacement, additional boolean logic mutations, nil substitutions. Higher operator count catches more subtle bugs and produces a more meaningful mutation score.","status":"closed","priority":2,"issue_type":"feature","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-13T09:57:03.039944039+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-21T10:13:27.879564703+07:00","closed_at":"2026-03-21T10:13:27.879564703+07:00","close_reason":"All requested operators implemented: method call removal, receiver replacement, block removal, send mutation, argument removal, conditional flip, range/regexp, and operator deepening. Operator count grown from 18 to 26.","external_ref":"gh-76","dependencies":[{"issue_id":"EV-42","depends_on_id":"EV-45","type":"parent-child","created_at":"0001-01-01T00:00:00Z"}]}
|
|
87
87
|
{"id":"EV-43","title":"Fix --version flag (returns 'version unknown')","description":"Running 'evilution --version' returns 'version unknown' instead of the actual version. The 'evilution version' subcommand works, but --version as a flag does not. Users expect --version to work.","status":"closed","priority":1,"issue_type":"bug","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-13T09:57:05.048211823+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-16T10:31:01.045381336+07:00","closed_at":"2026-03-16T10:31:01.045381336+07:00","close_reason":"Fixed and merged"}
|
|
88
88
|
{"id":"EV-44","title":"Re-introduce parallel execution","description":"Bring back parallel mutation execution. Consider whether two-level fork model is still right, temp-file isolation needs, and auto-detecting when parallelism is worthwhile. GH #35.","status":"closed","priority":2,"issue_type":"feature","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-16T10:15:42.050318104+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-16T16:26:30.341909415+07:00","closed_at":"2026-03-16T16:26:30.341909415+07:00","close_reason":"PR #90 merged — process-based parallel pool with --jobs flag"}
|
|
89
|
-
{"id":"EV-45","title":"Epic: Close mutation operator gap with mutant","description":"Track all missing mutation operators to approach mutant's ~78 mutations per method. Currently at ~19 with 18 operators. This is a long-term effort — prioritize high-value operators first.","status":"
|
|
89
|
+
{"id":"EV-45","title":"Epic: Close mutation operator gap with mutant","description":"Track all missing mutation operators to approach mutant's ~78 mutations per method. Currently at ~19 with 18 operators. This is a long-term effort — prioritize high-value operators first.","status":"closed","priority":2,"issue_type":"epic","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-16T21:50:07.597313227+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-21T10:13:38.546539396+07:00","closed_at":"2026-03-21T10:13:38.546539396+07:00","close_reason":"All 10 children complete. Operator count grew from 18 to 26 with deeper variant coverage across all operators, significantly closing the gap with mutant."}
|
|
90
90
|
{"id":"EV-46","title":"Operator: MethodCallRemoval","description":"Remove method call, keep receiver. e.g. obj.foo(x) → obj. High bug-finding value — catches untested side effects.","status":"closed","priority":3,"issue_type":"feature","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-16T21:50:22.890467537+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-19T15:45:33.224167585+07:00","closed_at":"2026-03-17T09:29:15.588415782+07:00","close_reason":"PR merged — MethodCallRemoval operator (19th operator)","external_ref":"gh-94","dependencies":[{"issue_id":"EV-46","depends_on_id":"EV-45","type":"parent-child","created_at":"0001-01-01T00:00:00Z"}]}
|
|
91
91
|
{"id":"EV-47","title":"Operator: BlockRemoval","description":"Remove block from method call. e.g. items.map { |x| x * 2 } → items.map. Catches untested block logic.","status":"closed","priority":3,"issue_type":"feature","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-16T21:50:23.067266925+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-19T16:07:53.696798964+07:00","closed_at":"2026-03-19T16:07:53.696798964+07:00","close_reason":"Implemented BlockRemoval operator with 8 specs","external_ref":"gh-95","dependencies":[{"issue_id":"EV-47","depends_on_id":"EV-45","type":"parent-child","created_at":"0001-01-01T00:00:00Z"}]}
|
|
92
92
|
{"id":"EV-48","title":"Operator: RangeReplacement","description":"Swap inclusive/exclusive ranges. e.g. 1..10 → 1...10 and vice versa. Trivial to implement.","status":"closed","priority":3,"issue_type":"feature","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-16T21:50:23.172934653+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-19T18:45:22.811573395+07:00","closed_at":"2026-03-19T18:45:22.811573395+07:00","close_reason":"Implemented RangeReplacement operator with 6 specs, 100% mutation score","external_ref":"gh-96","dependencies":[{"issue_id":"EV-48","depends_on_id":"EV-45","type":"parent-child","created_at":"0001-01-01T00:00:00Z"}]}
|
|
@@ -102,13 +102,13 @@
|
|
|
102
102
|
{"id":"EV-51","title":"Operator: SendMutation","description":"Replace known method families. e.g. flat_map → map, public_send → send, gsub → sub. Catches untested method semantics.","status":"closed","priority":4,"issue_type":"feature","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-16T21:50:23.483414117+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-20T23:14:12.811639741+07:00","closed_at":"2026-03-20T23:14:12.811639741+07:00","close_reason":"Implemented SendMutation operator with 17 method family replacements (flat_map/map, public_send/send, gsub/sub, detect/find, collect/map, each_with_object/inject, reverse_each/each, length/size, values_at/fetch_values). 19 specs passing.","external_ref":"gh-99","dependencies":[{"issue_id":"EV-51","depends_on_id":"EV-45","type":"parent-child","created_at":"0001-01-01T00:00:00Z"}]}
|
|
103
103
|
{"id":"EV-52","title":"Operator: ArgumentRemoval","description":"Remove, permute, and replace individual method call arguments. e.g. foo(a, b) → foo(a), foo(b, a), foo(a, nil). This is the #1 cited gap vs mutant — where mutant catches the most bugs evilution misses. Includes: removing each argument individually, swapping argument order, replacing with nil.","status":"closed","priority":2,"issue_type":"feature","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-16T21:50:23.587291445+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-19T15:45:33.832675896+07:00","closed_at":"2026-03-17T23:16:28.920737471+07:00","close_reason":"Merged","external_ref":"gh-100","dependencies":[{"issue_id":"EV-52","depends_on_id":"EV-45","type":"parent-child","created_at":"0001-01-01T00:00:00Z"}]}
|
|
104
104
|
{"id":"EV-53","title":"Operator: ReceiverReplacement","description":"Drop explicit self receiver. e.g. self.foo → foo. Low bug-finding value.","status":"closed","priority":3,"issue_type":"feature","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-16T21:50:23.692244528+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-19T20:56:21.943424181+07:00","closed_at":"2026-03-19T20:56:21.943424181+07:00","close_reason":"Implemented ReceiverReplacement operator with 8 specs, 100% mutation score","external_ref":"gh-101","dependencies":[{"issue_id":"EV-53","depends_on_id":"EV-45","type":"parent-child","created_at":"0001-01-01T00:00:00Z"}]}
|
|
105
|
-
{"id":"EV-54","title":"Deepen existing operators with more replacement variants","description":"Existing operators generate limited variants per node. E.g. integer 5 only produces 0 and 1, but could also produce -1 and nil. Expanding variant sets across all 18 operators would significantly increase mutation coverage.","status":"
|
|
106
|
-
{"id":"EV-54.1","title":"Add nil variants to literal operators","description":"Add nil as a mutation variant to IntegerLiteral, FloatLiteral, StringLiteral, BooleanLiteralReplacement, ArrayLiteral, HashLiteral, and SymbolLiteral operators. Nil-safety is one of the most common sources of production bugs in Ruby, making this the highest-ROI expansion.","status":"
|
|
107
|
-
{"id":"EV-54.2","title":"Expand NilReplacement with false, 0, and empty string variants","description":"Currently nil only mutates to true. Add false, 0, and empty string variants since nil is commonly used in boolean, numeric, and string contexts.","status":"
|
|
108
|
-
{"id":"EV-54.3","title":"Expand CollectionReplacement with more method swaps","description":"Add sort↔sort_by, find↔detect, any?↔all?, count↔length swaps. These are very common collection methods with subtle behavioral differences.","status":"
|
|
109
|
-
{"id":"EV-54.4","title":"Expand ComparisonReplacement with opposite direction flips","description":"Currently > only mutates to >= and ==. Add full opposite flips: >→<, >=→<=, <→>, <=→>=. Catches inverted comparison bugs.","status":"
|
|
110
|
-
{"id":"EV-54.5","title":"Add always-matching regexp variant","description":"Currently regexp only mutates to never-matching /a\\A/. Add always-matching /.*/ variant to test both match and no-match code paths.","status":"
|
|
111
|
-
{"id":"EV-54.6","title":"Expand ArithmeticReplacement with bitwise shift operators","description":"Add <<→>> swap. Niche but catches bitwise shift bugs. Lowest priority in this epic.","status":"
|
|
105
|
+
{"id":"EV-54","title":"Deepen existing operators with more replacement variants","description":"Existing operators generate limited variants per node. E.g. integer 5 only produces 0 and 1, but could also produce -1 and nil. Expanding variant sets across all 18 operators would significantly increase mutation coverage.","status":"closed","priority":3,"issue_type":"feature","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-16T21:50:23.797359784+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-21T10:12:24.735777721+07:00","closed_at":"2026-03-21T10:12:24.735777721+07:00","close_reason":"All 7 subtasks complete: nil variants for literals, NilReplacement expansion, CollectionReplacement swaps, ComparisonReplacement flips, regexp always-match, ArithmeticReplacement bitwise shifts, and ArgumentNilSubstitution.","external_ref":"gh-102","dependencies":[{"issue_id":"EV-54","depends_on_id":"EV-45","type":"parent-child","created_at":"0001-01-01T00:00:00Z"}]}
|
|
106
|
+
{"id":"EV-54.1","title":"Add nil variants to literal operators","description":"Add nil as a mutation variant to IntegerLiteral, FloatLiteral, StringLiteral, BooleanLiteralReplacement, ArrayLiteral, HashLiteral, and SymbolLiteral operators. Nil-safety is one of the most common sources of production bugs in Ruby, making this the highest-ROI expansion.","status":"closed","priority":2,"issue_type":"task","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-19T21:21:06.464726693+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-21T01:35:34.750595584+07:00","closed_at":"2026-03-21T01:35:34.750595584+07:00","close_reason":"Added nil variant to all 7 literal operators: IntegerLiteral, FloatLiteral, StringLiteral, BooleanLiteralReplacement, ArrayLiteral, HashLiteral, SymbolLiteral. 722 specs passing, 100% mutation score.","external_ref":"gh-167","dependencies":[{"issue_id":"EV-54.1","depends_on_id":"EV-54","type":"parent-child","created_at":"0001-01-01T00:00:00Z"}]}
|
|
107
|
+
{"id":"EV-54.2","title":"Expand NilReplacement with false, 0, and empty string variants","description":"Currently nil only mutates to true. Add false, 0, and empty string variants since nil is commonly used in boolean, numeric, and string contexts.","status":"closed","priority":2,"issue_type":"task","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-19T21:21:20.731673229+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-21T09:27:04.480824365+07:00","closed_at":"2026-03-21T09:27:04.480824365+07:00","close_reason":"Added false, 0, and empty string variants to NilReplacement alongside existing true. Each nil literal now produces 4 mutations. All 722 specs pass.","external_ref":"gh-168","dependencies":[{"issue_id":"EV-54.2","depends_on_id":"EV-54","type":"parent-child","created_at":"0001-01-01T00:00:00Z"}]}
|
|
108
|
+
{"id":"EV-54.3","title":"Expand CollectionReplacement with more method swaps","description":"Add sort↔sort_by, find↔detect, any?↔all?, count↔length swaps. These are very common collection methods with subtle behavioral differences.","status":"closed","priority":3,"issue_type":"task","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-19T21:21:20.842448904+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-21T09:33:35.1684274+07:00","closed_at":"2026-03-21T09:33:35.1684274+07:00","close_reason":"Added sort↔sort_by, find↔detect, any?↔all?, count↔length swaps. 731 specs pass.","external_ref":"gh-169","dependencies":[{"issue_id":"EV-54.3","depends_on_id":"EV-54","type":"parent-child","created_at":"0001-01-01T00:00:00Z"}]}
|
|
109
|
+
{"id":"EV-54.4","title":"Expand ComparisonReplacement with opposite direction flips","description":"Currently > only mutates to >= and ==. Add full opposite flips: >→<, >=→<=, <→>, <=→>=. Catches inverted comparison bugs.","status":"closed","priority":3,"issue_type":"task","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-19T21:21:20.944147572+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-21T09:45:06.468344416+07:00","closed_at":"2026-03-21T09:45:06.468344416+07:00","close_reason":"Added opposite direction flips (>↔<, >=↔<=). 731 specs pass, 100% mutation score.","external_ref":"gh-170","dependencies":[{"issue_id":"EV-54.4","depends_on_id":"EV-54","type":"parent-child","created_at":"0001-01-01T00:00:00Z"}]}
|
|
110
|
+
{"id":"EV-54.5","title":"Add always-matching regexp variant","description":"Currently regexp only mutates to never-matching /a\\A/. Add always-matching /.*/ variant to test both match and no-match code paths.","status":"closed","priority":3,"issue_type":"task","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-19T21:21:21.049402671+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-21T10:05:45.034075069+07:00","closed_at":"2026-03-21T10:05:45.034075069+07:00","close_reason":"Added always-matching /.*/ variant alongside never-matching /a\\A/. Each regexp now produces 2 mutations. 731 specs pass, 100% mutation score.","external_ref":"gh-171","dependencies":[{"issue_id":"EV-54.5","depends_on_id":"EV-54","type":"parent-child","created_at":"0001-01-01T00:00:00Z"}]}
|
|
111
|
+
{"id":"EV-54.6","title":"Expand ArithmeticReplacement with bitwise shift operators","description":"Add <<→>> swap. Niche but catches bitwise shift bugs. Lowest priority in this epic.","status":"closed","priority":4,"issue_type":"task","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-19T21:21:21.152296396+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-21T01:03:32.687774444+07:00","closed_at":"2026-03-21T01:03:32.687774444+07:00","close_reason":"Added << >> bitwise shift swap to ArithmeticReplacement operator. 714 specs passing, 100% mutation score.","external_ref":"gh-172","dependencies":[{"issue_id":"EV-54.6","depends_on_id":"EV-54","type":"parent-child","created_at":"0001-01-01T00:00:00Z"}]}
|
|
112
112
|
{"id":"EV-54.7","title":"Operator: ArgumentNilSubstitution","description":"Replace each method argument with nil, one at a time. E.g. foo(a, b) → foo(nil, b) and foo(a, nil). Works for single-arg calls too (unlike ArgumentRemoval which requires 2+). This catches 'default value' bugs where a method has a sensible fallback for nil — e.g. I18n.with_locale(user.locale) → I18n.with_locale(nil) silently falls back to default locale. Identified from real-world feedback where mutant caught a locale test gap that evilution missed (v0.7.0).","status":"closed","priority":2,"issue_type":"task","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-19T22:09:42.995246472+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-20T23:43:12.098455761+07:00","closed_at":"2026-03-20T23:43:12.098455761+07:00","close_reason":"Implemented ArgumentNilSubstitution operator. Replaces each argument with nil one at a time, works for single-arg calls too (unlike ArgumentRemoval). Skips splat, keyword, block, and forwarding args. 12 specs passing, 705 total.","external_ref":"gh-175","dependencies":[{"issue_id":"EV-54.7","depends_on_id":"EV-54","type":"parent-child","created_at":"0001-01-01T00:00:00Z"}]}
|
|
113
113
|
{"id":"EV-55","title":"Concrete suggestions: Comparison & Arithmetic operators","description":"Generate concrete RSpec it blocks for ComparisonReplacement and ArithmeticReplacement mutations. Test should assert exact return value at boundary conditions.","status":"open","priority":3,"issue_type":"feature","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-17T09:58:33.26716126+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-17T09:58:33.26716126+07:00"}
|
|
114
114
|
{"id":"EV-56","title":"Concrete suggestions: Boolean operators","description":"Generate concrete RSpec it blocks for BooleanOperatorReplacement, BooleanLiteralReplacement, and NegationInsertion mutations. Test should assert true/false explicitly.","status":"open","priority":3,"issue_type":"feature","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-17T09:58:33.370014645+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-17T09:58:33.370014645+07:00"}
|
|
@@ -143,7 +143,7 @@
|
|
|
143
143
|
{"id":"EV-70.8","title":"Lazy/streaming mutation generation","description":"Currently generate_mutations builds ALL mutations into an array before any are executed (runner.rb:38). For large codebases this holds thousands of Mutation objects (each with 2x full source strings + AST node) in memory simultaneously. Change to lazy enumeration so mutations are generated per-subject as needed.","status":"closed","priority":1,"issue_type":"bug","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-18T18:52:05.552816007+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-19T00:26:32.161184282+07:00","closed_at":"2026-03-19T00:26:32.161184282+07:00","close_reason":"Changed generate_mutations to lazy enumerator (subjects.lazy.flat_map). Mutations generated per-subject on demand, not all materialized upfront. Baseline now receives subjects instead of mutations. Pre-computed mutation_count passed to run methods for progress/fail_fast.","external_ref":"gh-132","labels":["v0.7.0"],"dependencies":[{"issue_id":"EV-70.8","depends_on_id":"EV-70","type":"parent-child","created_at":"0001-01-01T00:00:00Z"}]}
|
|
144
144
|
{"id":"EV-70.9","title":"Strip source strings from Mutation after result creation","description":"Each MutationResult retains its Mutation which holds original_source and mutated_source (full file contents). These are only needed for diff generation in reporters. Compute and cache the diff eagerly, then release the source strings to allow GC. Or store only the diff in MutationResult.","status":"closed","priority":2,"issue_type":"bug","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-18T18:52:08.1904857+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-19T10:31:19.673577645+07:00","closed_at":"2026-03-19T10:31:19.673577645+07:00","close_reason":"Implemented strip_sources! on Mutation (lazy diff caching, nil out source strings after execution). Wired into Runner sequential and parallel paths.","external_ref":"gh-133","labels":["v0.7.0"],"dependencies":[{"issue_id":"EV-70.9","depends_on_id":"EV-70","type":"parent-child","created_at":"0001-01-01T00:00:00Z"}]}
|
|
145
145
|
{"id":"EV-71","title":"Trim MCP tool response to reduce context window usage","description":"The evilution MCP tool returns full JSON report with all killed mutation diffs (~10k tokens). Only survived mutations are actionable. Trim killed/neutral/timed_out/errors detail arrays in MutateTool.call, keeping summary counts and survived details only.","status":"closed","priority":1,"issue_type":"task","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-19T23:23:32.672265145+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-19T23:31:18.511115181+07:00","closed_at":"2026-03-19T23:31:18.511115181+07:00","close_reason":"Trim killed/neutral/timed_out/errors detail arrays from MCP tool response, keeping only summary and survived details","external_ref":"marinazzio/evilution#178"}
|
|
146
|
-
{"id":"EV-72","title":"Add verbosity control to MCP tool response","description":"Add a verbosity parameter (full/summary/minimal) to the MCP tool to control response size. Default to summary for best context efficiency. Full keeps all entries without diffs, summary omits killed/neutral arrays, minimal keeps only summary + survived.","status":"
|
|
146
|
+
{"id":"EV-72","title":"Add verbosity control to MCP tool response","description":"Add a verbosity parameter (full/summary/minimal) to the MCP tool to control response size. Default to summary for best context efficiency. Full keeps all entries without diffs, summary omits killed/neutral arrays, minimal keeps only summary + survived.","status":"closed","priority":3,"issue_type":"feature","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-19T23:41:41.837080367+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-21T01:16:09.980410196+07:00","closed_at":"2026-03-21T01:16:09.980410196+07:00","close_reason":"Added verbosity parameter (full/summary/minimal) to MCP tool. Default is summary which omits killed/neutral/equivalent arrays. 718 specs passing, 100% mutation score. Updated README.","external_ref":"marinazzio/evilution#179"}
|
|
147
147
|
{"id":"EV-73","title":"Clean up deprecated CLI switches and config options","description":"Remove long-deprecated --diff, --no-coverage flags and coverage config option. GH #176.","status":"closed","priority":2,"issue_type":"task","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-21T00:37:09.266487789+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-21T00:49:24.827479374+07:00","closed_at":"2026-03-21T00:49:24.827479374+07:00","close_reason":"Removed deprecated --diff, --no-coverage CLI flags; removed diff_base, coverage, diff? from Config; removed filter_by_diff from Runner; deleted diff/parser, diff/file_filter, coverage/collector, coverage/test_map and their specs; cleaned up requires. 712 specs passing.","external_ref":"gh-176"}
|
|
148
148
|
{"id":"EV-8","title":"Investigate RSpec state accumulation across mutation runs","description":"RSpec.reset is called between mutations but loaded spec files persist in memory. Long mutation runs may accumulate state from previously loaded specs, potentially causing false positives/negatives. Investigate whether RSpec::Core::Runner.run properly isolates between runs or if additional cleanup is needed.","status":"closed","priority":2,"issue_type":"bug","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-02T16:21:48.618669476+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-05T11:57:56.965910759+07:00","closed_at":"2026-03-05T11:57:56.965910759+07:00","close_reason":"Investigation complete: fork isolation already prevents state accumulation. Added documentation explaining the guarantee and tests verifying independent consecutive runs."}
|
|
149
149
|
{"id":"EV-9","title":"Add multi-Ruby CI test matrix (3.2, 3.3, 4.0)","description":"CI currently only tests Ruby 4.0.1. Add a matrix strategy testing Ruby 3.2, 3.3, and 4.0 to ensure compatibility across supported Ruby versions. Prism ships with Ruby 3.3+ so 3.2 may need the prism gem as a dependency.","status":"closed","priority":2,"issue_type":"task","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-02T16:21:51.239774764+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-05T12:31:51.83181612+07:00","closed_at":"2026-03-05T12:31:51.83181612+07:00","close_reason":"Closed"}
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.11.0] - 2026-03-21
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- **Nil variants for literal operators** — BooleanLiteralReplacement, IntegerLiteralReplacement, FloatLiteralReplacement, StringLiteralReplacement, and SymbolLiteralReplacement now produce a `nil` mutation alongside their existing replacements (#193)
|
|
8
|
+
- **NilReplacement expansion** — `nil` now mutates to `true`, `false`, `0`, and `""` (was only `true`); covers boolean, numeric, and string contexts (#197)
|
|
9
|
+
- **CollectionReplacement expansion** — added 8 new method swaps: `sort`↔`sort_by`, `find`↔`detect`, `any?`↔`all?`, `count`↔`length` (14 total swaps, up from 6) (#198)
|
|
10
|
+
- **ComparisonReplacement expansion** — added opposite direction flips: `>`↔`<`, `>=`↔`<=` alongside existing boundary and equality mutations (#199)
|
|
11
|
+
- **RegexpMutation expansion** — added always-matching `/.*/` variant alongside the existing never-matching `/a\A/`; each regexp now produces 2 mutations (#200)
|
|
12
|
+
- **ArithmeticReplacement expansion** — added bitwise shift operators `<<`↔`>>` (#189)
|
|
13
|
+
- **MCP verbosity control** — MCP tool responses support configurable verbosity levels (#192)
|
|
14
|
+
|
|
15
|
+
### Changed
|
|
16
|
+
|
|
17
|
+
- **Dependency updates** (#191)
|
|
18
|
+
|
|
3
19
|
## [0.10.0] - 2026-03-21
|
|
4
20
|
|
|
5
21
|
### Added
|
data/README.md
CHANGED
|
@@ -42,10 +42,8 @@ evilution [command] [options] [files...]
|
|
|
42
42
|
| `-t`, `--timeout N` | Integer | 10 | Per-mutation timeout in seconds. |
|
|
43
43
|
| `-f`, `--format FORMAT` | String | `text` | Output format: `text` or `json`. |
|
|
44
44
|
| `--target METHOD` | String | _(none)_ | Only mutate the named method (e.g. `Foo::Bar#calculate`). |
|
|
45
|
-
| `--diff BASE` | String | _(none)_ | **DEPRECATED**: Use line-range targeting instead. Git ref. Only mutate methods whose definition line changed since BASE. |
|
|
46
45
|
| `--min-score FLOAT` | Float | 0.0 | Minimum mutation score (0.0–1.0) to pass. |
|
|
47
46
|
| `--spec FILES` | Array | _(none)_ | Spec files to run (comma-separated). Defaults to `spec/`. |
|
|
48
|
-
| `--no-coverage` | Boolean | false | **DEPRECATED, NO-OP**: Kept for backward compatibility. Will be removed. |
|
|
49
47
|
| `-j`, `--jobs N` | Integer | 1 | Number of parallel workers. Pool forks per batch; mutations run in-process inside workers. |
|
|
50
48
|
| `--no-baseline` | Boolean | _(enabled)_ | Skip baseline test suite check. By default, a baseline run detects pre-existing failures and marks those mutations as `neutral`. |
|
|
51
49
|
| `--fail-fast [N]` | Integer | _(none)_ | Stop after N surviving mutants (default 1 if no value given). |
|
|
@@ -162,6 +160,18 @@ If using Bundler, set the command to `bundle` and args to `["exec", "evilution",
|
|
|
162
160
|
|
|
163
161
|
The server exposes an `evilution-mutate` tool that accepts target files, method targets, spec overrides, parallelism, and timeout options — returning structured JSON results directly to the agent.
|
|
164
162
|
|
|
163
|
+
### Verbosity Control
|
|
164
|
+
|
|
165
|
+
The MCP tool accepts a `verbosity` parameter to control response size:
|
|
166
|
+
|
|
167
|
+
| Level | Default | What's included |
|
|
168
|
+
|-------------|---------|--------------------------------------------------------------|
|
|
169
|
+
| `summary` | Yes | `summary` + `survived` + `timed_out` + `errors` |
|
|
170
|
+
| `full` | | All entries (killed/neutral/equivalent diffs stripped) |
|
|
171
|
+
| `minimal` | | `summary` + `survived` only |
|
|
172
|
+
|
|
173
|
+
Use `minimal` when context window budget is tight and you only need to see what survived. Use `full` when you need to inspect killed/neutral/equivalent entries for debugging.
|
|
174
|
+
|
|
165
175
|
> **Note**: `.mcp.json` is gitignored by default since it is a local editor/agent configuration file.
|
|
166
176
|
|
|
167
177
|
## Recommended Workflows for AI Agents
|
|
@@ -182,8 +192,6 @@ bundle exec evilution run lib/foo.rb:15-30 lib/bar.rb:5-20 --format json --min-s
|
|
|
182
192
|
|
|
183
193
|
Target the exact lines you changed for fast, focused mutation testing. See line-range syntax below.
|
|
184
194
|
|
|
185
|
-
> **Note**: `--diff BASE` is deprecated and will be removed in a future version. Prefer line-range targeting for new workflows.
|
|
186
|
-
|
|
187
195
|
### 3. Line-range targeted scan (fastest)
|
|
188
196
|
|
|
189
197
|
```bash
|
|
@@ -8,7 +8,7 @@ require_relative "../reporter/json"
|
|
|
8
8
|
|
|
9
9
|
module Evilution
|
|
10
10
|
module MCP
|
|
11
|
-
class MutateTool < ::MCP::Tool
|
|
11
|
+
class MutateTool < ::MCP::Tool # rubocop:disable Metrics/ClassLength
|
|
12
12
|
tool_name "evilution-mutate"
|
|
13
13
|
description "Run mutation testing on Ruby source files"
|
|
14
14
|
input_schema(
|
|
@@ -38,19 +38,26 @@ module Evilution
|
|
|
38
38
|
type: "array",
|
|
39
39
|
items: { type: "string" },
|
|
40
40
|
description: "Spec files to run (overrides auto-detection)"
|
|
41
|
+
},
|
|
42
|
+
verbosity: {
|
|
43
|
+
type: "string",
|
|
44
|
+
enum: %w[full summary minimal],
|
|
45
|
+
description: "Response verbosity: full (all entries, diffs stripped from killed/neutral/equivalent), " \
|
|
46
|
+
"summary (omits killed/neutral/equivalent arrays; default), " \
|
|
47
|
+
"minimal (only summary + survived)"
|
|
41
48
|
}
|
|
42
49
|
}
|
|
43
50
|
)
|
|
44
51
|
|
|
45
52
|
class << self
|
|
46
|
-
def call(server_context:, files: [], target: nil, timeout: nil, jobs: nil, fail_fast: nil, spec: nil) # rubocop:disable Lint/UnusedMethodArgument
|
|
53
|
+
def call(server_context:, files: [], target: nil, timeout: nil, jobs: nil, fail_fast: nil, spec: nil, verbosity: nil) # rubocop:disable Lint/UnusedMethodArgument
|
|
47
54
|
parsed_files, line_ranges = parse_files(Array(files))
|
|
48
55
|
config_opts = build_config_opts(parsed_files, line_ranges, target, timeout, jobs, fail_fast, spec)
|
|
49
56
|
config = Config.new(**config_opts)
|
|
50
57
|
runner = Runner.new(config: config)
|
|
51
58
|
summary = runner.call
|
|
52
59
|
report = Reporter::JSON.new.call(summary)
|
|
53
|
-
compact = trim_report(report)
|
|
60
|
+
compact = trim_report(report, normalize_verbosity(verbosity))
|
|
54
61
|
|
|
55
62
|
::MCP::Tool::Response.new([{ type: "text", text: compact }])
|
|
56
63
|
rescue Evilution::Error => e
|
|
@@ -58,6 +65,8 @@ module Evilution
|
|
|
58
65
|
::MCP::Tool::Response.new([{ type: "text", text: ::JSON.generate(error_payload) }], error: true)
|
|
59
66
|
end
|
|
60
67
|
|
|
68
|
+
VALID_VERBOSITIES = %w[full summary minimal].freeze
|
|
69
|
+
|
|
61
70
|
private
|
|
62
71
|
|
|
63
72
|
def parse_files(raw_files)
|
|
@@ -99,11 +108,32 @@ module Evilution
|
|
|
99
108
|
opts
|
|
100
109
|
end
|
|
101
110
|
|
|
102
|
-
def
|
|
111
|
+
def normalize_verbosity(value)
|
|
112
|
+
normalized = value.to_s.strip.downcase
|
|
113
|
+
normalized = "summary" if normalized.empty?
|
|
114
|
+
return normalized if VALID_VERBOSITIES.include?(normalized)
|
|
115
|
+
|
|
116
|
+
raise ParseError, "invalid verbosity: #{value.inspect} (must be full, summary, or minimal)"
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def trim_report(json_string, verbosity)
|
|
103
120
|
data = ::JSON.parse(json_string)
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
121
|
+
case verbosity
|
|
122
|
+
when "full"
|
|
123
|
+
strip_diffs(data, "killed")
|
|
124
|
+
strip_diffs(data, "neutral")
|
|
125
|
+
strip_diffs(data, "equivalent")
|
|
126
|
+
when "summary"
|
|
127
|
+
data.delete("killed")
|
|
128
|
+
data.delete("neutral")
|
|
129
|
+
data.delete("equivalent")
|
|
130
|
+
when "minimal"
|
|
131
|
+
data.delete("killed")
|
|
132
|
+
data.delete("neutral")
|
|
133
|
+
data.delete("equivalent")
|
|
134
|
+
data.delete("timed_out")
|
|
135
|
+
data.delete("errors")
|
|
136
|
+
end
|
|
107
137
|
::JSON.generate(data)
|
|
108
138
|
end
|
|
109
139
|
|
|
@@ -12,6 +12,8 @@ module Evilution
|
|
|
12
12
|
node: node
|
|
13
13
|
)
|
|
14
14
|
|
|
15
|
+
add_nil_mutation(node)
|
|
16
|
+
|
|
15
17
|
super
|
|
16
18
|
end
|
|
17
19
|
|
|
@@ -23,8 +25,21 @@ module Evilution
|
|
|
23
25
|
node: node
|
|
24
26
|
)
|
|
25
27
|
|
|
28
|
+
add_nil_mutation(node)
|
|
29
|
+
|
|
26
30
|
super
|
|
27
31
|
end
|
|
32
|
+
|
|
33
|
+
private
|
|
34
|
+
|
|
35
|
+
def add_nil_mutation(node)
|
|
36
|
+
add_mutation(
|
|
37
|
+
offset: node.location.start_offset,
|
|
38
|
+
length: node.location.length,
|
|
39
|
+
replacement: "nil",
|
|
40
|
+
node: node
|
|
41
|
+
)
|
|
42
|
+
end
|
|
28
43
|
end
|
|
29
44
|
end
|
|
30
45
|
end
|
|
@@ -10,7 +10,15 @@ module Evilution
|
|
|
10
10
|
select: [:reject],
|
|
11
11
|
reject: [:select],
|
|
12
12
|
flat_map: [:map],
|
|
13
|
-
collect: [:each]
|
|
13
|
+
collect: [:each],
|
|
14
|
+
sort: [:sort_by],
|
|
15
|
+
sort_by: [:sort],
|
|
16
|
+
find: [:detect],
|
|
17
|
+
detect: [:find],
|
|
18
|
+
any?: [:all?],
|
|
19
|
+
all?: [:any?],
|
|
20
|
+
count: [:length],
|
|
21
|
+
length: [:count]
|
|
14
22
|
}.freeze
|
|
15
23
|
|
|
16
24
|
def visit_call_node(node)
|
|
@@ -5,10 +5,10 @@ module Evilution
|
|
|
5
5
|
module Operator
|
|
6
6
|
class ComparisonReplacement < Base
|
|
7
7
|
REPLACEMENTS = {
|
|
8
|
-
:> => %i[>= ==],
|
|
9
|
-
:< => %i[<= ==],
|
|
10
|
-
:>= => %i[> ==],
|
|
11
|
-
:<= => %i[< ==],
|
|
8
|
+
:> => %i[>= == <],
|
|
9
|
+
:< => %i[<= == >],
|
|
10
|
+
:>= => %i[> == <=],
|
|
11
|
+
:<= => %i[< == >=],
|
|
12
12
|
:== => [:!=],
|
|
13
13
|
:!= => [:==]
|
|
14
14
|
}.freeze
|
|
@@ -4,13 +4,17 @@ module Evilution
|
|
|
4
4
|
module Mutator
|
|
5
5
|
module Operator
|
|
6
6
|
class NilReplacement < Base
|
|
7
|
+
REPLACEMENTS = %w[true false 0 ""].freeze
|
|
8
|
+
|
|
7
9
|
def visit_nil_node(node)
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
10
|
+
REPLACEMENTS.each do |replacement|
|
|
11
|
+
add_mutation(
|
|
12
|
+
offset: node.location.start_offset,
|
|
13
|
+
length: node.location.length,
|
|
14
|
+
replacement: replacement,
|
|
15
|
+
node: node
|
|
16
|
+
)
|
|
17
|
+
end
|
|
14
18
|
|
|
15
19
|
super
|
|
16
20
|
end
|
|
@@ -5,14 +5,19 @@ module Evilution
|
|
|
5
5
|
module Operator
|
|
6
6
|
class RegexpMutation < Base
|
|
7
7
|
NEVER_MATCH = 'a\A'
|
|
8
|
+
ALWAYS_MATCH = ".*"
|
|
9
|
+
|
|
10
|
+
REPLACEMENTS = [NEVER_MATCH, ALWAYS_MATCH].freeze
|
|
8
11
|
|
|
9
12
|
def visit_regular_expression_node(node)
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
13
|
+
REPLACEMENTS.each do |replacement|
|
|
14
|
+
add_mutation(
|
|
15
|
+
offset: node.content_loc.start_offset,
|
|
16
|
+
length: node.content_loc.length,
|
|
17
|
+
replacement: replacement,
|
|
18
|
+
node: node
|
|
19
|
+
)
|
|
20
|
+
end
|
|
16
21
|
|
|
17
22
|
super
|
|
18
23
|
end
|
data/lib/evilution/version.rb
CHANGED
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.
|
|
4
|
+
version: 0.11.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-03-
|
|
11
|
+
date: 2026-03-21 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: diff-lcs
|