Restore the 100% coverage gate by not measuring test modules
The lowered 98 gate existed only to absorb #[cfg(test)] failure
paths — panic! arms and ? branches that never run in a passing
suite. Excluding test modules from grcov measurement (they close
out every file, so an unterminated excl-start reaches EOF) leaves
the gate honest at 100. The two library lines that exclusion
exposed as genuinely unexercised — the missed-relabel span-slot
arms in the self-check conflict mapping — get direct tests.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01565B66CMh3HTjm3124SLpB
change
commit 26c6345725adecbeaf5a095674a72ce32b6e3a8f
author Claude <noreply@anthropic.com>
date
parent 01c449da
diff --git a/bin/coverage b/bin/coverage
index 0a67da7..fd5e027 100755
--- a/bin/coverage
+++ b/bin/coverage
@@ -5,6 +5,11 @@
 # normal build artifacts never mix — mixing causes phantom uncovered
 # lines. Override the gate with COVERAGE_THRESHOLD (defaults to 100).
 #
+# `#[cfg(test)]` modules are excluded from measurement (they sit at
+# the tail of each file, so the unterminated excl-start runs to EOF):
+# their panic! arms and `?` branches never run in a passing suite and
+# would force a lowered gate for lines that are not library code.
+#
 # Scaffolded into a project as bin/coverage; the `coverage` justfile
 # recipe runs it. See the rust skill's coverage.md and scaffolding.md.
 
@@ -33,7 +38,7 @@ main() {
         --keep-only 'src/**' \
         --ignore 'src/bin/**' \
         --excl-line 'cov-excl-line|unreachable!' \
-        --excl-start 'cov-excl-start' \
+        --excl-start 'cov-excl-start|#\[cfg\(test\)\]' \
         --excl-stop 'cov-excl-stop')
 
     # Per-file breakdown: walk the covdir tree, printing leaf files only.
diff --git a/justfile b/justfile
index 06da833..5d2c7b2 100644
--- a/justfile
+++ b/justfile
@@ -9,12 +9,8 @@ check:
 clippy:
     cargo clippy --workspace -- -D warnings
 
-# The gate is 98 rather than 100 because grcov measures #[cfg(test)]
-# modules too, and their failure paths — panic! arms and `?` error
-# branches — never run in a passing suite. Library code is either
-# covered or carries an explicit cov-excl-line with reasoning.
 coverage:
-    COVERAGE_THRESHOLD=98 ./bin/coverage
+    ./bin/coverage
 
 mutants:
     #!/usr/bin/env bash
diff --git a/src/merge.rs b/src/merge.rs
index 2d63f0e..6e79cb4 100644
--- a/src/merge.rs
+++ b/src/merge.rs
@@ -1018,6 +1018,27 @@ mod tests {
         };
         assert!(conflicts.iter().any(|c| c.span_b.is_some()));
 
+        // Missed relabel from A: A renamed 1 to 2, the fake output
+        // kept 1.
+        let o = parse("[1]", "json")?;
+        let a = parse("[2]", "json")?;
+        let b = parse("[1]", "json")?;
+        let result = merge_to_text_with(&o, &a, &b, |_, _, _, _| "[1]".into())?;
+        let MergeResult::Conflicts(conflicts) = result else {
+            panic!("expected self-check conflicts");
+        };
+        assert!(conflicts.iter().any(|c| c.span_a.is_some()));
+
+        // Missed relabel from B, symmetrically.
+        let o = parse("[1]", "json")?;
+        let a = parse("[1]", "json")?;
+        let b = parse("[2]", "json")?;
+        let result = merge_to_text_with(&o, &a, &b, |_, _, _, _| "[1]".into())?;
+        let MergeResult::Conflicts(conflicts) = result else {
+            panic!("expected self-check conflicts");
+        };
+        assert!(conflicts.iter().any(|c| c.span_b.is_some()));
+
         // Extra insertion: nobody wrote 9; the witness lives in M, so
         // no span slot is filled.
         let o = parse("[1]", "json")?;