agentsclimarketplace

Mutation hunter

Skill lousy-agents/skills/skills/mutation-hunter

Uncover test coverage gaps by applying semantic mutations to production TypeScript, Go, or Python code and identifying which mutations survive (tests still pass). Use when hunting mutants, validating test coverage, or finding behavioral gaps; surviving mutations indicate areas where tests are insufficient.From its SKILL.md

Install
npx -y skills add lousy-agents/skills --skill mutation-hunter

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

SKILL.md

14.3 KB, as published. Nobody here has run it

Mutation Hunter

You are a mutation testing agent. Your job is to find surviving mutations — semantic changes to production code that do not cause any tests to fail. Each surviving mutation is evidence of a test coverage gap.

When to Use

Use this skill when a user asks to:

  • Run mutation testing or hunt surviving mutants
  • Validate behavioral test coverage in TypeScript, Go, or Python production code
  • Find tests that would miss a regression

Do not use this skill when:

  • The user wants new tests written from scratch without a mutation hunt (write tests directly, or use a test-authoring skill)
  • The user only wants to run the existing test suite
  • The user wants load, performance, or security fuzzing rather than semantic mutation testing
  • The target language is not TypeScript, Go, or Python
  • An external tool (Stryker, mutmut, go-mutesting, etc.) would replace the agent-owned apply → test → classify → always-revert loop — those tools may assist candidate discovery only

Inputs

ArgumentRequiredDescription
mutationsYesNumber of mutations to attempt (e.g., 10)
--targetNoGlob pattern for source files. Defaults are language-aware; see the selected adapter in ./references/.
--langNoExplicit language override: ts, go, or python. If omitted, use the deterministic selection rules below.

Language Selection

Select one language once before Step 1 and use that adapter for the entire run. Evaluate markers relative to the workspace root (current working directory for the hunt), not nested package folders, unless --target confines the hunt to a subtree (see Workspace roots). The first matching rule wins, in this exact order:

  1. An explicit --lang ts, --lang go, or --lang python flag.
  2. The presence of go.mod at the workspace root selects go.
  3. The presence of package.json at the workspace root plus either root tsconfig.json or any .ts/.tsx source under the workspace selects ts.
  4. The presence of root pyproject.toml, setup.py, setup.cfg, or any root requirements*.txt selects python.
  5. Otherwise, count supported source-file extensions under the target directory (or workspace root) and select the majority: .ts/.tsx → ts, .go → go, .py → python.

An explicit unsupported --lang value is an input error. Conflicting project markers are not guessed at: precedence above resolves them deterministically, and the selected language is recorded in metadata.language. If extension counts tie, use the fixed order ts, then go, then python; if no supported source extension exists, stop with a clear language-detection error. A marker-based selection always takes precedence over extension counts.

Nested language trees inside a polyglot monorepo (for example a js/ package inside a Go module) do not override root detection. To hunt that nested tree, pass both --lang and --target (for example --lang ts --target 'js/semantics/src/**/*.ts').

After selection, read only the matching adapter file (one level deep):

LanguageAdapter + catalogue
ts./references/typescript.md
go./references/go.md
python./references/python.md

The adapter is a small internal concept in this skill, not a runtime plugin system. It is the single source of truth for baseline/test command, discovery, exclusions, priority paths, language-only mutations, return zero-values, and compile/syntax kill rules.

Workspace roots and toolchains

Resolve a language package root before baseline:

LanguagePackage root
goDirectory of the controlling go.mod (walk up from workspace root / --target hits).
tsNearest directory to the discovery roots that contains package.json (and usually tsconfig.json). Pure TS apps keep the workspace root when package.json lives there. Nested monorepo packages (e.g. js/semantics/) use that package directory as cwd for npm test.
pythonDirectory of the controlling pyproject.toml / setup.py / setup.cfg / requirements file, else the workspace root.

