evilution 0.28.0 → 0.29.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 (81) hide show
  1. checksums.yaml +4 -4
  2. data/.beads/interactions.jsonl +52 -0
  3. data/CHANGELOG.md +7 -0
  4. data/lib/evilution/ast/constant_names.rb +28 -11
  5. data/lib/evilution/ast/pattern/parser.rb +29 -17
  6. data/lib/evilution/cli/commands/session_diff.rb +6 -4
  7. data/lib/evilution/cli/commands/subjects.rb +6 -3
  8. data/lib/evilution/cli/commands/util_mutation.rb +24 -19
  9. data/lib/evilution/cli/parser/command_extractor.rb +9 -11
  10. data/lib/evilution/cli/parser/file_args.rb +3 -1
  11. data/lib/evilution/cli/parser/options_builder.rb +29 -1
  12. data/lib/evilution/cli/parser/stdin_reader.rb +2 -2
  13. data/lib/evilution/cli/parser.rb +18 -20
  14. data/lib/evilution/cli/printers/environment.rb +19 -19
  15. data/lib/evilution/cli/printers/session_diff.rb +8 -8
  16. data/lib/evilution/compare/normalizer.rb +10 -5
  17. data/lib/evilution/config.rb +10 -10
  18. data/lib/evilution/disable_comment.rb +21 -12
  19. data/lib/evilution/integration/loading/mutation_applier.rb +17 -12
  20. data/lib/evilution/integration/minitest.rb +25 -16
  21. data/lib/evilution/integration/rspec.rb +4 -0
  22. data/lib/evilution/isolation/fork.rb +27 -17
  23. data/lib/evilution/mcp/info_tool/actions/subjects.rb +32 -23
  24. data/lib/evilution/mcp/info_tool/actions/tests.rb +22 -12
  25. data/lib/evilution/mcp/info_tool/request_parser.rb +3 -1
  26. data/lib/evilution/mcp/info_tool.rb +7 -1
  27. data/lib/evilution/mcp/mutate_tool/option_parser.rb +3 -1
  28. data/lib/evilution/mcp/mutate_tool/survived_enricher.rb +19 -9
  29. data/lib/evilution/mcp/mutate_tool.rb +27 -14
  30. data/lib/evilution/mcp/session_tool.rb +27 -18
  31. data/lib/evilution/mutation.rb +13 -15
  32. data/lib/evilution/mutator/base.rb +17 -15
  33. data/lib/evilution/mutator/operator/argument_nil_substitution.rb +11 -14
  34. data/lib/evilution/mutator/operator/argument_removal.rb +11 -14
  35. data/lib/evilution/mutator/operator/begin_unwrap.rb +17 -5
  36. data/lib/evilution/mutator/operator/bitwise_complement.rb +26 -19
  37. data/lib/evilution/mutator/operator/block_param_removal.rb +18 -8
  38. data/lib/evilution/mutator/operator/block_pass_removal.rb +19 -15
  39. data/lib/evilution/mutator/operator/case_when.rb +7 -5
  40. data/lib/evilution/mutator/operator/conditional_branch.rb +22 -22
  41. data/lib/evilution/mutator/operator/equality_to_identity.rb +8 -3
  42. data/lib/evilution/mutator/operator/explicit_super_mutation.rb +17 -13
  43. data/lib/evilution/mutator/operator/index_to_at.rb +5 -4
  44. data/lib/evilution/mutator/operator/index_to_dig.rb +12 -6
  45. data/lib/evilution/mutator/operator/index_to_fetch.rb +5 -4
  46. data/lib/evilution/mutator/operator/keyword_argument.rb +30 -25
  47. data/lib/evilution/mutator/operator/mixin_removal.rb +20 -14
  48. data/lib/evilution/mutator/operator/multiple_assignment.rb +12 -13
  49. data/lib/evilution/mutator/operator/receiver_replacement.rb +9 -6
  50. data/lib/evilution/mutator/operator/regex_simplification.rb +62 -67
  51. data/lib/evilution/mutator/operator/rescue_body_replacement.rb +9 -8
  52. data/lib/evilution/mutator/operator/rescue_removal.rb +4 -7
  53. data/lib/evilution/mutator/operator/superclass_removal.rb +21 -15
  54. data/lib/evilution/parallel/work_queue/dispatcher.rb +15 -8
  55. data/lib/evilution/parallel/work_queue/worker.rb +10 -7
  56. data/lib/evilution/parallel/work_queue.rb +35 -18
  57. data/lib/evilution/reporter/cli/item_formatters/coverage_gap.rb +13 -8
  58. data/lib/evilution/reporter/cli/line_formatters/mutations.rb +17 -8
  59. data/lib/evilution/reporter/json.rb +52 -18
  60. data/lib/evilution/reporter/suggestion/diff_helpers.rb +0 -13
  61. data/lib/evilution/reporter/suggestion/diff_lines.rb +28 -0
  62. data/lib/evilution/reporter/suggestion/templates/minitest.rb +20 -14
  63. data/lib/evilution/reporter/suggestion/templates/rspec.rb +19 -13
  64. data/lib/evilution/runner/baseline_runner.rb +15 -8
  65. data/lib/evilution/runner/diagnostics.rb +13 -9
  66. data/lib/evilution/runner/isolation_resolver.rb +11 -9
  67. data/lib/evilution/runner/mutation_executor/result_cache.rb +3 -1
  68. data/lib/evilution/runner/mutation_executor/strategy/parallel.rb +32 -10
  69. data/lib/evilution/runner/mutation_executor/strategy/sequential.rb +1 -1
  70. data/lib/evilution/runner/mutation_executor.rb +2 -0
  71. data/lib/evilution/runner/mutation_planner.rb +37 -17
  72. data/lib/evilution/runner/subject_pipeline.rb +21 -11
  73. data/lib/evilution/runner.rb +3 -3
  74. data/lib/evilution/session/diff.rb +15 -6
  75. data/lib/evilution/spec_ast_cache.rb +26 -12
  76. data/lib/evilution/version.rb +1 -1
  77. data/script/memory_check +11 -5
  78. data/scripts/benchmark_density +10 -9
  79. data/scripts/compare_mutations +38 -21
  80. data/scripts/mutant_json_adapter +7 -4
  81. metadata +3 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: dda90ba09660e134411bad5df79ba56075ed9661f8e94aa3b366478cc3191755
