evilution 0.5.0 → 0.6.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 02de08018abc9539f569f074024674e60264f525d5693de8931bfc4e61dbe0fe
4
- data.tar.gz: 3aab82bebf9a9f0d0732260e7852f72854b6aa774f548eff764127833af27445
3
+ metadata.gz: 90bb10673d940d85a42206ae1b20de288321dfb4f61802216dc4e9b68175f46f
4
+ data.tar.gz: a769255c8fd242d3432e811d36655eec1dae0ac6ef3c7288c0d68fe2c390182c
5
5
  SHA512:
6
- metadata.gz: a3051426f0e7469408fc04850369a7df0022119b30a2cb56ea85a61e61caf239ec6e8b33bb59870c960c85492e2b32dca8a95015cb77778393038dfc4478d5bc
7
- data.tar.gz: 252e15324dc314484fa3c136d25239856a39916da3af47fb5f5a399a5048804bdecbab54ac11f8c650a31033d485dc75f1c23132b2dddb6e8bdae59c31f5a6ec
6
+ metadata.gz: 3c06ec7f6e81c3f8427b4021dddd298d3111827896e0eae72ba1d764e076523d4ce63aac83438705f559199534b4408aac5683aeddf2c8566daa7ffd20015d88
7
+ data.tar.gz: cca406c93807c560cc70c7a5183c14d0c15fe139e12fe35f733341d8db839f28e74a7b34557ad5dabfdf3b7cbb34a76f8a954e30e4b0d91d0a9b5fb60ffed04f
@@ -1 +1 @@
1
- 1773589314
1
+ 1773676731
data/.beads/issues.jsonl CHANGED
@@ -36,9 +36,9 @@
36
36
  {"id":"EV-24","title":"Epic: JSON Output Improvements","description":"Make JSON output fully machine-parseable in all scenarios, including errors. Add diagnostic fields that help agents debug failures.","status":"closed","priority":1,"issue_type":"feature","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-10T06:17:37.450686472+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-16T11:15:24.900944562+07:00","closed_at":"2026-03-16T11:15:24.900944562+07:00","close_reason":"All children complete: structured errors, test_command in JSON, noise suppression","dependencies":[{"issue_id":"EV-24","depends_on_id":"EV-25","type":"blocks","created_at":"0001-01-01T00:00:00Z"},{"issue_id":"EV-24","depends_on_id":"EV-26","type":"blocks","created_at":"0001-01-01T00:00:00Z"},{"issue_id":"EV-24","depends_on_id":"EV-40","type":"blocks","created_at":"0001-01-01T00:00:00Z"}]}
37
37
  {"id":"EV-25","title":"Structured error responses in JSON mode","description":"When --format json is used and exit code is 2 (error), output a JSON object with error details instead of unstructured stderr text. Schema: { \"error\": { \"type\": \"config_error|parse_error|runtime_error\", \"message\": \"...\", \"file\": \"...\" } }. Agents currently have to regex-parse stderr which is fragile.","status":"closed","priority":1,"issue_type":"feature","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-10T06:17:38.283715502+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-15T22:41:54.370789377+07:00","closed_at":"2026-03-15T22:41:54.370789377+07:00","close_reason":"Merged PR #74 — structured JSON error output in CLI"}
38
38
  {"id":"EV-26","title":"Include test command in mutation result JSON","description":"Add a 'test_command' field to each mutation result in JSON output showing the exact command that was run to test that mutation. Helps agents debug when a mutation errors out or times out.","status":"closed","priority":3,"issue_type":"feature","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-10T06:17:39.227881462+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-16T10:11:01.311631114+07:00","closed_at":"2026-03-16T10:11:01.311631114+07:00","close_reason":"Already merged and released in v0.4.0"}