Run adapter baseline/test commands with cwd = package root. Prefer the project's pinned toolchain when present so binaries resolve correctly:

  • If mise.toml / .tool-versions / similar exists, invoke via that manager (e.g. mise exec -- go test ./..., mise exec -- npm test) or ensure the pinned go/node/python is on PATH before running bare commands.
  • TypeScript may still wrap with nvm use when .nvmrc exists and nvm is available.
  • Do not invent alternate test scripts; only ensure the adapter's command can run under the repo's version pins.

Workflow

Step 1 — Pre-flight baseline

Ensure all tests pass before starting. If the baseline fails, abort and report the failure.

Run the selected adapter's baseline command from its reference file, at the language package root, using the resolved toolchain.

If tests fail, output:

{ "error": "Baseline test run failed. Fix failing tests before running mutation-hunter.", "details": "<test output>" }

Then stop. Do not proceed with mutations on a broken baseline.

Step 2 — Discover mutation targets

If --target is set, expand that glob from the workspace root and keep only paths that pass the selected adapter's exclusions (tests, generated, fixtures, testdata, vendor/venv, etc.).
If --target is omitted, use the adapter's default discovery command and exclusions.

Apply the adapter's priority paths when ranking candidates. Skip files that are purely type definitions or otherwise have no executable code.

If the candidate set is empty, abort with a clear error (for TypeScript defaults, mention that src/ is required unless --target points at the real sources). Do not invent paths.

Step 3 — Select mutation candidates

For each candidate file, read it and identify mutatable constructs from the shared catalogue below plus the language-only catalogue in the selected adapter reference. Build an internal list of (file, line, mutation-type, original, mutated) tuples. Select from this list randomly until you have reached the requested mutations count, favouring files with more complex logic and the adapter's priority paths.

Step 4 — Hunt loop

For each mutation in your selection:

  1. Record the original source of the target line (exact bytes).
  2. Apply the mutation by editing the file (make the smallest possible change to a single construct).
  3. Run tests with the selected adapter's test command at the language package root (same command as baseline). Do not narrow the suite to a single package unless the adapter explicitly allows it — narrower suites can false-survive mutants only covered by other packages.
  4. Classify the result:
    • Tests fail — the mutation was killed (tests caught the change).
    • Tests pass — the mutation survived (test gap found).
    • Compile/syntax failures defined by the adapter also count as killed.
  5. Revert the mutation immediately by restoring the original bytes — never leave the code in a mutated state. After revert, confirm the file matches the recorded original (e.g. re-read or git diff that path is clean relative to pre-mutation content).
  6. Log the result internally and continue.

Important: Always revert before moving to the next mutation, even if the test runner crashes or times out. The codebase must be identical to the baseline when you finish. A failed revert stops the run and reports the affected file.

Step 5 — Produce output

Write the final JSON report to stdout. Format is described in the Output Format section below.

Shared Mutation Catalogue

Apply one mutation at a time — never combine multiple changes in a single trial. Each mutation must be semantically meaningful (changes program behavior) rather than purely syntactic.

  1. Comparison / relational boundary and negation — mutate >, >=, <, <=, equality, or inequality while preserving the language's syntax.
  2. Logical operators — mutate &&/|| in TypeScript and Go, or and/or in Python.
  3. Boolean literal flip — mutate a boolean literal used as a value.
  4. Arithmetic operators — mutate +/- or *// where operands are numeric and the result is meaningful.
  5. Return value — replace a result with the language-appropriate zero, empty, nil, None, or false value (see adapter).
  6. Early-return / null-guard / error-guard removal — remove a defensive guard when it protects an invalid state; do not remove an error-throwing guard indiscriminately.
  7. Off-by-one — shift an index or slice/length boundary by ±1.
  8. Conditional inversion — negate the complete condition while preserving language syntax.

Language-specific operators, examples, and extra constructs live only in the selected ./references/ adapter file. Do not invent a second copy of those lists here.

Output Format

Produce a single JSON object with the following schema. Write it to stdout.

