codecharter coverage [root] [options]
codecharter coverage finds the test projects under root (the current
directory by default), runs them with coverage collection, merges the results,
and fails the build when line coverage falls below the required minimum. The
same run also reports every uncovered region with its source snippet, so you
know exactly which lines are missing a test.
It is the second half of the quality gate: analyze
answers "is the code well written", coverage answers "is it actually tested".
Prerequisite
Coverage is collected through the XPlat Code Coverage collector, which needs
the coverlet.collector package in every test project:
<PackageReference Include="coverlet.collector" Version="6.*" PrivateAssets="all" />
A test project without it still runs its tests but produces no coverage data. The run names each affected project and prints the exact line to add.
Quick start
# Gate the repository at the configured minimum
codecharter coverage
# Gate one folder at 90%
codecharter coverage src --min-coverage 90
# Reuse the results of a test run you already did
codecharter coverage --skip-tests
# Write the report to a file instead of stdout
codecharter coverage --output-file coverage-report.json
The JSON report goes to stdout, diagnostics and a one-line summary go to
stderr. That split keeps codecharter coverage > report.json usable in a
pipeline without stripping anything out.
Options
| Option | Default | Description |
|---|---|---|
[root] |
current directory | Directory tree searched for test projects. |
--min-coverage <percent> |
from the configuration | Minimum required line coverage (0-100). Overrides the configured value for this run. The number is read locale-independently, so 99.5 is correct everywhere and 99,5 is not. |
--results-root <dir> |
<root>/TestResults/coverage |
Where test and coverage artifacts are written. A relative path is resolved against the current working directory, not against root. |
--skip-tests |
off | Do not run the tests; analyse the coverage files already present under the results root. |
--output-file <path> |
stdout | Write the JSON report to this file. |
--verbose |
off | Also print informational progress lines to stderr. |
--no-color |
off | Disable ANSI color codes. |
--skip-tests checks that a coverage file is present for every discovered test
project before it trusts the result, so a stale or half-finished run cannot pass
the gate unnoticed. If the results directory was produced by a different tool
and does not carry the per-project layout, the check falls back to comparing
counts and says so.
Configuration
The policy lives in .codecharter/config.yml, so it is committed once and
applies to everybody:
coverage:
minimum-percent: 99.5 # gate threshold (0-100), default 100
snippet-context-lines: 3 # context lines around each uncovered region
exclude: # files excluded from the metric (globs)
- "**/*.Designer.cs"
- "src/Generated/**"
All three keys are repository-wide; unlike the analysis sections, coverage
cannot be varied per path scope. Globs are matched case-sensitively against
paths relative to the run root, written with forward slashes (**/ spans
directories, * stays inside one path segment).
Note that coverage.exclude is separate from the top-level exclude used for
analysis. Excluding a folder from rule analysis does not silently drop it from
your coverage number, and the other way round.
The effective threshold is resolved per key in this order:
--min-coverageon the command line.codecharter/config.local.yml(your personal, machine-local overlay).codecharter/config.yml(the committed team setting)- the built-in default of 100
The report states which of the four won, so a surprising gate result can always
be traced back to its source. Note that a coverage.exclude list in the local
overlay replaces the committed list rather than extending it.
You can edit all of this from the command line instead of by hand — see codecharter config.
What counts towards the number
Positional records that only carry data have no behaviour worth testing, so
they are left out of the metric. A record is treated as pure data as long as
none of its members has a hand-written body: auto-properties (including init)
and the generated primary constructor keep it pure, while a computed property,
a validating constructor, a method body, an operator, an indexer, or an event
puts the whole record back into the gate.
Classes and structs are never excluded this way. The exception is deliberately limited to records, because only a record states the "data, not logic" contract outright.
Everything else you exclude is your decision, through coverage.exclude.
Exit codes
| Code | Meaning |
|---|---|
0 |
Coverage met the required minimum. |
1 |
Coverage below the required minimum. |
2 |
Tests failed, or the coverage data was incomplete. |
3 |
No coverage data at all. |
64 |
Usage, configuration, or environment error (for example a missing .NET SDK or an unwritable --output-file). |
The gate is fail-closed: coverage data that is missing, empty, or unreadable
never produces a pass. Exit code 3 says "nothing was measured", it never
appears as a green 100%.
The displayed percentage is rounded down, so 100.00% means every
measurable line is covered and nothing else. A single uncovered line in a large
solution shows as 99.99% and fails a 100% gate, instead of rounding up into a
false pass.
The report
The JSON report has three parts:
summary— measurable lines, covered lines, the percentage, the required minimum, where that minimum came from, whether the gate passed, and the age of the coverage files that were analysed.testResults— one entry per test project with its exit code (empty when you pass--skip-tests).uncoveredRegions— every contiguous block of never-executed lines, with the file (as an absolute path and relative to the run root), the containing method, the line numbers, and a source snippet.
The relative path is the stable key for CI annotations: it does not depend on where the build agent checked the repository out.
In CI
- name: Coverage gate
run: codecharter coverage --output-file coverage-report.json
# Exit code 1 turns the step red when coverage drops below the minimum.
Because the threshold lives in the configuration file, the pipeline definition does not need to change when the team raises the bar.
With an AI assistant
The same gate is available to AI coding assistants as the run_coverage tool
of the MCP server. The assistant gets the uncovered regions
including file, method, line numbers, and snippet, which is everything it needs
to write the missing tests without searching for them first.
Related
- codecharter analyze — the code-quality half of the gate.
- Configuration file — the
coveragesection in context. - Exit codes — the exit codes of the other commands.