Project Health Score
Duplication, dead code and complexity each tell part of the story. --health folds them into one number you can put on a README, track over time, or gate on, and --dashboard shows it on top of the screen.
Running it
jscpd src --health # the badge
jscpd src --health --reporters json,badge # jscpd-health.json + jscpd-health-badge.svg
jscpd src --health --health-input metrics.json # with coverage, tests, security
Health B 74/100 ███████████████░░░░░ 93 lines of code (XS)
duplication 75 (5.4%) · dead code 72 (14.0%) · complexity 76 (0.0% in complex files)
Each sub-score is followed by what it was measured from. A dimension that cannot be measured is named (dead code n/a) rather than scored as perfect.
How it is calculated
1. Three shares of the code lines. Because every dimension is a share, a project is not penalized for being large.
| Dimension | What is measured |
|---|---|
| Duplication | jscpd's duplication percentage, over code files |
| Dead code | Share of lines nothing runs (JavaScript, TypeScript, Python) |
| Complexity | Share of code lines that sit in complex files, those with a complexity of 50 or more. Complexity hurts when it piles up, and a mean would hide that |
Prose and data files (markdown, JSON, YAML, lock files) are not the project's code and are left out, so a folder of copied JSON snapshots does not lower the score.
2. A sub-score per dimension on a half-life curve: 100 · 2^(−share / halfLife). It is 100 at zero, 50 at one half-life and 25 at two, with no cliff and no dead zone. The half-lives are calibrated on 42 open-source projects so that the median project scores 75 in each dimension:
| Dimension | Half-life | Median project |
|---|---|---|
| Duplication | 8.5% | 3.5% |
| Dead code | 7.5% | 3.1% |
| Complexity | 50% in complex files | 20.9% |
3. Size. In a small project one finding is a large share, so each share is mixed with 2000 lines of "typical project" before it is scored. At 300 lines that prior dominates; at 50,000 it no longer matters. The JSON report carries both the measured value and the adjusted one, and the badge shows the size class (XS under 1K lines of code, S, M, L, XL from 1M).
4. One score. The weighted geometric mean of the sub-scores, so a project that is 40% dead code is not rescued by its low duplication. Grades: A from 85, B from 70, C from 55, D from 40, then E. On the calibration corpus that gives 5 A, 13 B, 15 C, 8 D and 1 E.
Dead code is skipped when JavaScript, TypeScript and Python are under 5% of the code, and otherwise weighs as much as the share of the code it could read. Two scores are comparable only when they are built from the same dimensions.
Metrics from other tools
--health-input FILE (config key healthInput) adds what jscpd cannot measure. A metric is either a ready 0-100 score, or a value with the halfLife that turns it into one; "direction": "higher" scores the distance to max (default 100).
{
"metrics": [
{ "id": "coverage", "value": 81, "direction": "higher", "halfLife": 40 },
{ "id": "failing-tests", "value": 2, "halfLife": 5 },
{ "id": "security", "score": 100, "weight": 2 }
]
}
With halfLife: 40, 81% coverage is 19 short of 100 and scores 72; 60% scores 50. A CI job typically writes this file from its coverage and audit steps, then runs jscpd . --health --health-input metrics.json.
Tuning
The same object can live under health in .jscpd.json, together with the tuning of the built-in dimensions:
{
"health": {
"duplication": { "halfLife": 5, "weight": 2 },
"deadCode": { "weight": 0 },
"complexFile": 80,
"metrics": [{ "id": "security", "score": 100 }]
}
}
A weight of 0 leaves a dimension out. An unknown key, or a metric that cannot be scored, is an error rather than a silently different score.
Reporters and exit codes
| Reporter | Output |
|---|---|
console | The badge |
ai | One line: health 74 B (duplication 75, dead-code 72, complexity 76; 93 code lines) |
json | jscpd-health.json: score, grade, size, and for each dimension its value, adjusted value, lines, half-life, weight and score |
badge | jscpd-health-badge.svg |
markdown / html | jscpd-health.md / jscpd-health.html: the same score and dimension table, for a PR comment or a status page |
The exit gates of a clone run apply: --threshold, --exit-code and --fail-on-empty. A runnable example, with a metrics file, is in fixtures/dashboard-demo.