39
- {"id":"EV-27","title":"Epic: Workflow Integration","description":"Make evilution easier to invoke from AI agent toolchains — MCP server for direct tool calls, stdin mode for piping.","status":"open","priority":2,"issue_type":"feature","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-10T06:17:43.883767452+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-10T06:17:43.883767452+07:00","dependencies":[{"issue_id":"EV-27","depends_on_id":"EV-28","type":"blocks","created_at":"0001-01-01T00:00:00Z"},{"issue_id":"EV-27","depends_on_id":"EV-29","type":"blocks","created_at":"0001-01-01T00:00:00Z"}]}
40
- {"id":"EV-28","title":"MCP server for direct tool invocation","description":"Implement a Model Context Protocol (MCP) server that exposes evilution as a tool. Agents could call evilution directly instead of shelling out and parsing output. The server should expose a 'mutate' tool that accepts target files, options, and returns structured results.","status":"open","priority":2,"issue_type":"feature","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-10T06:17:45.29866593+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-10T06:17:45.29866593+07:00"}
41
- {"id":"EV-29","title":"Add --stdin flag to accept file list from stdin","description":"Add a --stdin flag that reads target file paths (one per line) from stdin. Enables workflows like: git diff --name-only | evilution run --stdin --format json. Each line can include line-range syntax (e.g. lib/foo.rb:15-30).","status":"open","priority":3,"issue_type":"feature","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-10T06:17:46.306306092+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-10T06:17:46.306306092+07:00"}
39
+ {"id":"EV-27","title":"Epic: Workflow Integration","description":"Make evilution easier to invoke from AI agent toolchains — MCP server for direct tool calls, stdin mode for piping.","status":"closed","priority":2,"issue_type":"feature","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-10T06:17:43.883767452+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-16T22:59:12.241444308+07:00","closed_at":"2026-03-16T22:59:12.241444308+07:00","close_reason":"Both child issues done: MCP server (EV-28) and --stdin (EV-29)","dependencies":[{"issue_id":"EV-27","depends_on_id":"EV-28","type":"blocks","created_at":"0001-01-01T00:00:00Z"},{"issue_id":"EV-27","depends_on_id":"EV-29","type":"blocks","created_at":"0001-01-01T00:00:00Z"}]}
40
+ {"id":"EV-28","title":"MCP server for direct tool invocation","description":"Implement a Model Context Protocol (MCP) server that exposes evilution as a tool. Agents could call evilution directly instead of shelling out and parsing output. The server should expose a 'mutate' tool that accepts target files, options, and returns structured results.","status":"closed","priority":2,"issue_type":"feature","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-10T06:17:45.29866593+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-16T22:58:51.734461132+07:00","closed_at":"2026-03-16T22:58:51.734461132+07:00","close_reason":"PR #103 merged — MCP server with evilution-mutate tool via stdio transport"}
41
+ {"id":"EV-29","title":"Add --stdin flag to accept file list from stdin","description":"Add a --stdin flag that reads target file paths (one per line) from stdin. Enables workflows like: git diff --name-only | evilution run --stdin --format json. Each line can include line-range syntax (e.g. lib/foo.rb:15-30).","status":"closed","priority":3,"issue_type":"feature","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-10T06:17:46.306306092+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-16T18:03:28.998559073+07:00","closed_at":"2026-03-16T18:03:28.998559073+07:00","close_reason":"PR #92 merged — --stdin flag for piped file list workflows"}
42
42
  {"id":"EV-3","title":"Phase 2: Mutation Operators & CLI","description":"Implement remaining 17 mutation operators, build CLI with OptionParser, exe/evilution executable, human-readable reporter. Milestone: bundle exec evilution run lib/user.rb --format json","status":"closed","priority":2,"issue_type":"epic","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-02T00:05:00.492971295+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-02T11:21:32.168384165+07:00","closed_at":"2026-03-02T11:21:32.168384165+07:00","close_reason":"Phase 2 complete: all 18 operators, CLI, Reporter::CLI, Registry registration, executable","dependencies":[{"issue_id":"EV-3","depends_on_id":"EV-2","type":"blocks","created_at":"0001-01-01T00:00:00Z"}]}
43
43
  {"id":"EV-3.1","title":"Implement ArithmeticReplacement operator","description":"Targets CallNode where name is :+, :-, :*, :/, :%, :**. Replacements: + <-> -, * <-> /, % -> /, ** -> *. File: lib/evilution/mutator/operator/arithmetic_replacement.rb + spec.","status":"closed","priority":2,"issue_type":"task","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-02T00:06:13.025649082+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-02T11:06:10.922412304+07:00","closed_at":"2026-03-02T11:06:10.922412304+07:00","close_reason":"All 3 operators implemented with passing specs","dependencies":[{"issue_id":"EV-3.1","depends_on_id":"EV-3","type":"parent-child","created_at":"0001-01-01T00:00:00Z"},{"issue_id":"EV-3.1","depends_on_id":"EV-2.6","type":"blocks","created_at":"0001-01-01T00:00:00Z"}]}
