Skip to content

Release 3.0.0: explicit thresholds, complete CRAP scoring, deterministic file identity - #78

Merged
ANcpLua merged 1 commit into
mainfrom
claude/dotcov-3.0.0
Oct 13, 2026
Merged

ANcpLua merged 1 commit into
mainfrom
claude/dotcov-3.0.0

Conversation

@ANcpLua

@ANcpLua ANcpLua commented Oct 13, 2026

Copy link
Copy Markdown
Owner

Summary

Three gate contracts change; seven counting bugs are fixed. Every number a gate decides on may move.

 dotcov check <path>
-  --min-line 80 (implicit)        --min-branch 0
+  --min-line N and/or --min-branch N, 0..100, no default   → none: exit 2 before reading
 dotcov crap
-  unscored methods listed, gate passes on the rest
+  any unscored method → NODATA (exit 3), names the count and --metrics
 several <source> roots + relative filename
-  probe this machine's disk, first root on miss
+  lexical: first-root key, FileIdentityAmbiguous per file, gates NODATA
+  --probe-sources (2.0 probe, opt-in) | --source-root <root> (must be declared, else exit 2)
+--exclude-test-code (report, check, crap, snapshot, test; Fallout ExcludeTestCodeParam)
+  active exclusion → stderr lists rules + files; JSON gets excluded / exclusionRules
CoberturaParser.Parse/ParseAsync/ParseMethods(..., FileIdentity? identity)
  IdentityScope (per call)            # explicit root must be declared somewhere
  DocumentContext                      # AmbiguousFiles, warnings
  ClassContext.Complete               # one finalisation, sync and async (A1); <lines> element (A6)
  LineAccumulator.Flush               # per-<line> detail vs own aggregate (A5); set rule across blocks (A4)
  PathIdentity.RemoveDotSegments      # files and roots alike (A7)
CrapAnalysis.Analyze(MethodCoverageReport)
  FoldRawEntries   key = (file, type, method, signature | origin ordinal)   # A2, A3
  FoldSyntheticGroups  attach only to the one same-named method of the file; own body never joins a method with lines
  CrapReport.Evaluate  Disabled → ambiguous → unscored → no methods → Fail/Pass   # B1, B4

Brief decisions taken with the owner: cross-file folding never happens (the A2 test wins over the prose); synthetic groups that cannot be attributed stay scored rows; A5(b) warns on 2-way-shaped detail that disagrees, while non-2-way detail (coverlet switch, grcov per-outcome counts) stays silent as before.

Evidence

  • Before (5a50ddf): the new regression tests, copied onto 5a50ddf, fail — 25 cases across A1–A7, e.g.
    ParseAsync_MethodOnlyClass_YieldsTheSameReportAsSync, SameMethodInTwoFiles_IsTwoRows_NotOneAveragedRow,
    AsyncOverloads_KeepTheirOwnRowsAndScores, ConditionIdentity_OneDocumentWithTwoBlocks_EqualsTwoMergedDocuments,
    ConditionDetailClaimingMoreThanItsLine_IsDroppedWithAWarning, EmptyClassSummary_..._FileIsNoData(<lines/>),
    DotSegments_InFilenames_FoldIntoOneFile.
    After: fallout Coverage → PASS: line 100.0% (min 100%), branch 100.0% (min 100%), 1547/1547 tests;
    fallout Crap → PASS: worst CRAP 30.0 (max 30) - all 411 methods at or below threshold;
    git grep -n '"80"' src → nothing.
  • Contract tests per command: ThresholdContractTests, ExcludeTestCodeTests, FileIdentityCliTests (real process, two working directories, dotcov test against a stand-in dotnet), CliCrapTests, FalloutBuildTests, RepositoryBuildTests, BadgeActionTests.

Merge Danger

Door: two-way for the code; the v3.0.0 tag and NuGet packages that follow are one-way.

Blast Radius: consumers

