From 438a7c72de48fad9e5a5c02e0a262937bb0536db Mon Sep 17 00:00:00 2001 From: Aaron Lisman Date: Fri, 23 Oct 2026 14:44:12 +0000 Subject: [PATCH] planted-bugs: km-censored-as-death targets kaplanMeier's own tie loop kmMedianCi and rmst repeat kaplanMeier's tie-counting line, so the planted bug's site matched three times in lib/stats.ts and the check stopped before planting anything (failing on master since #59). The site now takes in the next line, which only kaplanMeier has. All 18 sites match exactly once; with this one planted, 14 unit tests fail. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01TyVfAhLnjshk2kWewGnLum --- scripts/check-mutants.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/scripts/check-mutants.ts b/scripts/check-mutants.ts index 239b693..92f5cba 100644 --- a/scripts/check-mutants.ts +++ b/scripts/check-mutants.ts @@ -46,8 +46,9 @@ interface Mutant { const MUTANTS: Mutant[] = [ { id: "km-censored-as-death", layer: "statistics", guard: "unit", file: "lib/stats.ts", effect: "Survival curves count patients who were still alive at last follow-up as deaths, so every curve drops too fast.", - find: "while (i < clean.length && clean[i].t === t) { if (clean[i].event) d++; i++; }", - replace: "while (i < clean.length && clean[i].t === t) { d++; i++; }" }, + // kaplanMeier's own tie loop: kmMedianCi and rmst repeat the line, then read the risk set as `n` + find: "while (i < clean.length && clean[i].t === t) { if (clean[i].event) d++; i++; }\n const atRisk = atRiskAt(t);", + replace: "while (i < clean.length && clean[i].t === t) { d++; i++; }\n const atRisk = atRiskAt(t);" }, { id: "km-never-leave-risk-set", layer: "statistics", guard: "unit", file: "lib/stats.ts", effect: "Patients stay in the at-risk group after they leave the study, so later deaths look rarer and curves sit too high.", find: "while (xi < exits.length && exits[xi] < t) { exited++; xi++; }",