44
44
  {"id":"EV-3.10","title":"Implement SymbolLiteral operator","description":"Targets SymbolNode. Mutation: :foo -> :__evilution_mutated__. File: lib/evilution/mutator/operator/symbol_literal.rb + spec.","status":"closed","priority":2,"issue_type":"task","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-02T00:06:13.935877816+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-02T11:13:36.541040613+07:00","closed_at":"2026-03-02T11:13:36.541040613+07:00","close_reason":"All 3 operators implemented with passing specs","dependencies":[{"issue_id":"EV-3.10","depends_on_id":"EV-3","type":"parent-child","created_at":"0001-01-01T00:00:00Z"},{"issue_id":"EV-3.10","depends_on_id":"EV-2.6","type":"blocks","created_at":"0001-01-01T00:00:00Z"}]}
@@ -86,6 +86,11 @@
86
86
  {"id":"EV-42","title":"Expand mutation operators (method call removal, receiver replacement, etc.)","description":"Currently 18 operators generating ~19 mutations vs mutant's 78 for the same method. Missing operators: method call removal, self receiver replacement, additional boolean logic mutations, nil substitutions. Higher operator count catches more subtle bugs and produces a more meaningful mutation score.","status":"open","priority":2,"issue_type":"feature","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-13T09:57:03.039944039+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-13T09:57:03.039944039+07:00"}
87
87
  {"id":"EV-43","title":"Fix --version flag (returns 'version unknown')","description":"Running 'evilution --version' returns 'version unknown' instead of the actual version. The 'evilution version' subcommand works, but --version as a flag does not. Users expect --version to work.","status":"closed","priority":1,"issue_type":"bug","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-13T09:57:05.048211823+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-16T10:31:01.045381336+07:00","closed_at":"2026-03-16T10:31:01.045381336+07:00","close_reason":"Fixed and merged"}
88
88
  {"id":"EV-44","title":"Re-introduce parallel execution","description":"Bring back parallel mutation execution. Consider whether two-level fork model is still right, temp-file isolation needs, and auto-detecting when parallelism is worthwhile. GH #35.","status":"closed","priority":2,"issue_type":"feature","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-16T10:15:42.050318104+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-16T16:26:30.341909415+07:00","closed_at":"2026-03-16T16:26:30.341909415+07:00","close_reason":"PR #90 merged — process-based parallel pool with --jobs flag"}
