diff --git a/README.md b/README.md
index 827d869..67c654f 100644
--- a/README.md
+++ b/README.md
@@ -733,8 +733,8 @@ Both commands need only Docker, git and bash 5.
- `./ci.sh` runs the gate: every check CI runs.
- `./mutation-tests/run.sh []` mutation-tests the source lines changed since ``,
- `origin/main` by default, running each mutant, in a 6 GB container, against only the test files
- that reach it.
+ `origin/main` by default, running each mutant against only the test files that reach it. It lists
+ the mutants that survived, and fails only when a mutant's test run crashed.
## Links
diff --git a/mutation-tests/run.sh b/mutation-tests/run.sh
index aa14dfd..f781d16 100755
--- a/mutation-tests/run.sh
+++ b/mutation-tests/run.sh
@@ -5,8 +5,8 @@ source ./docker-runner.sh
base=$(git merge-base "${1:-origin/main}" HEAD)
# Each hunk's added lines as path:first-last, in the working tree's numbering; a hunk that only deletes has none to mutate.
-mutate=$(git diff -U0 --no-color "$base" -- 'src/*.ts' ':!src/*.test.ts' ':!src/conformance/property-harness.ts' | awk '
- /^\+\+\+ / { path = substr($2, 3) }
+mutate=$(git diff --no-ext-diff --no-prefix -U0 --no-color "$base" -- 'src/*.ts' ':!src/*.test.ts' ':!src/conformance/property-harness.ts' | awk '
+ /^\+\+\+ / { path = $2 }
/^@@ / {
split($3, added, ",")
first = substr(added[1], 2)
@@ -19,4 +19,5 @@ if [ -z "$mutate" ]; then
fi
leg "install ($node_image)" in_image "$node_image" sh -c 'npm ci --no-audit --no-fund && npm ci --no-audit --no-fund --prefix mutation-tests'
-in_image_memory=6g leg "mutation tests ($node_image)" in_image "$node_image" sh -c 'mutation-tests/node_modules/.bin/stryker run mutation-tests/stryker.config.mjs --mutate "$1" && node mutation-tests/check-runtime-errors.mjs' sh "$mutate"
+# A test file peaks near 2.6 GB: past four workers 6 GB kills some, and a killed worker's mutant reads as a RuntimeError, which fails the run.
+in_image_memory=6g leg "mutation tests ($node_image)" in_image "$node_image" sh -c 'mutation-tests/node_modules/.bin/stryker run mutation-tests/stryker.config.mjs --concurrency 4 --mutate "$1" && node mutation-tests/check-runtime-errors.mjs' sh "$mutate"
diff --git a/mutation-tests/stryker.config.mjs b/mutation-tests/stryker.config.mjs
index a00dab2..d283005 100644
--- a/mutation-tests/stryker.config.mjs
+++ b/mutation-tests/stryker.config.mjs
@@ -1,6 +1,4 @@
export default {
- // A test file peaks near 2.6 GB: past four workers run.sh's 6 GB container kills some, and a killed worker's mutant reads as a RuntimeError, which fails the run.
- concurrency: 4,
coverageAnalysis: 'perTest',
ignorePatterns: ['/browser-tests', '/converter-comparison', '/dist', '/mutation-tests', '/package-tests'],
jsonReporter: { fileName: '/tmp/mutation-report.json' },
diff --git a/todo.md b/todo.md
index 90e1f8f..320616d 100644
--- a/todo.md
+++ b/todo.md
@@ -56,7 +56,7 @@
| 56 | 0.5.0 | | | **Give each piece of `blocks.ts`'s block-walk state and `inline-content.ts`'s `Scan` one owner that returns what it changes.** | 3 | 1 | 2 | 4 | 5 | code health by 5 | 13.1 |
| 118 | 0.5.0 | | | **Spell a portable link's title through `oneLine`.** | 1 | 1 | 1 | 1 | 1 | code health by 1 | 10.5 |
| 82 | 0.5.0 | | | **Give the directive tests and fixtures random UUID v7 ids in place of `a-1`, `a`, `x` and `h`.** | 2 | 1 | 1 | 2 | 3 | code health by 1 | 6.5 |
-| 58 | 0.5.0 | | | **Port `ci.sh`, `publish.sh` and `docker-runner.sh` to standalone Python scripts.** | 4 | 2 | 2 | 6 | 6 | code health by 3 | 4.4 |
+| 58 | 0.5.0 | | | **Port `ci.sh`, `publish.sh`, `docker-runner.sh` and `mutation-tests/run.sh` to standalone Python scripts.** | 4 | 2 | 2 | 6 | 6 | code health by 3 | 4.4 |
| 9 | 0.5.0 | | | **Ship an online sandbox: a web page with two textboxes converting between ADF and markdown on the library's browser build.** | 3 | 2 | 1 | 4 | 6 | 3 by 6 | 2.1 |
| 34 | 0.5.0 | | | **Read emphasis flanking by the whole character beside an astral symbol.** | 2 | 1 | 2 | 3 | 3 | 4 by 1 | 1.5 |
| 74 | 0.5.0 | | owner | **Keep the release path publishing after npm retires bypass-2FA tokens.** | 3 | 2 | 4 | 7 | 4 | | 0.0 |
@@ -466,10 +466,11 @@ Found by the principles audit of item 99, 2026-10-14.
Technical principle: tests use random UUID v7 ids; `portable-markdown-to-adf.test.ts` already does.
Found by the technical-principles audit of item 78, 2026-10-05.
-### 58. Port `ci.sh`, `publish.sh` and `docker-runner.sh` to standalone Python scripts.
+### 58. Port `ci.sh`, `publish.sh`, `docker-runner.sh` and `mutation-tests/run.sh` to standalone Python scripts.
Technical principle "Prefer a standalone Python script over a shell script": `AGENTS.md` ยง3 teaches
-bash footguns (`&&` chaining under `||`, `tee /dev/stderr`) the port removes.
+bash footguns (`&&` chaining under `||`, `tee /dev/stderr`) the port removes. The port tests
+`run.sh`'s turning of diff hunks into mutated line ranges, which a wrong range would pass silently.
### 34. Read emphasis flanking by the whole character beside an astral symbol.