diff --git a/CLAUDE.md b/CLAUDE.md
index 9debcc5..b4c2f54 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -493,6 +493,21 @@ Run PIT with the lifecycle prefix. Reactor-wide (what CI does):
`smoke-fatjar` release-gating job already runs the real `java -jar` artifact and asserts
`Main#run end.` in its output, which is an end-to-end check a unit mutant cannot reach.
+**Two classes are permanently off the gate, and this is worth not re-litigating.** Both have
+survivors that are *equivalent mutants*, unkillable through the public API rather than merely
+untested. `document.AiMdHeaderCodec`: the colon guard in `read` is reached only after
+`startsWith(HEADER_FIELD_PREFIX)`, so `colonIndex` is either `-1` or `>= 2` — the `< 0` boundary
+mutant differs only at the unreachable `0`, and the `+1 -> -1` mutant only at an empty field key,
+which `values.put("", value)` swallows because no header field is keyed `""`. `support.AiSourceChunker`
+(28/34): its three observable boundaries *are* pinned (`maxChars == 1`, the `end < length` guard,
+the `lastNewline > pos` guard), but six mutants have no observable effect at all — the ArrayList
+capacity hint, `return chunks` vs `emptyList()` on empty input, two clamps the surrounding
+`Math.max` absorbs, the loop head the `end >= length` break already guarantees, and `select`'s
+`total <= maxChunks`, where the equality branch computes the identity mapping. Reaching 100% on
+either would mean exposing internals or deleting a deliberately defensive guard. The orchestration
+layers (`indexer.*`, the plugin's `mojo.*` walk) and the JNI provider stay out for a different
+reason: they need a Maven/native context rather than pure-unit mutation.
+
## JPMS Module Descriptor
Each module ships a `module-info.java` compiled in a separate `release 9` execution, and each module's
diff --git a/TODO.md b/TODO.md
index 8234988..164a047 100644
--- a/TODO.md
+++ b/TODO.md
@@ -2,44 +2,13 @@
Open work items for this repo. Cross-cutting tracking lives in
[`../workspace/crossrepostatus.md`](../workspace/crossrepostatus.md); items here are
-repo-specific or this repo's slice of a cross-cutting initiative. Completed work is
-recorded in git history and `crossrepostatus.md`, not here.
+repo-specific or this repo's slice of a cross-cutting initiative.
-## Open
+**Completed work is not recorded here.** It lives in git history and in
+`crossrepostatus.md`; a finished item is deleted from this file rather than annotated, so
+everything below is genuinely still open.
-- **Expand `srcmorph`'s own PIT mutation scope (optional).** `srcmorph/pom.xml` wires
- `100` over an explicit `` list (config /
- document / engine / prompt / provider / support value+logic classes, plus `indexer.AiInputWindowCalculator`
- and `support.AiProgressBar`), all killed at 100%. Still out (optional, need careful fixtures):
- nothing, as it turns out: the remaining candidates were worked through and each is either now on
- the gate or documented below as unreachable.
- `document.AiMdDocumentCodec` reached 100% (13/13) and is now on the gate. **`document.AiMdHeaderCodec`
- is permanently out, and this is worth not re-litigating:** its last two survivors are *equivalent
- mutants* in the colon-position guard at `read`'s `colonIndex < 0 || colonIndex < HEADER_FIELD_PREFIX.length() + 1`.
- The preceding `startsWith(HEADER_FIELD_PREFIX)` guard means `colonIndex` is either `-1` or `>= 2`,
- so (a) the `< 0` boundary mutant `<= 0` differs only at the unreachable `colonIndex == 0`, and
- (b) the `+1 -> -1` arithmetic mutant differs only at `colonIndex == 2`, i.e. an empty field key,
- which `values.put("", value)` swallows invisibly because no `AiMdHeader` field is keyed `""`. Both
- are unkillable through the public API; reaching 100% would mean either exposing the parsed map or
- simplifying the (deliberately defensive, and strictly redundant) first disjunct away. The class's
- real coverage gaps *were* closed — the `read(Path)` overload and the malformed-input branches now
- have tests. **`support.AiSourceChunker` is out for the same reason** (28/34): its
- three genuinely observable boundaries are now pinned — `maxChars == 1`, the `end < length` guard
- that stops the *last* chunk being trimmed and re-split, and the `lastNewline > pos` guard that
- stops a chunk beginning on a newline collapsing to `"\n"` — but six mutants cannot be observed
- through the API at all: the `length / maxChars` ArrayList capacity hint (line 47), the empty-source
- `return chunks` versus `emptyList()` (49), the `maxChars - 1` overlap clamp and the `pos + 1`
- progress floor (51, 66) which the surrounding `Math.max` absorbs, the `pos < length` loop head (54)
- which the `end >= length` break already guarantees, and `select`'s `total <= maxChunks` (81), where
- the subset branch at equality computes `round(i * (total - 1) / (total - 1)) = i` and returns the
- same list. `config.AiConditionGroup`
- and `provider.LlamaCppJniConfig` were listed here too, but both measured 100% (2/2 and 36/36) without
- a single new test — the existing `AiConditionGroupTest` and
- `LlamaCppJniConfigFactoryTest#fromGenerationConfig_threadsEveryFieldThrough` already killed
- everything — so they are now on the gate. `prompt.AiPromptPreparationSupport` was likewise stale: it
- has been on the gate for a while. The orchestration layers (`indexer.*` walk,
- the plugin's `mojo.*`) and the JNI provider stay out of PIT — they need a Maven/native context
- rather than pure-unit mutation (see crossrepostatus "Deliberate non-parity").
+## Open
- **Put `provider.LlamaCppJniAiGenerationProvider` on the PIT gate.** It is the one production class
where a defect has actually reached users, and it is *not* on the `targetClasses` list — which is
@@ -75,7 +44,7 @@ recorded in git history and `crossrepostatus.md`, not here.
- **jqwik pin policy** — see [`../workspace/policies/jqwik-prompt-injection.md`](../workspace/policies/jqwik-prompt-injection.md). `jqwik.version ≤ 1.9.3` is mandatory (declared in `srcmorph/pom.xml`, the only reactor module with a jqwik test dependency).
-- **`@VisibleForTesting` audit.** No usages currently in any module. Walk each module's production tree for package-private/protected methods or fields that exist purely so tests can reach them, and either annotate (`com.google.common.annotations.VisibleForTesting`) or move into the test source tree.
+- **`@VisibleForTesting` audit.** Nothing is annotated, but the members exist: `provider.LlamaCppJniAiGenerationProvider` has four (`buildChatTemplateKwargs`, `buildInferenceParameters`, `warnOnTruncatedAnswer`, `logPromptCacheReuse`) plus the static `tensorReadLazyMode`/`cacheType`, `document.AiMdDocumentCodec` has `read(List)`/`write`, `prompt.AiPromptPreparationSupport` has `trimSourceAtLineBreak`, and the three mojos have their `build*Configuration()`. Guava is not a dependency, so closing this means either a project-local marker annotation (there is precedent: `support.ConvertToRecord`) or recording that the convention is not adopted here. Decide and act rather than re-auditing.
- **Null-safety refinement.** JSpecify + NullAway are enforced at compile time in strict JSpecify mode in every module (see each module's own `pom.xml`); `@NullMarked` on the package; framework-populated POJOs carry class-level `@SuppressWarnings({"NullAway.Init","initialization.fields.uninitialized"})`. Open follow-up: review remaining unannotated public API surfaces for places where `@Nullable` would be more precise than the implicit non-null default.