89
+ {"id":"EV-45","title":"Epic: Close mutation operator gap with mutant","description":"Track all missing mutation operators to approach mutant's ~78 mutations per method. Currently at ~19 with 18 operators. This is a long-term effort — prioritize high-value operators first.","status":"open","priority":4,"issue_type":"feature","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-16T21:50:07.597313227+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-16T21:50:07.597313227+07:00","dependencies":[{"issue_id":"EV-45","depends_on_id":"EV-46","type":"blocks","created_at":"0001-01-01T00:00:00Z"},{"issue_id":"EV-45","depends_on_id":"EV-47","type":"blocks","created_at":"0001-01-01T00:00:00Z"},{"issue_id":"EV-45","depends_on_id":"EV-48","type":"blocks","created_at":"0001-01-01T00:00:00Z"},{"issue_id":"EV-45","depends_on_id":"EV-49","type":"blocks","created_at":"0001-01-01T00:00:00Z"},{"issue_id":"EV-45","depends_on_id":"EV-50","type":"blocks","created_at":"0001-01-01T00:00:00Z"},{"issue_id":"EV-45","depends_on_id":"EV-51","type":"blocks","created_at":"0001-01-01T00:00:00Z"},{"issue_id":"EV-45","depends_on_id":"EV-52","type":"blocks","created_at":"0001-01-01T00:00:00Z"},{"issue_id":"EV-45","depends_on_id":"EV-53","type":"blocks","created_at":"0001-01-01T00:00:00Z"},{"issue_id":"EV-45","depends_on_id":"EV-54","type":"blocks","created_at":"0001-01-01T00:00:00Z"},{"issue_id":"EV-45","depends_on_id":"EV-42","type":"blocks","created_at":"0001-01-01T00:00:00Z"}]}
90
+ {"id":"EV-46","title":"Operator: MethodCallRemoval","description":"Remove method call, keep receiver. e.g. obj.foo(x) → obj. High bug-finding value — catches untested side effects.","status":"closed","priority":3,"issue_type":"feature","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-16T21:50:22.890467537+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-17T09:29:15.588415782+07:00","closed_at":"2026-03-17T09:29:15.588415782+07:00","close_reason":"PR merged — MethodCallRemoval operator (19th operator)"}
91
+ {"id":"EV-47","title":"Operator: BlockRemoval","description":"Remove block from method call. e.g. items.map { |x| x * 2 } → items.map. Catches untested block logic.","status":"open","priority":3,"issue_type":"feature","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-16T21:50:23.067266925+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-16T21:50:23.067266925+07:00"}
92
+ {"id":"EV-48","title":"Operator: RangeReplacement","description":"Swap inclusive/exclusive ranges. e.g. 1..10 → 1...10 and vice versa. Trivial to implement.","status":"open","priority":3,"issue_type":"feature","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-16T21:50:23.172934653+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-16T21:50:23.172934653+07:00"}
93
+ {"id":"EV-49","title":"Operator: RegexpMutation","description":"Replace regexp with always-failing pattern. e.g. /pattern/ → /a\\A/. Catches untested regex matching.","status":"open","priority":3,"issue_type":"feature","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-16T21:50:23.274989762+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-16T21:50:23.274989762+07:00"}
89
94
  {"id":"EV-5","title":"Phase 4: Polish","description":"Suggestion generator, .evilution.yml config file loading, evilution init subcommand, error handling, README","status":"closed","priority":2,"issue_type":"epic","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-02T00:05:03.497091872+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-02T11:54:32.782883746+07:00","closed_at":"2026-03-02T11:54:32.782883746+07:00","close_reason":"Closed","dependencies":[{"issue_id":"EV-5","depends_on_id":"EV-4","type":"blocks","created_at":"0001-01-01T00:00:00Z"}]}
90
95
  {"id":"EV-5.1","title":"Implement Evilution::Reporter::Suggestion","description":"Generates actionable fix suggestions per surviving mutant. Each operator type has a suggestion template. E.g., ComparisonReplacement >= -> > suggests 'Add a test for the boundary case where value equals exactly [threshold]'. File: lib/evilution/reporter/suggestion.rb + spec.","status":"closed","priority":2,"issue_type":"task","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-02T00:06:58.038411009+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-02T11:54:21.518470747+07:00","closed_at":"2026-03-02T11:54:21.518470747+07:00","close_reason":"Closed","dependencies":[{"issue_id":"EV-5.1","depends_on_id":"EV-5","type":"parent-child","created_at":"0001-01-01T00:00:00Z"},{"issue_id":"EV-5.1","depends_on_id":"EV-2.8","type":"blocks","created_at":"0001-01-01T00:00:00Z"},{"issue_id":"EV-5.1","depends_on_id":"EV-3.21","type":"blocks","created_at":"0001-01-01T00:00:00Z"}]}
91
96
  {"id":"EV-5.2","title":"Implement .evilution.yml config file loading","description":"Load config from .evilution.yml / config/evilution.yml if present. YAML keys map to Config fields. CLI flags override file values. File: update lib/evilution/config.rb + spec.","status":"closed","priority":2,"issue_type":"task","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-02T00:06:58.142538793+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-02T11:54:21.518473527+07:00","closed_at":"2026-03-02T11:54:21.518473527+07:00","close_reason":"Closed","dependencies":[{"issue_id":"EV-5.2","depends_on_id":"EV-5","type":"parent-child","created_at":"0001-01-01T00:00:00Z"},{"issue_id":"EV-5.2","depends_on_id":"EV-2.1","type":"blocks","created_at":"0001-01-01T00:00:00Z"}]}
@@ -93,6 +98,11 @@
93
98
  {"id":"EV-5.4","title":"Add error handling and edge cases","description":"Handle: no test files found, no subjects found, fork failures, marshal errors, invalid config, files that fail to parse. Use Evilution::Error subclasses. Ensure clean exit codes (2 for tool errors).","status":"closed","priority":2,"issue_type":"task","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-02T00:06:58.353265504+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-02T11:54:25.59628785+07:00","closed_at":"2026-03-02T11:54:25.59628785+07:00","close_reason":"Closed","dependencies":[{"issue_id":"EV-5.4","depends_on_id":"EV-5","type":"parent-child","created_at":"0001-01-01T00:00:00Z"},{"issue_id":"EV-5.4","depends_on_id":"EV-2.12","type":"blocks","created_at":"0001-01-01T00:00:00Z"}]}
