@intentsolutionsio/mutation-test-runner 1.0.0 → 1.0.5
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.
- package/agents/mutation-tester.md +30 -0
- package/package.json +1 -1
- package/skills/running-mutation-tests/SKILL.md +14 -6
- package/skills/running-mutation-tests/assets/mutation_report_template.md +6 -6
- package/skills/running-mutation-tests/references/README.md +0 -1
- package/skills/running-mutation-tests/scripts/README.md +1 -1
- package/skills/running-mutation-tests/scripts/mutation_analyzer.py +36 -37
|
@@ -1,6 +1,35 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: mutation-tester
|
|
3
3
|
description: Validate test quality through mutation testing
|
|
4
|
+
tools:
|
|
5
|
+
- Read
|
|
6
|
+
- Write
|
|
7
|
+
- Edit
|
|
8
|
+
- Bash
|
|
9
|
+
- Glob
|
|
10
|
+
- Grep
|
|
11
|
+
- WebFetch
|
|
12
|
+
- WebSearch
|
|
13
|
+
- Task
|
|
14
|
+
- TodoWrite
|
|
15
|
+
model: sonnet
|
|
16
|
+
color: orange
|
|
17
|
+
version: 1.0.0
|
|
18
|
+
author: Jeremy Longshore <jeremy@intentsolutions.io>
|
|
19
|
+
tags:
|
|
20
|
+
- testing
|
|
21
|
+
- mutation
|
|
22
|
+
- tester
|
|
23
|
+
disallowedTools: []
|
|
24
|
+
skills: []
|
|
25
|
+
background: false
|
|
26
|
+
# ── upgrade levers — uncomment + set when tuning this agent ──
|
|
27
|
+
# effort: high # reasoning depth: low/medium/high/xhigh/max (omit = inherit session)
|
|
28
|
+
# maxTurns: 50 # cap the agentic loop (omit = engine default)
|
|
29
|
+
# memory: project # persistent scope: user/project/local (omit = ephemeral)
|
|
30
|
+
# isolation: worktree # run in an isolated git worktree
|
|
31
|
+
# initialPrompt: "…" # seed the agent's first turn
|
|
32
|
+
# hooks / mcpServers / permissionMode → set at the PLUGIN level, not on a plugin agent
|
|
4
33
|
---
|
|
5
34
|
# Mutation Test Runner Agent
|
|
6
35
|
|
|
@@ -9,6 +38,7 @@ Validate test suite quality by introducing code mutations and verifying tests ca
|
|
|
9
38
|
## Mutation Testing Concept
|
|
10
39
|
|
|
11
40
|
Mutation testing modifies ("mutates") code to check if tests detect the changes:
|
|
41
|
+
|
|
12
42
|
- **Mutant killed** - Test failed (good, caught the bug)
|
|
13
43
|
- **Mutant survived** - Test passed (bad, missed the bug)
|
|
14
44
|
|
package/package.json
CHANGED
|
@@ -1,16 +1,21 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: running-mutation-tests
|
|
3
|
-
description:
|
|
4
|
-
|
|
3
|
+
description: 'Execute mutation testing to evaluate test suite effectiveness.
|
|
4
|
+
|
|
5
5
|
Use when performing specialized testing.
|
|
6
|
-
Trigger with phrases like "run mutation tests", "test the tests", or "validate test effectiveness".
|
|
7
6
|
|
|
7
|
+
Trigger with phrases like "run mutation tests", "test the tests", or "validate test
|
|
8
|
+
effectiveness".
|
|
9
|
+
|
|
10
|
+
'
|
|
8
11
|
allowed-tools: Read, Write, Edit, Grep, Glob, Bash(test:mutation-*)
|
|
9
12
|
version: 1.0.0
|
|
10
13
|
author: Jeremy Longshore <jeremy@intentsolutions.io>
|
|
11
14
|
license: MIT
|
|
12
|
-
|
|
13
|
-
|
|
15
|
+
tags:
|
|
16
|
+
- testing
|
|
17
|
+
- mutation-tests
|
|
18
|
+
compatibility: Designed for Claude Code, also compatible with Codex and OpenClaw
|
|
14
19
|
---
|
|
15
20
|
# Mutation Test Runner
|
|
16
21
|
|
|
@@ -75,6 +80,7 @@ Execute mutation testing to evaluate the effectiveness of a test suite by system
|
|
|
75
80
|
## Examples
|
|
76
81
|
|
|
77
82
|
**Stryker configuration for TypeScript project:**
|
|
83
|
+
|
|
78
84
|
```javascript
|
|
79
85
|
// stryker.config.mjs
|
|
80
86
|
export default {
|
|
@@ -89,6 +95,7 @@ export default {
|
|
|
89
95
|
```
|
|
90
96
|
|
|
91
97
|
**Example surviving mutant and fix:**
|
|
98
|
+
|
|
92
99
|
```
|
|
93
100
|
Mutant: src/utils/discount.ts:15 -- ConditionalExpression
|
|
94
101
|
Original: if (total > 100)
|
|
@@ -105,6 +112,7 @@ it('applies discount above 100', () => {
|
|
|
105
112
|
```
|
|
106
113
|
|
|
107
114
|
**mutmut for Python:**
|
|
115
|
+
|
|
108
116
|
```bash
|
|
109
117
|
# Run mutation testing
|
|
110
118
|
mutmut run --paths-to-mutate=src/ --tests-dir=tests/
|
|
@@ -122,4 +130,4 @@ mutmut show 42
|
|
|
122
130
|
- mutmut (Python): https://github.com/boxed/mutmut
|
|
123
131
|
- PITest (Java): https://pitest.org/
|
|
124
132
|
- go-mutesting: https://github.com/zimmski/go-mutesting
|
|
125
|
-
- Mutation testing theory: https://en.wikipedia.org/wiki/Mutation_testing
|
|
133
|
+
- Mutation testing theory: https://en.wikipedia.org/wiki/Mutation_testing
|
|
@@ -143,12 +143,12 @@ def is_valid_email(email):
|
|
|
143
143
|
|
|
144
144
|
**Example Recommendations:**
|
|
145
145
|
|
|
146
|
-
*
|
|
147
|
-
*
|
|
148
|
-
*
|
|
149
|
-
*
|
|
150
|
-
*
|
|
146
|
+
* **Increase Test Coverage:** Focus on adding tests to cover the areas identified by the surviving mutants and no coverage mutants.
|
|
147
|
+
* **Strengthen Assertions:** Review existing test assertions to ensure they are robust enough to detect subtle changes in behavior.
|
|
148
|
+
* **Test Edge Cases:** Pay particular attention to testing edge cases and boundary conditions.
|
|
149
|
+
* **Refactor Code:** In some cases, the surviving mutants may indicate areas of code that are overly complex or difficult to test. Consider refactoring these areas to improve testability.
|
|
150
|
+
* **Improve Test Data:** Ensure the test data used is diverse and representative of real-world scenarios.
|
|
151
151
|
|
|
152
152
|
## Conclusion
|
|
153
153
|
|
|
154
|
-
`[Summarize the key findings and recommendations. Reiterate the importance of mutation testing as a tool for improving test suite effectiveness and ensuring code quality. State the next steps for addressing the identified issues.]`
|
|
154
|
+
`[Summarize the key findings and recommendations. Reiterate the importance of mutation testing as a tool for improving test suite effectiveness and ensuring code quality. State the next steps for addressing the identified issues.]`
|
|
@@ -6,6 +6,6 @@ Bundled resources for mutation-test-runner skill
|
|
|
6
6
|
- [x] mutation_analyzer.py: Analyzes the mutation test results to identify weak coverage areas and suggest improvements.
|
|
7
7
|
- [x] test_selector.py: Selects the most relevant tests to run based on the mutations introduced, optimizing testing time.
|
|
8
8
|
|
|
9
|
-
|
|
10
9
|
## Auto-Generated
|
|
10
|
+
|
|
11
11
|
Scripts generated on 2025-12-10 03:48:17
|
|
@@ -5,31 +5,25 @@ Analyzes the mutation test results to identify weak coverage areas and suggest i
|
|
|
5
5
|
Generated: 2025-12-10 03:48:17
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
|
-
import os
|
|
9
8
|
import json
|
|
10
9
|
import argparse
|
|
11
10
|
from pathlib import Path
|
|
12
|
-
from typing import Dict
|
|
11
|
+
from typing import Dict
|
|
13
12
|
from datetime import datetime
|
|
14
13
|
|
|
14
|
+
|
|
15
15
|
class Analyzer:
|
|
16
16
|
def __init__(self, target_path: str):
|
|
17
17
|
self.target_path = Path(target_path)
|
|
18
|
-
self.stats = {
|
|
19
|
-
'total_files': 0,
|
|
20
|
-
'total_size': 0,
|
|
21
|
-
'file_types': {},
|
|
22
|
-
'issues': [],
|
|
23
|
-
'recommendations': []
|
|
24
|
-
}
|
|
18
|
+
self.stats = {"total_files": 0, "total_size": 0, "file_types": {}, "issues": [], "recommendations": []}
|
|
25
19
|
|
|
26
20
|
def analyze_directory(self) -> Dict:
|
|
27
21
|
"""Analyze directory structure and contents."""
|
|
28
22
|
if not self.target_path.exists():
|
|
29
|
-
self.stats[
|
|
23
|
+
self.stats["issues"].append(f"Path does not exist: {self.target_path}")
|
|
30
24
|
return self.stats
|
|
31
25
|
|
|
32
|
-
for file_path in self.target_path.rglob(
|
|
26
|
+
for file_path in self.target_path.rglob("*"):
|
|
33
27
|
if file_path.is_file():
|
|
34
28
|
self.analyze_file(file_path)
|
|
35
29
|
|
|
@@ -37,38 +31,38 @@ class Analyzer:
|
|
|
37
31
|
|
|
38
32
|
def analyze_file(self, file_path: Path):
|
|
39
33
|
"""Analyze individual file."""
|
|
40
|
-
self.stats[
|
|
41
|
-
self.stats[
|
|
34
|
+
self.stats["total_files"] += 1
|
|
35
|
+
self.stats["total_size"] += file_path.stat().st_size
|
|
42
36
|
|
|
43
37
|
# Track file types
|
|
44
38
|
ext = file_path.suffix.lower()
|
|
45
39
|
if ext:
|
|
46
|
-
self.stats[
|
|
40
|
+
self.stats["file_types"][ext] = self.stats["file_types"].get(ext, 0) + 1
|
|
47
41
|
|
|
48
42
|
# Check for potential issues
|
|
49
43
|
if file_path.stat().st_size > 100 * 1024 * 1024: # 100MB
|
|
50
|
-
self.stats[
|
|
44
|
+
self.stats["issues"].append(f"Large file: {file_path} ({file_path.stat().st_size // 1024 // 1024}MB)")
|
|
51
45
|
|
|
52
46
|
if file_path.stat().st_size == 0:
|
|
53
|
-
self.stats[
|
|
47
|
+
self.stats["issues"].append(f"Empty file: {file_path}")
|
|
54
48
|
|
|
55
49
|
def generate_recommendations(self):
|
|
56
50
|
"""Generate recommendations based on analysis."""
|
|
57
|
-
if self.stats[
|
|
58
|
-
self.stats[
|
|
51
|
+
if self.stats["total_files"] == 0:
|
|
52
|
+
self.stats["recommendations"].append("No files found - check target path")
|
|
59
53
|
|
|
60
|
-
if len(self.stats[
|
|
61
|
-
self.stats[
|
|
54
|
+
if len(self.stats["file_types"]) > 20:
|
|
55
|
+
self.stats["recommendations"].append("Many file types detected - consider organizing")
|
|
62
56
|
|
|
63
|
-
if self.stats[
|
|
64
|
-
self.stats[
|
|
57
|
+
if self.stats["total_size"] > 1024 * 1024 * 1024: # 1GB
|
|
58
|
+
self.stats["recommendations"].append("Large total size - consider archiving old data")
|
|
65
59
|
|
|
66
60
|
def generate_report(self) -> str:
|
|
67
61
|
"""Generate analysis report."""
|
|
68
62
|
report = []
|
|
69
|
-
report.append("\n" + "="*60)
|
|
70
|
-
report.append(
|
|
71
|
-
report.append("="*60)
|
|
63
|
+
report.append("\n" + "=" * 60)
|
|
64
|
+
report.append("ANALYSIS REPORT - mutation-test-runner")
|
|
65
|
+
report.append("=" * 60)
|
|
72
66
|
report.append(f"Target: {self.target_path}")
|
|
73
67
|
report.append(f"Generated: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
|
|
74
68
|
report.append("")
|
|
@@ -80,34 +74,37 @@ class Analyzer:
|
|
|
80
74
|
report.append(f" File Types: {len(self.stats['file_types'])}")
|
|
81
75
|
|
|
82
76
|
# Top file types
|
|
83
|
-
if self.stats[
|
|
77
|
+
if self.stats["file_types"]:
|
|
84
78
|
report.append("\n📁 TOP FILE TYPES")
|
|
85
|
-
sorted_types = sorted(self.stats[
|
|
79
|
+
sorted_types = sorted(self.stats["file_types"].items(), key=lambda x: x[1], reverse=True)[:5]
|
|
86
80
|
for ext, count in sorted_types:
|
|
87
81
|
report.append(f" {ext or 'no extension'}: {count} files")
|
|
88
82
|
|
|
89
83
|
# Issues
|
|
90
|
-
if self.stats[
|
|
84
|
+
if self.stats["issues"]:
|
|
91
85
|
report.append(f"\n⚠️ ISSUES ({len(self.stats['issues'])})")
|
|
92
|
-
for issue in self.stats[
|
|
86
|
+
for issue in self.stats["issues"][:10]:
|
|
93
87
|
report.append(f" - {issue}")
|
|
94
|
-
if len(self.stats[
|
|
88
|
+
if len(self.stats["issues"]) > 10:
|
|
95
89
|
report.append(f" ... and {len(self.stats['issues']) - 10} more")
|
|
96
90
|
|
|
97
91
|
# Recommendations
|
|
98
|
-
if self.stats[
|
|
92
|
+
if self.stats["recommendations"]:
|
|
99
93
|
report.append("\n💡 RECOMMENDATIONS")
|
|
100
|
-
for rec in self.stats[
|
|
94
|
+
for rec in self.stats["recommendations"]:
|
|
101
95
|
report.append(f" - {rec}")
|
|
102
96
|
|
|
103
97
|
report.append("")
|
|
104
98
|
return "\n".join(report)
|
|
105
99
|
|
|
100
|
+
|
|
106
101
|
def main():
|
|
107
|
-
parser = argparse.ArgumentParser(
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
parser.add_argument(
|
|
102
|
+
parser = argparse.ArgumentParser(
|
|
103
|
+
description="Analyzes the mutation test results to identify weak coverage areas and suggest improvements."
|
|
104
|
+
)
|
|
105
|
+
parser.add_argument("target", help="Target directory to analyze")
|
|
106
|
+
parser.add_argument("--output", "-o", help="Output report file")
|
|
107
|
+
parser.add_argument("--json", action="store_true", help="Output as JSON")
|
|
111
108
|
|
|
112
109
|
args = parser.parse_args()
|
|
113
110
|
|
|
@@ -127,8 +124,10 @@ def main():
|
|
|
127
124
|
else:
|
|
128
125
|
print(output)
|
|
129
126
|
|
|
130
|
-
return 0 if len(stats[
|
|
127
|
+
return 0 if len(stats["issues"]) == 0 else 1
|
|
128
|
+
|
|
131
129
|
|
|
132
130
|
if __name__ == "__main__":
|
|
133
131
|
import sys
|
|
132
|
+
|
|
134
133
|
sys.exit(main())
|