Skip to main content
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

Output formats

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 of mehen 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--coverage with the exact flag semantics of mehen metrics and mehen 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.
When coverage resolves for either side and no explicit --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 a mehen.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