94
99
  {"id":"EV-5.5","title":"Write README.md","description":"Replace placeholder README with: gem description, installation, quick start, CLI usage, configuration, output formats (JSON schema), operator list, comparison with mutant, contributing guide, license.","status":"closed","priority":2,"issue_type":"task","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-02T00:06:58.454635118+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-02T11:54:29.226280116+07:00","closed_at":"2026-03-02T11:54:29.226280116+07:00","close_reason":"Closed","dependencies":[{"issue_id":"EV-5.5","depends_on_id":"EV-5","type":"parent-child","created_at":"0001-01-01T00:00:00Z"},{"issue_id":"EV-5.5","depends_on_id":"EV-4.9","type":"blocks","created_at":"0001-01-01T00:00:00Z"},{"issue_id":"EV-5.5","depends_on_id":"EV-5.1","type":"blocks","created_at":"0001-01-01T00:00:00Z"}]}
95
100
  {"id":"EV-5.6","title":"Update CHANGELOG.md for v0.1.0","description":"Document all features in the initial release: operator list, RSpec integration, JSON/CLI output, parallel execution, coverage-based selection, diff-based targeting.","status":"closed","priority":2,"issue_type":"task","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-02T00:06:58.571499415+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-02T11:54:29.22634981+07:00","closed_at":"2026-03-02T11:54:29.22634981+07:00","close_reason":"Closed","dependencies":[{"issue_id":"EV-5.6","depends_on_id":"EV-5","type":"parent-child","created_at":"0001-01-01T00:00:00Z"},{"issue_id":"EV-5.6","depends_on_id":"EV-5.5","type":"blocks","created_at":"0001-01-01T00:00:00Z"}]}
101
+ {"id":"EV-50","title":"Operator: ConditionalFlip","description":"Flip if/unless. e.g. if cond → unless cond. Catches single-branch conditional testing.","status":"open","priority":3,"issue_type":"feature","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-16T21:50:23.379431887+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-16T21:50:23.379431887+07:00"}
102
+ {"id":"EV-51","title":"Operator: SendMutation","description":"Replace known method families. e.g. flat_map → map, public_send → send, gsub → sub. Catches untested method semantics.","status":"open","priority":4,"issue_type":"feature","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-16T21:50:23.483414117+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-16T21:50:23.483414117+07:00"}
103
+ {"id":"EV-52","title":"Operator: ArgumentRemoval","description":"Remove individual arguments from method calls. e.g. foo(a, b) → foo(a). More complex AST handling.","status":"open","priority":4,"issue_type":"feature","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-16T21:50:23.587291445+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-16T21:50:23.587291445+07:00"}
104
+ {"id":"EV-53","title":"Operator: ReceiverReplacement","description":"Drop explicit self receiver. e.g. self.foo → foo. Low bug-finding value.","status":"open","priority":4,"issue_type":"feature","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-16T21:50:23.692244528+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-16T21:50:23.692244528+07:00"}
105
+ {"id":"EV-54","title":"Deepen existing operators with more replacement variants","description":"Existing operators generate limited variants per node. E.g. integer 5 only produces 0 and 1, but could also produce -1 and nil. Expanding variant sets across all 18 operators would significantly increase mutation coverage.","status":"open","priority":4,"issue_type":"feature","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-16T21:50:23.797359784+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-16T21:50:23.797359784+07:00"}
96
106
  {"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"}
97
107
  {"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"}
98
108
  {"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."}
data/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.6.0] - 2026-03-17
4
+
5
+ ### Added
6
+
7
+ - **`--stdin` flag** — read target file paths from stdin (one per line), enabling piped workflows like `git diff --name-only | evilution run --stdin --format json`; supports line-range syntax (e.g. `lib/foo.rb:15-30`); errors if combined with positional file arguments
8
+ - **MCP server** (`evilution mcp`) — Model Context Protocol server for direct AI agent integration via stdio; exposes an `evilution-mutate` tool that accepts target files, options, and returns structured JSON results
9
+ - **MethodCallRemoval operator** — new mutation operator that removes method calls while keeping the receiver (e.g. `obj.foo(x)` → `obj`); catches untested side effects and return values
10
+
3
11
  ## [0.5.0] - 2026-03-16
