evilution 0.5.0 → 0.7.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 +44 -4
- data/CHANGELOG.md +26 -0
- data/README.md +51 -4
- data/Rakefile +2 -0
- data/lib/evilution/baseline.rb +100 -0
- data/lib/evilution/cli.rb +48 -2
- data/lib/evilution/config.rb +36 -15
- data/lib/evilution/isolation/fork.rb +5 -2
- data/lib/evilution/isolation/in_process.rb +78 -0
- data/lib/evilution/mcp/mutate_tool.rb +115 -0
- data/lib/evilution/mcp/server.rb +19 -0
- data/lib/evilution/memory/leak_check.rb +91 -0
- data/lib/evilution/memory.rb +40 -0
- data/lib/evilution/mutation.rb +15 -1
- data/lib/evilution/mutator/operator/argument_removal.rb +42 -0
- data/lib/evilution/mutator/operator/method_call_removal.rb +22 -0
- data/lib/evilution/mutator/registry.rb +3 -1
- data/lib/evilution/reporter/cli.rb +34 -12
- data/lib/evilution/reporter/json.rb +6 -0
- data/lib/evilution/reporter/suggestion.rb +3 -1
- data/lib/evilution/result/mutation_result.rb +11 -3
- data/lib/evilution/result/summary.rb +20 -1
- data/lib/evilution/runner.rb +178 -29
- data/lib/evilution/subject.rb +4 -1
- data/lib/evilution/version.rb +1 -1
- data/lib/evilution.rb +5 -0
- data/lib/tasks/memory_check.rake +9 -0
- data/script/memory_check +94 -0
- metadata +32 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c22da4a2f18a35a0947f708163505e560fef557fb50595cbb735676d220fb17c
|
|
4
|
+
data.tar.gz: a3427ee2af916eed4e59a73eb6431626e0089b7fb826136675e228ed712ddf08
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ebf55a8e9623bededf357bb684ff2a3d3a7e3af2e743e91d5d0875488118184cd1a2699905df5752f4784c8c7e6bef8c24f766430adf325850690ed0cd70ed45
|
|
7
|
+
data.tar.gz: c06b6ecece15c2695bc86f0baaed67217887777336abeb6635b263d3aa68bc17f4d2b68be826ccc4fa8ae9ae2794d167174b24e380675e5660c19b759bd54c1c
|
data/.beads/.migration-hint-ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
1773850606
|
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":"
|
|
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":"
|
|
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":"
|
|
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"}]}
|
|
@@ -62,7 +62,7 @@
|
|
|
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
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":"open","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-10T06:18:03.590579846+07:00","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":"open","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-10T06:18:05.964542284+07:00"}
|
|
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":"open","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-10T06:18:05.964542284+07:00","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"}]}
|
|
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"}
|
|
@@ -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":2,"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-17T10:14:41.815885279+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,7 +98,42 @@
|
|
|
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, permute, and replace individual method call arguments. e.g. foo(a, b) → foo(a), foo(b, a), foo(a, nil). This is the #1 cited gap vs mutant — where mutant catches the most bugs evilution misses. Includes: removing each argument individually, swapping argument order, replacing with nil.","status":"closed","priority":2,"issue_type":"feature","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-16T21:50:23.587291445+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-17T23:16:28.920737471+07:00","closed_at":"2026-03-17T23:16:28.920737471+07:00","close_reason":"Merged"}
|
|
104
|
+
{"id":"EV-53","title":"Operator: ReceiverReplacement","description":"Drop explicit self receiver. e.g. self.foo → foo. Low bug-finding value.","status":"open","priority":3,"issue_type":"feature","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-16T21:50:23.692244528+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-17T10:14:41.617278357+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":3,"issue_type":"feature","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-16T21:50:23.797359784+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-17T10:14:41.719145856+07:00"}
|
|
106
|
+
{"id":"EV-55","title":"Concrete suggestions: Comparison & Arithmetic operators","description":"Generate concrete RSpec it blocks for ComparisonReplacement and ArithmeticReplacement mutations. Test should assert exact return value at boundary conditions.","status":"open","priority":3,"issue_type":"feature","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-17T09:58:33.26716126+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-17T09:58:33.26716126+07:00"}
|
|
107
|
+
{"id":"EV-56","title":"Concrete suggestions: Boolean operators","description":"Generate concrete RSpec it blocks for BooleanOperatorReplacement, BooleanLiteralReplacement, and NegationInsertion mutations. Test should assert true/false explicitly.","status":"open","priority":3,"issue_type":"feature","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-17T09:58:33.370014645+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-17T09:58:33.370014645+07:00"}
|
|
108
|
+
{"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":"open","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-17T09:58:33.473990949+07:00"}
|
|
109
|
+
{"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":"open","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-17T09:58:33.577189348+07:00"}
|
|
110
|
+
{"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":"open","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-17T09:58:33.676644345+07:00"}
|
|
96
111
|
{"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"}
|
|
112
|
+
{"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":"open","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-17T09:58:33.777078104+07:00"}
|
|
113
|
+
{"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":"open","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-17T09:58:33.882878252+07:00"}
|
|
114
|
+
{"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"}
|
|
115
|
+
{"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"}
|
|
116
|
+
{"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":"open","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-17T10:15:03.858147999+07:00"}
|
|
117
|
+
{"id":"EV-65","title":"Incremental mode with result caching","description":"Cache mutation results and only re-run mutations on changed lines/methods. Big CI speed win for large codebases. Could key cache on file content hash + mutation fingerprint.","status":"open","priority":3,"issue_type":"feature","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-17T10:15:03.95132569+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-17T10:15:03.95132569+07:00"}
|
|
118
|
+
{"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":"open","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-17T10:15:04.04579177+07:00"}
|
|
119
|
+
{"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":"open","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-17T10:15:04.13552276+07:00"}
|
|
120
|
+
{"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":"open","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-17T10:15:04.226297798+07:00"}
|
|
121
|
+
{"id":"EV-69","title":"Minitest integration","description":"Add Minitest support alongside existing RSpec integration. Broader ecosystem support for non-RSpec projects.","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-17T10:15:04.313129974+07:00"}
|
|
97
122
|
{"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"}
|
|
123
|
+
{"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"]}
|
|
124
|
+
{"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"}]}
|
|
125
|
+
{"id":"EV-70.10","title":"Release Prism AST nodes after mutation generation","description":"Subject holds @node (Prism AST) which is only needed during registry.mutations_for(subject). After mutations are generated, the AST node is never used again but stays retained through the results chain. Clear or release the node reference after mutation generation.","status":"closed","priority":2,"issue_type":"bug","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-18T18:52:10.923467438+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-19T12:19:13.508955682+07:00","closed_at":"2026-03-19T12:19:13.508955682+07:00","close_reason":"Added release_node! to Subject, wired into Runner generate_mutations lazy pipeline to release Prism AST nodes after mutation generation.","external_ref":"gh-134","labels":["v0.7.0"],"dependencies":[{"issue_id":"EV-70.10","depends_on_id":"EV-70","type":"parent-child","created_at":"0001-01-01T00:00:00Z"}]}
|
|
126
|
+
{"id":"EV-70.11","title":"Reduce Marshal payload size in Parallel::Pool","description":"Pool workers Marshal.dump full MutationResult (including Mutation with source strings and Subject with AST) through pipes. Serialize only the minimal result data (status, duration, test_command, diff) to reduce memory pressure on both sides of the pipe.","status":"closed","priority":2,"issue_type":"bug","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-18T18:52:14.224229894+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-19T12:40:38.91378273+07:00","closed_at":"2026-03-19T12:40:38.91378273+07:00","close_reason":"Worker block now returns compact hash instead of full MutationResult through Marshal pipe. Parent rebuilds MutationResult from batch mutations and compact data.","external_ref":"gh-135","labels":["v0.7.0"],"dependencies":[{"issue_id":"EV-70.11","depends_on_id":"EV-70","type":"parent-child","created_at":"0001-01-01T00:00:00Z"}]}
|
|
127
|
+
{"id":"EV-70.12","title":"Drain StringIO buffers in InProcess isolation","description":"InProcess isolation captures stdout/stderr into StringIO objects that grow unbounded during test execution. Either drain/truncate them periodically, redirect to File::NULL like Fork does, or cap buffer size.","status":"closed","priority":3,"issue_type":"bug","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-18T18:52:17.052097022+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-19T12:49:51.334565974+07:00","closed_at":"2026-03-19T12:49:51.334565974+07:00","close_reason":"Replaced StringIO buffers with File::NULL in InProcess isolation suppress_output to prevent unbounded memory growth.","external_ref":"gh-136","labels":["v0.7.0"],"dependencies":[{"issue_id":"EV-70.12","depends_on_id":"EV-70","type":"parent-child","created_at":"0001-01-01T00:00:00Z"}]}
|
|
128
|
+
{"id":"EV-70.13","title":"Add memory budget RSpec specs","description":"Add specs that run N mutations in a loop and assert RSS doesn't grow linearly. Test sequential, parallel, fork, and in-process paths. Use a custom RSpec matcher like expect { block }.to not_leak_memory(iterations: 20, max_growth_mb: 10). These catch memory regressions in CI.","status":"closed","priority":2,"issue_type":"task","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-18T18:52:20.033283488+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-19T13:53:32.627830139+07:00","closed_at":"2026-03-19T13:53:32.627830139+07:00","close_reason":"Added rake memory:check task with LeakCheck class. Runs 4 checks (InProcess, Fork, mutation gen+strip, parallel pool) in a separate process, samples RSS at intervals, exits non-zero on regression. Configurable via MEMORY_CHECK_ITERATIONS and MEMORY_CHECK_MAX_GROWTH_KB env vars.","external_ref":"gh-137","labels":["v0.7.0"],"dependencies":[{"issue_id":"EV-70.13","depends_on_id":"EV-70","type":"parent-child","created_at":"0001-01-01T00:00:00Z"},{"issue_id":"EV-70.13","depends_on_id":"EV-70.1","type":"blocks","created_at":"0001-01-01T00:00:00Z"}]}
|
|
129
|
+
{"id":"EV-70.14","title":"Add GC stats tracking to verbose output","description":"When verbose mode is enabled, include GC.stat metrics (heap_live_slots, total_allocated_objects, total_freed_objects) at mutation boundaries. Helps distinguish real leaks from deferred GC.","status":"closed","priority":3,"issue_type":"task","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-18T18:52:22.258742699+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-18T23:57:52.062251033+07:00","closed_at":"2026-03-18T23:57:52.062251033+07:00","close_reason":"Added total_allocated_objects and total_freed_objects to both phase logs and per-mutation diagnostics via shared gc_stats_string helper","external_ref":"gh-138","labels":["v0.7.0"],"dependencies":[{"issue_id":"EV-70.14","depends_on_id":"EV-70","type":"parent-child","created_at":"0001-01-01T00:00:00Z"},{"issue_id":"EV-70.14","depends_on_id":"EV-70.5","type":"blocks","created_at":"0001-01-01T00:00:00Z"}]}
|
|
130
|
+
{"id":"EV-70.2","title":"Instrument Runner with per-phase memory snapshots","description":"Add memory measurements at key points in Runner#call: after parse_subjects, after generate_mutations, per-batch in parallel mode, and at completion. Emit to stderr when config.verbose is true. Report peak RSS in Summary.","status":"closed","priority":2,"issue_type":"task","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-18T18:51:48.544642382+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-18T19:56:39.228340976+07:00","closed_at":"2026-03-18T19:56:39.228340976+07:00","close_reason":"Added per-phase memory snapshots to Runner (parse_subjects, generate_mutations, run_mutations, per-batch). Gated on config.verbose && !config.quiet. 7 new specs.","external_ref":"gh-126","labels":["v0.7.0"],"dependencies":[{"issue_id":"EV-70.2","depends_on_id":"EV-70","type":"parent-child","created_at":"0001-01-01T00:00:00Z"},{"issue_id":"EV-70.2","depends_on_id":"EV-70.1","type":"blocks","created_at":"0001-01-01T00:00:00Z"}]}
|
|
131
|
+
{"id":"EV-70.3","title":"Instrument fork isolation with child memory reporting","description":"In Isolation::Fork, measure child process RSS after test execution and pipe it back alongside the test result via Marshal. Parent collects per-mutation memory stats. Enables detecting COW collapse and anomalous child memory usage.","status":"closed","priority":2,"issue_type":"task","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-18T18:51:51.289345346+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-18T22:48:01.087933624+07:00","closed_at":"2026-03-18T22:48:01.087933624+07:00","close_reason":"Child process measures RSS after test execution via Memory.rss_kb, pipes it back in Marshal result. MutationResult gains child_rss_kb attribute. 4 new specs.","external_ref":"gh-127","labels":["v0.7.0"],"dependencies":[{"issue_id":"EV-70.3","depends_on_id":"EV-70","type":"parent-child","created_at":"0001-01-01T00:00:00Z"},{"issue_id":"EV-70.3","depends_on_id":"EV-70.1","type":"blocks","created_at":"0001-01-01T00:00:00Z"}]}
|
|
132
|
+
{"id":"EV-70.4","title":"Instrument in-process isolation with memory deltas","description":"In Isolation::InProcess, measure RSS before and after each mutation execution. Track cumulative delta to detect linear memory growth (leak signature). Report via verbose output.","status":"closed","priority":2,"issue_type":"task","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-18T18:51:53.296371954+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-18T23:02:52.770787172+07:00","closed_at":"2026-03-18T23:02:52.770787172+07:00","close_reason":"InProcess isolation measures RSS before/after each mutation, computes memory_delta_kb. MutationResult gains memory_delta_kb attribute. Nil on timeout or when RSS unavailable. 4 new specs.","external_ref":"gh-128","labels":["v0.7.0"],"dependencies":[{"issue_id":"EV-70.4","depends_on_id":"EV-70","type":"parent-child","created_at":"0001-01-01T00:00:00Z"},{"issue_id":"EV-70.4","depends_on_id":"EV-70.1","type":"blocks","created_at":"0001-01-01T00:00:00Z"}]}
|
|
133
|
+
{"id":"EV-70.5","title":"Wire up config.verbose flag for memory diagnostics","description":"The verbose flag is declared in Config but unused. Wire it up so that when --verbose is passed, memory instrumentation output is emitted to stderr. Include per-mutation RSS, delta, and GC.stat[:heap_live_slots].","status":"closed","priority":2,"issue_type":"task","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-18T18:51:56.498046888+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-18T23:28:36.076408766+07:00","closed_at":"2026-03-18T23:28:36.076408766+07:00","close_reason":"Verbose flag now emits per-mutation diagnostics: child_rss (fork), memory delta (in-process), and GC heap_live_slots. Output gated on --verbose && !--quiet. 4 new specs (11 total in runner_memory_spec).","external_ref":"gh-129","labels":["v0.7.0"],"dependencies":[{"issue_id":"EV-70.5","depends_on_id":"EV-70","type":"parent-child","created_at":"0001-01-01T00:00:00Z"},{"issue_id":"EV-70.5","depends_on_id":"EV-70.1","type":"blocks","created_at":"0001-01-01T00:00:00Z"}]}
|
|
134
|
+
{"id":"EV-70.6","title":"Add memory stats to JSON and CLI reporters","description":"Extend Reporter::JSON and Reporter::CLI to include memory stats in output when available: peak RSS, per-mutation memory, total memory delta. Add peak_memory_mb and memory_deltas fields to Summary result object.","status":"closed","priority":3,"issue_type":"task","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-18T18:51:59.909768892+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-18T23:38:38.026960498+07:00","closed_at":"2026-03-18T23:38:38.026960498+07:00","close_reason":"Added peak_memory_mb to Summary, memory fields to JSON/CLI reporters, 10 new specs","external_ref":"gh-130","labels":["v0.7.0"],"dependencies":[{"issue_id":"EV-70.6","depends_on_id":"EV-70","type":"parent-child","created_at":"0001-01-01T00:00:00Z"},{"issue_id":"EV-70.6","depends_on_id":"EV-70.2","type":"blocks","created_at":"0001-01-01T00:00:00Z"},{"issue_id":"EV-70.6","depends_on_id":"EV-70.5","type":"blocks","created_at":"0001-01-01T00:00:00Z"}]}
|
|
135
|
+
{"id":"EV-70.7","title":"Eliminate double forking in parallel mode","description":"When jobs>1 with auto isolation, Pool already forks workers. Using Isolation::Fork inside each worker creates a redundant second fork. Fix: use in-process isolation inside pool workers (they already have process isolation from the pool fork). This is likely the single biggest contributor to 17 GB memory usage.","status":"closed","priority":1,"issue_type":"bug","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-18T18:52:02.309469841+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-19T00:06:16.517207415+07:00","closed_at":"2026-03-19T00:06:16.517207415+07:00","close_reason":"Eliminated double forking: parallel pool workers now use InProcess isolation since Pool already provides fork-based process isolation. Simplified resolve_isolation to never auto-select Fork.","external_ref":"gh-131","labels":["v0.7.0"],"dependencies":[{"issue_id":"EV-70.7","depends_on_id":"EV-70","type":"parent-child","created_at":"0001-01-01T00:00:00Z"}]}
|
|
136
|
+
{"id":"EV-70.8","title":"Lazy/streaming mutation generation","description":"Currently generate_mutations builds ALL mutations into an array before any are executed (runner.rb:38). For large codebases this holds thousands of Mutation objects (each with 2x full source strings + AST node) in memory simultaneously. Change to lazy enumeration so mutations are generated per-subject as needed.","status":"closed","priority":1,"issue_type":"bug","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-18T18:52:05.552816007+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-19T00:26:32.161184282+07:00","closed_at":"2026-03-19T00:26:32.161184282+07:00","close_reason":"Changed generate_mutations to lazy enumerator (subjects.lazy.flat_map). Mutations generated per-subject on demand, not all materialized upfront. Baseline now receives subjects instead of mutations. Pre-computed mutation_count passed to run methods for progress/fail_fast.","external_ref":"gh-132","labels":["v0.7.0"],"dependencies":[{"issue_id":"EV-70.8","depends_on_id":"EV-70","type":"parent-child","created_at":"0001-01-01T00:00:00Z"}]}
|
|
137
|
+
{"id":"EV-70.9","title":"Strip source strings from Mutation after result creation","description":"Each MutationResult retains its Mutation which holds original_source and mutated_source (full file contents). These are only needed for diff generation in reporters. Compute and cache the diff eagerly, then release the source strings to allow GC. Or store only the diff in MutationResult.","status":"closed","priority":2,"issue_type":"bug","owner":"denis.kiselyov@gmail.com","created_at":"2026-03-18T18:52:08.1904857+07:00","created_by":"Denis Kiselev","updated_at":"2026-03-19T10:31:19.673577645+07:00","closed_at":"2026-03-19T10:31:19.673577645+07:00","close_reason":"Implemented strip_sources! on Mutation (lazy diff caching, nil out source strings after execution). Wired into Runner sequential and parallel paths.","external_ref":"gh-133","labels":["v0.7.0"],"dependencies":[{"issue_id":"EV-70.9","depends_on_id":"EV-70","type":"parent-child","created_at":"0001-01-01T00:00:00Z"}]}
|
|
98
138
|
{"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."}
|
|
99
139
|
{"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,31 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.7.0] - 2026-03-19
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- **ArgumentRemoval operator** — new mutation operator that removes individual arguments from method calls with 2+ positional args (e.g. `foo(a, b, c)` → `foo(b, c)`, `foo(a, c)`, `foo(a, b)`)
|
|
8
|
+
- **Memory observability** — verbose mode (`-v`) now logs RSS and GC stats (heap_live_slots, allocated, freed) after each phase and per-mutation; includes child_rss and memory delta when available
|
|
9
|
+
- **Peak memory reporting** — text and JSON output include peak memory usage across all mutations
|
|
10
|
+
- **`rake memory:check`** — standalone memory leak detection task for pre-release validation; runs 4 checks (InProcess, Fork, mutation generation, parallel pool) and exits non-zero on regression; configurable via `MEMORY_CHECK_ITERATIONS` and `MEMORY_CHECK_MAX_GROWTH_KB` env vars
|
|
11
|
+
- **Neutral mutation detection** — baseline test suite run detects pre-existing failures; mutations in already-failing code are marked `neutral` instead of `survived`
|
|
12
|
+
|
|
13
|
+
### Fixed
|
|
14
|
+
|
|
15
|
+
- **Memory leak: source string retention** — `Mutation#strip_sources!` caches the diff then nils out original/mutated source strings after execution, allowing GC to reclaim them
|
|
16
|
+
- **Memory leak: AST node retention** — `Subject#release_node!` releases Prism AST nodes after mutation generation; nodes are no longer retained through the results chain
|
|
17
|
+
- **Memory leak: StringIO buffer growth** — InProcess isolation now redirects output to `/dev/null` instead of accumulating in unbounded StringIO buffers
|
|
18
|
+
- **Memory leak: Marshal payload bloat** — parallel pool workers now serialize only compact result hashes (status, duration, metrics) instead of full MutationResult objects with embedded Mutation/Subject/AST trees
|
|
19
|
+
- **Memory leak: double forking** — parallel mode uses InProcess isolation inside pool workers to avoid fork-inside-fork; sequential mode continues using Fork isolation
|
|
20
|
+
|
|
21
|
+
## [0.6.0] - 2026-03-17
|
|
22
|
+
|
|
23
|
+
### Added
|
|
24
|
+
|
|
25
|
+
- **`--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
|
|
26
|
+
- **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
|
|
27
|
+
- **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
|
|
28
|
+
|
|
3
29
|
## [0.5.0] - 2026-03-16
|
|
4
30
|
|
|
5
31
|
### Added
|
data/README.md
CHANGED
|
@@ -46,7 +46,10 @@ evilution [command] [options] [files...]
|
|
|
46
46
|
| `--min-score FLOAT` | Float | 0.0 | Minimum mutation score (0.0–1.0) to pass. |
|
|
47
47
|
| `--spec FILES` | Array | _(none)_ | Spec files to run (comma-separated). Defaults to `spec/`. |
|
|
48
48
|
| `--no-coverage` | Boolean | false | **DEPRECATED, NO-OP**: Kept for backward compatibility. Will be removed. |
|
|
49
|
-
| `-
|
|
49
|
+
| `-j`, `--jobs N` | Integer | 1 | Number of parallel workers. Pool forks per batch; mutations run in-process inside workers. |
|
|
50
|
+
| `--no-baseline` | Boolean | _(enabled)_ | Skip baseline test suite check. By default, a baseline run detects pre-existing failures and marks those mutations as `neutral`. |
|
|
51
|
+
| `--fail-fast [N]` | Integer | _(none)_ | Stop after N surviving mutants (default 1 if no value given). |
|
|
52
|
+
| `-v`, `--verbose` | Boolean | false | Verbose output with RSS memory and GC stats per phase and per mutation. |
|
|
50
53
|
| `-q`, `--quiet` | Boolean | false | Suppress output. |
|
|
51
54
|
|
|
52
55
|
### Exit Codes
|
|
@@ -87,7 +90,8 @@ Use `--format json` for machine-readable output. Schema:
|
|
|
87
90
|
"timed_out": "integer — mutations that exceeded timeout",
|
|
88
91
|
"errors": "integer — mutations that caused unexpected errors",
|
|
89
92
|
"score": "float — killed / (total - errors), range 0.0-1.0, rounded to 4 decimals",
|
|
90
|
-
"duration": "float — total wall-clock seconds, rounded to 4 decimals"
|
|
93
|
+
"duration": "float — total wall-clock seconds, rounded to 4 decimals",
|
|
94
|
+
"peak_memory_mb": "float (optional) — peak RSS across all mutation child processes, in MB"
|
|
91
95
|
},
|
|
92
96
|
"survived": [
|
|
93
97
|
{
|
|
@@ -133,6 +137,33 @@ Each operator name is stable and appears in JSON output under `survived[].operat
|
|
|
133
137
|
| `return_value_removal` | Strip return values | `return x` -> `return` |
|
|
134
138
|
| `collection_replacement` | Swap collection methods | `map` -> `each`, `select` <-> `reject` |
|
|
135
139
|
|
|
140
|
+
## MCP Server (AI Agent Integration)
|
|
141
|
+
|
|
142
|
+
Evilution includes a built-in [Model Context Protocol](https://modelcontextprotocol.io/) server for direct tool invocation by AI agents (Claude Code, VS Code Copilot, etc.).
|
|
143
|
+
|
|
144
|
+
### Setup
|
|
145
|
+
|
|
146
|
+
Create a `.mcp.json` file in your project root:
|
|
147
|
+
|
|
148
|
+
```json
|
|
149
|
+
{
|
|
150
|
+
"mcpServers": {
|
|
151
|
+
"evilution": {
|
|
152
|
+
"type": "stdio",
|
|
153
|
+
"command": "evilution",
|
|
154
|
+
"args": ["mcp"],
|
|
155
|
+
"env": {}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
If using Bundler, set the command to `bundle` and args to `["exec", "evilution", "mcp"]`.
|
|
162
|
+
|
|
163
|
+
The server exposes an `evilution-mutate` tool that accepts target files, method targets, spec overrides, parallelism, and timeout options — returning structured JSON results directly to the agent.
|
|
164
|
+
|
|
165
|
+
> **Note**: `.mcp.json` is gitignored by default since it is a local editor/agent configuration file.
|
|
166
|
+
|
|
136
167
|
## Recommended Workflows for AI Agents
|
|
137
168
|
|
|
138
169
|
### 1. Full project scan
|
|
@@ -208,14 +239,30 @@ bundle exec evilution run lib/ --format json --min-score 0.8 --quiet
|
|
|
208
239
|
|
|
209
240
|
Note: `--quiet` suppresses all stdout output (including JSON). Use it in CI only when you care about the exit code and do not need JSON output.
|
|
210
241
|
|
|
242
|
+
## Development
|
|
243
|
+
|
|
244
|
+
### Memory leak check
|
|
245
|
+
|
|
246
|
+
Run before releasing to verify no memory regressions:
|
|
247
|
+
|
|
248
|
+
```bash
|
|
249
|
+
bundle exec rake memory:check
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
Tests 4 paths (InProcess isolation, Fork isolation, mutation generation + stripping, parallel pool) by running repeated iterations and asserting RSS stays flat. Configurable via environment variables:
|
|
253
|
+
|
|
254
|
+
- `MEMORY_CHECK_ITERATIONS` — number of iterations per check (default: 50)
|
|
255
|
+
- `MEMORY_CHECK_MAX_GROWTH_KB` — maximum allowed RSS growth in KB (default: 10240 = 10 MB)
|
|
256
|
+
|
|
211
257
|
## Internals (for context, not for direct use)
|
|
212
258
|
|
|
213
259
|
1. **Parse** — Prism parses Ruby files into ASTs with exact byte offsets
|
|
214
260
|
2. **Extract** — Methods are identified as mutation subjects
|
|
215
261
|
3. **Mutate** — Operators produce text replacements at precise byte offsets (source-level surgery, no AST unparsing)
|
|
216
|
-
4. **Isolate** —
|
|
262
|
+
4. **Isolate** — Default isolation is in-process; `--isolation fork` uses forked child processes. Parallel mode (`--jobs N`) always uses in-process isolation inside pool workers to avoid double forking
|
|
217
263
|
5. **Test** — RSpec executes against the mutated source
|
|
218
|
-
6. **
|
|
264
|
+
6. **Collect** — Source strings and AST nodes are released after use to minimize memory retention
|
|
265
|
+
7. **Report** — Results aggregated into text or JSON, including peak memory usage
|
|
219
266
|
|
|
220
267
|
## Repository
|
|
221
268
|
|
data/Rakefile
CHANGED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "spec_resolver"
|
|
4
|
+
|
|
5
|
+
module Evilution
|
|
6
|
+
class Baseline
|
|
7
|
+
Result = Struct.new(:failed_spec_files, :duration) do
|
|
8
|
+
def initialize(**)
|
|
9
|
+
super
|
|
10
|
+
freeze
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def failed?
|
|
14
|
+
!failed_spec_files.empty?
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def initialize(spec_resolver: SpecResolver.new, timeout: 30)
|
|
19
|
+
@spec_resolver = spec_resolver
|
|
20
|
+
@timeout = timeout
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def call(subjects)
|
|
24
|
+
start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
25
|
+
spec_files = resolve_unique_spec_files(subjects)
|
|
26
|
+
failed = Set.new
|
|
27
|
+
|
|
28
|
+
spec_files.each do |spec_file|
|
|
29
|
+
failed.add(spec_file) unless run_spec_file(spec_file)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
duration = Process.clock_gettime(Process::CLOCK_MONOTONIC) - start_time
|
|
33
|
+
Result.new(failed_spec_files: failed, duration: duration)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def run_spec_file(spec_file)
|
|
37
|
+
read_io, write_io = IO.pipe
|
|
38
|
+
pid = fork_spec_runner(spec_file, read_io, write_io)
|
|
39
|
+
write_io.close
|
|
40
|
+
read_result(read_io, pid)
|
|
41
|
+
rescue StandardError
|
|
42
|
+
false
|
|
43
|
+
ensure
|
|
44
|
+
read_io&.close
|
|
45
|
+
write_io&.close
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def fork_spec_runner(spec_file, read_io, write_io)
|
|
49
|
+
Process.fork do
|
|
50
|
+
read_io.close
|
|
51
|
+
$stdout.reopen(File::NULL, "w")
|
|
52
|
+
$stderr.reopen(File::NULL, "w")
|
|
53
|
+
|
|
54
|
+
require "rspec/core"
|
|
55
|
+
::RSpec.reset
|
|
56
|
+
status = ::RSpec::Core::Runner.run(
|
|
57
|
+
["--format", "progress", "--no-color", "--order", "defined", spec_file]
|
|
58
|
+
)
|
|
59
|
+
Marshal.dump({ passed: status.zero? }, write_io)
|
|
60
|
+
write_io.close
|
|
61
|
+
exit!(status.zero? ? 0 : 1)
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
GRACE_PERIOD = 0.5
|
|
66
|
+
|
|
67
|
+
def read_result(read_io, pid)
|
|
68
|
+
if read_io.wait_readable(@timeout)
|
|
69
|
+
data = read_io.read
|
|
70
|
+
Process.wait(pid)
|
|
71
|
+
return false if data.empty?
|
|
72
|
+
|
|
73
|
+
result = Marshal.load(data) # rubocop:disable Security/MarshalLoad
|
|
74
|
+
result[:passed]
|
|
75
|
+
else
|
|
76
|
+
terminate_child(pid)
|
|
77
|
+
false
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def terminate_child(pid)
|
|
82
|
+
Process.kill("TERM", pid) rescue nil # rubocop:disable Style/RescueModifier
|
|
83
|
+
_, status = Process.waitpid2(pid, Process::WNOHANG)
|
|
84
|
+
return if status
|
|
85
|
+
|
|
86
|
+
sleep(GRACE_PERIOD)
|
|
87
|
+
_, status = Process.waitpid2(pid, Process::WNOHANG)
|
|
88
|
+
return if status
|
|
89
|
+
|
|
90
|
+
Process.kill("KILL", pid) rescue nil # rubocop:disable Style/RescueModifier
|
|
91
|
+
Process.wait(pid) rescue nil # rubocop:disable Style/RescueModifier
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
private
|
|
95
|
+
|
|
96
|
+
def resolve_unique_spec_files(subjects)
|
|
97
|
+
subjects.map { |s| @spec_resolver.call(s.file_path) || "spec" }.uniq
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
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,12 +92,18 @@ 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
|
|
92
99
|
|
|
93
100
|
def add_options(opts)
|
|
101
|
+
add_core_options(opts)
|
|
102
|
+
add_filter_options(opts)
|
|
103
|
+
add_flag_options(opts)
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def add_core_options(opts)
|
|
94
107
|
opts.on("-j", "--jobs N", Integer, "Number of parallel workers (default: 1)") { |n| @options[:jobs] = n }
|
|
95
108
|
opts.on("-t", "--timeout N", Integer, "Per-mutation timeout in seconds") { |n| @options[:timeout] = n }
|
|
96
109
|
opts.on("-f", "--format FORMAT", "Output format: text, json") { |f| @options[:format] = f.to_sym }
|
|
@@ -99,6 +112,9 @@ module Evilution
|
|
|
99
112
|
"Use line-range targeting instead: evilution run lib/foo.rb:15-30")
|
|
100
113
|
@options[:diff_base] = b
|
|
101
114
|
end
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def add_filter_options(opts)
|
|
102
118
|
opts.on("--min-score FLOAT", Float, "Minimum mutation score to pass") { |s| @options[:min_score] = s }
|
|
103
119
|
opts.on("--spec FILES", Array, "Spec files to run (comma-separated)") { |f| @options[:spec_files] = f }
|
|
104
120
|
opts.on("--target METHOD", "Only mutate the named method (e.g. Foo::Bar#calculate)") { |m| @options[:target] = m }
|
|
@@ -106,8 +122,14 @@ module Evilution
|
|
|
106
122
|
warn("Warning: --no-coverage is deprecated, currently has no effect, and will be removed in a future version.")
|
|
107
123
|
@options[:coverage] = false
|
|
108
124
|
end
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def add_flag_options(opts)
|
|
109
128
|
opts.on("--fail-fast", "Stop after N surviving mutants " \
|
|
110
129
|
"(default: disabled; if provided without N, uses 1; use --fail-fast=N)") { @options[:fail_fast] ||= 1 }
|
|
130
|
+
opts.on("--no-baseline", "Skip baseline test suite check") { @options[:baseline] = false }
|
|
131
|
+
opts.on("--isolation STRATEGY", "Isolation: auto, fork, in_process (default: auto)") { |s| @options[:isolation] = s }
|
|
132
|
+
opts.on("--stdin", "Read target file paths from stdin (one per line)") { @options[:stdin] = true }
|
|
111
133
|
opts.on("-v", "--verbose", "Verbose output") { @options[:verbose] = true }
|
|
112
134
|
opts.on("-q", "--quiet", "Suppress output") { @options[:quiet] = true }
|
|
113
135
|
end
|
|
@@ -124,6 +146,28 @@ module Evilution
|
|
|
124
146
|
0
|
|
125
147
|
end
|
|
126
148
|
|
|
149
|
+
def run_mcp
|
|
150
|
+
require_relative "mcp/server"
|
|
151
|
+
server = MCP::Server.build
|
|
152
|
+
transport = ::MCP::Server::Transports::StdioTransport.new(server)
|
|
153
|
+
transport.open
|
|
154
|
+
0
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def read_stdin_files
|
|
158
|
+
@stdin_error = "--stdin cannot be combined with positional file arguments" unless @files.empty?
|
|
159
|
+
return if @stdin_error
|
|
160
|
+
|
|
161
|
+
lines = []
|
|
162
|
+
@stdin.each_line do |line|
|
|
163
|
+
line = line.strip
|
|
164
|
+
lines << line unless line.empty?
|
|
165
|
+
end
|
|
166
|
+
stdin_files, stdin_ranges = parse_file_args(lines)
|
|
167
|
+
@files = stdin_files
|
|
168
|
+
@line_ranges = @line_ranges.merge(stdin_ranges)
|
|
169
|
+
end
|
|
170
|
+
|
|
127
171
|
def parse_file_args(raw_args)
|
|
128
172
|
files = []
|
|
129
173
|
ranges = {}
|
|
@@ -152,6 +196,8 @@ module Evilution
|
|
|
152
196
|
end
|
|
153
197
|
|
|
154
198
|
def run_mutations
|
|
199
|
+
raise ConfigError, @stdin_error if @stdin_error
|
|
200
|
+
|
|
155
201
|
file_options = Config.file_options
|
|
156
202
|
config = Config.new(**@options, target_files: @files, line_ranges: @line_ranges)
|
|
157
203
|
runner = Runner.new(config: config)
|