Skip to content

fix(checks): floor ImportFanOutOutlier thresholds and stop double-reporting (CD-338) - #218

Merged
TAJD merged 1 commit into
mainfrom
fix/cd-338
Aug 11, 2026
Merged

fix(checks): floor ImportFanOutOutlier thresholds and stop double-reporting (CD-338)#218
TAJD merged 1 commit into
mainfrom
fix/cd-338

Conversation

@TAJD

@TAJD TAJD commented Aug 11, 2026

Copy link
Copy Markdown
Owner

Fixes two defects introduced by CD-333 (#217), found by adversarial review.

The fan-out threshold had no absolute floor

threshold_out = mean_out + 3*stddev_out is purely relative. Only resolved in-project edges count, so a project importing mainly from node_modules has a sparse local graph — mean 0.1, stddev 0.3, threshold ~1.0. A file importing a single local module cleared the gate and was reported as a god module.

CD-333's regression test passed only because its synthetic leaf had fan_out == 0. Real leaves import something. Both metrics now require >= MIN_ABSOLUTE_FAN (8) as well as the relative bar.

Fan-in findings were emitted twice

The amended fan-in condition was a strict superset of the fan-out condition, so fan-in could never fire alone — and the engine has no issue-dedup pass (finalize_and_filter does override-retain, suppression-retain, severity stamp and sort). A qualifying file received two issues with the same check id, file and 1:1 location.

The branches are now exclusive: a combined hub finding when both metrics qualify, a fan-out finding otherwise, never both.

Measured effect

Across five local repos, Design.ImportFanOutOutlier goes from 43 findings to 20.

Every removed finding is fan-in-only on a shared module — projektor's services/errors.ts (47 importers), test/helpers.ts (58), utils/api-client.ts (33). Every survivor is a file importing 15 to 54 others.

Tests

Two new tests in both import_fan_out_outlier.rs and design/mod.rs, each confirmed failing before the fix:

  • sparse_graph_does_not_flag_a_leaf_that_imports_one_module — the case the existing suite misses, because its leaf has fan-out 0. Failed with 17 spurious findings.
  • a_file_is_never_reported_twice — asserts exactly one issue, not merely that a message matches. Failed with 2.

Also

Corrects the CHANGELOG [Unreleased] section, where a prior conflict resolution ran two bullets together and duplicated a third.

🤖 Generated with Claude Code

https://claude.ai/code/session_019afugU2bTYCtXn6Vy9dn1v

…orting (CD-338)

CD-333 left the fan-out threshold purely relative. Only in-project
resolved edges count, so a codebase importing mainly from node_modules
has a sparse graph where mean 0.1 and stddev 0.3 put mean + 3*stddev
below 1 — a file importing one local module cleared the gate and was
reported as a god module. Its regression test passed only because the
synthetic leaf had fan-out 0; real leaves import something.

Both metrics now also require an absolute floor of 8.

The amended fan-in condition was additionally a strict superset of the
fan-out condition, and the engine has no issue-dedup pass, so a
qualifying file received two issues with the same check id and location.
The branches are now exclusive: a combined hub finding when both metrics
qualify, a fan-out finding otherwise, never both.

Across five local repos this takes 43 findings to 20. Every removed
report is fan-in-only on a shared module (an error adapter imported by
25 files, a test helper by 58); every survivor imports 15 to 54 others.

Also corrects the CHANGELOG's [Unreleased] section, where a prior
conflict resolution ran two bullets together and duplicated a third.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019afugU2bTYCtXn6Vy9dn1v
@github-actions

Copy link
Copy Markdown

Criterion benchmark comparison (PR head vs base branch)

�[1m�[92m   Compiling�[0m cofferdam-checks v0.4.2 (/home/runner/work/cofferdam/cofferdam/crates/cofferdam-checks)
�[1m�[92m   Compiling�[0m cofferdam-engine v0.4.2 (/home/runner/work/cofferdam/cofferdam/crates/cofferdam-engine)
�[1m�[92m    Finished�[0m `bench` profile [optimized] target(s) in 50.32s
�[1m�[92m     Running�[0m benches/engine_bench.rs (target/release/deps/engine_bench-e0ce420cbfc0fb1d)
Gnuplot not found, using plotters backend
Benchmarking full_run_no_cache
Benchmarking full_run_no_cache: Warming up for 3.0000 s
Benchmarking full_run_no_cache: Collecting 20 samples in estimated 6.9374 s (420 iterations)
Benchmarking full_run_no_cache: Analyzing
full_run_no_cache       time:   [16.148 ms 16.277 ms 16.488 ms]
                        change: [-7.3395% -3.5119% -0.3024%] (p = 0.08 > 0.05)
                        No change in performance detected.
Found 3 outliers among 20 measurements (15.00%)
  3 (15.00%) high severe

Benchmarking single_file_edit_incremental
Benchmarking single_file_edit_incremental: Warming up for 3.0000 s

Warning: Unable to complete 20 samples in 5.0s. You may wish to increase target time to 9.7s, enable flat sampling, or reduce sample count to 10.
Benchmarking single_file_edit_incremental: Collecting 20 samples in estimated 9.6576 s (210 iterations)
Benchmarking single_file_edit_incremental: Analyzing
single_file_edit_incremental
                        time:   [6.4557 ms 6.5373 ms 6.6135 ms]
                        change: [-7.4617% -5.9770% -4.6785%] (p = 0.00 < 0.05)
                        Performance has improved.
Found 1 outliers among 20 measurements (5.00%)
  1 (5.00%) high mild


@TAJD
TAJD merged commit 156f13a into main Aug 11, 2026
18 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant