evilution 0.11.2 → 0.12.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/.migration-hint-ts +1 -1
- data/.beads/issues.jsonl +22 -10
- data/CHANGELOG.md +11 -0
- data/README.md +13 -5
- data/lib/evilution/cli.rb +1 -0
- data/lib/evilution/config.rb +11 -1
- data/lib/evilution/mcp/mutate_tool.rb +16 -5
- data/lib/evilution/reporter/html.rb +1 -1
- data/lib/evilution/reporter/suggestion.rb +273 -0
- data/lib/evilution/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 66bf48cbfe57daab8f06f98ace9a49b733e0143ae9c3e5e4c5fb516f465b30d7
|
|
4
|
+
data.tar.gz: d021601a2e2256e1c846f55c355845aaacfb8435116873f3c1d74c0e5d7dda33
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 731b85019e5571ddfd8d72904a8303f231c8ba6d5dac1c27782e1dbf11943d55be4d8796382e3bb49b20a719755b0690e2305076b9a42dfe7080103ac88252f0
|
|
7
|
+
data.tar.gz: e5d61aa19eeea415a19f7ab4ea98e09682de65a7694315a82a2989e31d874f176c22e58103a462055c5a73ed29f5f82d5136e26134cc74907cbbc5151dee0541
|
data/.beads/.migration-hint-ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
1774149286
|
data/.beads/issues.jsonl
CHANGED
|
@@ -61,8 +61,8 @@
|
|
|
61
61
|
{"id":"EV-3.7","title":"Implement StringLiteral operator","description":"Targets StringNode. Mutations: non-empty -> empty string, empty -> 'mutation'. File: lib/evilution/mutator/operator/string_literal.rb + spec.","status":"closed","priority":2,"issue_type":"task","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-02T00:06:13.633726423+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-02T11:10:56.39748015+07:00","closed_at":"2026-03-02T11:10:56.39748015+07:00","close_reason":"All 3 operators implemented with passing specs","dependencies":[{"issue_id":"EV-3.7","depends_on_id":"EV-3","type":"parent-child","created_at":"0001-01-01T00:00:00Z"},{"issue_id":"EV-3.7","depends_on_id":"EV-2.6","type":"blocks","created_at":"0001-01-01T00:00:00Z"}]}
|
|
62
62
|
{"id":"EV-3.8","title":"Implement ArrayLiteral operator","description":"Targets ArrayNode with elements. Mutation: [a, b, c] -> []. File: lib/evilution/mutator/operator/array_literal.rb + spec.","status":"closed","priority":2,"issue_type":"task","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-02T00:06:13.734617709+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-02T11:10:56.397565925+07:00","closed_at":"2026-03-02T11:10:56.397565925+07:00","close_reason":"All 3 operators implemented with passing specs","dependencies":[{"issue_id":"EV-3.8","depends_on_id":"EV-3","type":"parent-child","created_at":"0001-01-01T00:00:00Z"},{"issue_id":"EV-3.8","depends_on_id":"EV-2.6","type":"blocks","created_at":"0001-01-01T00:00:00Z"}]}
|
|
63
63
|
{"id":"EV-3.9","title":"Implement HashLiteral operator","description":"Targets HashNode with pairs. Mutation: {k: v} -> {}. File: lib/evilution/mutator/operator/hash_literal.rb + spec.","status":"closed","priority":2,"issue_type":"task","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-02T00:06:13.840779748+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-02T11:10:56.397570989+07:00","closed_at":"2026-03-02T11:10:56.397570989+07:00","close_reason":"All 3 operators implemented with passing specs","dependencies":[{"issue_id":"EV-3.9","depends_on_id":"EV-3","type":"parent-child","created_at":"0001-01-01T00:00:00Z"},{"issue_id":"EV-3.9","depends_on_id":"EV-2.6","type":"blocks","created_at":"0001-01-01T00:00:00Z"}]}
|
|
64
|
-
{"id":"EV-30","title":"Epic: Smarter Suggestions","description":"Improve the suggestion field in mutation results to provide concrete, actionable test code that agents can use directly.","status":"
|
|
65
|
-
{"id":"EV-31","title":"Generate concrete RSpec test code in suggestions","description":"Replace prose suggestions like 'add a test that checks the boundary' with actual RSpec code snippets that would kill the mutant. Use the mutation's operator, file context, and method signature to generate a concrete test example. The suggestion should be a valid RSpec 'it' block that an agent can drop into a spec file.","status":"
|
|
64
|
+
{"id":"EV-30","title":"Epic: Smarter Suggestions","description":"Improve the suggestion field in mutation results to provide concrete, actionable test code that agents can use directly.","status":"closed","priority":2,"issue_type":"feature","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-10T06:18:03.590579846+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-22T17:18:58.004667853+07:00","closed_at":"2026-03-22T17:18:58.004667853+07:00","close_reason":"Epic complete. Concrete RSpec test suggestions implemented for all 16 operator types, gated behind --suggest-tests flag, with MCP tool support and documentation.","dependencies":[{"issue_id":"EV-30","depends_on_id":"EV-31","type":"blocks","created_at":"0001-01-01T00:00:00Z"}]}
|
|
65
|
+
{"id":"EV-31","title":"Generate concrete RSpec test code in suggestions","description":"Replace prose suggestions like 'add a test that checks the boundary' with actual RSpec code snippets that would kill the mutant. Use the mutation's operator, file context, and method signature to generate a concrete test example. The suggestion should be a valid RSpec 'it' block that an agent can drop into a spec file.","status":"closed","priority":2,"issue_type":"feature","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-10T06:18:05.964542284+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-22T17:18:20.274404522+07:00","closed_at":"2026-03-22T17:18:20.274404522+07:00","close_reason":"All concrete suggestion templates implemented for every operator category (EV-55–61), CLI flag added (EV-74), MCP schema updated (EV-75), and docs written (EV-76).","dependencies":[{"issue_id":"EV-31","depends_on_id":"EV-55","type":"blocks","created_at":"0001-01-01T00:00:00Z"},{"issue_id":"EV-31","depends_on_id":"EV-56","type":"blocks","created_at":"0001-01-01T00:00:00Z"},{"issue_id":"EV-31","depends_on_id":"EV-57","type":"blocks","created_at":"0001-01-01T00:00:00Z"},{"issue_id":"EV-31","depends_on_id":"EV-58","type":"blocks","created_at":"0001-01-01T00:00:00Z"},{"issue_id":"EV-31","depends_on_id":"EV-59","type":"blocks","created_at":"0001-01-01T00:00:00Z"},{"issue_id":"EV-31","depends_on_id":"EV-60","type":"blocks","created_at":"0001-01-01T00:00:00Z"},{"issue_id":"EV-31","depends_on_id":"EV-61","type":"blocks","created_at":"0001-01-01T00:00:00Z"},{"issue_id":"EV-31","depends_on_id":"EV-74","type":"blocks","created_at":"0001-01-01T00:00:00Z"},{"issue_id":"EV-31","depends_on_id":"EV-75","type":"blocks","created_at":"0001-01-01T00:00:00Z"},{"issue_id":"EV-31","depends_on_id":"EV-76","type":"blocks","created_at":"0001-01-01T00:00:00Z"}]}
|
|
66
66
|
{"id":"EV-32","title":"Epic: Zero-friction Defaults","description":"Make evilution work well with zero configuration. Auto-detect what to mutate and which specs to run based on git state and file conventions.","status":"closed","priority":2,"issue_type":"feature","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-10T06:18:06.843446356+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-16T10:11:01.311639679+07:00","closed_at":"2026-03-16T10:11:01.311639679+07:00","close_reason":"Already merged and released in v0.4.0","dependencies":[{"issue_id":"EV-32","depends_on_id":"EV-33","type":"blocks","created_at":"0001-01-01T00:00:00Z"},{"issue_id":"EV-32","depends_on_id":"EV-34","type":"blocks","created_at":"0001-01-01T00:00:00Z"}]}
|
|
67
67
|
{"id":"EV-33","title":"Auto-detect changed files from git merge base","description":"When evilution is run with no file arguments, default to mutating files changed since the merge base of the current branch (git merge-base HEAD main). This makes 'evilution run --format json' just work in a feature branch without specifying files. Should be skippable if explicit files are provided.","status":"closed","priority":2,"issue_type":"feature","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-10T06:18:08.235421922+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-16T10:11:01.311622041+07:00","closed_at":"2026-03-16T10:11:01.311622041+07:00","close_reason":"Already merged and released in v0.4.0"}
|
|
68
68
|
{"id":"EV-34","title":"Convention-based spec file resolution","description":"Implement a mapping from source files to their spec files using Ruby/RSpec conventions: lib/foo/bar.rb -> spec/foo/bar_spec.rb, app/models/user.rb -> spec/models/user_spec.rb. This is a foundational piece used by both zero-friction defaults (auto-detect specs) and per-mutation spec targeting (run only relevant specs). Should handle common Rails and gem layouts.","status":"closed","priority":2,"issue_type":"feature","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-10T06:18:09.380269033+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-16T10:11:01.311627178+07:00","closed_at":"2026-03-16T10:11:01.311627178+07:00","close_reason":"Already merged and released in v0.4.0"}
|
|
@@ -110,14 +110,14 @@
|
|
|
110
110
|
{"id":"EV-54.5","title":"Add always-matching regexp variant","description":"Currently regexp only mutates to never-matching /a\\A/. Add always-matching /.*/ variant to test both match and no-match code paths.","status":"closed","priority":3,"issue_type":"task","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-19T21:21:21.049402671+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-21T10:05:45.034075069+07:00","closed_at":"2026-03-21T10:05:45.034075069+07:00","close_reason":"Added always-matching /.*/ variant alongside never-matching /a\\A/. Each regexp now produces 2 mutations. 731 specs pass, 100% mutation score.","external_ref":"gh-171","dependencies":[{"issue_id":"EV-54.5","depends_on_id":"EV-54","type":"parent-child","created_at":"0001-01-01T00:00:00Z"}]}
|
|
111
111
|
{"id":"EV-54.6","title":"Expand ArithmeticReplacement with bitwise shift operators","description":"Add <<→>> swap. Niche but catches bitwise shift bugs. Lowest priority in this epic.","status":"closed","priority":4,"issue_type":"task","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-19T21:21:21.152296396+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-21T01:03:32.687774444+07:00","closed_at":"2026-03-21T01:03:32.687774444+07:00","close_reason":"Added << >> bitwise shift swap to ArithmeticReplacement operator. 714 specs passing, 100% mutation score.","external_ref":"gh-172","dependencies":[{"issue_id":"EV-54.6","depends_on_id":"EV-54","type":"parent-child","created_at":"0001-01-01T00:00:00Z"}]}
|
|
112
112
|
{"id":"EV-54.7","title":"Operator: ArgumentNilSubstitution","description":"Replace each method argument with nil, one at a time. E.g. foo(a, b) → foo(nil, b) and foo(a, nil). Works for single-arg calls too (unlike ArgumentRemoval which requires 2+). This catches 'default value' bugs where a method has a sensible fallback for nil — e.g. I18n.with_locale(user.locale) → I18n.with_locale(nil) silently falls back to default locale. Identified from real-world feedback where mutant caught a locale test gap that evilution missed (v0.7.0).","status":"closed","priority":2,"issue_type":"task","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-19T22:09:42.995246472+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-20T23:43:12.098455761+07:00","closed_at":"2026-03-20T23:43:12.098455761+07:00","close_reason":"Implemented ArgumentNilSubstitution operator. Replaces each argument with nil one at a time, works for single-arg calls too (unlike ArgumentRemoval). Skips splat, keyword, block, and forwarding args. 12 specs passing, 705 total.","external_ref":"gh-175","dependencies":[{"issue_id":"EV-54.7","depends_on_id":"EV-54","type":"parent-child","created_at":"0001-01-01T00:00:00Z"}]}
|
|
113
|
-
{"id":"EV-55","title":"Concrete suggestions: Comparison & Arithmetic operators","description":"Generate concrete RSpec it blocks for ComparisonReplacement and ArithmeticReplacement mutations. Test should assert exact return value at boundary conditions.","status":"
|
|
114
|
-
{"id":"EV-56","title":"Concrete suggestions: Boolean operators","description":"Generate concrete RSpec it blocks for BooleanOperatorReplacement, BooleanLiteralReplacement, and NegationInsertion mutations. Test should assert true/false explicitly.","status":"
|
|
115
|
-
{"id":"EV-57","title":"Concrete suggestions: Literal operators","description":"Generate concrete RSpec it blocks for IntegerLiteral, FloatLiteral, StringLiteral, and SymbolLiteral mutations. Test should assert exact value returned.","status":"
|
|
116
|
-
{"id":"EV-58","title":"Concrete suggestions: Collection operators","description":"Generate concrete RSpec it blocks for ArrayLiteral, HashLiteral, and CollectionReplacement mutations. Test should assert contents or size.","status":"
|
|
117
|
-
{"id":"EV-59","title":"Concrete suggestions: Conditional operators","description":"Generate concrete RSpec it blocks for ConditionalNegation and ConditionalBranch mutations. Test should exercise both branches.","status":"
|
|
113
|
+
{"id":"EV-55","title":"Concrete suggestions: Comparison & Arithmetic operators","description":"Generate concrete RSpec it blocks for ComparisonReplacement and ArithmeticReplacement mutations. Test should assert exact return value at boundary conditions.","design":"Use Option A (enriched centralized module): Convert the static TEMPLATES entry in Reporter::Suggestion to a lambda/proc that receives the Mutation object and returns concrete RSpec code. Use mutation.original_source, mutation.mutated_source, and mutation.subject metadata to interpolate actual values into the generated test. When suggest_tests is off, return existing static text; when on, return concrete RSpec it-block.","status":"closed","priority":3,"issue_type":"feature","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-17T09:58:33.26716126+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-22T16:37:54.589252928+07:00","closed_at":"2026-03-22T16:37:54.589252928+07:00","close_reason":"Implemented concrete RSpec suggestions for comparison_replacement and arithmetic_replacement operators using Option A (enriched centralized module with lambdas in CONCRETE_TEMPLATES hash)"}
|
|
114
|
+
{"id":"EV-56","title":"Concrete suggestions: Boolean operators","description":"Generate concrete RSpec it blocks for BooleanOperatorReplacement, BooleanLiteralReplacement, and NegationInsertion mutations. Test should assert true/false explicitly.","design":"Use Option A (enriched centralized module): Convert the static TEMPLATES entry in Reporter::Suggestion to a lambda/proc that receives the Mutation object and returns concrete RSpec code. Use mutation.original_source, mutation.mutated_source, and mutation.subject metadata to interpolate actual values into the generated test. When suggest_tests is off, return existing static text; when on, return concrete RSpec it-block.","status":"closed","priority":3,"issue_type":"feature","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-17T09:58:33.370014645+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-22T16:45:46.097533929+07:00","closed_at":"2026-03-22T16:45:46.097533929+07:00","close_reason":"Added concrete RSpec suggestion lambdas for boolean_operator_replacement, boolean_literal_replacement, and negation_insertion operators in CONCRETE_TEMPLATES"}
|
|
115
|
+
{"id":"EV-57","title":"Concrete suggestions: Literal operators","description":"Generate concrete RSpec it blocks for IntegerLiteral, FloatLiteral, StringLiteral, and SymbolLiteral mutations. Test should assert exact value returned.","design":"Use Option A (enriched centralized module): Convert the static TEMPLATES entry in Reporter::Suggestion to a lambda/proc that receives the Mutation object and returns concrete RSpec code. Use mutation.original_source, mutation.mutated_source, and mutation.subject metadata to interpolate actual values into the generated test. When suggest_tests is off, return existing static text; when on, return concrete RSpec it-block.","status":"closed","priority":3,"issue_type":"feature","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-17T09:58:33.473990949+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-22T16:49:40.666070785+07:00","closed_at":"2026-03-22T16:49:40.666070785+07:00","close_reason":"Added concrete RSpec suggestion lambdas for integer_literal, float_literal, string_literal, and symbol_literal operators in CONCRETE_TEMPLATES"}
|
|
116
|
+
{"id":"EV-58","title":"Concrete suggestions: Collection operators","description":"Generate concrete RSpec it blocks for ArrayLiteral, HashLiteral, and CollectionReplacement mutations. Test should assert contents or size.","design":"Use Option A (enriched centralized module): Convert the static TEMPLATES entry in Reporter::Suggestion to a lambda/proc that receives the Mutation object and returns concrete RSpec code. Use mutation.original_source, mutation.mutated_source, and mutation.subject metadata to interpolate actual values into the generated test. When suggest_tests is off, return existing static text; when on, return concrete RSpec it-block.","status":"closed","priority":3,"issue_type":"feature","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-17T09:58:33.577189348+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-22T16:55:27.552776823+07:00","closed_at":"2026-03-22T16:55:27.552776823+07:00","close_reason":"Added concrete RSpec suggestion lambdas for array_literal, hash_literal, and collection_replacement operators. Also excluded suggestion.rb from ClassLength rubocop check since it's data-heavy."}
|
|
117
|
+
{"id":"EV-59","title":"Concrete suggestions: Conditional operators","description":"Generate concrete RSpec it blocks for ConditionalNegation and ConditionalBranch mutations. Test should exercise both branches.","design":"Use Option A (enriched centralized module): Convert the static TEMPLATES entry in Reporter::Suggestion to a lambda/proc that receives the Mutation object and returns concrete RSpec code. Use mutation.original_source, mutation.mutated_source, and mutation.subject metadata to interpolate actual values into the generated test. When suggest_tests is off, return existing static text; when on, return concrete RSpec it-block.","status":"closed","priority":3,"issue_type":"feature","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-17T09:58:33.676644345+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-22T17:01:30.626874811+07:00","closed_at":"2026-03-22T17:01:30.626874811+07:00","close_reason":"Added concrete RSpec suggestion lambdas for conditional_negation and conditional_branch operators in CONCRETE_TEMPLATES"}
|
|
118
118
|
{"id":"EV-6","title":"Fix pool fork tests hanging in CI and WSL2","description":"The spec/evilution/parallel/pool_spec.rb fork-based tests hang on both WSL2 and GitHub Actions. CI currently excludes them via --exclude-pattern. Root cause is likely double-fork (Pool forks workers, each Worker uses Isolation::Fork which forks again) causing pipe/process management issues. Needs investigation and fix so pool tests run reliably in CI.","status":"closed","priority":1,"issue_type":"bug","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-02T16:21:43.62587758+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-05T12:31:51.831163433+07:00","closed_at":"2026-03-05T12:31:51.831163433+07:00","close_reason":"Closed"}
|
|
119
|
-
{"id":"EV-60","title":"Concrete suggestions: Structural operators","description":"Generate concrete RSpec it blocks for StatementDeletion, MethodBodyReplacement, ReturnValueRemoval, and MethodCallRemoval mutations. Test should depend on side effect or return value.","status":"
|
|
120
|
-
{"id":"EV-61","title":"Concrete suggestions: Nil operator","description":"Generate concrete RSpec it block for NilReplacement mutations. Test should assert non-nil return value.","status":"
|
|
119
|
+
{"id":"EV-60","title":"Concrete suggestions: Structural operators","description":"Generate concrete RSpec it blocks for StatementDeletion, MethodBodyReplacement, ReturnValueRemoval, and MethodCallRemoval mutations. Test should depend on side effect or return value.","design":"Use Option A (enriched centralized module): Convert the static TEMPLATES entry in Reporter::Suggestion to a lambda/proc that receives the Mutation object and returns concrete RSpec code. Use mutation.original_source, mutation.mutated_source, and mutation.subject metadata to interpolate actual values into the generated test. When suggest_tests is off, return existing static text; when on, return concrete RSpec it-block.","status":"closed","priority":3,"issue_type":"feature","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-17T09:58:33.777078104+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-22T17:06:40.998608577+07:00","closed_at":"2026-03-22T17:06:40.998608577+07:00","close_reason":"Added concrete RSpec suggestion lambdas for statement_deletion, method_body_replacement, return_value_removal, and method_call_removal operators in CONCRETE_TEMPLATES"}
|
|
120
|
+
{"id":"EV-61","title":"Concrete suggestions: Nil operator","description":"Generate concrete RSpec it block for NilReplacement mutations. Test should assert non-nil return value.","design":"Use Option A (enriched centralized module): Convert the static TEMPLATES entry in Reporter::Suggestion to a lambda/proc that receives the Mutation object and returns concrete RSpec code. Use mutation.original_source, mutation.mutated_source, and mutation.subject metadata to interpolate actual values into the generated test. When suggest_tests is off, return existing static text; when on, return concrete RSpec it-block.","status":"closed","priority":3,"issue_type":"feature","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-17T09:58:33.882878252+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-22T17:13:11.879236112+07:00","closed_at":"2026-03-22T17:13:11.879236112+07:00","close_reason":"Added concrete RSpec suggestion lambda for nil_replacement operator in CONCRETE_TEMPLATES. This completes all concrete suggestion issues (EV-55 through EV-61)."}
|
|
121
121
|
{"id":"EV-62","title":"Neutral mutation detection","description":"Run the original unmutated code once before mutation testing. Flag any tests that already fail, so surviving mutants caused by pre-broken tests are reported as neutral rather than gaps. Eliminates false positives in mutation score.","status":"closed","priority":2,"issue_type":"feature","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-17T10:15:03.670730866+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-17T23:16:28.920679692+07:00","closed_at":"2026-03-17T23:16:28.920679692+07:00","close_reason":"Merged"}
|
|
122
122
|
{"id":"EV-63","title":"In-process mutation for performance","description":"Replace fork+file-write isolation with in-memory mutation (constant replacement or load-path override) for the common case. Current approach: 3.0 mutations/s vs mutant's 17.6 mutations/s (6x slower). Fork overhead is the main bottleneck. Keep fork as fallback for safety.","status":"closed","priority":2,"issue_type":"feature","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-17T10:15:03.765021194+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-18T18:31:15.344270883+07:00","closed_at":"2026-03-18T18:31:15.344270883+07:00","close_reason":"GH issue #114 closed, PR #123 merged"}
|
|
123
123
|
{"id":"EV-64","title":"Class-level --target filtering","description":"Extend --target to accept class names without method (e.g. --target Game) to mutate all methods in the class. Currently only supports fully-qualified method names like Game#method.","status":"closed","priority":3,"issue_type":"feature","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-17T10:15:03.858147999+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-19T21:31:38.601679951+07:00","closed_at":"2026-03-19T21:31:38.601679951+07:00","close_reason":"Implemented class-level --target filtering with 3 new specs, 100% mutation score"}
|
|
@@ -125,7 +125,7 @@
|
|
|
125
125
|
{"id":"EV-66","title":"Scope-aware spec resolution","description":"Improve convention-based spec resolution for nested modules. When a method is nested in a module, prefer spec/models/game_spec.rb over a generic match. Handle edge cases in app/ directory structures.","status":"closed","priority":3,"issue_type":"feature","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-17T10:15:04.04579177+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-19T22:15:01.15206232+07:00","closed_at":"2026-03-19T22:15:01.15206232+07:00","close_reason":"Implemented parent-path fallback in SpecResolver for nested module spec resolution"}
|
|
126
126
|
{"id":"EV-67","title":"HTML report with visual mutation map","description":"Generate an HTML report showing which lines are well-tested vs vulnerable. Visual mutation map per file with color-coded coverage.","status":"closed","priority":4,"issue_type":"feature","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-17T10:15:04.13552276+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-20T23:58:29.045246978+07:00","closed_at":"2026-03-20T23:58:29.045246978+07:00","close_reason":"Implemented HTML reporter with visual mutation map. Self-contained HTML with inline CSS (dark theme), per-file mutation maps with color-coded lines (green=killed, red=survived, yellow=timeout), expandable survived mutation details with diffs and suggestions. Writes to evilution-report.html. 17 reporter specs, 724 total passing."}
|
|
127
127
|
{"id":"EV-68","title":"Equivalent mutation detection","description":"Add heuristics to skip mutations that produce provably identical behavior (dead code paths, no-op transformations). Reduces noise in mutation results.","status":"closed","priority":4,"issue_type":"feature","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-17T10:15:04.226297798+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-21T00:22:25.796367972+07:00","closed_at":"2026-03-21T00:22:25.796367972+07:00","close_reason":"Implemented equivalent mutation detection with 4 heuristics: NoopSource (identical source), MethodBodyNil (empty/nil method bodies), AliasSwap (detect/find, length/size, collect/map), DeadCode (unreachable code after return/raise). Integrated into runner pipeline, reporters (CLI/JSON/HTML), and MCP tool. Equivalents excluded from score denominator. 100% mutation score (211/211). 753 total specs passing."}
|
|
128
|
-
{"id":"EV-69","title":"Minitest integration","description":"Add Minitest support alongside existing RSpec integration.
|
|
128
|
+
{"id":"EV-69","title":"Minitest integration","description":"Epic: Add Minitest support alongside existing RSpec integration. RSpec is currently deeply coupled across 12 files (integration, baseline, spec_resolver, runner, config, cli, suggestions, MCP tool). This epic introduces a test framework abstraction layer and implements Minitest as the first alternative integration.","status":"open","priority":4,"issue_type":"feature","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-17T10:15:04.313129974+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-22T17:27:58.96045095+07:00"}
|
|
129
129
|
{"id":"EV-7","title":"Enable true per-mutation isolation with temp file copies","description":"Currently all mutations for the same file go to one worker (PR #4 fix). This means single-file projects get no parallelism benefit. Implement temp file copy approach: each worker copies the source file to a temp location, mutates the copy, and runs tests against it. This enables safe parallel mutation of the same file across multiple workers.","status":"closed","priority":2,"issue_type":"feature","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-02T16:21:46.820210376+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-06T11:46:55.740292929+07:00","closed_at":"2026-03-06T11:46:55.740292929+07:00","close_reason":"Already merged in PR #20"}
|
|
130
130
|
{"id":"EV-70","title":"Epic: Fix memory leaks and add memory observability","description":"Evilution processes consume up to 17.2 GB each due to memory leaks. This epic covers: (1) identifying and fixing root causes — double forking, upfront mutation array, source string retention in results, Marshal round-trips; (2) adding runtime memory instrumentation behind --verbose; (3) adding memory budget specs to catch regressions in CI.","status":"closed","priority":1,"issue_type":"epic","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-18T18:51:39.345459861+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-19T14:34:58.765114612+07:00","closed_at":"2026-03-19T14:34:58.765114612+07:00","close_reason":"All 14 sub-issues complete. Memory leaks fixed, observability added, rake memory:check in place for regression detection.","external_ref":"gh-124","labels":["v0.7.0"]}
|
|
131
131
|
{"id":"EV-70.1","title":"Add RSS memory measurement utility","description":"Create a lightweight Memory module that reads RSS from /proc/$$/status (Linux) with no gem dependencies. Provide Memory.rss_mb method returning current process RSS in MB. This is the foundation for all other memory instrumentation.","status":"closed","priority":2,"issue_type":"task","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-18T18:51:45.894741371+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-18T19:02:42.603502332+07:00","closed_at":"2026-03-18T19:02:42.603502332+07:00","close_reason":"Implemented Memory module with rss_kb, rss_mb, rss_kb_for(pid), and delta methods. 8 specs passing.","external_ref":"gh-125","labels":["v0.7.0"],"dependencies":[{"issue_id":"EV-70.1","depends_on_id":"EV-70","type":"parent-child","created_at":"0001-01-01T00:00:00Z"}]}
|
|
@@ -145,5 +145,17 @@
|
|
|
145
145
|
{"id":"EV-71","title":"Trim MCP tool response to reduce context window usage","description":"The evilution MCP tool returns full JSON report with all killed mutation diffs (~10k tokens). Only survived mutations are actionable. Trim killed/neutral/timed_out/errors detail arrays in MutateTool.call, keeping summary counts and survived details only.","status":"closed","priority":1,"issue_type":"task","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-19T23:23:32.672265145+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-19T23:31:18.511115181+07:00","closed_at":"2026-03-19T23:31:18.511115181+07:00","close_reason":"Trim killed/neutral/timed_out/errors detail arrays from MCP tool response, keeping only summary and survived details","external_ref":"marinazzio/evilution#178"}
|
|
146
146
|
{"id":"EV-72","title":"Add verbosity control to MCP tool response","description":"Add a verbosity parameter (full/summary/minimal) to the MCP tool to control response size. Default to summary for best context efficiency. Full keeps all entries without diffs, summary omits killed/neutral arrays, minimal keeps only summary + survived.","status":"closed","priority":3,"issue_type":"feature","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-19T23:41:41.837080367+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-21T01:16:09.980410196+07:00","closed_at":"2026-03-21T01:16:09.980410196+07:00","close_reason":"Added verbosity parameter (full/summary/minimal) to MCP tool. Default is summary which omits killed/neutral/equivalent arrays. 718 specs passing, 100% mutation score. Updated README.","external_ref":"marinazzio/evilution#179"}
|
|
147
147
|
{"id":"EV-73","title":"Clean up deprecated CLI switches and config options","description":"Remove long-deprecated --diff, --no-coverage flags and coverage config option. GH #176.","status":"closed","priority":2,"issue_type":"task","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-21T00:37:09.266487789+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-21T00:49:24.827479374+07:00","closed_at":"2026-03-21T00:49:24.827479374+07:00","close_reason":"Removed deprecated --diff, --no-coverage CLI flags; removed diff_base, coverage, diff? from Config; removed filter_by_diff from Runner; deleted diff/parser, diff/file_filter, coverage/collector, coverage/test_map and their specs; cleaned up requires. 712 specs passing.","external_ref":"gh-176"}
|
|
148
|
+
{"id":"EV-74","title":"Add --suggest-tests CLI flag to enable concrete test suggestions","description":"Add a --suggest-tests CLI flag and Config option (default: false) that gates whether suggestions use static templates or concrete RSpec test code. GH #209","status":"closed","priority":2,"issue_type":"feature","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-22T10:24:05.280617605+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-22T10:30:31.273675421+07:00","closed_at":"2026-03-22T10:30:31.273675421+07:00","close_reason":"Added --suggest-tests CLI flag, Config attribute, predicate method, and default template entry. 737 specs pass, no rubocop offenses."}
|
|
149
|
+
{"id":"EV-75","title":"Add suggest_tests parameter to MCP tool schema","description":"Add suggest_tests boolean parameter to evilution-mutate MCP tool input schema so AI agents can opt into concrete test suggestions. GH #210","status":"closed","priority":2,"issue_type":"feature","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-22T10:24:06.101977203+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-22T10:45:56.197750084+07:00","closed_at":"2026-03-22T10:45:56.197750084+07:00","close_reason":"Added suggest_tests boolean parameter to MCP tool input schema, passed through to Config. 739 specs pass, rubocop clean, 100% mutation score.","dependencies":[{"issue_id":"EV-75","depends_on_id":"EV-74","type":"blocks","created_at":"0001-01-01T00:00:00Z"}]}
|
|
150
|
+
{"id":"EV-76","title":"Document --suggest-tests flag for AI agent consumers","description":"Update CLI help, MCP tool description, and README to document the suggest_tests capability for both human and AI agent consumers. GH #211","status":"closed","priority":2,"issue_type":"task","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-22T10:24:07.258670935+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-22T10:55:17.076339069+07:00","closed_at":"2026-03-22T10:55:17.076339069+07:00","close_reason":"Documented --suggest-tests flag in README (CLI options table, config example, MCP section, workflow guidance) and enhanced MCP tool description for agent discoverability. 739 specs pass, rubocop clean, 100% mutation score.","dependencies":[{"issue_id":"EV-76","depends_on_id":"EV-74","type":"blocks","created_at":"0001-01-01T00:00:00Z"},{"issue_id":"EV-76","depends_on_id":"EV-75","type":"blocks","created_at":"0001-01-01T00:00:00Z"}]}
|
|
151
|
+
{"id":"EV-77","title":"Refactor: Replace extract_diff_lines tuple return with a struct or named accessors","description":"The Suggestion.extract_diff_lines method returns a two-element array [original_line, mutated_line], which is then destructured at every call site in CONCRETE_TEMPLATES. Replace with a struct or object with named accessors for clarity and to avoid positional coupling.","notes":"GitHub issue: #222","status":"open","priority":3,"issue_type":"task","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-22T17:13:20.688409572+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-22T17:19:56.805536144+07:00"}
|
|
152
|
+
{"id":"EV-78","title":"Abstract test framework interface in Integration::Base","description":"Extract Integration::Base into a proper strategy pattern with run_tests, build_args, reset_state methods. The current Integration::RSpec hard-codes RSpec::Core::Runner.run() and RSpec.reset. Define abstract methods that any test framework integration must implement.","notes":"GitHub issue: #223","status":"open","priority":3,"issue_type":"task","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-22T17:28:15.862336424+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-22T17:28:30.068127772+07:00","dependencies":[{"issue_id":"EV-78","depends_on_id":"EV-69","type":"blocks","created_at":"0001-01-01T00:00:00Z"}]}
|
|
153
|
+
{"id":"EV-79","title":"Implement Integration::Minitest class","description":"Create Integration::Minitest implementing the test framework interface. Must handle Minitest::Runnable.run, test execution, and pass/fail detection via exit codes. Should support both minitest and minitest-reporters.","notes":"GitHub issue: #224","status":"open","priority":3,"issue_type":"feature","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-22T17:28:19.881523481+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-22T17:28:33.447325403+07:00","dependencies":[{"issue_id":"EV-79","depends_on_id":"EV-69","type":"blocks","created_at":"0001-01-01T00:00:00Z"},{"issue_id":"EV-79","depends_on_id":"EV-78","type":"blocks","created_at":"0001-01-01T00:00:00Z"}]}
|
|
148
154
|
{"id":"EV-8","title":"Investigate RSpec state accumulation across mutation runs","description":"RSpec.reset is called between mutations but loaded spec files persist in memory. Long mutation runs may accumulate state from previously loaded specs, potentially causing false positives/negatives. Investigate whether RSpec::Core::Runner.run properly isolates between runs or if additional cleanup is needed.","status":"closed","priority":2,"issue_type":"bug","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-02T16:21:48.618669476+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-05T11:57:56.965910759+07:00","closed_at":"2026-03-05T11:57:56.965910759+07:00","close_reason":"Investigation complete: fork isolation already prevents state accumulation. Added documentation explaining the guarantee and tests verifying independent consecutive runs."}
|
|
155
|
+
{"id":"EV-80","title":"Support Minitest file discovery in SpecResolver","description":"SpecResolver currently assumes _spec.rb suffix and spec/ directory. Add support for Minitest conventions: test/ directory, *_test.rb suffix. Make discovery strategy configurable based on the chosen integration.","notes":"GitHub issue: #225","status":"open","priority":3,"issue_type":"task","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-22T17:28:24.214856109+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-22T17:28:39.343862681+07:00","dependencies":[{"issue_id":"EV-80","depends_on_id":"EV-69","type":"blocks","created_at":"0001-01-01T00:00:00Z"}]}
|
|
156
|
+
{"id":"EV-81","title":"Abstract baseline runner from RSpec","description":"baseline.rb hard-codes require 'rspec/core', RSpec.reset, and RSpec::Core::Runner.run. Refactor to use the Integration interface so baseline verification works with any test framework.","notes":"GitHub issue: #226","status":"open","priority":3,"issue_type":"task","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-22T17:28:28.362534837+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-22T17:28:41.1884433+07:00","dependencies":[{"issue_id":"EV-81","depends_on_id":"EV-69","type":"blocks","created_at":"0001-01-01T00:00:00Z"},{"issue_id":"EV-81","depends_on_id":"EV-78","type":"blocks","created_at":"0001-01-01T00:00:00Z"}]}
|
|
157
|
+
{"id":"EV-82","title":"Add --integration CLI flag and config support for Minitest","description":"Config currently defaults to integration: :rspec with no alternative. Add integration: :minitest option in .evilution.yml and --integration CLI flag. Update config template and validation.","notes":"GitHub issue: #227","status":"open","priority":3,"issue_type":"task","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-22T17:28:33.571806703+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-22T17:28:43.084294853+07:00","dependencies":[{"issue_id":"EV-82","depends_on_id":"EV-69","type":"blocks","created_at":"0001-01-01T00:00:00Z"}]}
|
|
158
|
+
{"id":"EV-83","title":"Plugin-based runner dispatch for test integrations","description":"Runner currently uses a case statement with only 'when :rspec'. Refactor to a registry/plugin mechanism that resolves the integration class from config.integration. Also update module loading in evilution.rb to conditionally require integrations.","notes":"GitHub issue: #228","status":"open","priority":3,"issue_type":"task","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-22T17:28:40.5256819+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-22T17:28:54.689064249+07:00","dependencies":[{"issue_id":"EV-83","depends_on_id":"EV-69","type":"blocks","created_at":"0001-01-01T00:00:00Z"},{"issue_id":"EV-83","depends_on_id":"EV-78","type":"blocks","created_at":"0001-01-01T00:00:00Z"}]}
|
|
159
|
+
{"id":"EV-84","title":"Minitest concrete suggestion templates","description":"All 16 CONCRETE_TEMPLATES in Reporter::Suggestion generate RSpec it/expect blocks. Add parallel Minitest templates using assert_equal style. suggestion_for should select the right template set based on the configured integration.","notes":"GitHub issue: #229","status":"open","priority":3,"issue_type":"feature","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-22T17:28:41.5343649+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-22T17:28:59.242395265+07:00","dependencies":[{"issue_id":"EV-84","depends_on_id":"EV-69","type":"blocks","created_at":"0001-01-01T00:00:00Z"},{"issue_id":"EV-84","depends_on_id":"EV-79","type":"blocks","created_at":"0001-01-01T00:00:00Z"}]}
|
|
160
|
+
{"id":"EV-85","title":"Update documentation for Minitest support","description":"Update CLI help text, MCP tool descriptions, and README to reflect Minitest as a supported integration. Currently --suggest-tests and MCP tool explicitly say 'RSpec'. Make descriptions framework-aware or generic.","notes":"GitHub issue: #230","status":"open","priority":3,"issue_type":"task","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-22T17:28:51.219728494+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-22T17:29:06.644497418+07:00","dependencies":[{"issue_id":"EV-85","depends_on_id":"EV-69","type":"blocks","created_at":"0001-01-01T00:00:00Z"},{"issue_id":"EV-85","depends_on_id":"EV-79","type":"blocks","created_at":"0001-01-01T00:00:00Z"},{"issue_id":"EV-85","depends_on_id":"EV-82","type":"blocks","created_at":"0001-01-01T00:00:00Z"}]}
|
|
149
161
|
{"id":"EV-9","title":"Add multi-Ruby CI test matrix (3.2, 3.3, 4.0)","description":"CI currently only tests Ruby 4.0.1. Add a matrix strategy testing Ruby 3.2, 3.3, and 4.0 to ensure compatibility across supported Ruby versions. Prism ships with Ruby 3.3+ so 3.2 may need the prism gem as a dependency.","status":"closed","priority":2,"issue_type":"task","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-02T16:21:51.239774764+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-05T12:31:51.83181612+07:00","closed_at":"2026-03-05T12:31:51.83181612+07:00","close_reason":"Closed"}
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.12.0] - 2026-03-22
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- **Concrete RSpec test suggestions** (`--suggest-tests`) — surviving mutants now include ready-to-use RSpec `it` blocks instead of generic guidance; covers all operator families: arithmetic, comparison, boolean, literal, collection, conditional, structural, and nil operators (#209, #215, #216, #217, #218, #219, #220, #221)
|
|
8
|
+
- **MCP tool `suggest_tests` parameter** — enables concrete test suggestions in MCP tool responses (#213)
|
|
9
|
+
|
|
10
|
+
### Changed
|
|
11
|
+
|
|
12
|
+
- **RuboCop configuration cleanup** — added metrics targets and refactored cop values/exclusions (#204, #207)
|
|
13
|
+
|
|
3
14
|
## [0.11.0] - 2026-03-21
|
|
4
15
|
|
|
5
16
|
### Added
|
data/README.md
CHANGED
|
@@ -48,6 +48,7 @@ evilution [command] [options] [files...]
|
|
|
48
48
|
| `--no-baseline` | Boolean | _(enabled)_ | Skip baseline test suite check. By default, a baseline run detects pre-existing failures and marks those mutations as `neutral`. |
|
|
49
49
|
| `--fail-fast [N]` | Integer | _(none)_ | Stop after N surviving mutants (default 1 if no value given). |
|
|
50
50
|
| `-v`, `--verbose` | Boolean | false | Verbose output with RSS memory and GC stats per phase and per mutation. |
|
|
51
|
+
| `--suggest-tests` | Boolean | false | Generate concrete RSpec test code in suggestions instead of static descriptions. |
|
|
51
52
|
| `-q`, `--quiet` | Boolean | false | Suppress output. |
|
|
52
53
|
|
|
53
54
|
### Exit Codes
|
|
@@ -65,10 +66,11 @@ Generate default config: `bundle exec evilution init`
|
|
|
65
66
|
Creates `.evilution.yml`:
|
|
66
67
|
|
|
67
68
|
```yaml
|
|
68
|
-
# timeout: 10
|
|
69
|
-
# format: text
|
|
70
|
-
# min_score: 0.0
|
|
71
|
-
# integration: rspec
|
|
69
|
+
# timeout: 10 # seconds per mutation
|
|
70
|
+
# format: text # text | json
|
|
71
|
+
# min_score: 0.0 # 0.0–1.0
|
|
72
|
+
# integration: rspec # test framework
|
|
73
|
+
# suggest_tests: false # concrete RSpec test code in suggestions
|
|
72
74
|
```
|
|
73
75
|
|
|
74
76
|
**Precedence**: CLI flags override `.evilution.yml` values.
|
|
@@ -172,6 +174,12 @@ The MCP tool accepts a `verbosity` parameter to control response size:
|
|
|
172
174
|
|
|
173
175
|
Use `minimal` when context window budget is tight and you only need to see what survived. Use `full` when you need to inspect killed/neutral/equivalent entries for debugging.
|
|
174
176
|
|
|
177
|
+
### Concrete Test Suggestions
|
|
178
|
+
|
|
179
|
+
The MCP tool accepts a `suggest_tests` boolean parameter (default: `false`). When enabled, survived mutation suggestions contain concrete RSpec `it` blocks that an agent can drop into a spec file, instead of static description text.
|
|
180
|
+
|
|
181
|
+
Pass `suggest_tests: true` in the MCP tool call, or use `--suggest-tests` on the CLI, to activate this mode.
|
|
182
|
+
|
|
175
183
|
> **Note**: `.mcp.json` is gitignored by default since it is a local editor/agent configuration file.
|
|
176
184
|
|
|
177
185
|
## Recommended Workflows for AI Agents
|
|
@@ -234,7 +242,7 @@ Use when you know which file was modified and want to verify its test coverage.
|
|
|
234
242
|
For each entry in `survived[]`:
|
|
235
243
|
1. Read `file` at `line` to understand the code context
|
|
236
244
|
2. Read `operator` to understand what was changed
|
|
237
|
-
3. Read `suggestion` for a hint on what test to write
|
|
245
|
+
3. Read `suggestion` for a hint on what test to write (use `--suggest-tests` for concrete RSpec code)
|
|
238
246
|
4. Write a test that would fail if the mutation were applied
|
|
239
247
|
5. Re-run evilution on just that file to verify the mutant is now killed
|
|
240
248
|
|
data/lib/evilution/cli.rb
CHANGED
|
@@ -122,6 +122,7 @@ module Evilution
|
|
|
122
122
|
opts.on("--incremental", "Cache killed/timeout results; skip re-running them on unchanged files") { @options[:incremental] = true }
|
|
123
123
|
opts.on("--isolation STRATEGY", "Isolation: auto, fork, in_process (default: auto)") { |s| @options[:isolation] = s }
|
|
124
124
|
opts.on("--stdin", "Read target file paths from stdin (one per line)") { @options[:stdin] = true }
|
|
125
|
+
opts.on("--suggest-tests", "Generate concrete RSpec test code in suggestions") { @options[:suggest_tests] = true }
|
|
125
126
|
opts.on("-v", "--verbose", "Verbose output") { @options[:verbose] = true }
|
|
126
127
|
opts.on("-q", "--quiet", "Suppress output") { @options[:quiet] = true }
|
|
127
128
|
end
|
data/lib/evilution/config.rb
CHANGED
|
@@ -19,13 +19,15 @@ module Evilution
|
|
|
19
19
|
baseline: true,
|
|
20
20
|
isolation: :auto,
|
|
21
21
|
incremental: false,
|
|
22
|
+
suggest_tests: false,
|
|
22
23
|
line_ranges: {},
|
|
23
24
|
spec_files: []
|
|
24
25
|
}.freeze
|
|
25
26
|
|
|
26
27
|
attr_reader :target_files, :timeout, :format,
|
|
27
28
|
:target, :min_score, :integration, :verbose, :quiet,
|
|
28
|
-
:jobs, :fail_fast, :baseline, :isolation, :incremental, :
|
|
29
|
+
:jobs, :fail_fast, :baseline, :isolation, :incremental, :suggest_tests,
|
|
30
|
+
:line_ranges, :spec_files
|
|
29
31
|
|
|
30
32
|
def initialize(**options)
|
|
31
33
|
file_options = options.delete(:skip_config_file) ? {} : load_config_file
|
|
@@ -66,6 +68,10 @@ module Evilution
|
|
|
66
68
|
incremental
|
|
67
69
|
end
|
|
68
70
|
|
|
71
|
+
def suggest_tests?
|
|
72
|
+
suggest_tests
|
|
73
|
+
end
|
|
74
|
+
|
|
69
75
|
def self.file_options
|
|
70
76
|
CONFIG_FILES.each do |path|
|
|
71
77
|
next unless File.exist?(path)
|
|
@@ -104,6 +110,9 @@ module Evilution
|
|
|
104
110
|
|
|
105
111
|
# Stop after N surviving mutants (default: disabled)
|
|
106
112
|
# fail_fast: 1
|
|
113
|
+
|
|
114
|
+
# Generate concrete RSpec test code in suggestions (default: false)
|
|
115
|
+
# suggest_tests: false
|
|
107
116
|
YAML
|
|
108
117
|
end
|
|
109
118
|
|
|
@@ -134,6 +143,7 @@ module Evilution
|
|
|
134
143
|
@baseline = merged[:baseline]
|
|
135
144
|
@isolation = validate_isolation(merged[:isolation])
|
|
136
145
|
@incremental = merged[:incremental]
|
|
146
|
+
@suggest_tests = merged[:suggest_tests]
|
|
137
147
|
@line_ranges = merged[:line_ranges] || {}
|
|
138
148
|
@spec_files = Array(merged[:spec_files])
|
|
139
149
|
end
|
|
@@ -8,9 +8,10 @@ require_relative "../reporter/json"
|
|
|
8
8
|
|
|
9
9
|
module Evilution
|
|
10
10
|
module MCP
|
|
11
|
-
class MutateTool < ::MCP::Tool
|
|
11
|
+
class MutateTool < ::MCP::Tool
|
|
12
12
|
tool_name "evilution-mutate"
|
|
13
|
-
description "Run mutation testing on Ruby source files"
|
|
13
|
+
description "Run mutation testing on Ruby source files. " \
|
|
14
|
+
"Use suggest_tests: true to get concrete RSpec test code for surviving mutants."
|
|
14
15
|
input_schema(
|
|
15
16
|
properties: {
|
|
16
17
|
files: {
|
|
@@ -39,6 +40,11 @@ module Evilution
|
|
|
39
40
|
items: { type: "string" },
|
|
40
41
|
description: "Spec files to run (overrides auto-detection)"
|
|
41
42
|
},
|
|
43
|
+
suggest_tests: {
|
|
44
|
+
type: "boolean",
|
|
45
|
+
description: "When true, suggestions for survived mutants include concrete RSpec test code " \
|
|
46
|
+
"instead of static description text (default: false)"
|
|
47
|
+
},
|
|
42
48
|
verbosity: {
|
|
43
49
|
type: "string",
|
|
44
50
|
enum: %w[full summary minimal],
|
|
@@ -50,9 +56,12 @@ module Evilution
|
|
|
50
56
|
)
|
|
51
57
|
|
|
52
58
|
class << self
|
|
53
|
-
|
|
59
|
+
# rubocop:disable Lint/UnusedMethodArgument,Metrics/ParameterLists
|
|
60
|
+
def call(server_context:, files: [], target: nil, timeout: nil, jobs: nil,
|
|
61
|
+
fail_fast: nil, spec: nil, suggest_tests: nil, verbosity: nil)
|
|
54
62
|
parsed_files, line_ranges = parse_files(Array(files))
|
|
55
|
-
config_opts = build_config_opts(parsed_files, line_ranges, target, timeout, jobs, fail_fast, spec
|
|
63
|
+
config_opts = build_config_opts(parsed_files, line_ranges, target, timeout, jobs, fail_fast, spec,
|
|
64
|
+
suggest_tests)
|
|
56
65
|
config = Config.new(**config_opts)
|
|
57
66
|
runner = Runner.new(config: config)
|
|
58
67
|
summary = runner.call
|
|
@@ -64,6 +73,7 @@ module Evilution
|
|
|
64
73
|
error_payload = build_error_payload(e)
|
|
65
74
|
::MCP::Tool::Response.new([{ type: "text", text: ::JSON.generate(error_payload) }], error: true)
|
|
66
75
|
end
|
|
76
|
+
# rubocop:enable Lint/UnusedMethodArgument,Metrics/ParameterLists
|
|
67
77
|
|
|
68
78
|
VALID_VERBOSITIES = %w[full summary minimal].freeze
|
|
69
79
|
|
|
@@ -98,13 +108,14 @@ module Evilution
|
|
|
98
108
|
raise ParseError, "invalid line range: #{str.inspect}"
|
|
99
109
|
end
|
|
100
110
|
|
|
101
|
-
def build_config_opts(files, line_ranges, target, timeout, jobs, fail_fast, spec)
|
|
111
|
+
def build_config_opts(files, line_ranges, target, timeout, jobs, fail_fast, spec, suggest_tests)
|
|
102
112
|
opts = { target_files: files, line_ranges: line_ranges, format: :json, quiet: true, skip_config_file: true }
|
|
103
113
|
opts[:target] = target if target
|
|
104
114
|
opts[:timeout] = timeout if timeout
|
|
105
115
|
opts[:jobs] = jobs if jobs
|
|
106
116
|
opts[:fail_fast] = fail_fast if fail_fast
|
|
107
117
|
opts[:spec_files] = spec if spec
|
|
118
|
+
opts[:suggest_tests] = true if suggest_tests
|
|
108
119
|
opts
|
|
109
120
|
end
|
|
110
121
|
|
|
@@ -26,8 +26,263 @@ module Evilution
|
|
|
26
26
|
"argument_removal" => "Add a test that verifies the correct arguments are passed to this method call"
|
|
27
27
|
}.freeze
|
|
28
28
|
|
|
29
|
+
CONCRETE_TEMPLATES = {
|
|
30
|
+
"comparison_replacement" => lambda { |mutation|
|
|
31
|
+
method_name = parse_method_name(mutation.subject.name)
|
|
32
|
+
original_line, mutated_line = extract_diff_lines(mutation.diff)
|
|
33
|
+
<<~RSPEC.strip
|
|
34
|
+
# Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
|
|
35
|
+
# #{mutation.file_path}:#{mutation.line}
|
|
36
|
+
it 'returns the correct result at the comparison boundary in ##{method_name}' do
|
|
37
|
+
# Test with values where the original operator and mutated operator
|
|
38
|
+
# produce different results (e.g., equal values for > vs >=)
|
|
39
|
+
result = subject.#{method_name}(boundary_value)
|
|
40
|
+
expect(result).to eq(expected)
|
|
41
|
+
end
|
|
42
|
+
RSPEC
|
|
43
|
+
},
|
|
44
|
+
"arithmetic_replacement" => lambda { |mutation|
|
|
45
|
+
method_name = parse_method_name(mutation.subject.name)
|
|
46
|
+
original_line, mutated_line = extract_diff_lines(mutation.diff)
|
|
47
|
+
<<~RSPEC.strip
|
|
48
|
+
# Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
|
|
49
|
+
# #{mutation.file_path}:#{mutation.line}
|
|
50
|
+
it 'computes the correct arithmetic result in ##{method_name}' do
|
|
51
|
+
# Assert the exact numeric result, not just truthiness or sign
|
|
52
|
+
result = subject.#{method_name}(input_value)
|
|
53
|
+
expect(result).to eq(expected)
|
|
54
|
+
end
|
|
55
|
+
RSPEC
|
|
56
|
+
},
|
|
57
|
+
"boolean_operator_replacement" => lambda { |mutation|
|
|
58
|
+
method_name = parse_method_name(mutation.subject.name)
|
|
59
|
+
original_line, mutated_line = extract_diff_lines(mutation.diff)
|
|
60
|
+
<<~RSPEC.strip
|
|
61
|
+
# Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
|
|
62
|
+
# #{mutation.file_path}:#{mutation.line}
|
|
63
|
+
it 'returns the correct result when one condition is true and one is false in ##{method_name}' do
|
|
64
|
+
# Use inputs where only one operand is truthy to distinguish && from ||
|
|
65
|
+
result = subject.#{method_name}(input_value)
|
|
66
|
+
expect(result).to eq(expected)
|
|
67
|
+
end
|
|
68
|
+
RSPEC
|
|
69
|
+
},
|
|
70
|
+
"boolean_literal_replacement" => lambda { |mutation|
|
|
71
|
+
method_name = parse_method_name(mutation.subject.name)
|
|
72
|
+
original_line, mutated_line = extract_diff_lines(mutation.diff)
|
|
73
|
+
<<~RSPEC.strip
|
|
74
|
+
# Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
|
|
75
|
+
# #{mutation.file_path}:#{mutation.line}
|
|
76
|
+
it 'returns the expected boolean value from ##{method_name}' do
|
|
77
|
+
# Assert the exact true/false/nil value, not just truthiness
|
|
78
|
+
result = subject.#{method_name}(input_value)
|
|
79
|
+
expect(result).to eq(expected)
|
|
80
|
+
end
|
|
81
|
+
RSPEC
|
|
82
|
+
},
|
|
83
|
+
"negation_insertion" => lambda { |mutation|
|
|
84
|
+
method_name = parse_method_name(mutation.subject.name)
|
|
85
|
+
original_line, mutated_line = extract_diff_lines(mutation.diff)
|
|
86
|
+
<<~RSPEC.strip
|
|
87
|
+
# Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
|
|
88
|
+
# #{mutation.file_path}:#{mutation.line}
|
|
89
|
+
it 'returns the correct boolean from the predicate in ##{method_name}' do
|
|
90
|
+
# Assert the exact true/false result, not just truthiness
|
|
91
|
+
result = subject.#{method_name}(input_value)
|
|
92
|
+
expect(result).to eq(true).or eq(false)
|
|
93
|
+
end
|
|
94
|
+
RSPEC
|
|
95
|
+
},
|
|
96
|
+
"integer_literal" => lambda { |mutation|
|
|
97
|
+
method_name = parse_method_name(mutation.subject.name)
|
|
98
|
+
original_line, mutated_line = extract_diff_lines(mutation.diff)
|
|
99
|
+
<<~RSPEC.strip
|
|
100
|
+
# Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
|
|
101
|
+
# #{mutation.file_path}:#{mutation.line}
|
|
102
|
+
it 'returns the exact integer value from ##{method_name}' do
|
|
103
|
+
# Assert the exact numeric value, not just > 0 or truthy
|
|
104
|
+
result = subject.#{method_name}(input_value)
|
|
105
|
+
expect(result).to eq(expected)
|
|
106
|
+
end
|
|
107
|
+
RSPEC
|
|
108
|
+
},
|
|
109
|
+
"float_literal" => lambda { |mutation|
|
|
110
|
+
method_name = parse_method_name(mutation.subject.name)
|
|
111
|
+
original_line, mutated_line = extract_diff_lines(mutation.diff)
|
|
112
|
+
<<~RSPEC.strip
|
|
113
|
+
# Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
|
|
114
|
+
# #{mutation.file_path}:#{mutation.line}
|
|
115
|
+
it 'returns the exact float value from ##{method_name}' do
|
|
116
|
+
# Assert the exact floating-point result
|
|
117
|
+
result = subject.#{method_name}(input_value)
|
|
118
|
+
expect(result).to eq(expected)
|
|
119
|
+
end
|
|
120
|
+
RSPEC
|
|
121
|
+
},
|
|
122
|
+
"string_literal" => lambda { |mutation|
|
|
123
|
+
method_name = parse_method_name(mutation.subject.name)
|
|
124
|
+
original_line, mutated_line = extract_diff_lines(mutation.diff)
|
|
125
|
+
<<~RSPEC.strip
|
|
126
|
+
# Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
|
|
127
|
+
# #{mutation.file_path}:#{mutation.line}
|
|
128
|
+
it 'returns the exact string content from ##{method_name}' do
|
|
129
|
+
# Assert the exact string value, not just presence or non-empty
|
|
130
|
+
result = subject.#{method_name}(input_value)
|
|
131
|
+
expect(result).to eq(expected)
|
|
132
|
+
end
|
|
133
|
+
RSPEC
|
|
134
|
+
},
|
|
135
|
+
"symbol_literal" => lambda { |mutation|
|
|
136
|
+
method_name = parse_method_name(mutation.subject.name)
|
|
137
|
+
original_line, mutated_line = extract_diff_lines(mutation.diff)
|
|
138
|
+
<<~RSPEC.strip
|
|
139
|
+
# Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
|
|
140
|
+
# #{mutation.file_path}:#{mutation.line}
|
|
141
|
+
it 'returns the exact symbol from ##{method_name}' do
|
|
142
|
+
# Assert the exact symbol value, not just that it is a Symbol
|
|
143
|
+
result = subject.#{method_name}(input_value)
|
|
144
|
+
expect(result).to eq(expected)
|
|
145
|
+
end
|
|
146
|
+
RSPEC
|
|
147
|
+
},
|
|
148
|
+
"array_literal" => lambda { |mutation|
|
|
149
|
+
method_name = parse_method_name(mutation.subject.name)
|
|
150
|
+
original_line, mutated_line = extract_diff_lines(mutation.diff)
|
|
151
|
+
<<~RSPEC.strip
|
|
152
|
+
# Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
|
|
153
|
+
# #{mutation.file_path}:#{mutation.line}
|
|
154
|
+
it 'returns the expected array contents from ##{method_name}' do
|
|
155
|
+
# Assert the exact array elements, not just non-empty or truthy
|
|
156
|
+
result = subject.#{method_name}(input_value)
|
|
157
|
+
expect(result).to eq(expected)
|
|
158
|
+
end
|
|
159
|
+
RSPEC
|
|
160
|
+
},
|
|
161
|
+
"hash_literal" => lambda { |mutation|
|
|
162
|
+
method_name = parse_method_name(mutation.subject.name)
|
|
163
|
+
original_line, mutated_line = extract_diff_lines(mutation.diff)
|
|
164
|
+
<<~RSPEC.strip
|
|
165
|
+
# Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
|
|
166
|
+
# #{mutation.file_path}:#{mutation.line}
|
|
167
|
+
it 'returns the expected hash contents from ##{method_name}' do
|
|
168
|
+
# Assert the exact keys and values, not just non-empty or truthy
|
|
169
|
+
result = subject.#{method_name}(input_value)
|
|
170
|
+
expect(result).to eq(expected)
|
|
171
|
+
end
|
|
172
|
+
RSPEC
|
|
173
|
+
},
|
|
174
|
+
"collection_replacement" => lambda { |mutation|
|
|
175
|
+
method_name = parse_method_name(mutation.subject.name)
|
|
176
|
+
original_line, mutated_line = extract_diff_lines(mutation.diff)
|
|
177
|
+
<<~RSPEC.strip
|
|
178
|
+
# Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
|
|
179
|
+
# #{mutation.file_path}:#{mutation.line}
|
|
180
|
+
it 'uses the return value of the collection operation in ##{method_name}' do
|
|
181
|
+
# Assert the return value of the collection method, not just side effects
|
|
182
|
+
result = subject.#{method_name}(input_value)
|
|
183
|
+
expect(result).to eq(expected)
|
|
184
|
+
end
|
|
185
|
+
RSPEC
|
|
186
|
+
},
|
|
187
|
+
"conditional_negation" => lambda { |mutation|
|
|
188
|
+
method_name = parse_method_name(mutation.subject.name)
|
|
189
|
+
original_line, mutated_line = extract_diff_lines(mutation.diff)
|
|
190
|
+
<<~RSPEC.strip
|
|
191
|
+
# Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
|
|
192
|
+
# #{mutation.file_path}:#{mutation.line}
|
|
193
|
+
it 'exercises both branches of the conditional in ##{method_name}' do
|
|
194
|
+
# Test with inputs that make the condition true AND false
|
|
195
|
+
result = subject.#{method_name}(input_value)
|
|
196
|
+
expect(result).to eq(expected)
|
|
197
|
+
end
|
|
198
|
+
RSPEC
|
|
199
|
+
},
|
|
200
|
+
"conditional_branch" => lambda { |mutation|
|
|
201
|
+
method_name = parse_method_name(mutation.subject.name)
|
|
202
|
+
original_line, mutated_line = extract_diff_lines(mutation.diff)
|
|
203
|
+
<<~RSPEC.strip
|
|
204
|
+
# Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
|
|
205
|
+
# #{mutation.file_path}:#{mutation.line}
|
|
206
|
+
it 'exercises the removed branch of the conditional in ##{method_name}' do
|
|
207
|
+
# Test with inputs that trigger the branch removed by this mutation
|
|
208
|
+
result = subject.#{method_name}(input_value)
|
|
209
|
+
expect(result).to eq(expected)
|
|
210
|
+
end
|
|
211
|
+
RSPEC
|
|
212
|
+
},
|
|
213
|
+
"statement_deletion" => lambda { |mutation|
|
|
214
|
+
method_name = parse_method_name(mutation.subject.name)
|
|
215
|
+
original_line, _mutated_line = extract_diff_lines(mutation.diff)
|
|
216
|
+
<<~RSPEC.strip
|
|
217
|
+
# Mutation: deleted `#{original_line}` in #{mutation.subject.name}
|
|
218
|
+
# #{mutation.file_path}:#{mutation.line}
|
|
219
|
+
it 'depends on the side effect of the deleted statement in ##{method_name}' do
|
|
220
|
+
# Assert a side effect or return value that changes when this statement is removed
|
|
221
|
+
subject.#{method_name}(input_value)
|
|
222
|
+
expect(observable_side_effect).to eq(expected)
|
|
223
|
+
end
|
|
224
|
+
RSPEC
|
|
225
|
+
},
|
|
226
|
+
"method_body_replacement" => lambda { |mutation|
|
|
227
|
+
method_name = parse_method_name(mutation.subject.name)
|
|
228
|
+
original_line, mutated_line = extract_diff_lines(mutation.diff)
|
|
229
|
+
<<~RSPEC.strip
|
|
230
|
+
# Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
|
|
231
|
+
# #{mutation.file_path}:#{mutation.line}
|
|
232
|
+
it 'verifies the return value or side effects of ##{method_name}' do
|
|
233
|
+
# Assert the method produces a meaningful result, not just nil
|
|
234
|
+
result = subject.#{method_name}(input_value)
|
|
235
|
+
expect(result).to eq(expected)
|
|
236
|
+
end
|
|
237
|
+
RSPEC
|
|
238
|
+
},
|
|
239
|
+
"return_value_removal" => lambda { |mutation|
|
|
240
|
+
method_name = parse_method_name(mutation.subject.name)
|
|
241
|
+
original_line, mutated_line = extract_diff_lines(mutation.diff)
|
|
242
|
+
<<~RSPEC.strip
|
|
243
|
+
# Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
|
|
244
|
+
# #{mutation.file_path}:#{mutation.line}
|
|
245
|
+
it 'uses the return value of ##{method_name}' do
|
|
246
|
+
# Assert the caller depends on the return value, not just side effects
|
|
247
|
+
result = subject.#{method_name}(input_value)
|
|
248
|
+
expect(result).to eq(expected)
|
|
249
|
+
end
|
|
250
|
+
RSPEC
|
|
251
|
+
},
|
|
252
|
+
"method_call_removal" => lambda { |mutation|
|
|
253
|
+
method_name = parse_method_name(mutation.subject.name)
|
|
254
|
+
original_line, mutated_line = extract_diff_lines(mutation.diff)
|
|
255
|
+
<<~RSPEC.strip
|
|
256
|
+
# Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
|
|
257
|
+
# #{mutation.file_path}:#{mutation.line}
|
|
258
|
+
it 'depends on the return value or side effect of the call in ##{method_name}' do
|
|
259
|
+
# Assert the method call's effect is observable
|
|
260
|
+
result = subject.#{method_name}(input_value)
|
|
261
|
+
expect(result).to eq(expected)
|
|
262
|
+
end
|
|
263
|
+
RSPEC
|
|
264
|
+
},
|
|
265
|
+
"nil_replacement" => lambda { |mutation|
|
|
266
|
+
method_name = parse_method_name(mutation.subject.name)
|
|
267
|
+
original_line, mutated_line = extract_diff_lines(mutation.diff)
|
|
268
|
+
<<~RSPEC.strip
|
|
269
|
+
# Mutation: changed `#{original_line}` to `#{mutated_line}` in #{mutation.subject.name}
|
|
270
|
+
# #{mutation.file_path}:#{mutation.line}
|
|
271
|
+
it 'asserts the nil return value from ##{method_name}' do
|
|
272
|
+
# Assert the method returns nil, not a substituted value
|
|
273
|
+
result = subject.#{method_name}(input_value)
|
|
274
|
+
expect(result).to be_nil
|
|
275
|
+
end
|
|
276
|
+
RSPEC
|
|
277
|
+
}
|
|
278
|
+
}.freeze
|
|
279
|
+
|
|
29
280
|
DEFAULT_SUGGESTION = "Add a more specific test that detects this mutation"
|
|
30
281
|
|
|
282
|
+
def initialize(suggest_tests: false)
|
|
283
|
+
@suggest_tests = suggest_tests
|
|
284
|
+
end
|
|
285
|
+
|
|
31
286
|
# Generate suggestions for survived mutations.
|
|
32
287
|
#
|
|
33
288
|
# @param summary [Result::Summary]
|
|
@@ -46,8 +301,26 @@ module Evilution
|
|
|
46
301
|
# @param mutation [Mutation]
|
|
47
302
|
# @return [String]
|
|
48
303
|
def suggestion_for(mutation)
|
|
304
|
+
if @suggest_tests
|
|
305
|
+
concrete = CONCRETE_TEMPLATES[mutation.operator_name]
|
|
306
|
+
return concrete.call(mutation) if concrete
|
|
307
|
+
end
|
|
308
|
+
|
|
49
309
|
TEMPLATES.fetch(mutation.operator_name, DEFAULT_SUGGESTION)
|
|
50
310
|
end
|
|
311
|
+
|
|
312
|
+
class << self
|
|
313
|
+
def parse_method_name(subject_name)
|
|
314
|
+
subject_name.split(/[#.]/).last
|
|
315
|
+
end
|
|
316
|
+
|
|
317
|
+
def extract_diff_lines(diff)
|
|
318
|
+
lines = diff.split("\n")
|
|
319
|
+
original = lines.find { |l| l.start_with?("- ") }
|
|
320
|
+
mutated = lines.find { |l| l.start_with?("+ ") }
|
|
321
|
+
[original&.sub(/^- /, "")&.strip, mutated&.sub(/^\+ /, "")&.strip]
|
|
322
|
+
end
|
|
323
|
+
end
|
|
51
324
|
end
|
|
52
325
|
end
|
|
53
326
|
end
|
data/lib/evilution/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: evilution
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.12.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Denis Kiselev
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-03-
|
|
11
|
+
date: 2026-03-22 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: diff-lcs
|