coverage/, target/,
build/, and TestResults/ are gitignored in any healthy repository, and .nyc_output/ is
hidden. Bare --coverage (or --coverage=auto) therefore runs a dedicated discovery pass with
the inverse policy — every ignore rule off, hidden entries visible — while staying strictly
bounded and deterministic. (A configured [coverage] discover = false opts the scan out even
under the flag; configured reports still load.)
coverage.* metric
selector, a configured coverage.* threshold, or an opting-in [coverage] section in
mehen.toml. --coverage=off disables it unconditionally; explicit paths
(--coverage=lcov.info) skip discovery entirely.
Three input tiers
-
Explicit reports —
--coverage=<path>(repeatable) orreports = […]under[coverage]. These are your statement of intent: a missing or unparsable explicit report is a hard error, because an explicit gate input that silently disappears is a broken CI gate. -
Tool-config introspection — mehen reads declarative tool configs that say where reports
get written:
- the c8/nyc JSON rc family (
.c8rc,.c8rc.json,.nycrc,.nycrc.json— first found, in c8’s own precedence order):reports-dir/report-dir; pyproject.toml:[tool.coverage.xml] outputand[tool.coverage.lcov] output(coverage.py);phpunit.xml/phpunit.xml.dist:<clover|cobertura outputFile="…">and the legacy<log type="coverage-clover" target="…">— PHPUnit writes no coverage file unless configured, so this is the only zero-config path for PHP;tarpaulin.toml/.tarpaulin.toml(cargo-tarpaulin): the union ofoutformats andoutput-dirvalues across run profiles and the reserved[report]table. Tarpaulin’s file names are fixed (cobertura.xml,lcov.info), so introspection matters exactly whenoutput-dirredirects them into territory the scan prunes (e.g.target/cov/).
jest.config.ts,vitest.config.ts,.simplecov, Gradle DSLs, Pester scripts — are never executed and never regex-scraped. Their values are routinely computed (env vars, imported constants), so extraction would silently be wrong; their tools’ default output locations are already covered by the scan below. - the c8/nyc JSON rc family (
-
Artifact scan — well-known report names and locations, matched relative to each discovery
root (the enclosing repository work dir, so reports at the repo root are found even when you
analyze
./src):lcov.info,coverage.info,*.lcov,coverage.out/cover.out/coverage.txt/profile.cov/c.out/*.coverprofile,coverage-final.json,.nyc_output/*.json,jacoco.xml,jacocoTestReport.xml,site/jacoco/*.xml,reports/jacoco/**/*.xml,reports/kover/*.xml,coverage.xml,clover.xml,cobertura.xml,coverage.cobertura.xml. Every match is confirmed by content sniffing before it is believed.
Bounded by construction
Build directories are enormous, so the walk carries explicit bounds — each converts a pathological repository from “hangs” into “warns”:
An
extra-patterns entry whose first component names a pruned directory lifts that directory for
the run — the escape hatch for exotic layouts:
Deterministic selection
When several candidates survive, selection is order-independent by construction:- Same directory, several formats — one Jest run writes
lcov.info+coverage-final.json+clover.xmlintocoverage/; they describe the same test run, so only the highest-priority format is parsed and the rest are recorded as superseded. TestResults/<run>/re-runs — coverlet writes eachdotnet testrun into a fresh GUID directory; sibling runs holding the same report name keep only the newest (by mtime, lexicographic tie-break). This is the only place mtimes are trusted — a fresh CI clone stamps every file with clone time, so a global newest-wins rule would be meaningless.- Same file found twice (scanned and named by a tool config) — recorded once, attributed to the config.
Staleness
A report generated before the code it describes attributes hits to the wrong lines. When a discovered report’s mtime predates the newestHEAD commit across the discovery roots, mehen
warns (the report is still used — the heuristic has false positives around rebases and
cherry-picks, and silently dropping your only report would be worse). Disable with
stale-warning = false under [coverage].
Configuration reference
--coverage=off disables coverage regardless of
configuration, and --coverage=<path> uses exactly the supplied reports — but bare
--coverage/=auto honors a configured discover = false (it forces ingestion of configured
reports, not the scan). Unknown keys and wrong types are rejected at load time with a caret
into the TOML source, like every other mehen.toml mistake.
See also
- Supported formats — what the candidates are sniffed against.
- Path matching — what happens to the reports after ingestion.
- Configuration — the rest of
mehen.toml.
References
- c8 configuration and nyc configuration — the JSON rc family and its precedence.
- coverage.py configuration reference —
[xml] output/[lcov] output(the[tool.coverage.*]tables inpyproject.toml). - PHPUnit XML configuration — the
<coverage>/<report>elements. - cargo-tarpaulin config file — run
profiles, the reserved
[report]table, and theout/output-dirkeys. - coverlet VSTest integration
— why
TestResults/<guid>/coverage.cobertura.xmlre-run clusters exist. - Kover Gradle plugin — the
JaCoCo-compatible
build/reports/kover/XML.