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.

Analysis diagnostics

A parser or analyzer can reject one side of one file without invalidating the whole comparison. mehen diff records the affected path, side (base or head), severity, reason code, and message under the top-level JSON analysis_errors array. Markdown output adds the same information in an Analysis diagnostics table. Static metrics from an error- or fatal-severity side are withheld rather than computed from a partial syntax tree. Parser-independent measurements such as repository history remain available, and parseable files continue through normal metric and threshold evaluation. The command exits 0 by default when diagnostics are the only issue. Add --fail-on-analysis-error when incomplete analysis should remain a strict CI gate; the complete report is still emitted before exit 1.

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