Search/Improve a Mutation-Testing Tool Based on Its Current Code and Tests

Improve a Mutation-Testing Tool Based on Its Current Code and Tests

Use when changing a mutation-testing tool. It checks current code and tests first, keeps processing order and lazy evaluation intact, adds mutators that create no broken mutants, writes tests that catch mutants, and follows safe review, CI, and release steps.

To use this AI rule
## Check Current Code, Tests, and Design Notes Before Each Change Treat the current source tree, tests, schemas, architecture decisions, and contributor documentation as the specification. Do not trust memory, old assumptions, or a generic framework pattern when repository evidence disagrees. For each feature or fix: 1. Search the nearest unit, integration, end-to-end, architecture, and self-mutation tests, including related names, failures, invalid inputs, and edge cases. 2. Read the owning implementation to learn ordering, defaults, failure semantics, and performance constraints. 3. Read referenced design records and upstream discussions when intent is not clear from code. 4. Write or port a focused failing case before implementation and link it to its evidence. 5. Record the reason for non-obvious mechanics, version gates, and deliberate incompatibilities. Use the project glossary consistently. Model a mutation as a serializable description that may be applied in another process, and model a mutator as a distinct eligibility check plus transformation. Map the execution pipeline before editing it: configuration, source and coverage collection, AST enrichment, mutation generation, mutant execution, optional follow-up analysis, and reporting are ordered ownership boundaries. If a change makes a documented path, gate, or convention false, update that documentation in the same change. ## Keep Clear Rules for the Public API, Types, and Naming - Production code must not depend on tests or benchmarks. Put shipped test helpers in an explicit production testing namespace and enforce the direction mechanically. - Treat the public API as a whitelist. Mark everything else internal, document extension points, and classify any change to the whitelist as a compatibility event. - Use a hard `final` only for classes that are never mocked. Keep mockable services technically overridable without adding an interface solely to mock one class. - Import functions and constants consistently. For calls that can return `false`, choose explicit failure semantics: use a safe wrapper when failure is exceptional and a clearly named unsafe alias when the false return is required. - Prefer typed constants, explicit override markers, and immutable value objects. Do not make a class readonly when it memoizes into its own properties. - Use runtime assertions only for invariants that static analysis cannot prove. Assertion messages must identify the bug a failure would imply. - Make naming express contracts: `find*` returns nullable, `get*` guarantees a value, named constructors express domain meaning, and exceptions describe the domain failure rather than ending generically in `Exception`. - Reserve `Memoized*` for invisible once-per-instance reuse. Use `Cached*` only when callers can observe keys, invalidation, TTL, or a shared backend. - Make pure static or constant-only classes non-instantiable. Reserve dependency injection for stateful or IO collaborators. - Keep copied upstream code identifiable, preserve its attribution, and document every intentional local deviation. ## Keep Lazy Evaluation and Pass Subprocess Commands as Argument Arrays - Represent expensive reusable values with a memoized deferred abstraction. A raw generator is single-pass and must not be rewound, counted, or buffered. - Distinguish strict one-to-one mapping from one-to-many flat mapping. Do not let similar method names hide different stream cardinality. - Permit by-reference mutation on a measured hot path only when its ownership and in-place effect are documented. - Inject clock and filesystem abstractions rather than calling time or disk primitives directly. Tests must control time and filesystem outcomes. - Represent subprocess commands as string argument arrays end to end. One array element is one token; never pre-join a shell command or assume whitespace splitting. - Keep one explicit service registry with typed getters and lazy factories. Consumers must not use an untyped service lookup, and speculative unreachable services are forbidden. - Rebind configuration by cloning the container rather than mutating shared process state. Extract a typed builder when a wiring closure becomes complex. - Merge configuration according to each field's semantics: nullable overrides use null-coalescing, while flags that either source may force on use logical OR. Unifying those rules silently changes behavior. - Resolve relative output paths against the configuration file location, preserve explicitly allowed stream paths, and calculate a version-control base so only the user's intended changes are processed. ## Keep the AST Processing Order and Leave the Original Tree Unchanged - Treat visitor registration order as part of the algorithm. Resolve names and parents before consumers, apply exclusions before attaching tests, and reset reusable visitor state before every file. - Communicate through typed node-attribute accessors. Exclusion marks a node ineligible; it never removes or replaces nodes during enrichment. - Attach expensive coverage lookups lazily and only to eligible nodes. Preserve the two levels of laziness between coverage-file discovery and per-file test-location parsing. - Route XML queries through a safe namespace-aware wrapper. Keep ordered format fallbacks and their version rationale; a cleanup must not delete a still-supported branch. - Keep the process runner streaming. Worker identifiers that user tests consume are exact, 1-based API values. Follow-up analysis belongs to the process container chain, not to scheduler-specific branching. - Preserve unit semantics in scheduling calculations: time spent enqueuing work is a duration, not an item count. Release completed container references before reusing worker slots. - Keep version-specific test-runner configuration edits separate and composable. Preserve authoritative modern coverage configuration, user include filters, glob-capable paths, and legacy document shapes. - Match a mutation target by token positions and node class. Clone the original AST before mutation, preserve original tokens for format-preserving printing, and keep the original tree byte-equivalent. - Treat mutation hashes, field order, and required position attributes as public rerun contracts. Copy every child and attribute when rebuilding nodes; a valid replacement may be a scalar, one node, or a node list. ## Add Mutators That Avoid Broken or Equivalent Mutants A complete mutator change updates the implementation, every central registry and profile map, the configuration schema, a canonical test, and user documentation when the behavior is public. - Keep the public mutator surface minimal: definition, stable name, eligibility, and transformation, plus configuration access only when the mutator is configurable. - Make the definition explain the mutation, category, example diff, and how a user can kill the mutant. - In eligibility checks, compare language identifiers with their real case rules and walk ancestors when nested context matters. - Never generate syntax errors, guaranteed fatal errors, equivalent mutations, infinite loops, or a mutation already owned by another mutator. - When overlap or equivalence is possible, state the reasoning and add a named non-mutation case. - Copy all children and metadata when constructing a replacement AST node so attributes are not silently deleted. - Exercise mutators through the production traversal harness. Do not build a one-off traverser that bypasses eligibility or scope behavior. - Treat missing expected output as an explicit does-not-mutate assertion and make data-set names read as specifications. - Report per-mutator generated, killed, escaped, and timed-out counts when mutation volume changes. ## Write Repeatable Tests That Catch Mutants - Give every concrete source class a canonical test and every test an explicit coverage target. - Classify IO tests as integration tests and non-IO tests as non-integration tests. Enforce the boundary in both directions. - Keep tests deterministic: use generated temporary paths, pin concurrency for output-sensitive fixtures, back up environment variables, and avoid hard-coded nonexistent paths. - Use static data providers with descriptive case names. Prefer named arguments and defaults for wide test signatures, but do not share one provider across tests that consume different subsets. - Replace deep mock graphs with small immutable builders or fakes. Make mock expectations assert exact arguments so mutations cannot escape through loose assertions. - Structure production code for mutation visibility: prefer early returns, separate boolean outcomes, and named constants over untestable magic literals. - Avoid default constructor arguments that hide missing dependencies. Defaults in data providers are acceptable when they make each case state only what it varies. - Use the project's visitor harness, command harness, and other production-aware fixtures. Do not write a disposable probe script when a focused unit test should exist. - An escaped mutant indicates either a missing test or code that needs reshaping. Discuss genuinely unkillable or performance-only cases instead of writing a contrived test. - During iteration, run focused tests. Before handoff, run the repository formatter, architecture and static-analysis review, relevant integration groups, and the authoritative full suite. ## Keep Version-Specific Settings and Fallbacks When Cleaning Up Code - Keep version cutoffs and fallback ladders documented with the behavior they protect. Do not simplify a compatibility matrix merely because only the newest shape is familiar. - For ordered configuration mini-languages, process keys in order and let later entries win. A false sentinel must remove everything registered by that key, including nested profiles. - Preserve merge direction and explicit deduplication when combining settings. A command-line selection may replace file selection while still retaining global ignore rules. - Make one registry the source of truth for the supported mutator universe and derive automated coverage checks from it. - When a test-name filter approaches the regular-expression limit, degrade from precise filtering to a safe superset and finally omit the filter so the whole suite runs. Never degrade to running nothing. - Preserve the paired rule that a runner's “no tests executed” result is a pass when an empty selection is valid. - After a debugger-removal restart, do not infer capability from the currently loaded extension. Ask the restart handler which version was skipped and enable coverage only in the child that needs it. - Apply memory-limit adjustments only to the temporary child configuration, after static analysis, and make the adjustment best-effort with explicit guards. - Benchmark process-heavy optimizations before keeping them. Revalidate old performance workarounds when runtime or toolchain versions change. ## Protect the Lockfile and Merge Only Pull Requests With Passing CI Never bypass a frozen lockfile during an unattended install. If dependency resolution requires a lockfile change, stop and obtain explicit authorization. Exact versions, integrity hashes, and license compatibility are supply-chain boundaries. Never push directly to a protected default branch, bypass branch protection, or force an administrative merge. Use this sequence: 1. Create a focused feature branch. 2. Make the smallest coherent change and run targeted checks. 3. Commit with the repository's required message convention. 4. Push the branch and open a pull request. 5. Wait for every required format, type, unit, integration, and end-to-end check. 6. Fix failures instead of bypassing them. 7. Merge through the approved method and remove the branch. Do not mix refactoring with behavior changes. Fixed bugs require regression tests, compatibility breaks require explicit release notes and migration guidance, and user-facing behavior requires synchronized user documentation. Let release automation derive metadata from conventional commits. Use documented overrides only for deliberate reclassification, and never hand-edit generated release artifacts. Run CI for untrusted contributions without secrets and with read-only permissions. Secret-bearing previews must skip forks unless an authorized maintainer triggers them through an identity-gated command. Register every deployed example in the build matrix and add a smoke test for its route, status, and content. Release or deployment is complete only after required checks and post-deploy evidence pass.