4
12
 
5
13
  ### Added
data/lib/evilution/cli.rb CHANGED
@@ -8,14 +8,16 @@ require_relative "runner"
8
8
 
9
9
  module Evilution
10
10
  class CLI
11
- def initialize(argv)
11
+ def initialize(argv, stdin: $stdin)
12
12
  @options = {}
13
13
  @command = :run
14
+ @stdin = stdin
14
15
  argv = argv.dup
15
16
  argv = extract_command(argv)
16
17
  argv = preprocess_flags(argv)
17
18
  raw_args = build_option_parser.parse!(argv)
18
19
  @files, @line_ranges = parse_file_args(raw_args)
20
+ read_stdin_files if @options.delete(:stdin) && @command == :run
19
21
  end
20
22
 
21
23
  def call
@@ -25,6 +27,8 @@ module Evilution
25
27
  0
26
28
  when :init
27
29
  run_init
30
+ when :mcp
31
+ run_mcp
28
32
  when :run
29
33
  run_mutations
30
34
  end
@@ -40,6 +44,9 @@ module Evilution
40
44
  when "init"
41
45
  @command = :init
42
46
  argv.shift
47
+ when "mcp"
48
+ @command = :mcp
49
+ argv.shift
43
50
  when "run"
44
51
  argv.shift
45
52
  end
@@ -85,7 +92,7 @@ module Evilution
85
92
  opts.separator ""
86
93
  opts.separator "Line-range targeting: lib/foo.rb:15-30, lib/foo.rb:15, lib/foo.rb:15-"
87
94
  opts.separator ""
88
- opts.separator "Commands: run (default), init, version"
95
+ opts.separator "Commands: run (default), init, mcp, version"
89
96
  opts.separator ""
90
97
  opts.separator "Options:"
91
98
  end
@@ -108,6 +115,7 @@ module Evilution
108
115
  end
109
116
  opts.on("--fail-fast", "Stop after N surviving mutants " \
110
117
  "(default: disabled; if provided without N, uses 1; use --fail-fast=N)") { @options[:fail_fast] ||= 1 }
118
+ opts.on("--stdin", "Read target file paths from stdin (one per line)") { @options[:stdin] = true }
111
119
  opts.on("-v", "--verbose", "Verbose output") { @options[:verbose] = true }
112
120
  opts.on("-q", "--quiet", "Suppress output") { @options[:quiet] = true }
113
121
  end
@@ -124,6 +132,28 @@ module Evilution
124
132
  0
125
133
  end
126
134
 
135
+ def run_mcp
136
+ require_relative "mcp/server"
137
+ server = MCP::Server.build
138
+ transport = ::MCP::Server::Transports::StdioTransport.new(server)
139
+ transport.open
140
+ 0
141
+ end
142
+
143
+ def read_stdin_files
144
+ @stdin_error = "--stdin cannot be combined with positional file arguments" unless @files.empty?
145
+ return if @stdin_error
146
+
147
+ lines = []
148
+ @stdin.each_line do |line|
149
+ line = line.strip
150
+ lines << line unless line.empty?
151
+ end
152
+ stdin_files, stdin_ranges = parse_file_args(lines)
153
+ @files = stdin_files
154
+ @line_ranges = @line_ranges.merge(stdin_ranges)
155
+ end
156
+
127
157
  def parse_file_args(raw_args)
128
158
  files = []
129
159
  ranges = {}
@@ -152,6 +182,8 @@ module Evilution
152
182
  end
153
183
 
154
184
  def run_mutations
185
+ raise ConfigError, @stdin_error if @stdin_error
186
+
155
187
  file_options = Config.file_options
156
188
  config = Config.new(**@options, target_files: @files, line_ranges: @line_ranges)
157
189
  runner = Runner.new(config: config)