4
- data.tar.gz: c1c5606e0a2c082dd1d7ec24738f0558b7cc94bfc3632cd4c2fdd200d5ad5e11
3
+ metadata.gz: 607074e45fead28ec35facc8fadadbb44e0dc6429ee00ef37edf92145bc4b7fc
4
+ data.tar.gz: 3bc840215dff4272ea6da9aad5ebed982827507f765c6cadc444bd1f3fb63ddc
5
5
  SHA512:
6
- metadata.gz: 30d4d7dcdcb9d88615fa5d37205bd02a307f32b6150a7ee7fa55b8397b677d40dd26e5777043fb6dfeeddf2559c8e79ae23276574ede65627ccf95f9effcd31e
7
- data.tar.gz: a657c55edcffd46c6883c4146cd97471db4c51940def54027a49f01ff89a29e7fd6fd4c28e758d9d7207623d664c320ccc22a9a01430abd5d26574afb1142932
6
+ metadata.gz: 5febff050f2670b2ab5f1cbeac9bfc41b0f30edc3e5ee39591036da0ef9ccf5d1273c0abe0cc828d289a959c2e135adb348980882a931d263df65e4b8a4e2299
7
+ data.tar.gz: 1f0d38cab915255ce1742350255cce291fd9c6e3bbdc649af778750089ca9b219c7f2553d1de40c6b2ac72b128310c56d7fff3ecc99ee2c14db42680dd0f3b2b
@@ -258,3 +258,55 @@
258
258
  {"id":"int-b9cb7738","kind":"field_change","created_at":"2026-05-01T18:06:39.17748775Z","actor":"Denis Kiselev","issue_id":"EV-gffv","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"merged"}}
259
259
  {"id":"int-983c2fe6","kind":"field_change","created_at":"2026-05-02T02:47:27.113692488Z","actor":"Denis Kiselev","issue_id":"EV-3t8l","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"Audit confirmed feature already fully implemented in current master: --fail-fast CLI flag, .evilution.yml key, FailFast validator, ResultNotifier trips at threshold, sequential+parallel strategies short-circuit, summary.truncated? indicator, reporter notices (CLI/HTML/JSON), full spec coverage across notifier/sequential/parallel/runner/parser. CI signal available via summary.truncated? in reports."}}
260
260
  {"id":"int-1790a8b7","kind":"field_change","created_at":"2026-05-02T17:53:56.73309749Z","actor":"Denis Kiselev","issue_id":"EV-2gpj","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"Audit confirmed refactor already done: ProcessCleanup.safe_kill / safe_wait helpers extracted to lib/evilution/process_cleanup.rb (lines 8-18), used by baseline.rb (lines 85,93,94) and parallel/work_queue/worker.rb. No Style/RescueModifier disables remain anywhere in lib/. bundle exec rubocop lib/evilution/baseline.rb clean. Existing specs pass."}}
261
+ {"id":"int-a4ad60a6","kind":"field_change","created_at":"2026-05-06T03:30:34.744627494Z","actor":"Denis Kiselev","issue_id":"EV-s24s","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"Closed"}}
262
+ {"id":"int-076a3c83","kind":"field_change","created_at":"2026-05-06T03:30:41.864846348Z","actor":"Denis Kiselev","issue_id":"EV-s24s","extra":{"field":"status","new_value":"in_progress","old_value":"closed"}}
263
+ {"id":"int-01566863","kind":"field_change","created_at":"2026-05-06T07:19:50.242235949Z","actor":"Denis Kiselev","issue_id":"EV-y27w","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"Obsolete: methods refactored on master (commit c6177ee). Rubocop ABC clean for these files."}}
264
+ {"id":"int-2ead0736","kind":"field_change","created_at":"2026-05-06T07:19:50.702277885Z","actor":"Denis Kiselev","issue_id":"EV-2luk","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"Obsolete: methods refactored on master (commit c6177ee). Rubocop ABC clean for these files."}}
265
+ {"id":"int-57a7f7bd","kind":"field_change","created_at":"2026-05-06T07:19:51.207502619Z","actor":"Denis Kiselev","issue_id":"EV-6pj3","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"Obsolete: methods refactored on master (commit c6177ee). Rubocop ABC clean for these files."}}
266
+ {"id":"int-6c362b36","kind":"field_change","created_at":"2026-05-06T07:19:51.688513357Z","actor":"Denis Kiselev","issue_id":"EV-675y","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"Obsolete: methods refactored on master (commit c6177ee). Rubocop ABC clean for these files."}}
267
+ {"id":"int-5debf846","kind":"field_change","created_at":"2026-05-06T07:19:52.17818897Z","actor":"Denis Kiselev","issue_id":"EV-g8pq","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"Obsolete: methods refactored on master (commit c6177ee). Rubocop ABC clean for these files."}}
268
+ {"id":"int-53e2ac7d","kind":"field_change","created_at":"2026-05-06T07:19:52.618140112Z","actor":"Denis Kiselev","issue_id":"EV-7wi7","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"Obsolete: methods refactored on master (commit c6177ee). Rubocop ABC clean for these files."}}
269
+ {"id":"int-dccffc47","kind":"field_change","created_at":"2026-05-06T07:19:53.069605194Z","actor":"Denis Kiselev","issue_id":"EV-cz6e","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"Obsolete: methods refactored on master (commit c6177ee). Rubocop ABC clean for these files."}}
270
+ {"id":"int-58d8260f","kind":"field_change","created_at":"2026-05-06T07:19:53.543437653Z","actor":"Denis Kiselev","issue_id":"EV-rmzx","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"Obsolete: methods refactored on master (commit c6177ee). Rubocop ABC clean for these files."}}
271
+ {"id":"int-789dee22","kind":"field_change","created_at":"2026-05-06T07:19:53.965510546Z","actor":"Denis Kiselev","issue_id":"EV-5qg6","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"Obsolete: methods refactored on master (commit c6177ee). Rubocop ABC clean for these files."}}
272
+ {"id":"int-85fdbe1f","kind":"field_change","created_at":"2026-05-06T07:19:54.37777323Z","actor":"Denis Kiselev","issue_id":"EV-pfz5","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"Obsolete: methods refactored on master (commit c6177ee). Rubocop ABC clean for these files."}}
273
+ {"id":"int-c61f5bf7","kind":"field_change","created_at":"2026-05-06T07:19:54.831384506Z","actor":"Denis Kiselev","issue_id":"EV-ynvi","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"Obsolete: methods refactored on master (commit c6177ee). Rubocop ABC clean for these files."}}
274
+ {"id":"int-f66ee100","kind":"field_change","created_at":"2026-05-06T07:19:55.253094559Z","actor":"Denis Kiselev","issue_id":"EV-ltmi","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"Obsolete: methods refactored on master (commit c6177ee). Rubocop ABC clean for these files."}}
275
+ {"id":"int-d0d38103","kind":"field_change","created_at":"2026-05-06T07:19:55.730451387Z","actor":"Denis Kiselev","issue_id":"EV-dha6","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"Obsolete: methods refactored on master (commit c6177ee). Rubocop ABC clean for these files."}}
276
+ {"id":"int-14872708","kind":"field_change","created_at":"2026-05-06T07:19:56.276618255Z","actor":"Denis Kiselev","issue_id":"EV-gml5","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"Obsolete: methods refactored on master (commit c6177ee). Rubocop ABC clean for these files."}}
277
+ {"id":"int-3a46d2c8","kind":"field_change","created_at":"2026-05-06T07:19:56.746453231Z","actor":"Denis Kiselev","issue_id":"EV-wg1v","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"Obsolete: methods refactored on master (commit c6177ee). Rubocop ABC clean for these files."}}
278
+ {"id":"int-7e6cad25","kind":"field_change","created_at":"2026-05-06T07:19:57.199497315Z","actor":"Denis Kiselev","issue_id":"EV-jf8v","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"Obsolete: methods refactored on master (commit c6177ee). Rubocop ABC clean for these files."}}
279
+ {"id":"int-596241e1","kind":"field_change","created_at":"2026-05-06T07:19:57.648508843Z","actor":"Denis Kiselev","issue_id":"EV-p699","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"Obsolete: methods refactored on master (commit c6177ee). Rubocop ABC clean for these files."}}
280
+ {"id":"int-47d13782","kind":"field_change","created_at":"2026-05-06T07:19:58.119871705Z","actor":"Denis Kiselev","issue_id":"EV-mur2","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"Obsolete: methods refactored on master (commit c6177ee). Rubocop ABC clean for these files."}}
281
+ {"id":"int-5bcb3ba0","kind":"field_change","created_at":"2026-05-06T07:19:58.593580083Z","actor":"Denis Kiselev","issue_id":"EV-t05i","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"Obsolete: methods refactored on master (commit c6177ee). Rubocop ABC clean for these files."}}
282
+ {"id":"int-d0a8a49b","kind":"field_change","created_at":"2026-05-06T07:19:59.066030169Z","actor":"Denis Kiselev","issue_id":"EV-t3os","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"Obsolete: methods refactored on master (commit c6177ee). Rubocop ABC clean for these files."}}
283
+ {"id":"int-c5fd767e","kind":"field_change","created_at":"2026-05-06T07:19:59.519830731Z","actor":"Denis Kiselev","issue_id":"EV-70v7","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"Obsolete: methods refactored on master (commit c6177ee). Rubocop ABC clean for these files."}}
284
+ {"id":"int-ee96343f","kind":"field_change","created_at":"2026-05-06T07:19:59.945079843Z","actor":"Denis Kiselev","issue_id":"EV-g067","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"Obsolete: methods refactored on master (commit c6177ee). Rubocop ABC clean for these files."}}
285
+ {"id":"int-1088cf0f","kind":"field_change","created_at":"2026-05-06T07:20:00.369713429Z","actor":"Denis Kiselev","issue_id":"EV-vybg","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"Obsolete: methods refactored on master (commit c6177ee). Rubocop ABC clean for these files."}}
286
+ {"id":"int-677bcb4c","kind":"field_change","created_at":"2026-05-06T07:20:00.849595223Z","actor":"Denis Kiselev","issue_id":"EV-b2j8","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"Obsolete: methods refactored on master (commit c6177ee). Rubocop ABC clean for these files."}}
287
+ {"id":"int-49853be8","kind":"field_change","created_at":"2026-05-06T07:20:01.309796737Z","actor":"Denis Kiselev","issue_id":"EV-jxio","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"Obsolete: methods refactored on master (commit c6177ee). Rubocop ABC clean for these files."}}
288
+ {"id":"int-7d1d715d","kind":"field_change","created_at":"2026-05-06T07:20:01.802408893Z","actor":"Denis Kiselev","issue_id":"EV-v1df","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"Obsolete: methods refactored on master (commit c6177ee). Rubocop ABC clean for these files."}}
289
+ {"id":"int-f8751a4f","kind":"field_change","created_at":"2026-05-06T07:20:02.269343453Z","actor":"Denis Kiselev","issue_id":"EV-05x3","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"Obsolete: methods refactored on master (commit c6177ee). Rubocop ABC clean for these files."}}
290
+ {"id":"int-98efe12b","kind":"field_change","created_at":"2026-05-06T07:20:02.712854653Z","actor":"Denis Kiselev","issue_id":"EV-neqr","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"Obsolete: methods refactored on master (commit c6177ee). Rubocop ABC clean for these files."}}
291
+ {"id":"int-2fab6f44","kind":"field_change","created_at":"2026-05-06T07:20:03.192828055Z","actor":"Denis Kiselev","issue_id":"EV-ugrl","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"Obsolete: methods refactored on master (commit c6177ee). Rubocop ABC clean for these files."}}
292
+ {"id":"int-64c45537","kind":"field_change","created_at":"2026-05-06T07:20:03.645870239Z","actor":"Denis Kiselev","issue_id":"EV-1czz","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"Obsolete: methods refactored on master (commit c6177ee). Rubocop ABC clean for these files."}}
293
+ {"id":"int-b226aaaa","kind":"field_change","created_at":"2026-05-06T07:20:04.115581656Z","actor":"Denis Kiselev","issue_id":"EV-nsp3","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"Obsolete: methods refactored on master (commit c6177ee). Rubocop ABC clean for these files."}}
294
+ {"id":"int-40a144bf","kind":"field_change","created_at":"2026-05-06T07:20:04.57274421Z","actor":"Denis Kiselev","issue_id":"EV-bx35","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"Obsolete: methods refactored on master (commit c6177ee). Rubocop ABC clean for these files."}}
295
+ {"id":"int-bb3376cc","kind":"field_change","created_at":"2026-05-06T07:20:05.130537402Z","actor":"Denis Kiselev","issue_id":"EV-2viy","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"Obsolete: methods refactored on master (commit c6177ee). Rubocop ABC clean for these files."}}
296
+ {"id":"int-6ae5b449","kind":"field_change","created_at":"2026-05-06T07:20:05.597150157Z","actor":"Denis Kiselev","issue_id":"EV-0nre","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"Obsolete: methods refactored on master (commit c6177ee). Rubocop ABC clean for these files."}}
297
+ {"id":"int-f37c08ed","kind":"field_change","created_at":"2026-05-06T07:20:06.22033588Z","actor":"Denis Kiselev","issue_id":"EV-n4ai","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"Obsolete: methods refactored on master (commit c6177ee). Rubocop ABC clean for these files."}}
298
+ {"id":"int-f1cc76b6","kind":"field_change","created_at":"2026-05-06T07:20:06.659235495Z","actor":"Denis Kiselev","issue_id":"EV-oyus","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"Obsolete: methods refactored on master (commit c6177ee). Rubocop ABC clean for these files."}}
299
+ {"id":"int-7d54a6e7","kind":"field_change","created_at":"2026-05-06T07:20:07.091511841Z","actor":"Denis Kiselev","issue_id":"EV-h46p","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"Obsolete: methods refactored on master (commit c6177ee). Rubocop ABC clean for these files."}}
300
+ {"id":"int-2bea4b64","kind":"field_change","created_at":"2026-05-06T07:20:06.87585377Z","actor":"Denis Kiselev","issue_id":"EV-n05g","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"Obsolete: methods refactored on master (commit c6177ee). Rubocop ABC clean for these files."}}
301
+ {"id":"int-3b25e06c","kind":"field_change","created_at":"2026-05-06T07:20:06.448623384Z","actor":"Denis Kiselev","issue_id":"EV-htfi","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"Obsolete: methods refactored on master (commit c6177ee). Rubocop ABC clean for these files."}}
302
+ {"id":"int-9fedcb1f","kind":"field_change","created_at":"2026-05-06T07:20:06.870511987Z","actor":"Denis Kiselev","issue_id":"EV-86au","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"Obsolete: methods refactored on master (commit c6177ee). Rubocop ABC clean for these files."}}
303
+ {"id":"int-55d2834d","kind":"field_change","created_at":"2026-05-06T07:20:07.313207411Z","actor":"Denis Kiselev","issue_id":"EV-fyq8","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"Obsolete: methods refactored on master (commit c6177ee). Rubocop ABC clean for these files."}}
304
+ {"id":"int-44442358","kind":"field_change","created_at":"2026-05-06T07:20:07.761317317Z","actor":"Denis Kiselev","issue_id":"EV-jxr1","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"Obsolete: methods refactored on master (commit c6177ee). Rubocop ABC clean for these files."}}
305
+ {"id":"int-1ec5b145","kind":"field_change","created_at":"2026-05-06T07:20:08.263779524Z","actor":"Denis Kiselev","issue_id":"EV-0lhb","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"Obsolete: methods refactored on master (commit c6177ee). Rubocop ABC clean for these files."}}
306
+ {"id":"int-3177494f","kind":"field_change","created_at":"2026-05-06T07:20:08.690447157Z","actor":"Denis Kiselev","issue_id":"EV-6xeh","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"Obsolete: methods refactored on master (commit c6177ee). Rubocop ABC clean for these files."}}
307
+ {"id":"int-c3e862ed","kind":"field_change","created_at":"2026-05-06T07:20:09.150754493Z","actor":"Denis Kiselev","issue_id":"EV-n5vx","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"Obsolete: methods refactored on master (commit c6177ee). Rubocop ABC clean for these files."}}
308
+ {"id":"int-bbb95b03","kind":"field_change","created_at":"2026-05-06T07:20:09.610416065Z","actor":"Denis Kiselev","issue_id":"EV-pbo4","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"Obsolete: methods refactored on master (commit c6177ee). Rubocop ABC clean for these files."}}
309
+ {"id":"int-42dadd38","kind":"field_change","created_at":"2026-05-06T07:20:10.034048271Z","actor":"Denis Kiselev","issue_id":"EV-45kf","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"Obsolete: methods refactored on master (commit c6177ee). Rubocop ABC clean for these files."}}
310
+ {"id":"int-2973ca4f","kind":"field_change","created_at":"2026-05-06T07:20:10.503854404Z","actor":"Denis Kiselev","issue_id":"EV-t2o9","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"Obsolete: methods refactored on master (commit c6177ee). Rubocop ABC clean for these files."}}
311
+ {"id":"int-523d3c8c","kind":"field_change","created_at":"2026-05-06T07:20:11.036667234Z","actor":"Denis Kiselev","issue_id":"EV-qtvs","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"Obsolete: methods refactored on master (commit c6177ee). Rubocop ABC clean for these files."}}
312
+ {"id":"int-be905e1b","kind":"field_change","created_at":"2026-05-06T07:20:11.472283074Z","actor":"Denis Kiselev","issue_id":"EV-psit","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"Obsolete: methods refactored on master (commit c6177ee). Rubocop ABC clean for these files."}}
data/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.29.0] - 2026-05-06
4
+
5
+ ### Changed
6
+
7
+ - **Internal codebase hygiene sweep — `Metrics/AbcSize` ceiling tightened from `25` → `17`** — every method exceeding the new threshold was refactored via pure extract-method (no behavior change). ~48 sites across `lib/evilution/{ast,cli,compare,config,disable_comment,integration,mcp,mutation,mutator,parallel,reporter,runner,session,spec_ast_cache}` plus supporting `scripts/` utilities. No public API, CLI flag, or output changes; mutation operators and report emission are bit-identical. The upper bound on per-method ABC is now strictly enforced repo-wide — only `lib/evilution/runner.rb` remains in `.rubocop_todo.yml` (#371, PR #1160 + per-file sub-PRs)
8
+ - **Tuple-return methods across the runner pipeline migrated to named `Data.define` value objects** — internal-only refactor introducing typed return shapes for `Runner::MutationExecutor#call` (→`ExecutionResult`), `Runner::MutationPlanner#call` (→`Plan` plus internal `GenerationResult` / `DisabledFilterResult` / `SigFilterResult` / `EquivalentFilterResult`), `Parallel::WorkQueue` outputs, `Cache#partition` (→`Partition`), `Config.normalize_limit` (→`LimitResult`), `Mutation::Slicer.collect_chain` (→`Chain`), `slice_affected_lines` (→`AffectedSlices`), `CLI::Parser::FilesAndRanges` (→`ParsedPaths`), and assorted CLI command helpers. Improves call-site readability without affecting external behavior (#948, PR #1094; #949, PR #1095; #950, PR #1096; #951, PR #1097; #952, PR #1098; #953, PR #1099; #954, PR #1100; #955, PR #1101; #956, PR #1102)
9
+
3
10
  ## [0.28.0] - 2026-05-03
4
11
 
5
12
  ### Added
@@ -17,18 +17,35 @@ class Evilution::AST::ConstantNames
17
17
  private
18
18
 
19
19
  def collect(node, nesting = [])
20
- names = []
21
20
  case node
22
- when Prism::ModuleNode, Prism::ClassNode
23
- const = node.constant_path.full_name
24
- qualified = nesting.any? && !const.include?("::") ? "#{nesting.join("::")}::#{const}" : const
25
- names << qualified
26
- names.concat(collect(node.body, nesting + [const])) if node.body
27
- when Prism::ProgramNode
28
- names.concat(collect(node.statements, nesting)) if node.statements
29
- when Prism::StatementsNode
30
- node.body.each { |child| names.concat(collect(child, nesting)) }
21
+ when Prism::ModuleNode, Prism::ClassNode then collect_class(node, nesting)
22
+ when Prism::ProgramNode then collect_program(node, nesting)
23
+ when Prism::StatementsNode then collect_statements(node, nesting)
24
+ else []
31
25
  end
32
- names
26
+ end
27
+
28
+ def collect_class(node, nesting)
29
+ const = node.constant_path.full_name
30
+ qualified = qualify(const, nesting)
31
+ return [qualified] if node.body.nil?
32
+
33
+ [qualified] + collect(node.body, nesting + [const])
34
+ end
35
+
36
+ def collect_program(node, nesting)
37
+ return [] if node.statements.nil?
38
+
39
+ collect(node.statements, nesting)
40
+ end
41
+
42
+ def collect_statements(node, nesting)
43
+ node.body.flat_map { |child| collect(child, nesting) }
44
+ end
45
+
46
+ def qualify(const, nesting)
47
+ return const if nesting.empty? || const.include?("::")
48
+
49
+ "#{nesting.join("::")}::#{const}"
33
50
  end
34
51
  end
@@ -75,24 +75,36 @@ class Evilution::AST::Pattern::Parser
75
75
 
76
76
  def parse_value
77
77
  skip_whitespace
78
+ parse_negation || parse_deep_wildcard || parse_single_wildcard || parse_any_node || parse_value_or_nested
79
+ end
78
80
 
79
- if current_char == "!"
80
- advance(1)
81
- skip_whitespace
82
- inner = parse_value
83
- Evilution::AST::Pattern::NegationMatcher.new(inner)
84
- elsif current_char == "*" && !peek_string("**")
85
- advance(1)
86
- Evilution::AST::Pattern::WildcardValueMatcher.new
87
- elsif peek_string("**")
88
- advance(2)
89
- Evilution::AST::Pattern::DeepWildcardMatcher.new
90
- elsif current_char == "_" && !identifier_continues?(1)
91
- advance(1)
92
- Evilution::AST::Pattern::AnyNodeMatcher.new
93
- else
94
- parse_value_or_nested
95
- end
81
+ def parse_negation
82
+ return nil unless current_char == "!"
83
+
84
+ advance(1)
85
+ skip_whitespace
86
+ Evilution::AST::Pattern::NegationMatcher.new(parse_value)
87
+ end
88
+
89
+ def parse_deep_wildcard
90
+ return nil unless peek_string("**")
91
+
92
+ advance(2)
93
+ Evilution::AST::Pattern::DeepWildcardMatcher.new
94
+ end
95
+
96
+ def parse_single_wildcard
97
+ return nil unless current_char == "*"
98
+
99
+ advance(1)
100
+ Evilution::AST::Pattern::WildcardValueMatcher.new
101
+ end
102
+
103
+ def parse_any_node
104
+ return nil unless current_char == "_" && !identifier_continues?(1)
105
+
106
+ advance(1)
107
+ Evilution::AST::Pattern::AnyNodeMatcher.new
96
108
  end
97
109
 
98
110
  def parse_value_or_nested
@@ -14,10 +14,7 @@ class Evilution::CLI::Commands::SessionDiff < Evilution::CLI::Command
14
14
  def perform
15
15
  raise Evilution::ConfigError, "two session file paths required" unless @files.length == 2
16
16
 
17
- store = Evilution::Session::Store.new
18
- base_data = store.load(@files[0])
19
- head_data = store.load(@files[1])
20
- result = Evilution::Session::Diff.new.call(base_data, head_data)
17
+ result = compute_diff(@files)
21
18
  Evilution::CLI::Printers::SessionDiff.new(result, format: @options[:format]).render(@stdout)
22
19
  0
23
20
  rescue ::JSON::ParserError => e
@@ -25,6 +22,11 @@ class Evilution::CLI::Commands::SessionDiff < Evilution::CLI::Command
25
22
  rescue SystemCallError => e
26
23
  raise Evilution::Error, e.message
27
24
  end
25
+
26
+ def compute_diff(files)
27
+ store = Evilution::Session::Store.new
28
+ Evilution::Session::Diff.new.call(store.load(files[0]), store.load(files[1]))
29
+ end
28
30
  end
29
31
 
30
32
  Evilution::CLI::Dispatcher.register(:session_diff, Evilution::CLI::Commands::SessionDiff)
@@ -9,6 +9,9 @@ require_relative "../../runner"
9
9
  require_relative "../../mutator"
10
10
 
11
11
  class Evilution::CLI::Commands::Subjects < Evilution::CLI::Command
12
+ EntriesResult = Data.define(:entries, :total)
13
+ private_constant :EntriesResult
14
+
12
15
  private
13
16
 
14
17
  def perform
@@ -23,8 +26,8 @@ class Evilution::CLI::Commands::Subjects < Evilution::CLI::Command
23
26
  return 0
24
27
  end
25
28
 
26
- entries, total = collect_entries(subjects, config)
27
- Evilution::CLI::Printers::Subjects.new(entries, total_mutations: total).render(@stdout)
29
+ result = collect_entries(subjects, config)
30
+ Evilution::CLI::Printers::Subjects.new(result.entries, total_mutations: result.total).render(@stdout)
28
31
  0
29
32
  end
30
33
 
@@ -43,7 +46,7 @@ class Evilution::CLI::Commands::Subjects < Evilution::CLI::Command
43
46
  subj.release_node!
44
47
  end
45
48
 
46
- [entries, total]
49
+ EntriesResult.new(entries: entries, total: total)
47
50
  end
48
51
  end
49
52
 
@@ -11,11 +11,14 @@ require_relative "../../mutator/registry"
11
11
  require_relative "../../ast/parser"
12
12
 
13
13
  class Evilution::CLI::Commands::UtilMutation < Evilution::CLI::Command
14
+ SourceInput = Data.define(:source, :file_path)
15
+ private_constant :SourceInput
16
+
14
17
  private
15
18
 
16
19
  def perform
17
- source, file_path = resolve_util_mutation_source
18
- subjects = parse_source_to_subjects(source, file_path)
20
+ input = resolve_util_mutation_source
21
+ subjects = parse_source_to_subjects(input.source, input.file_path)
19
22
  config = Evilution::Config.new(**@options)
20
23
  registry = Evilution::Mutator::Registry.default
21
24
  operator_options = build_operator_options(config)
@@ -33,24 +36,26 @@ class Evilution::CLI::Commands::UtilMutation < Evilution::CLI::Command
33
36
  end
34
37
 
35
38
  def resolve_util_mutation_source
36
- if @options[:eval]
37
- tmpfile = Tempfile.new(["evilution_eval", ".rb"])
38
- tmpfile.write(@options[:eval])
39
- tmpfile.flush
40
- @util_tmpfile = tmpfile
41
- [@options[:eval], tmpfile.path]
42
- elsif @files.first
43
- path = @files.first
44
- raise Evilution::Error, "file not found: #{path}" unless File.exist?(path)
39
+ return build_eval_source(@options[:eval]) if @options[:eval]
40
+ return build_file_source(@files.first) if @files.first
45
41
 
46
- begin
47
- [File.read(path), path]
48
- rescue SystemCallError => e
49
- raise Evilution::Error, e.message
50
- end
51
- else
52
- raise Evilution::Error, "source required: use -e 'code' or provide a file path"
53
- end
42
+ raise Evilution::Error, "source required: use -e 'code' or provide a file path"
43
+ end
44
+
45
+ def build_eval_source(code)
46
+ tmpfile = Tempfile.new(["evilution_eval", ".rb"])
47
+ tmpfile.write(code)
48
+ tmpfile.flush
49
+ @util_tmpfile = tmpfile
50
+ SourceInput.new(source: code, file_path: tmpfile.path)
51
+ end
52
+
53
+ def build_file_source(path)
54
+ raise Evilution::Error, "file not found: #{path}" unless File.exist?(path)
55
+
56
+ SourceInput.new(source: File.read(path), file_path: path)
57
+ rescue SystemCallError => e
58
+ raise Evilution::Error, e.message
54
59
  end
55
60
 
56
61
  def parse_source_to_subjects(source, file_label)
@@ -20,6 +20,13 @@ class Evilution::CLI::Parser::CommandExtractor
20
20
  ENVIRONMENT_SUBCOMMANDS = { "show" => :environment_show }.freeze
21
21
  UTIL_SUBCOMMANDS = { "mutation" => :util_mutation }.freeze
22
22
 
23
+ SUBCOMMAND_FAMILIES = {
24
+ "session" => [SESSION_SUBCOMMANDS, "session", "list, show, diff, gc"],
25
+ "tests" => [TESTS_SUBCOMMANDS, "tests", "list"],
26
+ "environment" => [ENVIRONMENT_SUBCOMMANDS, "environment", "show"],
27
+ "util" => [UTIL_SUBCOMMANDS, "util", "mutation"]
28
+ }.freeze
29
+
23
30
  Result = Struct.new(:command, :remaining_argv, :parse_error)
24
31
 
25
32
  def self.call(argv)
@@ -46,18 +53,9 @@ class Evilution::CLI::Parser::CommandExtractor
46
53
  @argv.shift
47
54
  elsif first == "run"
48
55
  @argv.shift
49
- elsif first == "session"
50
- @argv.shift
51
- extract_subcommand(SESSION_SUBCOMMANDS, "session", "list, show, diff, gc")
52
- elsif first == "tests"
53
- @argv.shift
54
- extract_subcommand(TESTS_SUBCOMMANDS, "tests", "list")
55
- elsif first == "environment"
56
- @argv.shift
57
- extract_subcommand(ENVIRONMENT_SUBCOMMANDS, "environment", "show")
58
- elsif first == "util"
56
+ elsif SUBCOMMAND_FAMILIES.key?(first)
59
57
  @argv.shift
60
- extract_subcommand(UTIL_SUBCOMMANDS, "util", "mutation")
58
+ extract_subcommand(*SUBCOMMAND_FAMILIES[first])
61
59
  end
62
60
  end
63
61
 
@@ -1,6 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Evilution::CLI::Parser::FileArgs
4
+ ParsedPaths = Data.define(:files, :ranges)
5
+
4
6
  module_function
5
7
 
6
8
  def parse(raw_args)
@@ -15,7 +17,7 @@ module Evilution::CLI::Parser::FileArgs
15
17
  ranges[file] = parse_line_range(range_str)
16
18
  end
17
19
 
18
- [files, ranges]
20
+ ParsedPaths.new(files: files, ranges: ranges)
19
21
  end
20
22
 
21
23
  def expand_spec_dir(dir)
@@ -21,6 +21,8 @@ class Evilution::CLI::Parser::OptionsBuilder
21
21
  add_core_options(opts)
22
22
  add_filter_options(opts)
23
23
  add_flag_options(opts)
24
+ add_runner_mode_options(opts)
25
+ add_output_options(opts)
24
26
  add_profile_options(opts)
25
27
  add_extra_flag_options(opts)
26
28
  add_session_options(opts)
@@ -47,11 +49,19 @@ class Evilution::CLI::Parser::OptionsBuilder
47
49
  end
48
50
 
49
51
  def add_filter_options(opts)
52
+ add_spec_filter_options(opts)
53
+ add_targeting_options(opts)
54
+ end
55
+
56
+ def add_spec_filter_options(opts)
50
57
  opts.on("--min-score FLOAT", Float, "Minimum mutation score to pass") { |s| @options[:min_score] = s }
51
58
  opts.on("--spec FILES", Array, "Spec files to run (comma-separated)") { |f| @options[:spec_files] = f }
52
59
  opts.on("--spec-dir DIR", "Include all specs in DIR") { |d| expand_spec_dir(d) }
53
60
  opts.on("--spec-pattern GLOB",
54
61
  "Restrict resolved spec candidates to files matching GLOB") { |p| @options[:spec_pattern] = p }
62
+ end
63
+
64
+ def add_targeting_options(opts)
55
65
  opts.on("--no-example-targeting",
56
66
  "Disable per-mutation example targeting (run all examples in resolved spec files)") do
57
67
  @options[:example_targeting] = false
@@ -76,13 +86,19 @@ class Evilution::CLI::Parser::OptionsBuilder
76
86
  "Use --no-incremental to override `incremental: true` from the config file for one run.") do |v|
77
87
  @options[:incremental] = v
78
88
  end
89
+ opts.on("--stdin", "Read target file paths from stdin (one per line)") { @options[:stdin] = true }
90
+ end
91
+
92
+ def add_runner_mode_options(opts)
79
93
  opts.on("--integration NAME", "Test integration: rspec, minitest (default: rspec)") { |i| @options[:integration] = i }
80
94
  opts.on("--isolation STRATEGY", "Isolation: auto, fork, in_process (default: auto)") { |s| @options[:isolation] = s }
81
95
  opts.on("--preload FILE", "Preload FILE in the parent process before forking " \
82
96
  "(default: auto-detect spec/rails_helper.rb -> spec/spec_helper.rb -> " \
83
97
  "test/test_helper.rb for Rails projects)") { |f| @options[:preload] = f }
84
98
  opts.on("--no-preload", "Disable parent-process preload even for Rails projects") { @options[:preload] = false }
85
- opts.on("--stdin", "Read target file paths from stdin (one per line)") { @options[:stdin] = true }
99
+ end
100
+
101
+ def add_output_options(opts)
86
102
  opts.on("--suggest-tests", "Generate concrete test code in suggestions (RSpec or Minitest)") { @options[:suggest_tests] = true }
87
103
  opts.on("--no-progress", "Disable progress bar") { @options[:progress] = false }
88
104
  opts.on("--quiet-children",
@@ -103,6 +119,12 @@ class Evilution::CLI::Parser::OptionsBuilder
103
119
  end
104
120
 
105
121
  def add_extra_flag_options(opts)
122
+ add_mutation_behavior_options(opts)
123
+ add_session_persistence_options(opts)
124
+ add_misc_extra_options(opts)
125
+ end
126
+
127
+ def add_mutation_behavior_options(opts)
106
128
  opts.on("--skip-heredoc-literals", "Skip all string literal mutations inside heredocs") { @options[:skip_heredoc_literals] = true }
107
129
  opts.on("--related-specs-heuristic", "Append related request/integration/feature/system specs for includes() mutations") do
108
130
  @options[:related_specs_heuristic] = true
@@ -112,8 +134,14 @@ class Evilution::CLI::Parser::OptionsBuilder
112
134
  @options[:fallback_to_full_suite] = true
113
135
  end
114
136
  opts.on("--show-disabled", "Report mutations skipped by # evilution:disable") { @options[:show_disabled] = true }
137
+ end
138
+
139
+ def add_session_persistence_options(opts)
115
140
  opts.on("--baseline-session PATH", "Compare against a baseline session in HTML report") { |p| @options[:baseline_session] = p }
116
141
  opts.on("--save-session", "Save session results to .evilution/results/") { @options[:save_session] = true }
142
+ end
143
+
144
+ def add_misc_extra_options(opts)
117
145
  opts.on("-e", "--eval CODE", "Evaluate code snippet (for util mutation)") { |c| @options[:eval] = c }
118
146
  opts.on("-v", "--verbose", "Verbose output") { @options[:verbose] = true }
119
147
  opts.on("-q", "--quiet", "Suppress output") { @options[:quiet] = true }
@@ -22,7 +22,7 @@ class Evilution::CLI::Parser::StdinReader
22
22
  line = line.strip
23
23
  lines << line unless line.empty?
24
24
  end
25
- files, ranges = Evilution::CLI::Parser::FileArgs.parse(lines)
26
- Result.new(files, ranges, nil)
25
+ parsed_paths = Evilution::CLI::Parser::FileArgs.parse(lines)
26
+ Result.new(parsed_paths.files, parsed_paths.ranges, nil)
27
27
  end
28
28
  end
@@ -20,7 +20,9 @@ class Evilution::CLI::Parser
20
20
 
21
21
  preprocess_flags
22
22
  remaining = OptionsBuilder.build(@options).parse!(@argv)
23
- @files, @line_ranges = FileArgs.parse(remaining)
23
+ parsed_paths = FileArgs.parse(remaining)
24
+ @files = parsed_paths.files
25
+ @line_ranges = parsed_paths.ranges
24
26
  read_stdin_files if @options.delete(:stdin) && %i[run subjects].include?(@command)
25
27
  build_parsed_args
26
28
  end
@@ -37,27 +39,23 @@ class Evilution::CLI::Parser
37
39
  def preprocess_flags
38
40
  result = []
39
41
  i = 0
40
- while i < @argv.length
41
- arg = @argv[i]
42
- if arg == "--fail-fast"
43
- next_arg = @argv[i + 1]
42
+ i = consume_token(i, result) while i < @argv.length
43
+ @argv = result
44
+ end
44
45
 
45
- if next_arg && next_arg.match?(/\A-?\d+\z/)
46
- @options[:fail_fast] = next_arg
47
- i += 2
48
- else
49
- result << arg
50
- i += 1
51
- end
52
- elsif arg.start_with?("--fail-fast=")
53
- @options[:fail_fast] = arg.delete_prefix("--fail-fast=")
54
- i += 1
55
- else
56
- result << arg
57
- i += 1
58
- end
46
+ def consume_token(i, result)
47
+ arg = @argv[i]
48
+ next_arg = @argv[i + 1]
49
+ if arg == "--fail-fast" && !next_arg.nil? && next_arg.match?(/\A-?\d+\z/)
50
+ @options[:fail_fast] = next_arg
51
+ return i + 2
59
52
  end
60
- @argv = result
53
+ if arg.start_with?("--fail-fast=")
54
+ @options[:fail_fast] = arg.delete_prefix("--fail-fast=")
55
+ return i + 1
56
+ end
57
+ result << arg
58
+ i + 1
61
59
  end
62
60
 
63
61
  def read_stdin_files
@@ -3,6 +3,12 @@
3
3
  require_relative "../printers"
4
4
 
5
5
  class Evilution::CLI::Printers::Environment
6
+ PLAIN_SETTINGS = %i[
7
+ timeout format integration jobs isolation baseline incremental
8
+ verbose quiet progress min_score suggest_tests save_session
9
+ skip_heredoc_literals
10
+ ].freeze
11
+
6
12
  def initialize(config, config_file:)
7
13
  @config = config
8
14
  @config_file = config_file
@@ -30,24 +36,18 @@ class Evilution::CLI::Printers::Environment
30
36
  end
31
37
 
32
38
  def settings_lines
33
- [
34
- " timeout: #{@config.timeout}",
35
- " format: #{@config.format}",
36
- " integration: #{@config.integration}",
37
- " jobs: #{@config.jobs}",
38
- " isolation: #{@config.isolation}",
39
- " baseline: #{@config.baseline}",
40
- " incremental: #{@config.incremental}",
41
- " verbose: #{@config.verbose}",
42
- " quiet: #{@config.quiet}",
43
- " progress: #{@config.progress}",
44
- " fail_fast: #{@config.fail_fast || "(disabled)"}",
45
- " min_score: #{@config.min_score}",
46
- " suggest_tests: #{@config.suggest_tests}",
47
- " save_session: #{@config.save_session}",
48
- " target: #{@config.target || "(all files)"}",
49
- " skip_heredoc_literals: #{@config.skip_heredoc_literals}",
50
- " ignore_patterns: #{@config.ignore_patterns.empty? ? "(none)" : @config.ignore_patterns.inspect}"
51
- ]
39
+ plain_lines = PLAIN_SETTINGS.map { |k| setting_line(k, @config.public_send(k)) }
40
+ plain_lines.insert(10, setting_line(:fail_fast, @config.fail_fast || "(disabled)"))
41
+ plain_lines.insert(14, setting_line(:target, @config.target || "(all files)"))
42
+ plain_lines << setting_line(:ignore_patterns, format_ignore_patterns(@config.ignore_patterns))
43
+ plain_lines
44
+ end
45
+
46
+ def setting_line(key, value)
47
+ " #{key}: #{value}"
48
+ end
49
+
50
+ def format_ignore_patterns(patterns)
51
+ patterns.empty? ? "(none)" : patterns.inspect
52
52
  end
53
53
  end
@@ -32,16 +32,16 @@ class Evilution::CLI::Printers::SessionDiff
32
32
  end
33
33
 
34
34
  def print_summary(io, summary)
35
- delta_str = format("%+.2f%%", summary.score_delta * 100)
36
35
  io.puts("Session Diff")
37
36
  io.puts("=" * 40)
38
- io.puts(format("Base score: %<score>6.2f%% (%<killed>d/%<total>d killed)",
39
- score: summary.base_score * 100, killed: summary.base_killed,
40
- total: summary.base_total))
41
- io.puts(format("Head score: %<score>6.2f%% (%<killed>d/%<total>d killed)",
42
- score: summary.head_score * 100, killed: summary.head_killed,
43
- total: summary.head_total))
44
- io.puts("Delta: #{delta_str}")
37
+ io.puts(score_line("Base", summary.base_score, summary.base_killed, summary.base_total))
38
+ io.puts(score_line("Head", summary.head_score, summary.head_killed, summary.head_total))
39
+ io.puts("Delta: #{format("%+.2f%%", summary.score_delta * 100)}")
40
+ end
41
+
42
+ def score_line(label, score, killed, total)
43
+ format("%<label>s score: %<score>6.2f%% (%<killed>d/%<total>d killed)",
44
+ label: label, score: score * 100, killed: killed, total: total)
45
45
  end
46
46
 
47
47
  def print_section(io, title, mutations, color)