mehen diff compares metrics between two git revisions and emits a per-file delta report. It is the
engine behind the GitHub Action and the recommended way to wire mehen into a
PR workflow.
Examples
- PR head vs main (Markdown)
- JSON for a CI script
- Custom metrics + polarity
- Strict gate on docs
Output formats
- Markdown
- JSON
GitHub-flavored Markdown table optimized for sticky PR comments. This is what the
GitHub Action posts on each PR.
What gets compared
mehen asks Git for the changed-file set, runs the equivalent ofmehen metrics against each side,
joins by repository-relative path, and computes per-metric deltas. It does not recursively walk
untracked working-tree content, so ignored build and dependency directories are never candidates.
Files that exist on only one side are reported as new or deleted. Tracked files matched by
linguist-generated, linguist-vendored, or binary Git attributes are skipped by default.
Added and modified files use attributes from the requested head revision; deleted files use the
base revision where they still exist. Revision filtering reads committed .gitattributes files
only, so checkout-local .git/info/attributes and configured global attributes cannot change a
historical report. Use --ignore-git-attributes=false to include attributed files.
When no --metrics are given, a mixed pull request (e.g. both .ts and .sql
files) produces a single table whose columns are the union of each language’s
default metrics. Every row populates only the columns its analyzer publishes;
columns from another language render as –. This keeps SQL changes visible in
the default GitHub Action path — without it, SQL files would be measured only
against source-code metrics they never emit and silently drop out as unchanged.
The source-code default columns follow a one-column-per-dimension design:
Cognitive (control-flow understandability), ABC (computational volume),
MI (the one deliberate composite rollup), Hotspot
(history.hotspot = cognitive complexity × commit frequency — is this a
fragile, frequently-touched file?), and Churn (history.churn.relative —
how much of the file’s full repository history has moved, normalized by its
size). The two history columns are computed by walking the git history of each
compared revision — see the history metric family. All columns are review-prioritization signals, not quality
verdicts: a rising number flags where to look, not that the code is bad.
Coverage trend columns
mehen diff folds test coverage into both sides of the comparison:
- Head side —
--coveragewith the exact flag semantics ofmehen metricsandmehen top-offenders(explicit paths hard-error when missing; discovered reports degrade to warnings). - Base side —
--base-coverage=<PATH>, explicit paths only. The diff re-analyzes the base revision’s blobs, so a report generated at the base revision lines up with the recomputed base spans without any line mapping. mehen never retrieves reports itself (it stays network-free); a CI layer such as the GitHub Action fetches the base report and hands over plain paths.
--metrics list is given, a Coverage
column (the coverage.line rate — the one dimension every supported format measures) joins the
default columns. With explicit --metrics, select coverage columns yourself (coverage.line,
coverage.branch, coverage.function, or their .covered/.total counters).
The measured-or-absent doctrine extends to the diff:
A base report whose file modification time predates the base commit itself cannot describe that
commit’s code (recency-based retrieval fallbacks land exactly here); mehen warns on stderr, and the
warning honors the same
stale-warning key under [coverage] in mehen.toml.
Configured coverage.* thresholds gate the head side like every other metric this diff reports,
and a configured coverage threshold is itself a lazy ingestion trigger — no flag needed.
--fail-on thresholds
--fail-on accepts a comma-separated list of band-crossing rule IDs:
The rules align with the severity-1 / severity-2 indicators on the
PR comment design page.
Configured thresholds
When amehen.toml is present, the head side of every changed source-code
file is checked against the configured limits for the metrics this diff reports (the explicit
--metrics list, or the per-language defaults). Evaluation happens before the unchanged-row
filter, so an unchanged-but-over-limit metric still fails the gate even when its row is hidden.
Deleted files have no head side to gate; Markdown files stay on the --fail-on documentation
gates above. Crossed limits print a grouped report on stderr — after the Markdown or JSON output,
which stays complete for machine consumers — and the command exits 1.
For delta gating (e.g. “fail if the cyclomatic delta exceeds 5”), use the
GitHub Action’s thresholds input, which post-processes the JSON report.
See Concepts → Thresholds and diffs for how the gates compose.
Exit codes
See also
- GitHub Action — runs
mehen difffor you and posts the report on PRs. - PR comment design — Markdown comment template.
- Concepts → Thresholds and diffs.