@@ -0,0 +1,115 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+ require "mcp"
5
+ require_relative "../config"
6
+ require_relative "../runner"
7
+ require_relative "../reporter/json"
8
+
9
+ module Evilution
10
+ module MCP
11
+ class MutateTool < ::MCP::Tool
12
+ tool_name "evilution-mutate"
13
+ description "Run mutation testing on Ruby source files"
14
+ input_schema(
15
+ properties: {
16
+ files: {
17
+ type: "array",
18
+ items: { type: "string" },
19
+ description: "Target files, supports line-range syntax (e.g. lib/foo.rb:15-30)"
20
+ },
21
+ target: {
22
+ type: "string",
23
+ description: "Only mutate the named method (e.g. Foo#bar)"
24
+ },
25
+ timeout: {
26
+ type: "integer",
27
+ description: "Per-mutation timeout in seconds (default: 30)"
28
+ },
29
+ jobs: {
30
+ type: "integer",
31
+ description: "Number of parallel workers (default: 1)"
32
+ },
33
+ fail_fast: {
34
+ type: "integer",
35
+ description: "Stop after N surviving mutants"
36
+ },
37
+ spec: {
38
+ type: "array",
39
+ items: { type: "string" },
40
+ description: "Spec files to run (overrides auto-detection)"
41
+ }
42
+ }
43
+ )
44
+
45
+ class << self
46
+ def call(server_context:, files: [], target: nil, timeout: nil, jobs: nil, fail_fast: nil, spec: nil) # rubocop:disable Lint/UnusedMethodArgument
47
+ parsed_files, line_ranges = parse_files(Array(files))
48
+ config_opts = build_config_opts(parsed_files, line_ranges, target, timeout, jobs, fail_fast, spec)
49
+ config = Config.new(**config_opts)
50
+ runner = Runner.new(config: config)
51
+ summary = runner.call
52
+ report = Reporter::JSON.new.call(summary)
53
+
54
+ ::MCP::Tool::Response.new([{ type: "text", text: report }])
55
+ rescue Evilution::Error => e
56
+ error_payload = build_error_payload(e)
57
+ ::MCP::Tool::Response.new([{ type: "text", text: ::JSON.generate(error_payload) }], error: true)
58
+ end
59
+
60
+ private
61
+
62
+ def parse_files(raw_files)
63
+ files = []
64
+ ranges = {}
65
+
66
+ raw_files.each do |arg|
67
+ file, range_str = arg.split(":", 2)
68
+ files << file
69
+ next unless range_str
70
+
71
+ ranges[file] = parse_line_range(range_str)
72
+ end
73
+
74
+ [files, ranges]
75
+ end
76
+
77
+ def parse_line_range(str)
78
+ if str.include?("-")
79
+ start_str, end_str = str.split("-", 2)
80
+ start_line = Integer(start_str)
81
+ end_line = end_str.empty? ? Float::INFINITY : Integer(end_str)
82
+ start_line..end_line
83
+ else
84
+ line = Integer(str)
85
+ line..line
86
+ end
87
+ rescue ArgumentError, TypeError
88
+ raise ParseError, "invalid line range: #{str.inspect}"
89
+ end
90
+
91
+ def build_config_opts(files, line_ranges, target, timeout, jobs, fail_fast, spec)
92
+ opts = { target_files: files, line_ranges: line_ranges, format: :json, quiet: true, skip_config_file: true }
93
+ opts[:target] = target if target
94
+ opts[:timeout] = timeout if timeout
95
+ opts[:jobs] = jobs if jobs
96
+ opts[:fail_fast] = fail_fast if fail_fast
97
+ opts[:spec_files] = spec if spec
98
+ opts
99
+ end
100
+
101
+ def build_error_payload(error)
102
+ error_type = case error
103
+ when ConfigError then "config_error"
104
+ when ParseError then "parse_error"
105
+ else "runtime_error"
106
+ end
107
+
108
+ payload = { type: error_type, message: error.message }
109
+ payload[:file] = error.file if error.file
110
+ { error: payload }
111
+ end
112
+ end
113
+ end
114
+ end
115
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "mcp"
4
+ require_relative "../version"
5
+ require_relative "mutate_tool"
6
+
7
+ module Evilution
8
+ module MCP
9
+ class Server
10
+ def self.build
11
+ ::MCP::Server.new(
12
+ name: "evilution",
13
+ version: Evilution::VERSION,
14
+ tools: [MutateTool]
15
+ )
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Evilution
4
+ module Mutator
5
+ module Operator
6
+ class MethodCallRemoval < Base
7
+ def visit_call_node(node)
8
+ if node.receiver
9
+ add_mutation(
10
+ offset: node.location.start_offset,
11
+ length: node.location.length,
12
+ replacement: node.receiver.slice,
13
+ node: node
14
+ )
15
+ end
16
+
17
+ super
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -23,7 +23,8 @@ module Evilution
23
23
  Operator::MethodBodyReplacement,