{
    "metadata": {
        "target": "src/",
        "language": "ts",
        "mutations_requested": 10,
        "timestamp": "<ISO-8601>"
    },
    "summary": {
        "files_analyzed": 5,
        "mutations_attempted": 10,
        "mutations_killed": 7,
        "mutations_survived": 3,
        "survival_rate": 0.3,
        "coverage_grade": "C"
    },
    "surviving_mutations": [
        {
            "id": "mut-001",
            "file": "src/use-cases/build-permission-policy.ts",
            "line": 42,
            "mutation_type": "comparison_operator",
            "original_code": "if (size > MAX_SIZE) {",
            "mutated_code": "if (size >= MAX_SIZE) {",
            "description": "Boundary condition weakened: `>` changed to `>=`",
            "coverage_gap": "No test exercises the exact boundary where size equals MAX_SIZE.",
            "advice": "Add a test case that produces a policy with size exactly equal to MAX_SIZE and assert that the function does NOT throw. Then add a second test at MAX_SIZE + 1 and assert that it DOES throw. This will pin down the inclusive/exclusive boundary."
        }
    ],
    "killed_mutations": [
        {
            "id": "mut-002",
            "file": "src/entities/policy-document.ts",
            "line": 10,
            "mutation_type": "boolean_literal",
            "original_code": "Effect: \"Allow\"",
            "mutated_code": "Effect: \"Deny\"",
            "description": "Effect field changed from Allow to Deny",
            "killed_by_test": "src/use-cases/build-permission-policy.test.ts"
        }
    ]
}

Coverage Grade

Derive coverage_grade from survival_rate (surviving / attempted):

Survival rateGradeInterpretation
0%AExcellent — tests killed every mutation
1–10%BGood — minor gaps
11–25%CAcceptable — some gaps worth addressing
26–50%DWeak — significant test coverage gaps
> 50%FPoor — tests are insufficient to catch most regressions

The metadata.language value must be exactly ts, go, or python. Keep every other report field and array shape unchanged.

Advice Generation Guidelines

For each surviving mutation, generate advice that is:

  1. Specific — reference the exact line and condition that survived, not generic advice like "add more tests".
  2. Actionable — describe the exact input value or scenario that would kill the mutation (a test with x === boundary is better than "test the boundary").
  3. Contextual — if the surviving mutation is in a validation function, the advice should mention testing the invalid input that should have been rejected.
  4. Minimal — suggest the fewest tests needed to kill the mutation, not an exhaustive suite.

Error Handling

SituationAction
Unsupported explicit language or no detectable languageAbort immediately with a clear input error; do not mutate
Empty discovery (no eligible source files)Abort immediately with a clear error; do not mutate
Nested language tree without --lang/--target in a polyglot repoDo not guess; root detection wins, or require explicit --lang + --target
Baseline tests failAbort immediately, output error JSON, do not mutate
Toolchain binary missing (go/npm/pytest not on PATH)Resolve via project pin (mise/asdf/nvm) or abort with install/pin guidance; do not skip baseline
Adapter-defined compile/syntax error (TS/go test/Python)Count as "killed", revert, continue
Test runner hangs > 60sKill the process, count as "killed" (timeout = detectable failure), revert, continue
File cannot be editedSkip the mutation, log a warning in metadata
Revert failsStop immediately, report the partially-mutated file as an error so the user can restore it manually

External mutation tools are not required. They may be used only for optional candidate discovery; the agent owns mutation application, one-at-a-time classification, reversion, and advice generation.

Constraints

  • Never leave the codebase in a mutated state when finished.
  • Never apply more than one mutation at a time.
  • Work in small, atomic changes — single-line edits preferred.
  • Prefer mutations in business logic and the adapter's priority paths over composition roots, commands, or gateways.
  • Never mutate test files, fixtures, generated files, migrations, pure type-definition files, or other adapter exclusions (see the selected ./references/ file).
  • Keep TypeScript defaults, discovery, priority folders, exclusions, ten mutation types, examples, and test behavior compatible with the existing TypeScript path in ./references/typescript.md.

Keep looking

Skills are one crate of 627,559. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.