evilution 0.29.0 → 0.30.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/interactions.jsonl +54 -0
- data/.rubocop_todo.yml +7 -0
- data/CHANGELOG.md +42 -0
- data/README.md +194 -8
- data/docs/versioning.md +53 -0
- data/lib/evilution/ast/heredoc_span.rb +99 -0
- data/lib/evilution/baseline.rb +15 -2
- data/lib/evilution/cli/commands/compare.rb +13 -0
- data/lib/evilution/cli/parser/command_extractor.rb +3 -1
- data/lib/evilution/cli/parser/options_builder.rb +2 -2
- data/lib/evilution/config/file_loader.rb +40 -1
- data/lib/evilution/config.rb +11 -1
- data/lib/evilution/equivalent/heuristic/dead_code.rb +8 -1
- data/lib/evilution/feedback/setup_warning.rb +79 -0
- data/lib/evilution/gem_detector.rb +132 -0
- data/lib/evilution/integration/loading/body_call_neutralizer.rb +190 -0
- data/lib/evilution/integration/loading/mutation_applier.rb +20 -5
- data/lib/evilution/integration/loading/redefinition_recovery.rb +58 -1
- data/lib/evilution/integration/minitest.rb +37 -2
- data/lib/evilution/integration/rspec/result_builder.rb +20 -1
- data/lib/evilution/integration/rspec.rb +16 -1
- data/lib/evilution/isolation/fork.rb +77 -10
- data/lib/evilution/mcp/info_tool/response_formatter.rb +14 -1
- data/lib/evilution/mcp/info_tool.rb +3 -1
- data/lib/evilution/mcp/mutate_tool/option_parser.rb +1 -1
- data/lib/evilution/mcp/mutate_tool/report_trimmer.rb +58 -1
- data/lib/evilution/mcp/mutate_tool.rb +22 -3
- data/lib/evilution/mcp/session_tool.rb +7 -4
- data/lib/evilution/mcp.rb +6 -0
- data/lib/evilution/mutation.rb +13 -1
- data/lib/evilution/mutator/base.rb +49 -1
- data/lib/evilution/mutator/operator/argument_method_call_replacement.rb +59 -0
- data/lib/evilution/mutator/operator/block_param_removal.rb +32 -0
- data/lib/evilution/mutator/operator/explicit_super_mutation.rb +20 -2
- data/lib/evilution/mutator/operator/index_to_at.rb +13 -1
- data/lib/evilution/mutator/operator/last_expression_removal.rb +46 -0
- data/lib/evilution/mutator/operator/receiver_replacement.rb +29 -1
- data/lib/evilution/mutator/operator/rescue_removal.rb +59 -10
- data/lib/evilution/mutator/operator/splat_operator.rb +28 -1
- data/lib/evilution/mutator/operator/string_literal.rb +83 -6
- data/lib/evilution/mutator/registry.rb +2 -0
- data/lib/evilution/reporter/cli/line_formatters/error_rate_warning.rb +29 -0
- data/lib/evilution/reporter/cli/metrics_block.rb +2 -0
- data/lib/evilution/reporter/json.rb +2 -0
- data/lib/evilution/result/mutation_result.rb +12 -6
- data/lib/evilution/runner/baseline_runner.rb +5 -1
- data/lib/evilution/runner/isolation_resolver.rb +69 -8
- data/lib/evilution/runner/mutation_planner.rb +18 -1
- data/lib/evilution/session/schema.rb +44 -0
- data/lib/evilution/session/store.rb +5 -1
- data/lib/evilution/version.rb +1 -1
- data/lib/evilution.rb +2 -0
- data/schema/evilution.config.schema.json +205 -0
- data/script/build_runtime_snapshot +88 -0
- data/script/run_self_baseline +79 -0
- data/script/run_self_validation +54 -0
- metadata +15 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 34bc612e6537f856f98a6b1911989ddb89d5211bca426ebd1a08f8dddfe4eade
|
|
4
|
+
data.tar.gz: c6875ae71bb4092184df2c7a05f3efccca97a9db57c91c85e09f6bbf20c3c8d9
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 46d8c283c0fef3758c0369533a00a8db31337b263119d182133ad5d86d2dab7c2d60d2baa3ffcc53f87d7e605a84c880be2f9f9692a1bdef63642b670a50cb57
|
|
7
|
+
data.tar.gz: 7f492bfbff46d333d68e271b63b1ac10f1b9adf97a9d38a20f1255b77f3ada298ae92bad102a8835797432450130b179009129ce826a622a33b5346f9bfac58b
|
data/.beads/interactions.jsonl
CHANGED
|
@@ -310,3 +310,57 @@
|
|
|
310
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
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
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."}}
|
|
313
|
+
{"id":"int-b2204b5b","kind":"field_change","created_at":"2026-05-06T07:59:55.142970285Z","actor":"Denis Kiselev","issue_id":"EV-inyq","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"Already addressed upstream in v0.29.0 (rubocop ABC clean; tuple-return migrations landed in PRs #1094-#1102)."}}
|
|
314
|
+
{"id":"int-20a87f61","kind":"field_change","created_at":"2026-05-06T07:59:55.620019557Z","actor":"Denis Kiselev","issue_id":"EV-bvwe","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"Already addressed upstream in v0.29.0 (rubocop ABC clean; tuple-return migrations landed in PRs #1094-#1102)."}}
|
|
315
|
+
{"id":"int-3b31029e","kind":"field_change","created_at":"2026-05-06T07:59:56.145819366Z","actor":"Denis Kiselev","issue_id":"EV-crru","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"Already addressed upstream in v0.29.0 (rubocop ABC clean; tuple-return migrations landed in PRs #1094-#1102)."}}
|
|
316
|
+
{"id":"int-af4d932d","kind":"field_change","created_at":"2026-05-06T07:59:55.93110283Z","actor":"Denis Kiselev","issue_id":"EV-0nep","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"Already addressed upstream in v0.29.0 (rubocop ABC clean; tuple-return migrations landed in PRs #1094-#1102)."}}
|
|
317
|
+
{"id":"int-a16fb04a","kind":"field_change","created_at":"2026-05-07T08:38:44.320058211Z","actor":"Denis Kiselev","issue_id":"EV-i5yp","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"merged via PR #1163"}}
|
|
318
|
+
{"id":"int-3571ab77","kind":"field_change","created_at":"2026-05-07T09:00:32.015564768Z","actor":"Denis Kiselev","issue_id":"EV-vtoe","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"merged"}}
|
|
319
|
+
{"id":"int-59058caf","kind":"field_change","created_at":"2026-05-07T10:50:55.015519734Z","actor":"Denis Kiselev","issue_id":"EV-jmq4","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"merged"}}
|
|
320
|
+
{"id":"int-3fb9d199","kind":"field_change","created_at":"2026-05-07T13:05:45.604108546Z","actor":"Denis Kiselev","issue_id":"EV-1l8z","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"merged"}}
|
|
321
|
+
{"id":"int-3d3c2194","kind":"field_change","created_at":"2026-05-08T15:20:43.738723453Z","actor":"Denis Kiselev","issue_id":"EV-yyd8","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"Merged via PR #1175. Harness shipped: bin/evilution-self, script/build_runtime_snapshot, script/run_self_baseline, script/run_self_validation. All 10 originally-crashing dirs measurable. Follow-ups EV-9qh1/EV-u8n5/EV-2jex tracked separately."}}
|
|
322
|
+
{"id":"int-9093e7db","kind":"field_change","created_at":"2026-05-08T18:03:23.097429903Z","actor":"Denis Kiselev","issue_id":"EV-9qh1","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"Merged via PR #1180. Length-prefix marshal + polling waitpid loop in subject Isolation::Fork. Snapshot patch_runtime_protocol step removed (no longer needed). Spec coverage added for nested-fork pipe-EOF regression. Unblocks EV-u8n5."}}
|
|
323
|
+
{"id":"int-9510ef73","kind":"field_change","created_at":"2026-05-08T18:24:44.05157246Z","actor":"Denis Kiselev","issue_id":"EV-u8n5","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"Resolved by EV-9qh1. Both acceptance bullets verified post-merge (parallel jobs=4 completes; self-mutation of fork.rb/worker.rb/baseline.rb succeeds). No new code needed beyond dropping the script/run_self_baseline skip list."}}
|
|
324
|
+
{"id":"int-93b88ded","kind":"field_change","created_at":"2026-05-10T01:58:49.010521846Z","actor":"Denis Kiselev","issue_id":"EV-2jex","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"Merged. Full self-mutation re-baseline recorded: 30657 muts, 22525 killed, 7672 survived, 0 timeouts, score 74.59% across all 20 lib/ subdirs. Numbers posted to EV-j2kz description + GH#1178."}}
|
|
325
|
+
{"id":"int-e5511e59","kind":"field_change","created_at":"2026-05-10T03:05:25.311104857Z","actor":"Denis Kiselev","issue_id":"EV-pn5y","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"Merged via PR #1183. index_to_at now skips symbol/string keyed access (Hash-shape heuristic). Verified post-fix: in_process.rb self-mut produced 0 Hash.at NoMethodError, 250 measurable muts (was 260 with ~10 invalid Hash.at attempts)."}}
|
|
326
|
+
{"id":"int-42aeec67","kind":"field_change","created_at":"2026-05-10T04:26:08.578561775Z","actor":"Denis Kiselev","issue_id":"EV-s5br","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"Merged via PR #1184. 6 delegating accessors on MutationResult swapped from nil? checks to is_a?(ExpectedClass) guards. Verified: self-mut of subject mutation_result.rb produced 0 'undefined method ... for false' crashes (235 muts, 177 killed, exit 0)."}}
|
|
327
|
+
{"id":"int-c6021b93","kind":"field_change","created_at":"2026-05-10T19:05:56.88581869Z","actor":"Denis Kiselev","issue_id":"EV-t7kh","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"Merged via PR #1185. Minimal verbosity now surfaces a trimmed errors sample (first 3 entries × 5-line backtrace head + key fields) when errors>0; absent when errors=0. Tool description + README updated. Unblocks paired EV-187j (default-verbosity token caps) and agent-driven debugging on partly-broken runs."}}
|
|
328
|
+
{"id":"int-96158302","kind":"field_change","created_at":"2026-05-11T03:15:27.20532545Z","actor":"Denis Kiselev","issue_id":"EV-187j","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"Merged via PR #1186. Summary verbosity now strips diff + error_backtrace from timed_out/errors/unresolved entries; error_message preserved. 100-error synthetic payload measured <50KB. Tool description + README updated. Pairs with EV-t7kh — together unblock agent-driven debugging at scale."}}
|
|
329
|
+
{"id":"int-f7afa606","kind":"field_change","created_at":"2026-05-11T05:14:25.551359428Z","actor":"Denis Kiselev","issue_id":"EV-nrgw","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"Merged via PR #1187. Reframed scope: bead's stale-cache hypothesis didn't reproduce — Evilution::Cache is content-addressed via SHA(file_source), so source changes invalidate correctly. Real silent-failure mode: score = killed / (total - errors - neutral - equivalent - unresolved - unparseable), so high error rates produce 'PASS 100%' that masks 16/19 errored. Fix: ErrorRateWarning line formatter prints '! High error rate: N/T (X%)' under the Score line when errors / total > 25%. Verified with Dry::Struct repro (10/11 errored → warning surfaces under misleading 100% score)."}}
|
|
330
|
+
{"id":"int-1f633a18","kind":"field_change","created_at":"2026-05-11T05:44:28.177265737Z","actor":"Denis Kiselev","issue_id":"EV-a43i","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"Implemented explicit preload fallthrough: missing config.preload under :fork+rails warns to stderr and falls through to auto-detect chain; combined error lists both missing explicit + chain candidates when chain also empty. PR #1188 merged."}}
|
|
331
|
+
{"id":"int-13cc794a","kind":"field_change","created_at":"2026-05-11T06:16:58.805696639Z","actor":"Denis Kiselev","issue_id":"EV-corn","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"Implemented option (a): added 'mutate' as run alias in CommandExtractor (RUN_ALIASES). Updated CLI --help banner, README CLI table, and MCP tool description (with [files...] placeholder). PR #1189 merged. GH #1172."}}
|
|
332
|
+
{"id":"int-980f1e21","kind":"field_change","created_at":"2026-05-11T18:16:52.733542472Z","actor":"Denis Kiselev","issue_id":"EV-kjkd","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"probe only"}}
|
|
333
|
+
{"id":"int-90d90758","kind":"field_change","created_at":"2026-05-12T01:35:59.390842791Z","actor":"Denis Kiselev","issue_id":"EV-hmd5","extra":{"field":"status","new_value":"open","old_value":"in_progress"}}
|
|
334
|
+
{"id":"int-9426e0f2","kind":"field_change","created_at":"2026-05-12T09:22:16.445201172Z","actor":"Denis Kiselev","issue_id":"EV-99r2","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"dry-monads canary complete: 3 surfaced bugs (EV-174x #1197, EV-wwx3 #1198, EV-wzmq #1199) all merged; post-merge canary 385/246/0/0 score 1.0"}}
|
|
335
|
+
{"id":"int-89a6abbb","kind":"field_change","created_at":"2026-05-12T09:38:41.132122586Z","actor":"Denis Kiselev","issue_id":"EV-hmd5","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"dry-types canary complete: 3 mutator output bugs filed (EV-2cv1 #1200, EV-nmhi #1201, EV-jjpt #1202); zero evilution crashes; score 0.79"}}
|
|
336
|
+
{"id":"int-7daa2e34","kind":"field_change","created_at":"2026-05-12T09:49:30.104392229Z","actor":"Denis Kiselev","issue_id":"EV-3xxc","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"pundit canary clean: 800 mutations / 789 killed / 0 errors / score 1.0; 2 new mutator bugs filed (EV-bjot, EV-xsg2)"}}
|
|
337
|
+
{"id":"int-dfdf0230","kind":"field_change","created_at":"2026-05-12T09:56:50.344492509Z","actor":"Denis Kiselev","issue_id":"EV-mw2h","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"dotenv canary complete: 984/624 killed score 0.66; 3 evilution bugs filed (EV-b0ee preload, EV-kws8 + EV-xsg2 sighting mutator); 31 NameErrors are gem-side missing require, not evilution"}}
|
|
338
|
+
{"id":"int-3bf7bd77","kind":"field_change","created_at":"2026-05-12T10:12:38.509609431Z","actor":"Denis Kiselev","issue_id":"EV-o5p5","extra":{"field":"status","new_value":"blocked","old_value":"in_progress"}}
|
|
339
|
+
{"id":"int-2fcc28b2","kind":"field_change","created_at":"2026-05-12T13:55:33.618634552Z","actor":"Denis Kiselev","issue_id":"EV-ui96","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"thor canary clean: 517/359 killed score 0.70; zero evilution bugs; survivors are thor test gaps"}}
|
|
340
|
+
{"id":"int-a2ec0eeb","kind":"field_change","created_at":"2026-05-12T14:05:07.302733558Z","actor":"Denis Kiselev","issue_id":"EV-lnjm","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"Recursion stress canary passed (score 0.8768, 0 evilution bugs)"}}
|
|
341
|
+
{"id":"int-f2e146f0","kind":"field_change","created_at":"2026-05-12T14:17:32.642381092Z","actor":"Denis Kiselev","issue_id":"EV-4i26","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"IO/HTTP canary passed (score 0.6361, 0 new bugs, 5 known EV-xsg2 sightings appended)"}}
|
|
342
|
+
{"id":"int-66c73ae9","kind":"field_change","created_at":"2026-05-12T14:27:33.553239753Z","actor":"Denis Kiselev","issue_id":"EV-jm43","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"AST canary done: pass_with_blocking_bug. 1 new evilution bug (EV-l19q heredoc neutralizer), 2 existing bugs (EV-bjot, EV-wzmq) appended/reopened"}}
|
|
343
|
+
{"id":"int-2e92815c","kind":"field_change","created_at":"2026-05-12T15:01:03.20238706Z","actor":"Denis Kiselev","issue_id":"EV-l19q","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"Merged via GH#1209 (PR for GH#1208). BodyCallNeutralizer now extends replacement range past heredoc terminators."}}
|
|
344
|
+
{"id":"int-908402ad","kind":"field_change","created_at":"2026-05-12T16:03:48.265773859Z","actor":"Denis Kiselev","issue_id":"EV-xpfi","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"Rails canary pass (yaml+config smoke clean, 0 evilution bugs)"}}
|
|
345
|
+
{"id":"int-ff8e85b2","kind":"field_change","created_at":"2026-05-12T17:03:40.966592538Z","actor":"Denis Kiselev","issue_id":"EV-l6gx","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"Merged via PR #1210 (GH#1207). Minitest 6.x + 5.x compatibility restored. Unblocks 9 canaries."}}
|
|
346
|
+
{"id":"int-5b7b8149","kind":"field_change","created_at":"2026-05-12T17:57:03.394930914Z","actor":"Denis Kiselev","issue_id":"EV-o5p5","extra":{"field":"status","new_value":"closed","old_value":"blocked","reason":"EV-l6gx fix verified; new follow-up EV-blnq filed for fork-isolation hang. Direct integration probe confirms Minitest works."}}
|
|
347
|
+
{"id":"int-0dfac9d1","kind":"field_change","created_at":"2026-05-13T01:38:31.908382613Z","actor":"Denis Kiselev","issue_id":"EV-70hd","extra":{"field":"assignee","new_value":"Denis Kiselev","old_value":""}}
|
|
348
|
+
{"id":"int-7616b8d5","kind":"field_change","created_at":"2026-05-13T01:40:01.770243671Z","actor":"Denis Kiselev","issue_id":"EV-aati","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"roda canary complete: score 1.0 (0 survivors), 1 new HIGH bug filed (EV-70hd #1212 BodyCallNeutralizer lazy-load), 4 mutator bug sightings appended (nmhi/xsg2/bjot/kws8). 32724 mutations / 30273 killed / 0 survived."}}
|
|
349
|
+
{"id":"int-05b998b4","kind":"field_change","created_at":"2026-05-13T01:58:27.852827156Z","actor":"Denis Kiselev","issue_id":"EV-720r","extra":{"field":"assignee","new_value":"Denis Kiselev","old_value":""}}
|
|
350
|
+
{"id":"int-a323a995","kind":"field_change","created_at":"2026-05-13T02:49:22.801975686Z","actor":"Denis Kiselev","issue_id":"EV-lqpn","extra":{"field":"assignee","new_value":"Denis Kiselev","old_value":""}}
|
|
351
|
+
{"id":"int-49c2877f","kind":"field_change","created_at":"2026-05-13T02:49:23.250796095Z","actor":"Denis Kiselev","issue_id":"EV-05tp","extra":{"field":"assignee","new_value":"Denis Kiselev","old_value":""}}
|
|
352
|
+
{"id":"int-1db1cd93","kind":"field_change","created_at":"2026-05-13T02:50:33.4238377Z","actor":"Denis Kiselev","issue_id":"EV-ien5","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"sinatra canary complete: 1 new HIGH bug filed (EV-lqpn #1214 TypeError superclass mismatch on Struct.new superclass — 6782 errors blocking 92% of base.rb mutations, score 0.0); 1 new LOW bug (EV-05tp #1215 explicit_super dangling comma); 3 mutator bug sightings appended (xsg2/kws8/bjot)"}}
|
|
353
|
+
{"id":"int-6b08fe75","kind":"field_change","created_at":"2026-05-13T02:54:40.126050522Z","actor":"Denis Kiselev","issue_id":"EV-n3au","extra":{"field":"assignee","new_value":"Denis Kiselev","old_value":""}}
|
|
354
|
+
{"id":"int-78d79b10","kind":"field_change","created_at":"2026-05-13T02:54:40.58615495Z","actor":"Denis Kiselev","issue_id":"EV-74e3","extra":{"field":"assignee","new_value":"Denis Kiselev","old_value":""}}
|
|
355
|
+
{"id":"int-5f3c2a62","kind":"field_change","created_at":"2026-05-13T02:54:41.035195233Z","actor":"Denis Kiselev","issue_id":"EV-m47s","extra":{"field":"assignee","new_value":"Denis Kiselev","old_value":""}}
|
|
356
|
+
{"id":"int-a891f124","kind":"field_change","created_at":"2026-05-13T16:10:07.942340376Z","actor":"Denis Kiselev","issue_id":"EV-wzmq","extra":{"field":"status","new_value":"closed","old_value":"in_progress","reason":"PR #1221 merged: widened adjacent_string_concat? to detect mixed plain+interp adjacent (line-continued + same-line). Predicate tightened per Copilot review to require StringNode/InterpolatedStringNode type AND own opening_loc, rejecting pure-interpolation EmbeddedStatementsNode parts. 3649 specs pass."}}
|
|
357
|
+
{"id":"int-5243c70e","kind":"field_change","created_at":"2026-05-13T16:46:03.689538886Z","actor":"Denis Kiselev","issue_id":"EV-2cv1","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"PR #1222 merged: skip block_param_removal when anonymous & param + body forwards via &. Per Copilot review, walker stops at nested DefNode boundary."}}
|
|
358
|
+
{"id":"int-77c3d7b0","kind":"field_change","created_at":"2026-05-13T17:00:36.3309915Z","actor":"Denis Kiselev","issue_id":"EV-nmhi","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"PR merged: string_literal skips StringNode chunks of InterpolatedSymbolNode / InterpolatedRegularExpressionNode / InterpolatedXStringNode. Chunks are not standalone literals."}}
|
|
359
|
+
{"id":"int-205e0413","kind":"field_change","created_at":"2026-05-14T04:35:40.188276228Z","actor":"Denis Kiselev","issue_id":"EV-n3au","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"PR #1224 merged: MCP preload param + setup_warning heuristic. Copilot review addressed (schema oneOf, mutations vs workers wording)."}}
|
|
360
|
+
{"id":"int-0f60034f","kind":"field_change","created_at":"2026-05-14T05:36:59.692727916Z","actor":"Denis Kiselev","issue_id":"EV-xsg2","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"PR #1225 merged: receiver_replacement skips reserved-keyword method names. Per Copilot review, writer suffix (=) stripped before keyword lookup to cover self.class= form."}}
|
|
361
|
+
{"id":"int-f7e471ae","kind":"field_change","created_at":"2026-05-14T06:18:02.455563481Z","actor":"Denis Kiselev","issue_id":"EV-bjot","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"PR #1226 merged: centralized HeredocSpan extension in Mutator::Base.add_mutation. Per Copilot review, skip-heuristic tightened to regex matching heredoc-anchor identifier pattern (not bare <<). Covers 8 operators via single infrastructure fix."}}
|
|
362
|
+
{"id":"int-3871825c","kind":"field_change","created_at":"2026-05-14T06:57:02.232644676Z","actor":"Denis Kiselev","issue_id":"EV-kws8","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"PR #1228 merged: rescue_removal walks BeginNode + uses structural clause boundaries. Covers orphan-else and orphan-rescue-class variants. Copilot review evaluated and rejected (end_keyword_loc is populated for implicit-begin shapes, no NoMethodError path)."}}
|
|
363
|
+
{"id":"int-f2bc98ca","kind":"field_change","created_at":"2026-05-14T07:13:14.206329238Z","actor":"Denis Kiselev","issue_id":"EV-jjpt","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"PR merged: splat_operator skips kwarg-preceded **splat to avoid positional-after-keyword."}}
|
|
364
|
+
{"id":"int-78870411","kind":"field_change","created_at":"2026-05-14T07:28:17.94685909Z","actor":"Denis Kiselev","issue_id":"EV-05tp","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"PR merged: explicit_super_mutation extends byte range structurally (block start / rparen / args end). Short-circuit spec switched to synthetic in-test operator."}}
|
|
365
|
+
{"id":"int-36e5c972","kind":"field_change","created_at":"2026-05-14T08:23:06.045690955Z","actor":"Denis Kiselev","issue_id":"EV-b0ee","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"PR merged: GemDetector multi-gemspec disambiguation via exact-entry, lib-subdir, then shortest-name fallback."}}
|
|
366
|
+
{"id":"int-00368bf4","kind":"field_change","created_at":"2026-05-14T09:07:21.14041556Z","actor":"Denis Kiselev","issue_id":"EV-blnq","extra":{"field":"status","new_value":"closed","old_value":"open","reason":"Not a hang — slow run with no progress feedback. Investigated 2026-05-14: 269 mutations / 202 killed / 42 survived / 25 timed_out / 0 errors / score 0.75 in 4min with -j 4 -t 10 on Shopify/liquid v5.12.0. Per-mutation Minitest test_helper re-bootstrap + 25 worker timeouts caused the perceived 10-min hang. Combined with --no-progress + non-TTY stderr (no parent output) the run looks silent until the JSON dump. Worker logs (--quiet-children --quiet-children-dir DIR) show steady progress. README updated with a 'Long Minitest fork runs — not a hang' section under §7 explaining the timing math + recommended -j/-t flags. No code change needed."}}
|
data/.rubocop_todo.yml
CHANGED
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,47 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
Versioning policy: see [docs/versioning.md](docs/versioning.md).
|
|
4
|
+
|
|
5
|
+
## [0.30.0] - 2026-05-15
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- **Two new mutation operators bring the default registry to 74** — `LastExpressionRemoval` strips a trailing literal (`true`/`false`/`nil`/integer/symbol) from a method body, targeting the idiomatic `def predicate?; side_effect; true; end` pattern where the explicit literal return value is the high-signal behavior under test (EV-74e3, PR #1236). `ArgumentMethodCallReplacement` drops a method call appearing in argument position (`fn(x.attr)` → `fn(x)`, also inside hash values, array elements, and keyword arguments), surfacing the common log-payload / structured-data substitution pattern under its own operator name instead of buried under `method_call_removal` (EV-m47s, PR #1237)
|
|
10
|
+
- **`MutationPlanner` now deduplicates byte-identical mutations across operators** — key is `(file_path, mutated_source)`; first-registered operator wins. Eliminates wasted compute and inflated denominators when multiple operators produce the same edit (e.g. `statement_deletion` + `last_expression_removal` both deleting a trailing literal). `DeadCode` equivalence heuristic widened to recognize `last_expression_removal` so unreachable trailing literals retain equivalent-classification regardless of which operator name survives dedup (PR #1236 and follow-up review)
|
|
11
|
+
- **`BodyCallNeutralizer`: strip non-idempotent class/module body calls before re-eval in fork workers** — DSL registries (`register_mixin`, plugin registration, etc.) raise on second invocation because they assume single-eval semantics. The parent's preload already executed them; re-running them in the child fork is wasted work that aborts the eval before the mutated method takes effect. The neutralizer walks the Prism tree, replaces non-allowlisted top-level call statements with `nil` byte-for-byte, and extends the replacement range to cover heredoc bodies and terminators. Lazy-load-aware: a `$LOADED_FEATURES` snapshot taken at parent preload-end gates neutralization on whether the target file was actually preloaded — first-load-in-child files (e.g. roda's `lib/roda/plugins/typecast_params.rb`) are left intact so subsequent statements depending on those DSL-defined methods do not cascade `NameError` (#1195, EV-70hd PR #1232)
|
|
12
|
+
- **Setup-warning subsystem surfaces silent mutation failures** — when ≥80% of mutations error with ≥80% of those errors sharing a single error class, the warning formatter emits a class-specific hint (`NameError` → preload pointer; `LoadError` → require path; etc.) telling the user the run's numbers are unreliable and where to look. Wired into the CLI text reporter; visible in MCP `evilution-mutate` output too (#1216, #1168)
|
|
13
|
+
- **`mutate` CLI alias for `run`** — `evilution mutate path/foo.rb` is now equivalent to `evilution run path/foo.rb`; matches the `gem` name users reach for first (#1172)
|
|
14
|
+
- **`--preload PATH` flag with explicit fallback + error surface** — supersedes implicit autoloading. Resolves the path with explicit error messages when the file is missing or unreadable; falls back to the inferred convention only when explicitly opted in. MCP option parser accepts boolean `false` to disable preload entirely (#1171)
|
|
15
|
+
- **Method splicing for live method redefinition (`Evilution::Mutator::Splice`)** — supports method-level swaps in workers that previously required full source re-eval. Plumbed alongside the existing source-evaluator strategy (#1194)
|
|
16
|
+
- **Fork-protocol length-prefixed payloads** — replaces the ad-hoc line-terminated protocol that could hang when payload contents contained newlines. Worker self-baseline script can now use multiple jobs safely; the previous skip list for fork-using files was removed (#1176, #1177, #1178)
|
|
17
|
+
- **MCP / session / config schema versioning** — MCP tool envelopes carry `Evilution::MCP::CONTRACT_VERSION` (currently `1`); session JSON files declare a `schema_version`; configuration files (`.evilution.yml`) validated against a versioned schema. Forward compatibility now follows a documented policy (#856, #857, #858)
|
|
18
|
+
- **`docs/versioning.md`** — explicit policy on what counts as the public contract, what triggers a major bump, and how deprecations work. Linked from the top of `CHANGELOG.md` and the `## Versioning` section of `README.md` (#864)
|
|
19
|
+
- **Dual-runtime harness scripts for self-mutation testing** — run evilution's own suite against mutated evilution code in a separate Ruby process, isolating bootstrap effects (#1175)
|
|
20
|
+
|
|
21
|
+
### Fixed
|
|
22
|
+
|
|
23
|
+
- **Mutation scoring no longer silently inflates kills when RSpec returns nonzero with zero examples loaded** — `Baseline` now accepts `test_files:` and honors `--spec` at the baseline phase (previously the `--spec` flag was only consulted at mutation time, so the misleading "No matching test found... Use --spec to specify the test file" warning fired even when the user *did* pass `--spec`). `Integration::RSpec#execute_run` captures `RSpec.world.example_count` after the run; if the status is nonzero and zero examples loaded, `ResultBuilder#from_run` returns an explicit error hash so `classify_status` reports `:error` instead of falling through to `:killed`. Without this, environments with `fail_if_no_examples = true`, autoload mismatches, or spec-file load failures would mark every mutation as killed regardless of whether any example ran (EV-720r, PR #1234)
|
|
24
|
+
- **`class X < Struct.new(...)` (or `Data.define`, dynamic `Class.new`) no longer crashes the eval with `TypeError: superclass mismatch`** — `RedefinitionRecovery` now handles `TypeError` for messages containing "superclass mismatch": strips the constants the source declares (same path as the existing `ArgumentError 'already defined'` recovery) and retries once. If the retry still raises, the mutation reports `:error` rather than being silently miscounted as survived. Unblocks sinatra's `lib/sinatra/base.rb` and similar Rack-middleware-style anonymous-parent class patterns (EV-lqpn, PR #1235)
|
|
25
|
+
- **Gem detector now disambiguates multiple gemspecs in a single root** — when a repository ships e.g. `dotenv.gemspec` alongside `dotenv-rails.gemspec`, the previous `Dir.glob.first` was filesystem-order-dependent and often picked the wrong companion gem, triggering `uninitialized constant Rails` at preload. New three-tier resolution: exact-entry match against the target's `lib/foo.rb` path, then first-lib-subdirectory match, then the shortest gemspec basename as the conventional "parent" gem (EV-b0ee, PR #1224)
|
|
26
|
+
- **Heredoc-body orphaning across mutation operators centralized in `Evilution::AST::HeredocSpan`** — operators whose byte edit straddles a `<<~MARKER` anchor (argument_removal, argument_nil_substitution, method_call_removal, statement_deletion, method_body_replacement, block_removal, conditional_branch, string_interpolation, etc.) previously emitted unparseable mutations whenever the kept range included the heredoc opener without its body/terminator. `Mutator::Base#add_mutation` now extends the byte range via a single shared visitor; when the replacement itself re-references a heredoc anchor, the mutation is skipped rather than emitted as broken bytes (EV-bjot, PR #1223)
|
|
27
|
+
- **`receiver_replacement` skips reserved-keyword method names** — replacing `obj.if` (where `if` is the method name spelled as a Ruby keyword) with bare `if` produced unparseable Ruby. The operator now strips writer-form `=` suffixes and bails out when the candidate is a Ruby reserved keyword (EV-xsg2, PR #1222)
|
|
28
|
+
- **`rescue_removal` removes the orphan `else` clause when stripping the sole `rescue`** — `begin; ...; rescue Foo; ...; else; ...; end` with the rescue clause removed previously left a dangling `else` without a matching `rescue`, which is invalid Ruby. The operator now visits `BeginNode` directly and computes structural clause boundaries, dropping orphan else atoms (EV-kws8, PR #1221)
|
|
29
|
+
- **`explicit_super` "remove all args" trims the dangling comma** — `super(a, b)` → `super()` was correctly handled; `super(a, b,)` → `super(,)` was not. The trailing-args boundary now walks block-start / rparen / args-end so the resulting `super` is always parseable (EV-05tp, PR #1230)
|
|
30
|
+
- **`positional-after-keyword` syntax error in argument removal** — eliminated cases where positional arg removal left a kwarg followed by a positional in the result, which Ruby rejects (#1202)
|
|
31
|
+
- **Interpolated-node string parts no longer crash mutators on non-string segments** — `visit_interpolated_symbol_node` / `visit_interpolated_regular_expression_node` / `visit_interpolated_x_string_node` now skip embedded StringNode parts instead of treating them as standalone string literals (EV-nmhi, #1201)
|
|
32
|
+
- **`block_param_removal` anonymous-forward safety check** — body uses of `&` / `*` / `**` previously crashed when the corresponding block-param was removed. The operator now scans the body for anonymous forwards before deciding to remove (EV-2cv1, #1200)
|
|
33
|
+
- **`string_literal` adjacent-string concatenation handled** — Ruby implicitly concatenates adjacent string literals at parse time; mutating one half could leave a syntactically wrong fragment. The operator now detects adjacent quoted literals via Prism's part-type predicates and `opening_loc`, and skips when the result would be unparseable (#1220)
|
|
34
|
+
- **`string_literal` backslash-continuation handling** — multi-line literals joined by trailing `\` survived mutation as half-statements; now correctly span the continuation range (#1196)
|
|
35
|
+
- **`BodyCallNeutralizer` heredoc handling preserves parseable output** — when a neutralized call carried a heredoc argument, the replacement-range collector now walks every variant of interpolated-string / x-string / interpolated-symbol / regex with a `heredoc?` predicate and extends the end offset to the heredoc terminator's line (#1208)
|
|
36
|
+
- **Minitest version compatibility (Minitest 6 removed `__run`)** — `Integration::Minitest` now dispatches to the correct runner method based on the installed Minitest version; integration spec updated to assert the dispatch contract without `NoMethodError` regressions (#1207)
|
|
37
|
+
- **Nil-replacement crash in parallel cleanup accessors** — pool teardown could call accessors on partially-initialized worker state; nil-guards added in the affected paths (#1174)
|
|
38
|
+
- **`indexable?` skips symbol/string keys** — the index-conversion operators no longer attempt receiver mutation on call sites whose argument is itself a symbol/string literal (the receiver is necessarily indexable in those cases; the mutation produced no new signal) (#1173)
|
|
39
|
+
- **Error rate warning formatter wired into text reporter** — high error rates now produce a visible single-line warning before the mutations breakdown (#1168)
|
|
40
|
+
|
|
41
|
+
### Changed
|
|
42
|
+
|
|
43
|
+
- **MCP `evilution-mutate` default verbosity tightened to fit agent token caps** — minimal verbosity reports only the high-signal slice (summary + survived); full verbosity audited for unresolved-diffs leaks. Error sampling at minimal verbosity (cap per error class) prevents deadlock when thousands of mutations fail with the same exception (#1169, #1170, EV-r1tt)
|
|
44
|
+
|
|
3
45
|
## [0.29.0] - 2026-05-06
|
|
4
46
|
|
|
5
47
|
### Changed
|
data/README.md
CHANGED
|
@@ -67,11 +67,13 @@ evilution [command] [options] [files...]
|
|
|
67
67
|
|
|
68
68
|
The shorter alias `evil` ships alongside `evilution` and accepts identical arguments (handy with `alias be='bundle exec'` → `be evil run ...`).
|
|
69
69
|
|
|
70
|
+
Every command, subcommand, and flag listed in this section is part of evilution's public CLI contract; see [docs/versioning.md](docs/versioning.md) for stability and deprecation rules.
|
|
71
|
+
|
|
70
72
|
### Commands
|
|
71
73
|
|
|
72
74
|
| Command | Description | Default |
|
|
73
75
|
|----------------------|----------------------------------------------------|---------|
|
|
74
|
-
| `run`
|
|
76
|
+
| `run` (alias `mutate`) | Execute mutation testing against files | Yes |
|
|
75
77
|
| `init` | Generate `.evilution.yml` config file | |
|
|
76
78
|
| `version` | Print version string | |
|
|
77
79
|
| `subjects [files]` | List mutation subjects with locations and counts | |
|
|
@@ -116,16 +118,33 @@ The shorter alias `evil` ships alongside `evilution` and accepts identical argum
|
|
|
116
118
|
| `--skip-heredoc-literals` | Boolean | false | Skip all string literal mutations inside heredocs. |
|
|
117
119
|
| `--show-disabled` | Boolean | false | Report mutations skipped by `# evilution:disable` comments. |
|
|
118
120
|
| `--fallback-full-suite` | Boolean | false | When no matching spec/test resolves for a mutation, run the whole test suite instead of marking it `:unresolved` and skipping. |
|
|
121
|
+
| `--related-specs-heuristic` | Boolean | false | When a mutation removes an `includes(...)` call, also run matching specs from `spec/{requests,integration,features,system}` (Rails-style domain match on the source file's basename). Trades extra spec runs for higher kill rate on ORM mutations. |
|
|
119
122
|
| `--baseline-session PATH` | String | _(none)_ | Saved session file for HTML report comparison. |
|
|
120
123
|
| `-e CODE`, `--eval CODE` | String | _(none)_ | Inline Ruby code for `util mutation` command. |
|
|
121
124
|
| `--profile NAME` | String | `default` | Operator profile: `default` or `strict`. `strict` adds aggressive truthiness mutators (e.g. replaces `x.predicate?` with `nil`) intended for pre-merge audits. |
|
|
122
125
|
| `--strict` | Boolean | false | Shortcut for `--profile=strict`. |
|
|
123
126
|
|
|
127
|
+
### Options (for `session` subcommands)
|
|
128
|
+
|
|
129
|
+
| Flag | Type | Default | Description |
|
|
130
|
+
|---------------------|---------|----------------------|----------------------------------------------------------------------------------------------|
|
|
131
|
+
| `--results-dir DIR` | String | `.evilution/results` | Directory containing session result JSON files. Honored by `session list` and `session gc`. |
|
|
132
|
+
| `--limit N` | Integer | _(none)_ | (`session list`) Show only the N most recent sessions. |
|
|
133
|
+
| `--since DATE` | String | _(none)_ | (`session list`) Show only sessions created on or after `DATE` (`YYYY-MM-DD`). |
|
|
134
|
+
| `--older-than D` | String | _(required)_ | (`session gc`) Delete sessions older than `D` (e.g. `30d`, `24h`, `1w`). |
|
|
135
|
+
|
|
136
|
+
### Options (for `compare` command)
|
|
137
|
+
|
|
138
|
+
| Flag | Type | Default | Description |
|
|
139
|
+
|------------------|--------|-----------|------------------------------------------------------------------------------------------------------|
|
|
140
|
+
| `--against PATH` | String | _(none)_ | Prior (older) session JSON to diff against. Positional first argument is accepted as a fallback. |
|
|
141
|
+
| `--current PATH` | String | _(none)_ | Current (newer) session JSON. Positional second argument is accepted as a fallback. |
|
|
142
|
+
|
|
124
143
|
### Operator Profiles
|
|
125
144
|
|
|
126
145
|
Two profiles ship out of the box:
|
|
127
146
|
|
|
128
|
-
- **`default`** — the
|
|
147
|
+
- **`default`** — the 74 stable operators registered in `Mutator::Registry.default`. Suitable for everyday CI runs; balances coverage signal against survivor noise.
|
|
129
148
|
- **`strict`** — adds extra truthiness mutators on top of `default`. Currently `PredicateToNil` (replaces every `x.predicate?` call with `nil` to surface tests that only assert truthiness rather than exact return values). Use for pre-merge audits where you want maximum sensitivity at the cost of more survivors.
|
|
130
149
|
|
|
131
150
|
Set via `--profile=strict`, the `--strict` shortcut, or `profile: strict` in `.evilution.yml`.
|
|
@@ -145,6 +164,7 @@ Generate default config: `bundle exec evilution init`
|
|
|
145
164
|
Creates `.evilution.yml`:
|
|
146
165
|
|
|
147
166
|
```yaml
|
|
167
|
+
schema_version: 1 # opts into strict validation (rejects unknown keys, refuses future versions)
|
|
148
168
|
# timeout: 30 # seconds per mutation
|
|
149
169
|
# format: text # text | json | html
|
|
150
170
|
# min_score: 0.0 # 0.0–1.0
|
|
@@ -165,6 +185,62 @@ Creates `.evilution.yml`:
|
|
|
165
185
|
|
|
166
186
|
**Precedence**: CLI flags override `.evilution.yml` values.
|
|
167
187
|
|
|
188
|
+
### Schema versioning
|
|
189
|
+
|
|
190
|
+
`.evilution.yml` may declare an integer `schema_version` (currently `1`). Behavior:
|
|
191
|
+
|
|
192
|
+
- **When declared** — strict mode. Unknown top-level keys raise `Evilution::ConfigError`. A `schema_version` greater than the installed gem supports is rejected so an old gem cannot silently misread a newer config.
|
|
193
|
+
- **When omitted** — legacy lenient mode. Unknown keys are ignored.
|
|
194
|
+
|
|
195
|
+
Compatibility policy for the `1.x` gem line:
|
|
196
|
+
|
|
197
|
+
- New configuration keys are added in MINOR releases (additive only). Each new key takes a default that preserves prior behavior.
|
|
198
|
+
- Existing keys are not removed, renamed, or have their semantics changed in any `1.x` release. Deprecated keys keep working through the entire `1.x` line; see [docs/versioning.md](docs/versioning.md).
|
|
199
|
+
- `schema_version` is bumped only on incompatible changes — i.e. only at the next MAJOR release. `schema_version: 2` will ship with `evilution 2.0`.
|
|
200
|
+
|
|
201
|
+
A JSON Schema covering every supported key lives at [`schema/evilution.config.schema.json`](schema/evilution.config.schema.json). Point editor / IDE YAML extensions at it for autocomplete and inline validation (e.g. VS Code `yaml.schemas`, JetBrains "Custom JSON Schema").
|
|
202
|
+
|
|
203
|
+
### Configuration reference
|
|
204
|
+
|
|
205
|
+
All keys recognised under `schema_version: 1`:
|
|
206
|
+
|
|
207
|
+
| Key | Type | Default | Description |
|
|
208
|
+
|------------------------------|-------------------------------|----------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------|
|
|
209
|
+
| `schema_version` | Integer | `1` | Config schema version. Declaring it enables strict validation; omit for lenient mode. |
|
|
210
|
+
| `timeout` | Integer | `30` | Per-mutation timeout in seconds. |
|
|
211
|
+
| `format` | String | `text` | Output format: `text`, `json`, `html`. |
|
|
212
|
+
| `target` | String / null | `null` | Filter expression: method (`Foo#bar`), class (`Foo`), namespace (`Foo*`), descendants (`descendants:Foo`), source glob (`source:**/*.rb`). |
|
|
213
|
+
| `min_score` | Float | `0.0` | Minimum mutation score (0.0–1.0) for exit code 0. |
|
|
214
|
+
| `integration` | String | `rspec` | Test framework: `rspec` or `minitest`. |
|
|
215
|
+
| `verbose` | Boolean | `false` | Verbose output (RSS/GC stats per phase, error details for errored mutations). |
|
|
216
|
+
| `quiet` | Boolean | `false` | Suppress output. |
|
|
217
|
+
| `jobs` | Integer | `1` | Number of parallel workers. |
|
|
218
|
+
| `fail_fast` | Integer / null | `null` | Stop after N surviving mutants. `null` = disabled. |
|
|
219
|
+
| `baseline` | Boolean | `true` | Run baseline test suite to detect pre-existing failures (marked `:neutral`). |
|
|
220
|
+
| `isolation` | String | `auto` | Isolation strategy: `auto`, `fork`, `in_process`. `auto` selects `fork` for Rails projects. |
|
|
221
|
+
| `incremental` | Boolean | `false` | Cache killed/timeout results across runs. |
|
|
222
|
+
| `suggest_tests` | Boolean | `false` | Generate concrete test code in survivor suggestions (matches `integration`). |
|
|
223
|
+
| `progress` | Boolean | `true` | TTY progress bar. |
|
|
224
|
+
| `save_session` | Boolean | `false` | Save session JSON under `.evilution/results/`. |
|
|
225
|
+
| `line_ranges` | Hash | `{}` | Per-file line-range constraints. Typically set via CLI; rare in YAML. |
|
|
226
|
+
| `spec_files` | Array<String> | `[]` | Explicit spec files to run. Bypasses auto-detection when non-empty. |
|
|
227
|
+
| `ignore_patterns` | Array<String> | `[]` | AST patterns to skip during mutation generation. See [docs/ast_pattern_syntax.md](docs/ast_pattern_syntax.md). |
|
|
228
|
+
| `show_disabled` | Boolean | `false` | Report mutations skipped by `# evilution:disable` comments. |
|
|
229
|
+
| `baseline_session` | String / null | `null` | Saved session file path for HTML report comparison. |
|
|
230
|
+
| `skip_heredoc_literals` | Boolean | `false` | Skip string literal mutations inside heredocs. |
|
|
231
|
+
| `related_specs_heuristic` | Boolean | `false` | Append related request/integration/feature/system specs for `includes(...)` mutations. |
|
|
232
|
+
| `fallback_to_full_suite` | Boolean | `false` | When no matching spec resolves, run the entire suite instead of marking the mutation `:unresolved`. |
|
|
233
|
+
| `preload` | String / Boolean / null | `null` | File to preload in parent before forking. `false` to disable. `null` to auto-detect for Rails. |
|
|
234
|
+
| `spec_mappings` | Hash<String, String/Array> | `{}` | Custom mapping from source path to spec path(s). |
|
|
235
|
+
| `spec_pattern` | String / null | `null` | Glob restricting resolved spec candidates. |
|
|
236
|
+
| `example_targeting` | Boolean | `true` | Per-mutation example-level targeting. |
|
|
237
|
+
| `example_targeting_fallback` | String | `full_file` | When targeting finds no example: `full_file` or `unresolved`. |
|
|
238
|
+
| `example_targeting_cache` | Hash | `{ max_files: 50, max_blocks: 10000 }` | LRU cache bounds for the example-targeting AST parser. |
|
|
239
|
+
| `quiet_children` | Boolean | `false` | Redirect each worker's stdout/stderr to per-pid files under `quiet_children_dir`. |
|
|
240
|
+
| `quiet_children_dir` | String | `tmp/evilution_children` | Directory for `--quiet-children` per-pid log files. |
|
|
241
|
+
| `profile` | String | `default` | Operator profile: `default` or `strict`. |
|
|
242
|
+
| `hooks` | Hash<String, String> | `{}` | Lifecycle hooks: event name → path to a Ruby file returning a `Proc`. |
|
|
243
|
+
|
|
168
244
|
## Disable Comments
|
|
169
245
|
|
|
170
246
|
Suppress mutations on specific code with inline comments:
|
|
@@ -190,10 +266,13 @@ Use `--show-disabled` to see which mutations were skipped.
|
|
|
190
266
|
|
|
191
267
|
## JSON Output Schema
|
|
192
268
|
|
|
193
|
-
Use `--format json` for machine-readable output.
|
|
269
|
+
Use `--format json` for machine-readable output. The same shape is used for both stdout reports (`--format json`) and saved session files (`--save-session` → `.evilution/results/*.json`); session files add a small set of extra top-level fields described under [Session JSON files](#session-json-files) below.
|
|
270
|
+
|
|
271
|
+
Schema:
|
|
194
272
|
|
|
195
273
|
```json
|
|
196
274
|
{
|
|
275
|
+
"schema_version": "integer — schema version of this JSON document (current: 1)",
|
|
197
276
|
"version": "string — gem version",
|
|
198
277
|
"timestamp": "string — ISO 8601 timestamp of the report",
|
|
199
278
|
"summary": {
|
|
@@ -251,6 +330,36 @@ Use `--format json` for machine-readable output. Schema:
|
|
|
251
330
|
|
|
252
331
|
**Key metric**: `summary.score` — the mutation score. Higher is better. 1.0 means all mutations were caught.
|
|
253
332
|
|
|
333
|
+
### Session JSON files
|
|
334
|
+
|
|
335
|
+
Sessions saved by `--save-session` (under `.evilution/results/*.json`) and consumed by `evilution session show`, `evilution session diff`, `evilution compare`, and the HTML reporter share the schema above with these additions:
|
|
336
|
+
|
|
337
|
+
| Field | Type | Description |
|
|
338
|
+
|----------------------|---------|---------------------------------------------------------------------------------------------------|
|
|
339
|
+
| `git` | Object | `{ "sha": "<full SHA or null>", "branch": "<branch name or null>" }` captured at run time. |
|
|
340
|
+
| `killed_count` | Integer | Top-level convenience counter; mirrors `summary.killed`. |
|
|
341
|
+
| `timed_out_count` | Integer | Mirrors `summary.timed_out`. |
|
|
342
|
+
| `error_count` | Integer | Mirrors `summary.errors`. |
|
|
343
|
+
| `neutral_count` | Integer | Mirrors `summary.neutral`. |
|
|
344
|
+
| `equivalent_count` | Integer | Mirrors `summary.equivalent`. |
|
|
345
|
+
| `skipped_count` | Integer | Mutations skipped by `# evilution:disable` (omitted from `summary` unless positive). |
|
|
346
|
+
|
|
347
|
+
Saved sessions also omit the per-status arrays (`killed`, `neutral`, `equivalent`, `unresolved`, `unparseable`, `timed_out`, `errors`) — only `survived` and `coverage_gaps` are persisted. The score, totals, and timestamps are stable for diff/compare consumers.
|
|
348
|
+
|
|
349
|
+
#### Schema versioning
|
|
350
|
+
|
|
351
|
+
Every session and stdout JSON document carries a top-level `schema_version` integer (currently `1`). On read:
|
|
352
|
+
|
|
353
|
+
- **`schema_version` matches what this gem supports** — proceed normally.
|
|
354
|
+
- **`schema_version` is omitted** — treated as version `1` (the JSON shape that defined version 1). Sessions written before this field existed continue to load.
|
|
355
|
+
- **`schema_version` is greater than what this gem supports** — `Evilution::Session::Store#load`, `evilution compare`, `evilution session show`, `evilution session diff`, and the HTML reporter raise `Evilution::Error` with the offending file path and a "Upgrade the evilution gem" message. We refuse to silently misread a newer document.
|
|
356
|
+
|
|
357
|
+
Compatibility policy for the `1.x` gem line:
|
|
358
|
+
|
|
359
|
+
- New top-level fields are added in MINOR releases (additive only). Consumers that ignore unknown fields keep working without changes.
|
|
360
|
+
- Existing fields are not removed, renamed, or have their semantics changed in any `1.x` release.
|
|
361
|
+
- `schema_version` is bumped only on incompatible changes — i.e. only at the next MAJOR release. `schema_version: 2` will ship with `evilution 2.0`. See [docs/versioning.md](docs/versioning.md) for the umbrella SemVer policy.
|
|
362
|
+
|
|
254
363
|
### Mutation Statuses
|
|
255
364
|
|
|
256
365
|
| Status | Meaning | Counted in score? |
|
|
@@ -266,7 +375,7 @@ Use `--format json` for machine-readable output. Schema:
|
|
|
266
375
|
|
|
267
376
|
Unresolved mutations indicate a missing test mapping — the file has no corresponding test file that the resolver could find (for example, an RSpec `_spec.rb` file or a Minitest `_test.rb` file, depending on configuration). They are reported separately so you can act on them (add a test, adjust test naming, or opt in to the full-suite fallback) without inflating the error count.
|
|
268
377
|
|
|
269
|
-
## Mutation Operators (
|
|
378
|
+
## Mutation Operators (74 total)
|
|
270
379
|
|
|
271
380
|
Each operator name is stable and appears in JSON output under `survived[].operator`.
|
|
272
381
|
|
|
@@ -344,6 +453,8 @@ Each operator name is stable and appears in JSON output under `survived[].operat
|
|
|
344
453
|
| `lambda_body` | Replace lambda body with nil | `-> { expr }` -> `-> { nil }` |
|
|
345
454
|
| `begin_unwrap` | Remove begin/end wrapper | `begin; expr; end` -> `expr` |
|
|
346
455
|
| `block_param_removal` | Remove explicit block parameter | `def foo(&block)` -> `def foo` |
|
|
456
|
+
| `last_expression_removal` | Strip trailing literal return from method body | `def foo?; warn; true; end` -> `def foo?; warn; end` |
|
|
457
|
+
| `argument_method_call_replacement` | Replace a method-call argument with its receiver | `fn(x.attr)` -> `fn(x)` |
|
|
347
458
|
|
|
348
459
|
## MCP Server (AI Agent Integration)
|
|
349
460
|
|
|
@@ -382,11 +493,11 @@ The `evilution-mutate` tool accepts a `verbosity` parameter to control response
|
|
|
382
493
|
|
|
383
494
|
| Level | Default | What's included |
|
|
384
495
|
|-------------|---------|--------------------------------------------------------------|
|
|
385
|
-
| `summary` | Yes | `summary` + `survived` + `timed_out` + `errors`
|
|
496
|
+
| `summary` | Yes | `summary` + `survived` + `timed_out` + `errors` + `unresolved` (non-survived entries shed `diff` and `error_backtrace` to bound payload size) |
|
|
386
497
|
| `full` | | All entries (killed/neutral/equivalent diffs stripped) |
|
|
387
|
-
| `minimal` | | `summary` + `survived`
|
|
498
|
+
| `minimal` | | `summary` + `survived` (plus a trimmed sample of up to 3 errored entries when `errors > 0`) |
|
|
388
499
|
|
|
389
|
-
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.
|
|
500
|
+
Use `minimal` when context window budget is tight and you only need to see what survived. The trimmed `errors` sample (each entry: `error_message`, `error_class`, location, plus the first 5 backtrace lines) is added so a partly-broken run is still self-diagnosable without escalating verbosity. Use `full` when you need to inspect killed/neutral/equivalent entries for debugging.
|
|
390
501
|
|
|
391
502
|
### Enriched Survived Entries
|
|
392
503
|
|
|
@@ -440,6 +551,62 @@ When evilution causes friction (errors, usage problems, missing capabilities you
|
|
|
440
551
|
|
|
441
552
|
Discussion URL: <https://github.com/marinazzio/evilution/discussions>
|
|
442
553
|
|
|
554
|
+
### Contract stability
|
|
555
|
+
|
|
556
|
+
The three MCP tools (`evilution-mutate`, `evilution-session`, `evilution-info`) form evilution's public contract for AI agents. From `1.0.0` onwards the following are governed by the gem's [SemVer policy](docs/versioning.md):
|
|
557
|
+
|
|
558
|
+
- **Tool names**: `evilution-mutate`, `evilution-session`, `evilution-info`.
|
|
559
|
+
- **Input schemas**: every parameter listed in each tool's `input_schema` (name, type, enum values, `required`).
|
|
560
|
+
- **Action enumerations**: the action enum on `evilution-session` (`list`, `show`, `diff`) and `evilution-info` (`subjects`, `tests`, `environment`, `statuses`, `feedback`).
|
|
561
|
+
- **Output payload top-level shape**: the keys and value types documented per action below.
|
|
562
|
+
- **Error envelope**: an error response is a single text content with the response's `error` flag set to true. The body is a JSON object with at minimum an `error` key shaped `{ "type": <string>, "message": <string> }`; tools may add additional top-level keys (e.g. `evilution-mutate` includes `feedback_url` and `feedback_hint` to point agents at the public Discussions channel). Consumers must read the `error.type` discriminator and ignore unknown extras. The error type strings — currently `config_error`, `parse_error`, `not_found`, and `runtime_error` — are part of the contract; new types may be added in MINOR releases (additive).
|
|
563
|
+
|
|
564
|
+
#### Output `schema_version`
|
|
565
|
+
|
|
566
|
+
Successful MCP responses carry a top-level `schema_version` integer. Two distinct version spaces are in play; both happen to be `1` today and either may be bumped independently at the next MAJOR release:
|
|
567
|
+
|
|
568
|
+
- **MCP contract `schema_version`** — `Evilution::MCP::CONTRACT_VERSION` (currently `1`). Stamped on envelopes that exist solely to wrap MCP tool output: `evilution-info` action responses, `evilution-session list`, `evilution-session diff`. Bumped only when the MCP envelope shape itself changes incompatibly.
|
|
569
|
+
- **Session JSON `schema_version`** — `Evilution::Session::Schema::CURRENT_VERSION` (currently `1`). Embedded inside payloads whose shape is also written to disk and consumed elsewhere: `evilution-mutate` (returns a mutation report) and `evilution-session show` (returns a session JSON document). Bumped only when the report/session shape itself changes incompatibly.
|
|
570
|
+
|
|
571
|
+
Per-tool placement:
|
|
572
|
+
|
|
573
|
+
| Tool / action | `schema_version` source | Location in payload |
|
|
574
|
+
|--------------------------------|------------------------------------------|------------------------------------------------------------------------------------------------------|
|
|
575
|
+
| `evilution-mutate` | `Session::Schema::CURRENT_VERSION` | Top-level of the mutation report JSON (same shape as `--save-session` output). |
|
|
576
|
+
| `evilution-session` `list` | `MCP::CONTRACT_VERSION` | Top-level of envelope: `{ "schema_version": 1, "sessions": [...] }`. |
|
|
577
|
+
| `evilution-session` `show` | `Session::Schema::CURRENT_VERSION` | Inside the returned session JSON document. |
|
|
578
|
+
| `evilution-session` `diff` | `MCP::CONTRACT_VERSION` | Top-level alongside `summary` / `fixed` / `new_survivors` / `persistent`. |
|
|
579
|
+
| `evilution-info` (all actions) | `MCP::CONTRACT_VERSION` | Top-level of every successful response, injected by the action response formatter. |
|
|
580
|
+
|
|
581
|
+
#### Per-tool output shapes
|
|
582
|
+
|
|
583
|
+
- **`evilution-mutate`** — full schema in the [JSON Output Schema](#json-output-schema) section. MCP-specific additions to each `survived` entry are documented in [Enriched Survived Entries](#enriched-survived-entries).
|
|
584
|
+
- **`evilution-session` `list`** — `{ "schema_version": Integer, "sessions": Array<{ file, timestamp, total, killed, survived, score, duration }> }`. Sessions are reverse-chronological; the array is filtered by `limit` when provided.
|
|
585
|
+
- **`evilution-session` `show`** — the parsed session JSON document, exactly as written under `.evilution/results/*.json`. Field reference: see [Session JSON files](#session-json-files).
|
|
586
|
+
- **`evilution-session` `diff`** — `{ "schema_version": Integer, "summary": { base_score, head_score, score_delta, base_survived, head_survived, base_total, head_total, base_killed, head_killed }, "fixed": Array, "new_survivors": Array, "persistent": Array }`. The mutation arrays carry the same per-mutation fields the session `survived` list uses (`operator`, `file`, `line`, `subject`, `diff`).
|
|
587
|
+
- **`evilution-info` `subjects`** — `{ "schema_version": Integer, "subjects": Array<{ name, file, line, mutations }>, "total_subjects": Integer, "total_mutations": Integer }`.
|
|
588
|
+
- **`evilution-info` `tests`** — `{ "schema_version": Integer, "specs": Array<{ source, spec }>, "unresolved": Array<String>, "total_sources": Integer, "total_specs": Integer }`.
|
|
589
|
+
- **`evilution-info` `environment`** — `{ "schema_version": Integer, "version": String, "ruby": String, "config_file": String|null, ... }` mirroring the effective `Evilution::Config`.
|
|
590
|
+
- **`evilution-info` `statuses`** — `{ "schema_version": Integer, "statuses": Array<{ name, meaning, in_score }> }`.
|
|
591
|
+
- **`evilution-info` `feedback`** — `{ "schema_version": Integer, "discussion_url": String, "consent": String, "privacy": String }`.
|
|
592
|
+
|
|
593
|
+
#### Deprecation cycle
|
|
594
|
+
|
|
595
|
+
When a parameter, action, or output field on the public MCP contract is deprecated:
|
|
596
|
+
|
|
597
|
+
1. The deprecation is announced in the CHANGELOG and the tool's `description` text gains a deprecation note.
|
|
598
|
+
2. The deprecated form remains functional for the entire `1.x` line — a deprecation introduced in `1.X` continues to work in every subsequent `1.X+N` release.
|
|
599
|
+
3. The earliest release that may remove the deprecated form is `2.0`. Removals are listed in the major-release migration guide.
|
|
600
|
+
4. Adding new parameters, new actions, or new top-level output fields is additive and ships in MINOR releases (existing consumers continue to work).
|
|
601
|
+
|
|
602
|
+
#### Not covered by the contract
|
|
603
|
+
|
|
604
|
+
- The exact wording of error `message` strings (the `type` is contract; the message is a hint that may be reworded).
|
|
605
|
+
- The exact ordering of arrays whose contract calls only for membership (e.g. `unresolved` source files).
|
|
606
|
+
- Progress-stream notification payloads sent through `server_context.notifications` — these are best-effort UI signals, not a stable API.
|
|
607
|
+
- Performance characteristics (request latency, memory, parallelism) — improvements ship in any release; regressions are bugs but not contract violations.
|
|
608
|
+
- Default values that are part of the gem's user-facing semantics (timeout, integration default, etc.) — these follow the umbrella SemVer policy and may be tuned.
|
|
609
|
+
|
|
443
610
|
## Recommended Workflows for AI Agents
|
|
444
611
|
|
|
445
612
|
### 1. Full project scan
|
|
@@ -516,6 +683,21 @@ For each entry in `survived[]`:
|
|
|
516
683
|
|
|
517
684
|
Entries in the JSON `errors[]` array represent mutations that raised an exception (syntax error, load failure, or runtime crash) rather than producing a test outcome. Each entry includes `error_class`, `error_message`, and the first 5 `error_backtrace` lines. Use these fields to decide whether the error is a bug in the mutation operator (file an issue), a load-time problem in the mutated source (often `NoMethodError: super called outside of method` or constant-redefinition issues), or a genuine crash that the original tests should have caught. Run with `--verbose` to stream the same error details to stderr during the run.
|
|
518
685
|
|
|
686
|
+
### Long Minitest fork runs — not a hang
|
|
687
|
+
|
|
688
|
+
Minitest projects under `--isolation=fork` re-bootstrap the test environment (`test_helper.rb`, plugins, runnable state) once per mutation. On constant-heavy files (e.g. Shopify/liquid's `lib/liquid/lexer.rb`, ~270 mutations) the wall-clock cost is dominated by that per-fork bootstrap and any mutations that hit a `--timeout` rather than killing the test fast. A single-worker run (`-j 1`) on a few hundred mutations can take 4+ minutes; combined with `--no-progress` and a non-TTY stderr (CI, redirected logs) the run looks silent the entire time.
|
|
689
|
+
|
|
690
|
+
Recommended invocation for Minitest fork canaries:
|
|
691
|
+
|
|
692
|
+
```bash
|
|
693
|
+
RUBYOPT="-Itest" bundle exec evilution mutate lib/<file>.rb \
|
|
694
|
+
-j 4 -t 10 \
|
|
695
|
+
--integration=minitest --isolation=fork \
|
|
696
|
+
--spec test/<dir>/<file>_test.rb
|
|
697
|
+
```
|
|
698
|
+
|
|
699
|
+
`-j 4` parallelises across workers, `-t 10` caps any mutation that pathologically loops at 10 s. Expect the run to print progress only when stderr is a TTY (use `bundle exec evilution mutate ... 2>&1 | tee log` to get progress while still saving output). The historical "Minitest fork hangs on liquid" report (EV-blnq / GH #1211) turned out to be a slow run + silent UX, not an actual deadlock — the worker logs show steady forward progress when captured via `--quiet-children --quiet-children-dir DIR`.
|
|
700
|
+
|
|
519
701
|
### 8. CI gate
|
|
520
702
|
|
|
521
703
|
```bash
|
|
@@ -588,12 +770,16 @@ Tests 4 paths (InProcess isolation, Fork isolation, mutation generation + stripp
|
|
|
588
770
|
1. **Parse** — Prism parses Ruby files into ASTs with exact byte offsets
|
|
589
771
|
2. **Extract** — Methods are identified as mutation subjects
|
|
590
772
|
3. **Filter** — Disable comments, Sorbet `sig` blocks, and AST ignore patterns exclude mutations before execution
|
|
591
|
-
4. **Mutate** —
|
|
773
|
+
4. **Mutate** — 74 operators produce text replacements at precise byte offsets (source-level surgery, no AST unparsing); heredoc literal text is skipped by default. Identical byte-mutations from different operators are deduplicated by `(file_path, mutated_source)` so the count is not inflated by overlap
|
|
592
774
|
5. **Isolate** — Mutations are applied to temporary file copies (never modifying originals); load-path redirection ensures `require` resolves the mutated copy. Default isolation is in-process for plain Ruby projects and fork for Rails projects (auto-detected); `--isolation fork` forces forked child processes. Both sequential and parallel (`--jobs N`) modes respect the configured isolation strategy
|
|
593
775
|
6. **Test** — The configured test framework (RSpec or Minitest) executes against the mutated source
|
|
594
776
|
7. **Collect** — Source strings and AST nodes are released after use to minimize memory retention
|
|
595
777
|
8. **Report** — Results aggregated into text, JSON, or HTML, including efficiency metrics and peak memory usage
|
|
596
778
|
|
|
779
|
+
## Versioning
|
|
780
|
+
|
|
781
|
+
`evilution` follows [Semantic Versioning](https://semver.org). The full policy — what counts as the public contract, what triggers a major bump, how deprecations work — is documented in [docs/versioning.md](docs/versioning.md).
|
|
782
|
+
|
|
597
783
|
## Repository
|
|
598
784
|
|
|
599
785
|
https://github.com/marinazzio/evilution
|
data/docs/versioning.md
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# Versioning & Upgrade Policy
|
|
2
|
+
|
|
3
|
+
This document defines what `evilution` promises across releases.
|
|
4
|
+
|
|
5
|
+
## SemVer interpretation (1.x)
|
|
6
|
+
|
|
7
|
+
| Bump | Triggered by |
|
|
8
|
+
|---------------|-------------------------------------------------------------------------------|
|
|
9
|
+
| MAJOR (`2.0`) | Removing or renaming anything in the public contract; changing semantics; tightening input validation. |
|
|
10
|
+
| MINOR (`1.X`) | Adding a new CLI flag, config key, mutation operator, public Ruby method, or session/MCP field; relaxing validation; adding an operator to the `default` profile (whether brand-new or promoted from `strict`). |
|
|
11
|
+
| PATCH (`1.X.Y`) | Bug fix, performance improvement, documentation, internal refactor with no observable contract effect. |
|
|
12
|
+
|
|
13
|
+
## Public contract surface
|
|
14
|
+
|
|
15
|
+
The following surfaces are covered by the SemVer guarantees above:
|
|
16
|
+
|
|
17
|
+
- **Public Ruby API** — classes and methods explicitly documented as public. Everything else is internal and may change in any release.
|
|
18
|
+
- **CLI flags and commands** — the README "Command Reference" tables are the authoritative list.
|
|
19
|
+
- **`.evilution.yml` configuration keys** — see the README "Configuration" section.
|
|
20
|
+
- **Session JSON files** (`.evilution/results/*.json`) — see the README "JSON Output Schema" section.
|
|
21
|
+
- **MCP tool input/output schemas** (`evilution-mutate`, `evilution-session`, `evilution-info`) — see the README "MCP Server" section.
|
|
22
|
+
- **Process exit codes** — `0` pass, `1` fail, `2` error. Documented in the README "Exit Codes" section.
|
|
23
|
+
|
|
24
|
+
Anything not on this list is internal. It can change in any release without a deprecation cycle.
|
|
25
|
+
|
|
26
|
+
## Deprecation cycle
|
|
27
|
+
|
|
28
|
+
When a feature on the public contract surface is deprecated:
|
|
29
|
+
|
|
30
|
+
1. It is marked with the YARD `@deprecated` tag (Ruby API), or with a deprecation note in the relevant doc table (CLI flags, config keys, MCP fields).
|
|
31
|
+
2. Where the call site is reachable at runtime, a one-line warning is emitted to stderr.
|
|
32
|
+
3. The deprecated form remains functional for the entire `1.x` line. A feature deprecated in any `1.X` release continues to work in every subsequent `1.X+N` release.
|
|
33
|
+
4. The earliest release that may remove the feature is the next major (`2.0`), per the SemVer table above.
|
|
34
|
+
5. Each removal is recorded in the CHANGELOG under the major-release entry.
|
|
35
|
+
|
|
36
|
+
## Explicitly NOT contract
|
|
37
|
+
|
|
38
|
+
The following are not part of the versioned contract and may change in any release, including patches:
|
|
39
|
+
|
|
40
|
+
- **Mutation score values.** The score depends on the registered operator set, the operator profile, and your test suite. Adding a new operator to the `default` profile is a MINOR change (additive feature) but will shift scores. Pin both the gem version and the operator profile (`profile: default` or `profile: strict`) if you need a stable score across runs.
|
|
41
|
+
- **Mutation operator output text.** Operator *names* (the `operator` field in JSON output, e.g. `arithmetic_replacement`) are part of the contract. The exact mutated source string an operator emits is diagnostic and may change to fix bugs or improve clarity.
|
|
42
|
+
- **Internal classes** (any class not explicitly documented as part of the public Ruby API).
|
|
43
|
+
- **Log lines, progress output, and human-readable report wording.**
|
|
44
|
+
- **Performance characteristics** (timing, memory, parallel scheduling). Improvements ship in any release; regressions are bugs but not contract violations.
|
|
45
|
+
|
|
46
|
+
## Upgrading
|
|
47
|
+
|
|
48
|
+
- **Patch (`1.X.Y` → `1.X.Y+1`)** and **minor (`1.X` → `1.X+1`)**: drop in. Read the CHANGELOG for new flags or config keys you may want to opt into.
|
|
49
|
+
- **Major (`1.X` → `2.0`)**: a migration guide ships with the release, listing every removed contract surface and the replacement path.
|
|
50
|
+
|
|
51
|
+
## References
|
|
52
|
+
|
|
53
|
+
- [CHANGELOG](../CHANGELOG.md) — chronological list of changes per release.
|