Source: merge commit 939ea840d348c13c4296b721768af26bd64eeeb3 — review-heavy (2 substantive review rounds).
Proposed learning: module-level constants are invisible to mutation testing; hoist them into functions to make the seams observable. The reviewer identified the load-bearing move in this change as converting six module-level path constants into functions. The mechanism is specific and generalizable: a top-level const initializer runs once per Stryker worker process, so mutants of its literal segments are pinned for the lifetime of that process and are never attributed to any individual test. They surface as survivors that no amount of test quality can kill. Making the value a function call moves the mutated code into the test's own execution frame, where it can be observed and killed. The review notes this is the same class of finding — and the same fix — as a previously reviewed script, which makes it a recurring trap rather than a one-off.
Second, independently reusable: the discipline around suppression directives. Survivors went 22 to 3 to 1, and the remaining ones were deleted rather than silenced. A main() that was pure indirection with a single caller was inlined, which removes its block-statement mutant entirely. A readFile(path, 'utf8') whose result was immediately .toString()-ed had the 'utf8' argument removed rather than hedged behind a directive. The discriminating detail worth writing down: only the JSON.parse-fed read needed that treatment, because JSON.parse silently coerces a Buffer while .split/.replaceAll throw — the other readFile(path, 'utf8') calls in the same file were already killable. A first pass that restructured a pathExists try/catch into .then() purely to make a directive placeable was reverted, on the principle that reshaping code to accommodate a suppression turns a mutation guard into theater.
Third: the framing that ties both together. A build script that grades its own output with its own comparator is a closed loop, and the only way to open it is to make the seams observable from outside. That is the general statement of the problem this change solved; the constant-to-function conversion and the directive deletions are both instances of it.
Suggested capture: docs/solutions/testing/ or docs/solutions/best-practices/, alongside the existing note on enumerating mutator variants before adding a Stryker directive. Scope: why top-level consts produce unkillable mutants under Stryker's per-worker model, the function-hoisting fix, the Buffer-coercion asymmetry that determines which 'utf8' arguments are safe to drop, and the delete-the-code-or-add-a-seam-but-never-reshape-to-suppress ordering.
Source: merge commit
939ea840d348c13c4296b721768af26bd64eeeb3— review-heavy (2 substantive review rounds).Proposed learning: module-level constants are invisible to mutation testing; hoist them into functions to make the seams observable. The reviewer identified the load-bearing move in this change as converting six module-level path constants into functions. The mechanism is specific and generalizable: a top-level
constinitializer runs once per Stryker worker process, so mutants of its literal segments are pinned for the lifetime of that process and are never attributed to any individual test. They surface as survivors that no amount of test quality can kill. Making the value a function call moves the mutated code into the test's own execution frame, where it can be observed and killed. The review notes this is the same class of finding — and the same fix — as a previously reviewed script, which makes it a recurring trap rather than a one-off.Second, independently reusable: the discipline around suppression directives. Survivors went 22 to 3 to 1, and the remaining ones were deleted rather than silenced. A
main()that was pure indirection with a single caller was inlined, which removes its block-statement mutant entirely. AreadFile(path, 'utf8')whose result was immediately.toString()-ed had the'utf8'argument removed rather than hedged behind a directive. The discriminating detail worth writing down: only theJSON.parse-fed read needed that treatment, becauseJSON.parsesilently coerces a Buffer while.split/.replaceAllthrow — the otherreadFile(path, 'utf8')calls in the same file were already killable. A first pass that restructured apathExiststry/catch into.then()purely to make a directive placeable was reverted, on the principle that reshaping code to accommodate a suppression turns a mutation guard into theater.Third: the framing that ties both together. A build script that grades its own output with its own comparator is a closed loop, and the only way to open it is to make the seams observable from outside. That is the general statement of the problem this change solved; the constant-to-function conversion and the directive deletions are both instances of it.
Suggested capture:
docs/solutions/testing/ordocs/solutions/best-practices/, alongside the existing note on enumerating mutator variants before adding a Stryker directive. Scope: why top-levelconsts produce unkillable mutants under Stryker's per-worker model, the function-hoisting fix, the Buffer-coercion asymmetry that determines which'utf8'arguments are safe to drop, and the delete-the-code-or-add-a-seam-but-never-reshape-to-suppress ordering.