24
24
  Operator::NegationInsertion,
25
25
  Operator::ReturnValueRemoval,
26
- Operator::CollectionReplacement
26
+ Operator::CollectionReplacement,
27
+ Operator::MethodCallRemoval
27
28
  ].each { |op| registry.register(op) }
28
29
  registry
29
30
  end
@@ -21,7 +21,8 @@ module Evilution
21
21
  "method_body_replacement" => "Add a test that checks the method's return value or side effects",
22
22
  "negation_insertion" => "Add a test where the predicate result matters (not just truthiness)",
23
23
  "return_value_removal" => "Add a test that uses the return value of this method",
24
- "collection_replacement" => "Add a test that checks the return value of the collection operation, not just side effects"
24
+ "collection_replacement" => "Add a test that checks the return value of the collection operation, not just side effects",
25
+ "method_call_removal" => "Add a test that depends on the return value or side effect of this method call"
25
26
  }.freeze
26
27
 
27
28
  DEFAULT_SUGGESTION = "Add a more specific test that detects this mutation"
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Evilution
4
- VERSION = "0.5.0"
4
+ VERSION = "0.6.0"
5
5
  end
data/lib/evilution.rb CHANGED
@@ -25,6 +25,7 @@ require_relative "evilution/mutator/operator/statement_deletion"
25
25
  require_relative "evilution/mutator/operator/method_body_replacement"
26
26
  require_relative "evilution/mutator/operator/return_value_removal"
27
27
  require_relative "evilution/mutator/operator/collection_replacement"
28
+ require_relative "evilution/mutator/operator/method_call_removal"
28
29
  require_relative "evilution/mutator/registry"
29
30
  require_relative "evilution/isolation/fork"
30
31
  require_relative "evilution/parallel/pool"
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.5.0
4
+ version: 0.6.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-16 00:00:00.000000000 Z
11
+ date: 2026-03-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: diff-lcs
@@ -30,6 +30,26 @@ dependencies:
30
30
  - - "<"
31
31
  - !ruby/object:Gem::Version
32
32
  version: '3'
33
+ - !ruby/object:Gem::Dependency
34
+ name: mcp
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: '0.8'
40
+ - - "<"
41
+ - !ruby/object:Gem::Version
42
+ version: '2'
43
+ type: :runtime
44
+ prerelease: false
45
+ version_requirements: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - ">="
48
+ - !ruby/object:Gem::Version
49
+ version: '0.8'
50
+ - - "<"
51
+ - !ruby/object:Gem::Version
52
+ version: '2'
33
53
  description: Evilution is a mutation testing tool for Ruby. It validates test suite
34
54
  quality by making small code changes and checking if tests catch them. AI-agent-first
35
55
  design with JSON output, diff-based targeting, and coverage-based filtering.
@@ -70,6 +90,8 @@ files:
70
90
  - lib/evilution/integration/base.rb
71
91
  - lib/evilution/integration/rspec.rb
72
92
  - lib/evilution/isolation/fork.rb
93
+ - lib/evilution/mcp/mutate_tool.rb
94
+ - lib/evilution/mcp/server.rb
73
95
  - lib/evilution/mutation.rb
74
96
  - lib/evilution/mutator/base.rb
75
97
  - lib/evilution/mutator/operator/arithmetic_replacement.rb
@@ -84,6 +106,7 @@ files:
84
106
  - lib/evilution/mutator/operator/hash_literal.rb
85
107
  - lib/evilution/mutator/operator/integer_literal.rb
86
108
  - lib/evilution/mutator/operator/method_body_replacement.rb
109
+ - lib/evilution/mutator/operator/method_call_removal.rb
87
110
  - lib/evilution/mutator/operator/negation_insertion.rb
88
111
  - lib/evilution/mutator/operator/nil_replacement.rb
89
112
  - lib/evilution/mutator/operator/return_value_removal.rb