Test · Configuration
The Test family is opt-in. Off by default — turn it on once
your project has (or is willing to wire up) an lcov.info produced
by cargo llvm-cov, pytest --cov, nyc, or scoverage. heal
never executes tests itself; everything that needs the test suite
to actually run (flakiness, mutation score, runtime trends) stays
out of scope.
For what each metric flags, see Test › Metrics. For the skills, see Test › Skills.
Quick enable
Section titled “Quick enable”[features.test]enabled = true
[features.test.coverage]enabled = trueDefaults cover Rust / TypeScript / JavaScript / Python / Go / Scala
test conventions and the four conventional lcov.info paths. Most
projects don’t need to override anything.
If you don’t have an lcov.info yet, run the tests step of the setup
skill — it inspects your stack and wires up a reporter, asking before
each step. In Claude Code:
/heal:setup testsFor details see Test › Skills.
[features.test]
Section titled “[features.test]”[features.test]enabled = false # master switchtest_paths = [ "tests/**", "**/*_test.rs", "**/*.test.ts", "**/*.test.tsx", "**/*.test.js", "**/*.test.jsx", "**/*.spec.ts", "**/*.spec.tsx", "**/*.spec.js", "**/*.spec.jsx", "**/__tests__/**", "**/*_test.go", "**/test_*.py", "**/*_test.py", "**/*Test.scala", "**/*Spec.scala",]enabled(defaultfalse) — master switch. While false, every test observer is a no-op.test_paths(default: language conventions above) — gitignore- syntax globs that mark which source files are tests. Theskip_ratioobserver walks these files; every Finding whose primary file matches is also taggedis_test_file = true.
When test_paths is empty, heal falls back to a built-in
heuristic covering the same conventions.
The globs are anchored like .gitignore: the default tests/** matches
only a tests/ directory at the project root. In a workspace with
nested test directories such as crates/<name>/tests/, use
**/tests/**. Once you set test_paths yourself, the
Semantic (Jev) tasks also rely on your globs alone;
with the defaults they add the built-in heuristic, which treats any
directory named test/ as tests.
is_test_file flag
Section titled “is_test_file flag”When [features.test] is enabled, every Finding gains an
is_test_file: bool flag. Skills filter on this to read test- and
production-side severities independently — /heal:tests
focuses on test findings; /heal:refactor focuses on
production findings.
The flag is omitted from JSON output when false, so projects that
don’t enable the test family see byte-identical latest.json
content to before.
[features.test.coverage]
Section titled “[features.test.coverage]”[features.test.coverage]enabled = falselcov_paths = [ "lcov.info", "coverage/lcov.info", "target/llvm-cov/lcov.info", "coverage/lcov-report/lcov.info",]enabled(defaultfalse) — sub-feature switch. Keep[features.test]on but[features.test.coverage]off when you wantis_test_filetagging andskip_ratiowithout yet wiring up a reporter.lcov_paths— project-relative paths probed in order. Every existing file is read and merged, so a polyglot monorepo can list onelcov.infoper package and each one counts. When two files describe the same source file, counters take the max (mirroring how duplicate records inside one lcov file merge). Missing files are silent — no warning at startup; files that exist but can’t be read warn on stderr.SF:paths that a reporter wrote relative to its package root (vitest, jest, scoverage) are resolved against the lcov file’s own directory tree, so per-package files “just work” without a merge script.post_commit_refresh(default unset) — optional shell command the post-commit hook spawns in the background to re-run your reporter after every commit. The process is detached and its output discarded so your commit flow doesn’t wait. Pair it with the same command/heal:setup testsproposes (cargo llvm-cov --workspace --lcov --output-path lcov.info --locked --ignore-run-fail,pytest --cov=..., etc.) so the nextheal statusreads freshlcov.info. Skipped silently when[features.test]or[features.test.coverage]is off.
heal reads what your CI / local reporter produces. The default probe order covers:
| Reporter | Path written |
|---|---|
cargo llvm-cov --lcov |
target/llvm-cov/lcov.info |
pytest --cov --cov-report=lcov |
coverage/lcov.info |
nyc --reporter=lcov |
coverage/lcov-report/lcov.info |
scoverage (Scala) |
varies; symlink to lcov.info if needed |
The lcov reader is permissive — it tolerates unknown record types and recovers totals from per-line records when reporters omit the summary fields, so most reporter dialects work out of the box.
Calibration
Section titled “Calibration”Two new sections appear in .heal/calibration.toml when you run
heal calibrate --force with the test family on:
[calibration.coverage_pct]# Heal stores INVERTED values (100 - coverage_pct) so the same# `value >= p95 → Critical` cascade applies as for the rest of the# metrics — worst still maps to Critical.p50 = 30.0 # 70% coveragep75 = 50.0 # 50% coveragep90 = 70.0 # 30% coveragep95 = 85.0 # 15% coveragefloor_critical = 95.0 # ≤ 5% coverage → Critical regardless of percentilefloor_ok = 25.0 # > 75% coverage → Ok regardless of percentile
[calibration.skip_ratio]p50 = 0.0p75 = 1.0p90 = 5.0p95 = 10.0floor_critical = 20.0 # > 20% skip → Criticalfloor_ok = 0.5 # < 0.5% skip → OkThese are the literature-anchored fallbacks heal uses until you
run heal calibrate --force. Floors belong in config.toml, not
here, so they survive recalibration:
[metrics.coverage_pct]floor_critical = 90.0 # tightens to "≤ 10% coverage → Critical"
[metrics.skip_ratio]floor_ok = 0.0 # any skipped test surfaces(coverage_pct overrides apply to the inverted form —
floor_critical = 90.0 means “≤ 10% line coverage”, not “≤ 90%”.)
Post-commit nudge
Section titled “Post-commit nudge”When [features.test.coverage] is on, the post-commit hook adds an
indented second line to the nudge:
heal: recorded · 3 critical, 7 high · heal status · 2 uncovered hotspotThe count is the number of coverage_pct findings at High or
Critical severity that also carry hotspot=true. The line is
suppressed when the coverage feature is off.
Strict by design
Section titled “Strict by design”Like every other section, [features.test] and
[features.test.coverage] reject unknown keys:
[features.test]test_path = ["tests/**"] # ✘ unknown — heal errors here # (it's `test_paths`, plural)