Testing

Mutation Testing Advisor

Manually 'mutate' small pieces of logic (flip a comparison, off-by-one a bound) and check whether any existing test actually catches it -- a cheap way to find tests that pass but don't test anything.

When to use this

  • A test suite has high coverage but you're not confident it would actually catch a regression.
  • No mutation-testing tool is configured for this language/repo and running one is too heavy for the moment -- this is the manual, cheap version.
  • Not a replacement for a real mutation-testing tool (Stryker, mutmut, PIT) when one is available and fast enough to run -- prefer the real tool's exhaustive result over this manual spot-check.

The skill file

Copy this verbatim. It's written in the SKILL.md format (YAML frontmatter + markdown instructions) that Claude Code, and increasingly other agent tools, read directly.

SKILL.md
---
name: mutation-testing-advisor
description: Manually introduce small, targeted logic mutations (flip a comparison operator, shift a boundary by one, invert a condition) into already-tested code and check whether the existing tests fail. Use to spot-check whether coverage is real assertion strength, not just line execution.
---

# Mutation Testing Advisor

Line coverage tells you code *ran* during a test. It doesn't tell you the
test would notice if that code were wrong. This skill checks the second
thing, cheaply, by hand.

## Process

1. Pick a function with test coverage that matters (business logic,
   validation, a calculation) -- not a trivial pass-through.
2. Introduce ONE small mutation at a time, in a scratch copy or a
   throwaway branch, never in the real diff:
   - Flip a comparison operator (`<` to `<=`, `==` to `!=`).
   - Off-by-one a boundary constant.
   - Invert a boolean condition.
   - Change a `return` value to a different valid-looking value.
3. Run the existing tests against the mutated code.
4. If all tests still pass, that's a **surviving mutant** -- the test
   suite has a gap at exactly that logic, regardless of what the coverage
   report says.
5. Revert the mutation. Repeat for a handful of the function's important
   branches -- this is a spot-check, not exhaustive coverage of every
   possible mutation.

## Output

For each mutation tried: what was mutated, whether a test caught it, and
for every surviving mutant, what test (or assertion inside an existing
test) would need to be added to catch it.

Installing it elsewhere

The frontmatter/body split above is Claude Code's convention. Here's how to carry the same instructions into other tools:

Claude Code
.claude/skills/mutation-testing-advisor/SKILL.md

Save the file below verbatim (frontmatter included) at that path, project-local or in ~/.claude/skills/ for a user-level skill. Claude Code loads the name/description pair to decide when to pull it in, or you invoke it directly as /mutation-testing-advisor.

Cursor
.cursor/rules/mutation-testing-advisor.mdc

Convert the YAML frontmatter to Cursor's rule format (description, globs, alwaysApply: false) and keep the markdown body as the rule content. Cursor surfaces it by description match, same idea as Claude Code's auto-load.

Codex CLI / Copilot
AGENTS.md

Codex CLI (and increasingly other agentic CLIs) read AGENTS.md at the repo root as always-on instructions. Paste the markdown body under a heading like ## {title}; for GitHub Copilot's coding agent, the equivalent file is .github/copilot-instructions.md.

Windsurf
.windsurfrules

Append the markdown body to .windsurfrules at the repo root. Windsurf treats the whole file as always-on context, so keep only the instructions you want applied on every request.

Where this goes wrong
  • Leaving a mutation in the actual codebase instead of a scratch copy -- always revert before finishing.
  • Trying to mutate everything instead of a targeted sample of the logic that actually matters.
  • Treating a surviving mutant in truly inconsequential code (a log message, a debug flag) as equally urgent to one in business logic.