Gates that relied on the implicit 80 now exit 2; CRAP gates with unscored methods and coverage.py reports with several roots now exit 3 until --metrics / --probe-sources / --source-root. CI's consumer and README-badge jobs opt into --probe-sources. Library: ParseAsync takes the identity before the token, CrapAnalysis.ExcludeFiles is replaced by MethodCoverageReport.Exclude, ICoverageReport.MinLine/MinBranch are string?.

🤖 Generated with Claude Code

…tic file identity

Three gate contracts change: check, test, the Fallout target and the badge action have no
default threshold; the CRAP gate needs every method scored; a report with several source
roots no longer triggers a disk probe, and unresolved files make every gate NODATA until
--probe-sources or --source-root resolves them. --exclude-test-code joins
--exclude-generated, and an active exclusion is listed on stderr or in JSON.

Seven counting fixes: ParseAsync finishes method-only classes; CRAP rows stay per file and
per overload; condition identity no longer depends on XML packaging; contradictory branch
data warns instead of inflating rates; an empty class summary decides the file; and
./src/C.cs and src/C.cs are one file.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Copilot AI balanced review requested due to automatic review settings October 13, 2026 05:06

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 13, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-13T05:14:37.022001Z 03ae1f2 PR opened
🔒 Security Review ✅ Completed 2026-10-13T05:11:19.562410Z 03ae1f2 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@ANcpLua
ANcpLua merged commit 52e595e into main Oct 13, 2026
28 checks passed
@ANcpLua
ANcpLua deleted the claude/dotcov-3.0.0 branch October 13, 2026 05:09

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 03ae1f238e

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

.DistinctBy(PathIdentity.NormalizeRoot, StringComparer.Ordinal).ToList()
.DistinctBy(PathIdentity.NormalizeRoot, StringComparer.Ordinal).ToList(),
AmbiguousFiles = AmbiguousFile.Union(a.AmbiguousFiles.Concat(b.AmbiguousFiles)),
Exclusion = a.Exclusion.Union(b.Exclusion)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Remove reintroduced files from merged exclusion metadata

When reports were filtered differently before being merged, unioning their exclusion records can claim that a file was excluded even though the other report reintroduces that file into merged.Values. For example, merging a report that excluded tests/A.Tests/ATests.cs with one that retained it includes the file in coverage totals while JSON and diagnostics still list it under ExcludedFiles. Reconcile the combined exclusion metadata against the final merged file paths, or retain only exclusions that remain absent from the result.

Useful? React with 👍 / 👎.

{
logical = new LogicalMethod(typeKey, typeIdentity, methodKey, arity, folded, mc.File);
int? arity = origin is null ? MethodIdentity.SignatureArity(mc.Signature) : null;
logical = new LogicalMethod(name, typeKey, arity, origin);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Preserve OwnBody when combining synthetic entries

When a lambda from an async overload appears before that overload's state-machine entry, both share the same folded ordinal and this first-created LogicalMethod permanently retains the lambda's OwnBody=false. The later state machine is merged without promoting that flag, so if a covered synchronous overload with the same name is the sole direct candidate, FoldSyntheticGroups incorrectly folds the async overload into it; reversing the XML/list order keeps them separate. This can dilute the async method's CRAP score into a false pass, so the grouped origin must retain OwnBody=true if any merged entry represents its own state-machine body.

Useful? React with 👍 / 👎.

Comment on lines +141 to +144
if (Unscored.Count > 0)
return new CrapGateResult(GateOutcome.NoData, maxCrap, Methods.Count, above, worst,
Invariant($"{Unscored.Count} of {Methods.Count + Unscored.Count} methods could not be scored - ") +
"supply --metrics <file> (dotnet msbuild /t:Metrics); a verdict needs every method scored");

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Withhold CRAP passes for unmatched metrics methods

When --metrics contains a method or accessor with no coverage counterpart, CollectUnmatched records it but this completeness check considers only Unscored; if every covered method is below the limit, the gate still returns PASS. An unmatched member can be an uninstrumented method with no coverage measurement at all—and may be the riskiest method—so the new “every method must be scored” contract should return NODATA when UnmatchedMetricsMembers is nonempty rather than merely listing it.

AGENTS.md reference: AGENTS.md:L21-L23

Useful? React with 👍 / 👎.

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.

2 participants