> ## Documentation Index
> Fetch the complete documentation index at: https://docs.fallow.tools/llms.txt
> Use this file to discover all available pages before exploring further.

# fallow health

> CLI reference for fallow health. Analyze function complexity, per-file maintainability, git-churn hotspots, refactoring targets, static coverage gaps, and runtime coverage.

Analyze function complexity and file-level health across your codebase. By default, fallow reports all standard health sections: health score, complexity findings, file health scores, hotspot analysis, and refactoring targets. Coverage layers are opt-in: static reachability via `--coverage-gaps`, exact CRAP scoring via `--coverage`, and runtime production evidence via `--runtime-coverage`.

Angular templates are scanned alongside JavaScript and TypeScript functions: external `.html` files referenced via `templateUrl` and inline `@Component({ template: \`...\` })`literals both contribute synthetic`\<template>` findings when the template uses control-flow blocks (`@if`, `@for`, `@switch`, `@case`, `@defer (when ...)`, `@let`), legacy structural directives (`\*ngIf`, `\*ngFor`), or expression-bound attributes (`\[x]`, `(x)`). Inline-template findings anchor at the `@Component`decorator line and are suppressible with`// fallow-ignore-next-line complexity`directly above the decorator; external-template findings carry an HTML comment action and are suppressible with`\<!-- fallow-ignore-file complexity -->\` at the top of the template.

Angular components whose class AND template both contribute complexity also emit a synthetic `&lt;component&gt;` rollup finding. The rollup's cyclomatic and cognitive totals are `worst_class_method + template`, so a component whose class scores moderately (3/4) and whose template scores moderately (6/9) surfaces as one heavy finding (cyc 9, cog 13) rather than two scattered medium findings. The JSON `component_rollup` payload carries the pre-summation breakdown (`class_worst_function`, `class_cyclomatic`/`class_cognitive`, `template_path`, `template_cyclomatic`/`template_cognitive`, plus a `component` identifier derived from the `.ts` owner's file stem). The rollup is strictly additive: per-function and per-`&lt;template&gt;` entries stay where they are, so existing suppression sites continue to work. The rollup is anchored at the worst class method's line, so a `// fallow-ignore-next-line complexity` placed above that method hides both the function finding and the rollup. Ranking and `--targets` use the rolled-up numbers so a template-heavy component wins selection over scattered same-magnitude function findings.

<Tip>
  All sections are enabled by default, including the health score. Use section flags to select individual sections. Add `--top N` to limit results.
</Tip>

```bash theme={null}
fallow health
```

## Options

### Thresholds

| Flag                   | Description                                                                                                                                                                                                                                                                                                      |
| :--------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `--max-cyclomatic <N>` | Maximum cyclomatic complexity before reporting (default: 20)                                                                                                                                                                                                                                                     |
| `--max-cognitive <N>`  | Maximum cognitive complexity before reporting (default: 15)                                                                                                                                                                                                                                                      |
| `--max-crap <N>`       | Maximum [CRAP](/explanations/health#crap-score) score before reporting (default: 30.0). Functions meeting or exceeding this score appear alongside complexity findings. Pair with [`--coverage`](#coverage-istanbul) for accurate per-function CRAP; without it fallow estimates coverage from the module graph. |
| `--effort <LEVEL>`     | Filter refactoring targets by effort level: `low`, `medium`, or `high`. Only targets at or below the specified effort are shown. Implies `--targets`.                                                                                                                                                            |

Use `health.thresholdOverrides[]` in config for documented local ceilings on known legacy functions. Overrides match `files` globs and optional exact `functions`, can set `maxCyclomatic`, `maxCognitive`, and `maxCrap`, and can include a `reason`. They keep exceptions visible in output instead of hiding them with suppression comments.

### Output

| Flag                    | Description                                                                                                                                                                                                                |
| :---------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `-f, --format <FORMAT>` | Output format: `human` (default), `json`, `sarif`, `compact`, `markdown`, `codeclimate`, `gitlab-codequality`, `pr-comment-github`, `pr-comment-gitlab`, `review-github`, `review-gitlab`, [`badge`](/integrations/badges) |
| `--top <N>`             | Show only the top N results per section (findings, file scores, hotspots, targets)                                                                                                                                         |
| `--sort <METRIC>`       | Sort complexity findings by: `cyclomatic` (default), `cognitive`, `lines`, `severity`                                                                                                                                      |
| `--explain`             | Add metric explanations. In human format, prints a `Description:` line under each section header. In JSON format, adds a `_meta` object with metric descriptions and docs links.                                           |
| `--summary`             | Print a one-line summary of counts at the end of the run. In JSON format, adds a `summary` counts object.                                                                                                                  |

### Section selection

By default, all standard sections are included. Use these flags to select individual sections:

| Flag                              | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        |
| :-------------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `--complexity`                    | Show only complexity findings (functions exceeding thresholds).                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |
| `--complexity-breakdown`          | Add a per-decision-point `contributions[]` array to each complexity finding in `--format json`, explaining which constructs drove the score. Each entry names the construct (`if`, `else-if`, `ternary`, `&&` / `\|\|` / `??`, loop, `case`, `catch`, `optional-chain`, ...) and carries its source line, the metric it adds to (`cyclomatic` or `cognitive`), its weight, and the nesting depth. Off by default to keep CLI/CI output lean; used by the VS Code inline editor breakdown and the MCP `check_health` tool.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          |
| `--css`                           | Add a structural CSS analytics section (`css_analytics` in JSON, `CSS health` in human, `## CSS Health` in markdown). Surfaces codebase-scale CSS slop that per-rule linters do not aggregate: specificity hotspots (id selectors, deep compound selectors), `!important` density, over-complex selectors, deep nesting, empty rules, and design-token sprawl (the count of distinct color, `font-size`, `z-index`, `box-shadow`, `border-radius`, and `line-height` values across the whole codebase). Cleanup candidates include custom properties (`--x`) and `@keyframes` defined but never referenced, dead Vue `<style scoped>` classes, unused `@property` / `@layer` at-rules, Tailwind arbitrary-value bypasses like `w-[13px]`, Tailwind v4 `@theme` design tokens (`unused_theme_tokens`), unused `@font-face` families (`unused_font_faces`), class typo candidates (`unresolved_class_references`), undefined animations (`undefined_keyframes`), and mixed font-size units (`font_size_unit_mix`). Each finding carries a read-only verification step; everything is framed as a candidate (a token can still be used from JavaScript, inline HTML, Tailwind plugins, or external templates), never a gated finding. Opt-in because it reads project stylesheets and cross-checks source files. Standard CSS and Vue/Svelte/Astro standard `<style>` blocks are parsed structurally; Sass/Less sources are scanned only where fallow can stay conservative without expanding preprocessor semantics. |
| `--file-scores`                   | Show only per-file health scores. Requires the full analysis pipeline (graph + dead code detection). File scores are sorted by risk-aware triage concern: lower maintainability index and higher CRAP risk first. `--sort` and `--baseline` apply to complexity findings only, not file scores.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |
| `--hotspots`                      | Show only hotspot analysis: files that are both complex and frequently changing. Combines git churn history with complexity data. Uses git history; outside a git repository the section degrades to empty (with a stderr note) and exits 0 instead of erroring, so combined-mode `--format json` always emits a single document.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  |
| `--targets`                       | Show only refactoring targets: ranked recommendations with priority scores, effort estimates, contributing factors, and evidence for AI agents. Each target's JSON can include `direct_callers[]` (direct importers with the symbols they import) and `clone_siblings[]` (duplicate-code siblings with stable `dup:<8hex>` fingerprints, ready for `fallow dupes --trace`); both arrays are omitted when empty. Human output adds `importers:` and `clones:` lines only when that evidence is present.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             |
| `--score`                         | Show only the project health score (0-100) with letter grade (A/B/C/D/F). The score is included by default when no section flags are set. As of v2.55.0, plain `--score` skips the churn-backed hotspot penalty so the score doesn't run a `git log` shell-out per invocation. Pass `--hotspots` (or `--targets` with `--score`) to include the hotspot penalty; snapshot (`--save-snapshot`) and trend (`--trend`) flows still trigger hotspot vital signs so saved data stays complete.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          |
| `--coverage-gaps`                 | Show runtime files and exports that no test dependency path reaches. Opt-in (default off). Configure severity via the `coverage-gaps` rule.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        |
| `--coverage <PATH>`               | Path to Istanbul-format coverage data (`coverage-final.json`) for accurate per-function CRAP scores. Produced by Jest, Vitest, c8, nyc. When provided, uses the canonical formula `CC^2 * (1-cov/100)^3 + CC` instead of the static binary model. Relative paths resolve against `--root`. Falls back to `FALLOW_COVERAGE`, then `health.coverage`, then auto-detection.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           |
| `--coverage-root <PATH>`          | Absolute prefix to strip from file paths in coverage data before prepending the project root. Use when coverage was generated in a different environment (CI runner, Docker), for example `/home/runner/work/myapp`. Falls back to `FALLOW_COVERAGE_ROOT`, then `health.coverageRoot`.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             |
| `--runtime-coverage <PATH>`       | Merge runtime coverage into the health report. Accepts a V8 coverage directory, a single V8 JSON file, or a single Istanbul coverage map JSON file. A single local capture is free and does not require a license; continuous or multi-capture runtime monitoring requires a valid license or trial.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               |
| `--min-invocations-hot <N>`       | Threshold for hot-path classification when `--runtime-coverage` is active (default: `100`).                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        |
| `--min-observation-volume <N>`    | Minimum total trace volume before the sidecar may emit high-confidence `safe_to_delete` / `review_required` verdicts. Below this, confidence is capped at `medium` (default: `5000`).                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              |
| `--low-traffic-threshold <RATIO>` | Fraction of total trace count below which an invoked function is classified `low_traffic` rather than `active` (default: `0.001` = 0.1%).                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          |

Multiple section flags can be combined (e.g., `--complexity --hotspots`). Without any section flags, all sections are included.

### Health score options

| Flag                     | Description                                                                                                                                                                                                                                                                                                                                      |
| :----------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `--min-score <N>`        | Fail (exit code 1) only when the health score is below this threshold. Implies `--score`. The authoritative CI quality gate: when set, complexity findings are demoted to informational and the exit code is driven solely by the score, so `--min-score 0` always exits 0. Composes with `--min-severity` (the run fails if either gate trips). |
| `--min-severity <LEVEL>` | Only exit with an error for findings at or above this severity (`moderate`, `high`, `critical`). Composes with `--min-score`.                                                                                                                                                                                                                    |
| `--report-only`          | Print the score and findings but never fail CI (always exit 0). Advisory mode for surfacing health in logs without blocking. Mutually exclusive with `--min-score` and `--min-severity`.                                                                                                                                                         |

### Hotspot options

| Flag                        | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       |
| :-------------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `--since <DURATION>`        | Git history window for hotspot analysis (default: `6m`). Accepts durations (`6m`, `90d`, `1y`, `2w`) or ISO dates (`2025-06-01`). Ignored when `--churn-file` is set (the file is authoritative for the window).                                                                                                                                                                                                                                                                                                  |
| `--min-commits <N>`         | Minimum number of commits for a file to be included in hotspot ranking (default: 3).                                                                                                                                                                                                                                                                                                                                                                                                                              |
| `--churn-file <PATH>`       | Import change history from a `fallow-churn/v1` JSON file instead of `git log`, so hotspots, ownership, and targets work on projects with no git repository (Yandex Arc, Mercurial, Perforce). Resolved relative to `--root`; wins over git when both are present. See [Importing churn from a non-git VCS](#importing-churn-from-a-non-git-vcs).                                                                                                                                                                  |
| `--ownership`               | Attach ownership signals to each hotspot entry: bus factor (Avelino truck factor), contributor count, top contributor with stale-days, recent contributors (top-3), `suggested_reviewers`, declared CODEOWNERS owner, `ownership_state`, ownership drift, and unowned-hotspot detection. Human output gains a project-level summary line above the hotspot list. Test paths get a `[test]` tag. Implies `--hotspots`. Uses git history; outside a git repository the hotspot/ownership section degrades to empty. |
| `--ownership-emails <MODE>` | Privacy mode for author emails: `handle` (default, local-part only with GitHub noreply unwrap and deterministic same-handle disambiguation), `anonymized` (stable `xxh3:` pseudonym), legacy `hash` (same as `anonymized`), or `raw` (full email). Implies `--ownership`. Use `anonymized` in regulated environments where author identities are sensitive. Configure the default via `health.ownership.emailMode` in config.                                                                                     |

### Scoping

| Flag                                              | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      |
| :------------------------------------------------ | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `-w, --workspace <NAME>`                          | Scope output to one or more workspace packages while keeping the full cross-workspace graph. Comma-separated values, globs (`apps/*`, `@scope/*`), and `!`-prefixed negation are supported. Vital signs, health score, hotspots, file scores, findings, and `summary.files_analyzed` are all recomputed against the scoped subset.                                                                                                                                                                                                                                                                                                                                                                                                                                                                               |
| `--changed-since <REF>`                           | Only analyze functions in files changed since the given git ref (e.g., `main`, `HEAD~5`). Also applies to hotspot analysis.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      |
| `--group-by <owner\|directory\|package\|section>` | Partition the report into per-group sections. JSON output adds `grouped_by` plus a `groups` array; each group contains its own `vital_signs`, `health_score`, `findings`, `file_scores`, `hotspots`, `large_functions`, and `targets` recomputed against the group's files. The top-level metrics stay project-wide so consumers that ignore grouping still see the project headline. Human output adds a per-group score / files / hot / p90 summary block (sorted worst-first when `--score`). SARIF tags every result with `properties.group` and CodeClimate adds a top-level `group` field per issue, so GitHub Code Scanning and GitLab Code Quality can partition findings per team / package. Compact, markdown, and badge fall back to ungrouped output with a stderr note pointing at `--format json`. |

### Snapshots & trends

| Flag                     | Description                                                                                                                                                                                              |
| :----------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `--save-snapshot [PATH]` | Save a vital signs snapshot for trend tracking. Default path: `.fallow/snapshots/<timestamp>.json`. Forces file-scores and hotspot computation for complete metrics.                                     |
| `--trend`                | Compare current metrics against the most recent saved snapshot. Reads from `.fallow/snapshots/` and shows per-metric deltas with directional indicators (improving/declining/stable). Implies `--score`. |

<Tip>
  `--score`, `--trend`, and `--save-snapshot` also work on bare `fallow` (all analyses combined). See [global flags](/cli/global-flags) for details.
</Tip>

### Baseline

| Flag              | Description                                                                                                          |
| :---------------- | :------------------------------------------------------------------------------------------------------------------- |
| `--save-baseline` | Save current complexity findings as a baseline for future comparison                                                 |
| `--baseline`      | Compare against a saved baseline and only report new findings. Applies to complexity findings only, not file scores. |

## Exit codes

The default `fallow health` (no gate flag) is advisory-by-default for plain visibility but still fails on any finding, for back-compatibility. The gate flags change what drives the exit code:

| Invocation                                         | Exit 0 when                                 | Exit 1 when                               |
| :------------------------------------------------- | :------------------------------------------ | :---------------------------------------- |
| `fallow health` (no gate flag)                     | no function exceeds a threshold             | any function exceeds a threshold          |
| `fallow health --min-score N`                      | score >= N (findings are informational)     | score \< N                                |
| `fallow health --min-severity LEVEL`               | no finding at or above LEVEL                | any finding at or above LEVEL             |
| `fallow health --min-score N --min-severity LEVEL` | score >= N AND no finding at or above LEVEL | score \< N OR a finding at or above LEVEL |
| `fallow health --report-only`                      | always                                      | never                                     |

`--report-only` cannot be combined with `--min-score` or `--min-severity` (exit code 2). The runtime-coverage gate (`--runtime-coverage`) and coverage-gap gate remain independent explicit opt-ins and are not demoted by `--min-score`. For a gate on newly-introduced complexity only, use [`fallow audit --gate new-only`](/cli/audit).

## Maintainability index formula

File scores are ordered by risk-aware triage concern: the larger of low-MI concern and CRAP risk. This keeps files with very high untested complexity near the top even when their Maintainability Index is not the lowest.

The per-file maintainability index is a weighted composite score:

```
fan_out_penalty = min(ln(fan_out + 1) × 4, 15)
MI = 100 - (complexity_density × 30) - (dead_code_ratio × 20) - fan_out_penalty
```

Clamped to 0–100. Higher is better. Files with zero functions (barrel/re-export files) are excluded by default.

| Component            | Description                                                                                                              | Weight |
| :------------------- | :----------------------------------------------------------------------------------------------------------------------- | :----- |
| `complexity_density` | Total cyclomatic complexity / lines of code                                                                              | ×30    |
| `dead_code_ratio`    | Fraction of **value** exports with zero references (0.0–1.0). Type-only exports (interfaces, type aliases) are excluded. | ×20    |
| `fan_out_penalty`    | Logarithmic scaling of import count, capped at 15 points. Each additional import adds less penalty than the last.        | max 15 |

Additional metrics reported per file (informational, not in the formula):

| Metric                 | Description                                                                                                                                                                                  |
| :--------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `fan_in`               | Number of files that import this file                                                                                                                                                        |
| `total_cyclomatic`     | Sum of cyclomatic complexity across all functions                                                                                                                                            |
| `total_cognitive`      | Sum of cognitive complexity across all functions                                                                                                                                             |
| `function_count`       | Number of functions in the file                                                                                                                                                              |
| `lines`                | Total lines of code                                                                                                                                                                          |
| `crap_max`             | Highest CRAP score among the file's functions. Combines cyclomatic complexity with test coverage to estimate untested risk. See [CRAP metric](/explanations/health#crap-metric) for details. |
| `crap_above_threshold` | Number of functions in the file with a CRAP score at or above the threshold (default: 30).                                                                                                   |

## Hotspot score formula

The hotspot score identifies files that are both complex and frequently changed. These files are the most likely to harbor bugs and slow down development. The score combines git churn with complexity density:

```
normalized_churn = weighted_commits / max_weighted_commits (0..1)
normalized_complexity = complexity_density / max_density (0..1)
score = normalized_churn × normalized_complexity × 100
```

Clamped to 0–100. Higher means higher risk. Scores are normalized within the project, so the highest-risk file always scores close to 100.

| Component            | Description                                                                                                                  |
| :------------------- | :--------------------------------------------------------------------------------------------------------------------------- |
| `weighted_commits`   | Recency-weighted commit count using exponential decay with a 90-day half-life. Recent changes contribute more than old ones. |
| `complexity_density` | Cyclomatic complexity / lines of code. Measures how densely complex the code is.                                             |

### Per-file hotspot metrics

Each hotspot entry includes:

| Metric               | Description                                                                            |
| :------------------- | :------------------------------------------------------------------------------------- |
| `path`               | Relative file path                                                                     |
| `score`              | 0–100 hotspot score (higher = higher risk)                                             |
| `commits`            | Raw commit count in the analysis window                                                |
| `weighted_commits`   | Recency-weighted commit count (exponential decay, 90-day half-life)                    |
| `lines_added`        | Total lines added in the analysis window                                               |
| `lines_deleted`      | Total lines deleted in the analysis window                                             |
| `complexity_density` | Cyclomatic complexity / lines of code                                                  |
| `fan_in`             | Blast radius: number of files importing this file                                      |
| `trend`              | `Accelerating`, `Stable`, or `Cooling`. Shows the direction of recent change activity. |

### Hotspot summary

The hotspot summary (included in JSON output) provides context about the analysis:

| Field            | Description                                               |
| :--------------- | :-------------------------------------------------------- |
| `since`          | Display string for the analysis window (e.g., "6 months") |
| `min_commits`    | Minimum commit threshold used                             |
| `files_analyzed` | Files meeting the `min_commits` threshold                 |
| `files_excluded` | Files below the `min_commits` threshold                   |
| `shallow_clone`  | Detection flag. Warns if a shallow clone was detected.    |

<Note>
  Hotspot analysis uses git history. Outside a git repository the section degrades to empty with a `note: hotspot analysis skipped: no git repository found at project root` on stderr (suppressed by `--quiet`); standalone `fallow health --hotspots --format json` exits 0 with `hotspots` and `hotspot_summary` omitted, and combined-mode `--format json` always emits a single JSON document. Shallow clones may produce incomplete results. Fallow detects this and warns you. For best results, ensure a full clone with `git fetch --unshallow`.
</Note>

## Ownership analysis

Pass `--ownership` (alongside or instead of `--hotspots`) to enrich each hotspot entry with ownership signals derived from git author history and the repository's CODEOWNERS file. Useful for surfacing knowledge-loss risk, finding unowned high-churn files, and routing review requests.

```bash theme={null}
fallow health --hotspots --ownership
fallow health --hotspots --ownership --ownership-emails anonymized --format json
```

### Project-level summary

Human output prepends a summary line above the hotspot list showing how many hotspots depend on a single recent contributor and the top authors across the set:

```
● Hotspots (10 files, since 6 months)

  9/10 hotspots depend on a single recent contributor  ·  top authors: alice (6), bob (4)
```

### Per-hotspot ownership fields

| Field                 | Description                                                                                                                                                                                                                                                          |
| :-------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `bus_factor`          | Avelino truck factor: minimum contributors covering at least 50% of recency-weighted commits. `1` is the canonical "single point of failure" signal.                                                                                                                 |
| `contributor_count`   | Distinct authors after bot-pattern filtering.                                                                                                                                                                                                                        |
| `top_contributor`     | Object with `identifier`, `format` (`raw`/`handle`/`anonymized`/`hash`), `share` (0..1), `stale_days`, and `commits` for the highest-share contributor. The `identifier` is rendered per the configured `ownership.emailMode`; do not assume it is an email address. |
| `recent_contributors` | Up to three additional contributors by share (top-3 excluding the top contributor).                                                                                                                                                                                  |
| `suggested_reviewers` | Subset of `recent_contributors` whose `stale_days` is below 90. First-class field so AI agents can route "Request review from @X, @Y" without re-filtering. Omitted when empty.                                                                                      |
| `declared_owner`      | The CODEOWNERS-resolved primary owner for this file, when a rule matches.                                                                                                                                                                                            |
| `ownership_state`     | Stable discriminator for ownership routing: `active`, `unowned`, `declared_inactive`, or `drifting`. Declared owners suppress vague git-history drift only when fallow can match them to an active contributor offline.                                              |
| `unowned`             | Tristate: `true` = a CODEOWNERS file exists but no rule matches; `false` = a rule matches; `null` = no CODEOWNERS file was discovered (cannot determine).                                                                                                            |
| `drift`               | True when the file's original author no longer maintains it. Fires only when file age >= 30 days AND the original author's recency-weighted share is below 10%.                                                                                                      |
| `drift_reason`        | Human-readable explanation of the drift, populated only when `drift` is `true`.                                                                                                                                                                                      |

### Severity in human output

The `bus=` marker is color-coded proportional to the actual risk:

* `bus=1 (sole author)`: **red + bold** when share is 100% (one person, no co-authors)
* `bus=1 (at risk)`: **red + bold** when bus=1 AND trend is accelerating (active, high-concentration file)
* `bus=1`: **yellow** for the common case (bus=1 without extreme share or acceleration)
* `bus=2` / `bus=N`: dimmed (healthy)

This keeps red reserved for the strongest signals so it stays meaningful on repos where most hotspots are single-contributor.

### Test-path tag

Files matching common test conventions (`**/__tests__/**`, `**/__mocks__/**`, `**/*.test.*`, `**/*.spec.*`, `**/test/**`, `**/tests/**`) are intentionally kept in the hotspot ranking (test maintenance IS real work) but tagged with `[test]` so readers can distinguish them from production code. JSON consumers see an `is_test_path: true` field.

### Ownership-derived JSON actions

When `--ownership` is enabled, the `actions` array on hotspot entries gains up to three additional action types so AI agents can act on ownership signals:

| Action type       | When emitted                                           | Suggested follow-up                                                                                                                                                                                                                    |
| :---------------- | :----------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `low-bus-factor`  | `bus_factor == 1`                                      | Add a second reviewer to the file. The `note` names specific candidate reviewers (from `suggested_reviewers`) when they exist, softens for low-commit files, or is omitted to avoid boilerplate.                                       |
| `unowned-hotspot` | `unowned == true` (CODEOWNERS exists, no rule matches) | Add a CODEOWNERS entry. The action's `suggested_pattern` field offers a sensible default (the deepest directory containing the file, e.g. `/src/api/users/`), and a `heuristic: "directory-deepest"` field discriminates the strategy. |
| `ownership-drift` | `ownership_state == "drifting"` and `drift == true`    | Update CODEOWNERS to the new top contributor.                                                                                                                                                                                          |

### Email privacy

By default, fallow renders author emails as their local-part (e.g. `alice@example.com` shows as `alice`). GitHub-style noreply prefixes are unwrapped (`12345+alice@users.noreply.github.com` shows as `alice`). Override with `--ownership-emails`:

| Mode               | Output                                                                                          | When to use                                                   |
| :----------------- | :---------------------------------------------------------------------------------------------- | :------------------------------------------------------------ |
| `handle` (default) | Local-part only, with deterministic domain suffixes for same-handle collisions on the same file | Most projects. Balances readability and privacy.              |
| `anonymized`       | `xxh3:<16hex>` non-cryptographic pseudonym                                                      | Regulated environments where author identities are sensitive. |
| `hash`             | Same as `anonymized`                                                                            | Legacy configs and scripts.                                   |
| `raw`              | Full email address                                                                              | Public OSS repositories where git history is already exposed. |

The anonymized/hash mode uses `xxh3` for stable pseudonyms across runs but is **not** a cryptographic primitive: a known list of org emails can be brute-forced into a rainbow table. The intent is to keep raw PII out of CI artifacts (SARIF, code-scanning uploads), not to provide strong privacy.

### Configuration

Configure ownership defaults under `health.ownership`:

```json theme={null}
{
  "health": {
    "ownership": {
      "botPatterns": ["custom-svc-*", "*\\[bot\\]*"],
      "emailMode": "handle"
    }
  }
}
```

`botPatterns` are glob patterns matched against the raw author email. The default list covers `*\[bot\]*` (escaped brackets, since globset treats `[abc]` as a character class), `dependabot*`, `renovate*`, `github-actions*`, `svc-*`, and `*-service-account*`. `*noreply*` is intentionally NOT a default: most human GitHub contributors commit from `<id>+<handle>@users.noreply.github.com` (GitHub's privacy default), so filtering on `noreply` would silently exclude the majority of real authors. The actual bot accounts already match via `\[bot\]`.

<Note>
  Ownership signals are computed only when `--hotspots` runs and a file passes the `min_commits` threshold (default 3). This gates the analysis on enough history to be meaningful. Squash-merged commits inflate single-author dominance, and shallow clones distort the picture further; fallow warns about both when `--ownership` is active.
</Note>

## Importing churn from a non-git VCS

`--hotspots`, `--ownership`, and `--targets` read change history from `git log`. On a project with no git repository (Yandex Arc, Mercurial, Perforce) they would otherwise print `note: hotspot analysis skipped: no git repository found` and exit 0.

`--churn-file <PATH>` lets you feed history from a normalized JSON file instead. A small wrapper translates your VCS log into the contract; fallow then runs all the same recency-weighting, trend, and ownership logic on the imported events. The file is resolved relative to `--root` and wins over git when both are present.

```bash theme={null}
fallow health --hotspots --ownership --churn-file churn.json
```

### The `fallow-churn/v1` contract

```json theme={null}
{
  "schema": "fallow-churn/v1",
  "events": [
    { "path": "src/foo.ts", "timestamp": 1717459200, "author": "dev@corp", "added": 10, "deleted": 5 }
  ]
}
```

One entry per `(commit, file)` touched, the natural shape of a `<vcs> log --numstat`.

| Field               | Required | Meaning                                                                                                                                                                             |
| :------------------ | :------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `path`              | yes      | Repo-root-relative, forward-slash. Joined to `--root`.                                                                                                                              |
| `timestamp`         | yes      | Commit time, unix **seconds** UTC (not milliseconds). Drives the recency half-life and the accelerating/stable/cooling trend.                                                       |
| `author`            | no       | Opaque identity, email recommended. Drives ownership and bus-factor. Absent leaves the event counting toward churn/trend but contributing no author signal.                         |
| `added` / `deleted` | yes      | Lines changed in that file in that commit. Best-effort: the hotspot score keys on commit-event count, not lines, so imprecise counts still rank correctly; lines feed display only. |

Extra fields are ignored, so a wrapper may carry additional metadata (a `commit` id, a timezone) without breaking.

### Producer requirements

fallow's git path runs `git log --numstat --no-merges --no-renames --use-mailmap`. A wrapper should reproduce these four behaviors or hotspots and ownership skew:

1. **Omit merge commits.** Otherwise the merge author looks like a contributor to every merged file, inflating bus-factor and skewing the trend.
2. **Omit binary-file rows entirely** (do not emit `0`/`0`). git skips binary `-`/`-` numstat rows.
3. **Renames: emit under the new path only**, with the real add/delete count; do not emit a delete-of-old row. (Perforce models a move as delete-old + add-new full-size rows, which would create fake hotspots.)
4. **One row per `(commit, file)`**, with `timestamp` in UTC **seconds**.

Two more notes:

* **Author identity is an opaque key.** fallow does NOT apply mailmap to imported authors, so emit a single canonical form per person (email preferred). If your VCS commits land under a merge/CI bot identity, add it to `health.ownership.botPatterns` in config or bus-factor collapses to 1 everywhere.
* **Validation is strict where the math depends on it.** A `timestamp` more than a year in the future (almost always a millisecond value mistaken for seconds) is rejected with exit 2; an empty `events` array is valid (no hotspots), not an error; a malformed file is a loud hard error, not a silent skip.

### Reference wrapper (Mercurial)

The wrapper translates your VCS log into the contract. This Mercurial example pulls the last six months, excludes merges, and emits one event per file per commit:

```bash theme={null}
# hg-churn.sh: emit fallow-churn/v1 for the last 6 months (no merges).
hg log --no-merges --rev 'date(">6 months ago")' \
  --template '{date|hgdate}\t{author|email}\t{join(files, "\t")}\n' \
| python3 -c '
import json, sys
events = []
for line in sys.stdin:
    cols = line.rstrip("\n").split("\t")
    if len(cols) < 3:
        continue
    ts = int(cols[0].split()[0])   # hgdate is "epoch tzoffset"; take the epoch
    author = cols[1]
    for path in cols[2:]:
        events.append({"path": path, "timestamp": ts, "author": author,
                       "added": 0, "deleted": 0})
print(json.dumps({"schema": "fallow-churn/v1", "events": events}))
' > churn.json
```

The hg template separates fields with tabs (`\t`), which never appear in paths or emails. Per-file `added`/`deleted` are left `0`; fill them in if your VCS exposes line counts cheaply. The hotspot score is driven by commit-event count rather than line totals, so ranking stays correct either way. For Yandex Arc, drive the same shape from `arc log`; for Perforce, from `p4 changes` + `p4 describe`.

### Scope

`--churn-file` powers the churn-backed health signals only: `--hotspots`, `--ownership`, and `--targets`. The diff-based commands stay git-only because they need the base revision's tree, not just history:

* `--changed-since` line/file filtering already has a file-based equivalent: `--diff-file` / `--diff-stdin` accept a unified diff. If your VCS can emit a git-format patch, point those at it.
* `fallow audit` and `fallow impact` check out the base revision into a temporary git worktree and re-analyze it, which needs real base-tree contents. They require git.

## Vital signs

When file-scores are enabled (either explicitly via `--file-scores` or implicitly via `--save-snapshot`), the JSON output includes a `vital_signs` object with high-level codebase health metrics. These metrics provide a single-glance summary of code quality.

| Metric                        | Description                                                                                                                                                        |
| :---------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `dead_file_pct`               | Percentage of files with zero inbound references                                                                                                                   |
| `dead_export_pct`             | Percentage of value exports with zero references                                                                                                                   |
| `avg_cyclomatic`              | Average cyclomatic complexity across all functions                                                                                                                 |
| `critical_complexity_pct`     | Percentage of functions at or above the critical cyclomatic threshold. Used by health score formula v2.                                                            |
| `p90_cyclomatic`              | 90th percentile cyclomatic complexity                                                                                                                              |
| `duplication_pct`             | Percentage of duplicated code. Populated automatically when `--score` is used; null otherwise.                                                                     |
| `hotspot_count`               | Number of files with a hotspot score >= 50                                                                                                                         |
| `hotspot_top_pct_count`       | Number of positive-score files in the top 1% of the within-project hotspot ranking                                                                                 |
| `maintainability_avg`         | Average maintainability index across all scored files                                                                                                              |
| `maintainability_low_pct`     | Percentage of scored files with maintainability index below 70                                                                                                     |
| `unused_dep_count`            | Number of unused dependencies detected                                                                                                                             |
| `unused_deps_per_k_files`     | Unused dependencies per 1,000 files                                                                                                                                |
| `circular_dep_count`          | Number of circular dependency cycles detected                                                                                                                      |
| `circular_deps_per_k_files`   | Circular dependency cycles per 1,000 files                                                                                                                         |
| `unit_size_profile`           | Per-function risk distribution by LOC (percentage per bin)                                                                                                         |
| `functions_over_60_loc_per_k` | Functions above 60 LOC per 1,000 functions                                                                                                                         |
| `unit_interfacing_profile`    | Per-function risk distribution by parameter count (percentage per bin)                                                                                             |
| `p95_fan_in`                  | 95th percentile fan-in across files                                                                                                                                |
| `coupling_high_pct`           | Percentage of files exceeding the fan-in threshold                                                                                                                 |
| `counts`                      | Raw numerator/denominator counts behind the percentage metrics (e.g. `dead_files`, `total_files`, `dead_exports`, `total_exports`, `unused_deps`, `circular_deps`) |

## Snapshots

Use `--save-snapshot` to capture a point-in-time record of your codebase's vital signs. Snapshots enable trend tracking across builds, sprints, or releases.

```bash theme={null}
# Save snapshot to the default path (.fallow/snapshots/<timestamp>.json)
fallow health --save-snapshot

# Save snapshot to a specific path
fallow health --save-snapshot ./snapshot.json
```

The `--save-snapshot` flag forces file-scores and hotspot computation so that all vital signs metrics are available, regardless of which section flags are passed.

### Snapshot format

```json title="snapshot.json" theme={null}
{
  "snapshot_schema_version": 8,
  "version": "2.102.0",
  "timestamp": "2026-03-25T14:30:00Z",
  "git_sha": "a1b2c3d",
  "git_branch": "main",
  "shallow_clone": false,
  "vital_signs": {
    "dead_file_pct": 4.2,
    "dead_export_pct": 12.8,
    "avg_cyclomatic": 3.1,
    "critical_complexity_pct": 1.2,
    "p90_cyclomatic": 11,
    "duplication_pct": null,
    "hotspot_count": 5,
    "hotspot_top_pct_count": 3,
    "maintainability_avg": 72.4,
    "maintainability_low_pct": 9.1,
    "unused_dep_count": 3,
    "unused_deps_per_k_files": 11.5,
    "circular_dep_count": 1,
    "circular_deps_per_k_files": 3.8,
    "unit_size_profile": {
      "low_risk": 82.1,
      "medium_risk": 11.4,
      "high_risk": 4.3,
      "very_high_risk": 2.2
    },
    "functions_over_60_loc_per_k": 22.0,
    "unit_interfacing_profile": {
      "low_risk": 95.6,
      "medium_risk": 3.8,
      "high_risk": 0.5,
      "very_high_risk": 0.1
    },
    "p95_fan_in": 8,
    "coupling_high_pct": 2.3
  },
  "counts": {
    "dead_files": 11,
    "total_files": 262,
    "dead_exports": 48,
    "total_exports": 375,
    "unused_deps": 3,
    "circular_deps": 1
  }
}
```

| Field                     | Description                                                   |
| :------------------------ | :------------------------------------------------------------ |
| `snapshot_schema_version` | Schema version for forward compatibility (currently v8)       |
| `version`                 | Fallow version that produced the snapshot                     |
| `timestamp`               | ISO 8601 timestamp of the snapshot                            |
| `git_sha`                 | Current git commit SHA (if in a git repo)                     |
| `git_branch`              | Current git branch name                                       |
| `shallow_clone`           | Whether a shallow clone was detected                          |
| `vital_signs`             | High-level health metrics (see [Vital signs](#vital-signs))   |
| `counts`                  | Raw numerators and denominators behind the percentage metrics |
| `score`                   | Project health score (0-100). Present in v2+ snapshots.       |
| `grade`                   | Letter grade (A/B/C/D/F). Present in v2+ snapshots.           |

<Tip>
  Store snapshots in CI artifacts or commit them to your repo to build a history of codebase health over time. Snapshots automatically include the health score and grade.
</Tip>

## Examples

<CodeGroup>
  ```bash Basic analysis theme={null}
  # Report functions exceeding default thresholds
  fallow health
  ```

  ```bash Top hotspots theme={null}
  # Show the 10 most complex functions
  fallow health --top 10

  # Sort by cognitive complexity
  fallow health --top 10 --sort cognitive

  # Sort by lines of code
  fallow health --top 10 --sort lines
  ```

  ```bash Health score theme={null}
  # Project health score with letter grade
  fallow health --score

  # CI quality gate: fail if score below 70
  fallow health --min-score 70

  # JSON output with penalty breakdown
  fallow health --score --format json
  ```

  ```bash File health scores theme={null}
  # Per-file health scores
  fallow health --file-scores

  # Top 20 files by triage concern
  fallow health --file-scores --top 20

  # JSON output with file scores
  fallow health --file-scores --format json
  ```

  ```bash Hotspot analysis theme={null}
  # Identify hotspots (complex + frequently changed)
  fallow health --hotspots

  # Custom history window and commit threshold
  fallow health --hotspots --since 90d --min-commits 5

  # Top 10 hotspots in JSON
  fallow health --hotspots --top 10 --format json

  # Hotspots for a specific workspace
  fallow health --hotspots --workspace my-app

  # Hotspots using an ISO date
  fallow health --hotspots --since 2025-06-01

  # Combine with file scores for full picture
  fallow health --hotspots --file-scores
  ```

  ```bash Refactoring targets theme={null}
  # Ranked refactoring recommendations with effort estimates
  fallow health --targets

  # Only low-effort quick wins
  fallow health --targets --effort low

  # Top 5 targets
  fallow health --targets --top 5

  # JSON with evidence for AI agents
  fallow health --targets --format json

  # Combine with file scores and hotspots
  fallow health --targets --file-scores --hotspots

  # Save baseline, then only show new targets
  fallow health --targets --save-baseline fallow-baselines/health.json
  fallow health --targets --baseline fallow-baselines/health.json
  ```

  ```bash Custom thresholds theme={null}
  # Stricter thresholds
  fallow health --max-cyclomatic 10 --max-cognitive 8

  # Only check cognitive complexity
  fallow health --max-cyclomatic 999 --max-cognitive 10

  # Fail when any function has CRAP >= 30 (complex AND poorly tested)
  fallow health --max-crap 30 --coverage ./coverage/coverage-final.json

  # Effectively disable the CRAP gate for focused cyclomatic runs
  fallow health --max-cyclomatic 10 --max-crap 10000
  ```

  ```bash Workspace scoping theme={null}
  # Scope to a single workspace package
  fallow health --workspace my-app

  # Only check changed files in a PR
  fallow health --changed-since main
  ```

  ```bash Baseline comparison theme={null}
  # Save current findings as baseline
  fallow health --save-baseline

  # Only report new findings compared to baseline
  fallow health --baseline

  # SARIF output for GitHub Code Scanning
  fallow health --format sarif
  ```

  ```bash CI integration theme={null}
  # JSON output for pipelines
  fallow health --format json

  # Compact output for summaries
  fallow health --format compact

  # Markdown for PR comments
  fallow health --file-scores --format markdown

  # Markdown piped to a PR comment
  fallow health --file-scores --format markdown | gh pr comment --body-file -
  ```

  ```bash Snapshots & trends theme={null}
  # Save snapshot to default path (.fallow/snapshots/<timestamp>.json)
  fallow health --save-snapshot

  # Save snapshot to a specific file
  fallow health --save-snapshot ./snapshot.json

  # CI: save snapshot as a build artifact
  fallow health --save-snapshot ./snapshot.json --format json

  # Compare current metrics against the most recent snapshot
  fallow health --trend

  # JSON output with trend deltas
  fallow health --trend --format json
  ```

  ```bash Coverage gaps theme={null}
  # Find runtime code not reached by any test
  fallow health --coverage-gaps

  # Coverage gaps in JSON
  fallow health --coverage-gaps --format json

  # Combine with file scores
  fallow health --coverage-gaps --file-scores
  ```
</CodeGroup>

## Example output

```bash title="$ fallow health --file-scores --top 3" theme={null}
● High complexity functions (3 shown, 24 total)
  CRAP scores are estimated from export references; run `fallow health --coverage <coverage-final.json>` for exact scores.
  src/diff/index.js
    :48 diff
          67 cyclomatic  138 cognitive  290 lines
         420.0 CRAP
    :381 diffElementNodes
          63 cyclomatic  105 cognitive  200 lines
         305.0 CRAP
  src/utils/parser.ts
    :15 parseExpression
          25 cyclomatic   31 cognitive   98 lines
          42.0 CRAP
  Functions exceeding cyclomatic, cognitive, or CRAP thresholds (https://docs.fallow.tools/explanations/health#complexity-metrics)

● File health scores (3 files) · sorted by triage concern

   52.3    src/legacy/handler.ts      risk
            312 LOC    2 fan-in   18 fan-out   45% dead  0.38 density  42.0 risk

   68.4    src/diff/index.js          risk
            847 LOC    3 fan-in   12 fan-out    0% dead  0.89 density  12.0 risk

   75.1    src/utils/parser.ts        structure
            198 LOC    8 fan-in    4 fan-out   25% dead  0.22 density   6.0 risk

  Sorted by triage concern: the larger of low-MI concern and CRAP risk. The risk / structure tag marks which one placed each file. MI reflects complexity, coupling, and dead code; risk reflects untested complexity (CRAP) and can diverge from MI. Risk: low <15, moderate 15-30, high >=30. CRAP estimated from export references (85% direct, 40% indirect, 0% untested). Run `fallow health --coverage <coverage-final.json>` for exact scores. https://docs.fallow.tools/explanations/health#file-health-scores

✗ 24 above threshold · 847 analyzed (0.08s)
```

```bash title="$ fallow health --hotspots --top 3" theme={null}
● Hotspots (3 files, since 6 months)

   92.0 ▲  src/diff/index.js
           47 commits   2460 churn  0.89 density   3 fan-in  ▲ accelerating

   71.0 ─  src/legacy/handler.ts
           23 commits    720 churn  0.38 density   2 fan-in  ─ stable

   38.0 ▼  src/utils/parser.ts
           12 commits    300 churn  0.22 density   8 fan-in  ▼ cooling

  18 files excluded (< 3 commits)

  Files with high churn and high complexity: https://docs.fallow.tools/explanations/health#hotspot-metrics

✗ 3 hotspots · 847 analyzed (0.32s)
```

## JSON output

### Complexity findings

```json title="$ fallow health --format json --top 2" theme={null}
{
  "schema_version": 3,
  "version": "2.102.0",
  "elapsed_ms": 140,
  "summary": {
    "files_analyzed": 252,
    "functions_analyzed": 5424,
    "functions_above_threshold": 24,
    "max_cyclomatic_threshold": 20,
    "max_cognitive_threshold": 15
  },
  "findings": [
    {
      "path": "src/diff/index.js",
      "name": "diff",
      "line": 48,
      "col": 0,
      "cyclomatic": 67,
      "cognitive": 138,
      "line_count": 290,
      "exceeded": "both"
    }
  ]
}
```

When configured threshold overrides affect a run, JSON includes `threshold_overrides[]` with `active`, `stale`, or `no_match` status. Complexity findings evaluated with local ceilings include `effective_thresholds` and `threshold_source: "override"`.

### With file scores

When `--file-scores` is used, the JSON output includes additional fields:

```json title="$ fallow health --file-scores --format json --top 2" theme={null}
{
  "schema_version": 3,
  "version": "2.102.0",
  "elapsed_ms": 320,
  "summary": {
    "files_analyzed": 252,
    "functions_analyzed": 5424,
    "functions_above_threshold": 24,
    "max_cyclomatic_threshold": 20,
    "max_cognitive_threshold": 15,
    "files_scored": 2,
    "average_maintainability": 68.9,
    "coverage_model": "static_estimated"
  },
  "findings": [
    {
      "path": "src/diff/index.js",
      "name": "diff",
      "line": 48,
      "col": 0,
      "cyclomatic": 67,
      "cognitive": 138,
      "line_count": 290,
      "exceeded": "both"
    }
  ],
  "file_scores": [
    {
      "path": "demo/index.jsx",
      "fan_in": 0,
      "fan_out": 24,
      "dead_code_ratio": 1.0,
      "complexity_density": 0.04,
      "maintainability_index": 66.8,
      "total_cyclomatic": 2,
      "total_cognitive": 1,
      "function_count": 2,
      "lines": 54,
      "crap_max": 6.0,
      "crap_above_threshold": 0
    }
  ]
}
```

<Note>
  Without `--file-scores`, the `file_scores` array and `summary.files_scored` / `summary.average_maintainability` / `summary.coverage_model` fields are omitted entirely from the JSON output. The `coverage_model` field indicates how CRAP coverage was determined: `static_estimated` (default, per-function estimation from export references: 85% direct, 40% indirect, 0% untested) or `istanbul` (real per-function statement coverage from `--coverage` flag or auto-detected `coverage-final.json`). CRAP findings carry `coverage_source`; when such findings are emitted, `summary.coverage_source_consistency` reports `uniform` or `mixed` even if `summary.coverage_model` is omitted. Each file score includes `crap_max` (highest CRAP score among the file's functions) and `crap_above_threshold` (count of functions at or above the CRAP threshold).
</Note>

### With hotspots

When `--hotspots` is used, the JSON output includes a `hotspots` array and `hotspot_summary`:

```json title="$ fallow health --hotspots --format json --top 2" theme={null}
{
  "schema_version": 3,
  "version": "2.102.0",
  "elapsed_ms": 480,
  "summary": {
    "files_analyzed": 252,
    "functions_analyzed": 5424,
    "functions_above_threshold": 24,
    "max_cyclomatic_threshold": 20,
    "max_cognitive_threshold": 15
  },
  "findings": [
    {
      "path": "src/diff/index.js",
      "name": "diff",
      "line": 48,
      "col": 0,
      "cyclomatic": 67,
      "cognitive": 138,
      "line_count": 290,
      "exceeded": "both"
    }
  ],
  "hotspot_summary": {
    "since": "6 months",
    "min_commits": 3,
    "files_analyzed": 21,
    "files_excluded": 18,
    "shallow_clone": false
  },
  "hotspots": [
    {
      "path": "src/diff/index.js",
      "score": 92,
      "commits": 47,
      "weighted_commits": 38.4,
      "lines_added": 1840,
      "lines_deleted": 620,
      "complexity_density": 0.89,
      "fan_in": 3,
      "trend": "Accelerating"
    },
    {
      "path": "src/legacy/handler.ts",
      "score": 71,
      "commits": 23,
      "weighted_commits": 14.2,
      "lines_added": 540,
      "lines_deleted": 180,
      "complexity_density": 0.38,
      "fan_in": 2,
      "trend": "Stable"
    }
  ]
}
```

<Note>
  Without `--hotspots`, the `hotspots` array and `hotspot_summary` fields are omitted entirely from the JSON output. Hotspot analysis can be combined with `--file-scores` to include all sections in a single output.
</Note>

### With refactoring targets

When `--targets` is used, the JSON output includes a `targets` array:

```json title="$ fallow health --targets --format json --top 2" theme={null}
{
  "schema_version": 3,
  "version": "2.102.0",
  "elapsed_ms": 520,
  "summary": {
    "files_analyzed": 252,
    "functions_analyzed": 5424,
    "functions_above_threshold": 24,
    "max_cyclomatic_threshold": 20,
    "max_cognitive_threshold": 15
  },
  "targets": [
    {
      "path": "src/core/processor.ts",
      "priority": 92.3,
      "efficiency": 30.8,
      "recommendation": "Actively-changing file with growing complexity, stabilize before adding features",
      "category": "urgent_churn_complexity",
      "effort": "high",
      "confidence": "low",
      "factors": [
        {
          "metric": "complexity_density",
          "value": 0.83,
          "threshold": 0.3,
          "detail": "density 0.83 exceeds 0.3"
        }
      ]
    },
    {
      "path": "src/helpers/util.ts",
      "priority": 38.8,
      "efficiency": 38.8,
      "recommendation": "Remove 12 unused exports to reduce surface area (86% dead)",
      "category": "remove_dead_code",
      "effort": "low",
      "confidence": "high",
      "factors": [
        {
          "metric": "dead_code_ratio",
          "value": 0.86,
          "threshold": 0.5,
          "detail": "12 unused of 14 value exports (86%)"
        }
      ],
      "evidence": {
        "unused_exports": ["assertEqual", "assertIs", "assertNever"]
      }
    }
  ],
  "target_thresholds": {
    "fan_in_p95": 12.0,
    "fan_in_p75": 5.0,
    "fan_out_p95": 15.0,
    "fan_out_p90": 8
  }
}
```

Targets are sorted by `efficiency` (priority / effort) descending, surfacing quick wins first. Each target includes `efficiency`, `effort` (`low`/`medium`/`high`), `confidence` (`high`/`medium`/`low`, based on data source reliability), and `factors` with raw `value`/`threshold` for programmatic use. The `target_thresholds` object exposes the adaptive percentile-based thresholds used for scoring, so consumers can interpret scores in context. The `evidence` field provides actionable detail for supported categories (`remove_dead_code`, `extract_complex_functions`, `break_circular_dependency`, `add_test_coverage`); omitted for other categories.

### Target categories

| Category                    | Label               | Description                                                                                                                                                                                                  |
| :-------------------------- | :------------------ | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `urgent_churn_complexity`   | churn+complexity    | Actively-changing file with growing complexity                                                                                                                                                               |
| `break_circular_dependency` | circular dependency | File participates in a dependency cycle                                                                                                                                                                      |
| `split_high_impact`         | high impact         | High fan-in with high complexity; changes ripple widely                                                                                                                                                      |
| `remove_dead_code`          | dead code           | Majority of exports are unused                                                                                                                                                                               |
| `extract_complex_functions` | complexity          | Contains functions with very high cognitive complexity                                                                                                                                                       |
| `extract_dependencies`      | coupling            | Excessive imports reduce testability and increase coupling                                                                                                                                                   |
| `add_test_coverage`         | untested risk       | Multiple complex functions lack test dependency path. Fires when a file has 2+ functions above the CRAP threshold and complexity density > 0.3. The `crap_max` contributing factor appears on these targets. |

<Note>
  Without `--targets`, the `targets` array and `target_thresholds` are omitted entirely from the JSON output.
</Note>

### With vital signs

When file-scores are enabled (via `--file-scores` or implicitly via `--save-snapshot`), the JSON output includes a top-level `vital_signs` object:

```json title="$ fallow health --file-scores --format json (vital_signs excerpt)" theme={null}
{
  "schema_version": 3,
  "version": "2.102.0",
  "vital_signs": {
    "dead_file_pct": 4.2,
    "dead_export_pct": 12.8,
    "avg_cyclomatic": 3.1,
    "critical_complexity_pct": 1.2,
    "p90_cyclomatic": 11,
    "duplication_pct": null,
    "hotspot_count": 5,
    "hotspot_top_pct_count": 3,
    "maintainability_avg": 72.4,
    "maintainability_low_pct": 9.1,
    "unused_dep_count": 3,
    "unused_deps_per_k_files": 11.5,
    "circular_dep_count": 1,
    "circular_deps_per_k_files": 3.8,
    "unit_size_profile": {
      "low_risk": 82.1,
      "medium_risk": 11.4,
      "high_risk": 4.3,
      "very_high_risk": 2.2
    },
    "functions_over_60_loc_per_k": 22.0,
    "unit_interfacing_profile": {
      "low_risk": 95.6,
      "medium_risk": 3.8,
      "high_risk": 0.5,
      "very_high_risk": 0.1
    },
    "p95_fan_in": 8,
    "coupling_high_pct": 2.3,
    "counts": {
      "dead_files": 11,
      "total_files": 262,
      "dead_exports": 48,
      "total_exports": 375,
      "unused_deps": 3,
      "circular_deps": 1
    }
  }
}
```

<Note>
  Without file-scores enabled, the `vital_signs` object is omitted from the JSON output. The `duplication_pct` field is populated automatically when `--score` is used, or when duplication analysis runs via `fallow dupes`, bare `fallow`, or `fallow dead-code --include-dupes`. It is `null` otherwise.
</Note>

### With health score

When `--score` is used, the JSON output includes a `health_score` object with score, grade, and penalty breakdown:

```json title="$ fallow health --score --format json (health_score excerpt)" theme={null}
{
  "health_score": {
    "formula_version": 2,
    "score": 72.9,
    "grade": "B",
    "penalties": {
      "dead_files": 3.1,
      "dead_exports": 6.0,
      "complexity": 0.0,
      "p90_complexity": 0.0,
      "maintainability": 0.0,
      "unused_deps": 10.0,
      "circular_deps": 4.0,
      "unit_size": 0.0,
      "coupling": 0.0,
      "duplication": 4.0
    }
  }
}
```

The score is reproducible from the penalties: `100 - sum(penalties) == score`. `formula_version` identifies the scoring formula; version 2 uses scale-invariant density and tail metrics such as `critical_complexity_pct`, `hotspot_top_pct_count`, and dependency densities per 1,000 files. Penalty fields are `null` (absent from JSON) when the corresponding pipeline didn't run. `--score` computes the score and automatically runs duplication analysis; add `--hotspots` (or combine `--score --targets`) when the score should include the churn-backed hotspot penalty.

**Letter grades:** A (score >= 85), B (70-84), C (55-69), D (40-54), F (below 40).

<Note>
  Without `--score`, the `health_score` object is omitted entirely from the JSON output. `--min-score` implies `--score`.
</Note>

### With trend

When `--trend` is used, the JSON output includes a `health_trend` object comparing current metrics against the most recent saved snapshot:

```json title="$ fallow health --trend --format json (health_trend)" theme={null}
{
  "health_trend": {
    "compared_to": {
      "timestamp": "2026-03-25T14:30:00Z",
      "git_sha": "a1b2c3d",
      "score": 74.2,
      "grade": "B"
    },
    "metrics": [
      {
        "name": "score",
        "label": "Health Score",
        "previous": 74.2,
        "current": 76.9,
        "delta": 2.7,
        "direction": "improving",
        "unit": ""
      },
      {
        "name": "dead_file_pct",
        "label": "Dead Files",
        "previous": 5.1,
        "current": 4.2,
        "delta": -0.9,
        "direction": "improving",
        "unit": "%",
        "previous_count": { "value": 13, "total": 255 },
        "current_count": { "value": 11, "total": 262 }
      },
      {
        "name": "dead_export_pct",
        "label": "Dead Exports",
        "previous": 12.8,
        "current": 12.8,
        "delta": 0.0,
        "direction": "stable",
        "unit": "%",
        "previous_count": { "value": 46, "total": 359 },
        "current_count": { "value": 48, "total": 375 }
      },
      {
        "name": "avg_cyclomatic",
        "label": "Avg Cyclomatic",
        "previous": 3.0,
        "current": 3.1,
        "delta": 0.1,
        "direction": "declining",
        "unit": ""
      },
      {
        "name": "maintainability_avg",
        "label": "Maintainability",
        "previous": 71.8,
        "current": 72.4,
        "delta": 0.6,
        "direction": "improving",
        "unit": ""
      },
      {
        "name": "unused_dep_count",
        "label": "Unused Deps",
        "previous": 4.0,
        "current": 3.0,
        "delta": -1.0,
        "direction": "improving",
        "unit": ""
      },
      {
        "name": "circular_dep_count",
        "label": "Circular Deps",
        "previous": 1.0,
        "current": 1.0,
        "delta": 0.0,
        "direction": "stable",
        "unit": ""
      },
      {
        "name": "hotspot_count",
        "label": "Hotspots",
        "previous": 6.0,
        "current": 5.0,
        "delta": -1.0,
        "direction": "improving",
        "unit": ""
      },
      {
        "name": "unit_size_very_high_pct",
        "label": "Oversized Fns",
        "previous": 2.8,
        "current": 2.2,
        "delta": -0.6,
        "direction": "improving",
        "unit": "%"
      },
      {
        "name": "p95_fan_in",
        "label": "P95 Fan-in",
        "previous": 9.0,
        "current": 8.0,
        "delta": -1.0,
        "direction": "improving",
        "unit": ""
      }
    ],
    "snapshots_loaded": 3,
    "overall_direction": "improving"
  }
}
```

Each metric includes `direction` (`improving`, `declining`, or `stable`) based on whether the change is beneficial. Percentage metrics include `previous_count` and `current_count` with raw numerator/denominator. The `overall_direction` summarizes across all metrics by majority vote.

<Note>
  `--trend` requires at least one saved snapshot in `.fallow/snapshots/`. Use `--save-snapshot` to create snapshots first. Without any snapshots, the `health_trend` object is omitted.
</Note>

### With coverage gaps

When `--coverage-gaps` is used, the JSON output includes a `coverage_gaps` object listing runtime files and exports that no test dependency path reaches. This helps identify production code with no transitive test coverage.

The `coverage-gaps` rule supports `error`, `warn`, or `off` severity in your config (default `off`):

<CodeGroup>
  ```jsonc .fallowrc.json theme={null}
  {
    "rules": {
      "coverage-gaps": "warn"
    }
  }
  ```

  ```toml fallow.toml theme={null}
  [rules]
  coverage-gaps = "warn"
  ```
</CodeGroup>

```json title="$ fallow health --coverage-gaps --format json" theme={null}
{
  "schema_version": 3,
  "version": "2.102.0",
  "elapsed_ms": 280,
  "coverage_gaps": {
    "summary": {
      "runtime_files": 84,
      "covered_files": 72,
      "file_coverage_pct": 85.7,
      "untested_files": 12,
      "untested_exports": 38
    },
    "files": [
      {
        "path": "src/utils/parser.ts",
        "value_export_count": 2
      },
      {
        "path": "src/legacy/handler.ts",
        "value_export_count": 1
      }
    ],
    "exports": [
      {
        "path": "src/core/processor.ts",
        "export_name": "transform",
        "line": 42,
        "col": 0
      }
    ]
  }
}
```

The `files` array lists runtime files where no export is reached by any test dependency path. The `exports` array lists individual exports in otherwise-covered files that lack test reachability.

<Note>
  Without `--coverage-gaps`, the `coverage_gaps` object is omitted entirely from the JSON output. The flag is opt-in and the `coverage-gaps` rule defaults to `off`.
</Note>

### With runtime coverage

When `--runtime-coverage` is used, the JSON output includes a `runtime_coverage` object that merges runtime evidence into the standard health report.

```json title="$ fallow health --runtime-coverage ./coverage --format json" theme={null}
{
  "schema_version": 4,
  "version": "2.102.0",
  "elapsed_ms": 412,
  "runtime_coverage": {
    "verdict": "hot-path-touched",
    "signals": ["cold-code-detected", "hot-path-touched"],
    "summary": {
      "functions_tracked": 128,
      "functions_hit": 93,
      "functions_unhit": 21,
      "functions_untracked": 14,
      "coverage_percent": 72.7,
      "trace_count": 284729,
      "period_days": 7,
      "deployments_seen": 3,
      "capture_quality": {
        "window_seconds": 604800,
        "instances_observed": 3,
        "lazy_parse_warning": false,
        "untracked_ratio_percent": 10.9
      }
    },
    "findings": [
      {
        "id": "fallow:prod:a7f3b2c1",
        "path": "src/server/featureFlags.ts",
        "function": "staleGate",
        "line": 44,
        "verdict": "review_required",
        "invocations": 0,
        "confidence": "medium",
        "evidence": {
          "static_status": "used",
          "test_coverage": "not_covered",
          "v8_tracking": "tracked",
          "observation_days": 7,
          "deployments_observed": 3
        },
        "actions": [
          {
            "type": "remove-dead-code",
            "description": "Tracked in runtime coverage with zero invocations.",
            "auto_fixable": false
          }
        ]
      }
    ],
    "hot_paths": [
      {
        "id": "fallow:hot:c9f5d4e3",
        "path": "src/server/router.ts",
        "function": "handleRequest",
        "line": 42,
        "invocations": 1842,
        "percentile": 99
      }
    ]
  }
}
```

Key points:

| Field                   | Meaning                                                                                                                                                                                                                                                                                                                                                                                                                       |
| :---------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `verdict`               | Overall runtime verdict: `clean`, `cold-code-detected`, `hot-path-touched`, `license-expired-grace`, or `unknown`. Promotes `hot-path-touched` over `cold-code-detected` in PR-review contexts when `--diff-file` (or `--changed-since`) is set.                                                                                                                                                                              |
| `signals`               | Array of every signal post-processing detected, independent of `verdict` (the single most actionable one). Ordered severity-descending; omitted when empty. A PR run that hits both cold code and a hot path emits `["cold-code-detected", "hot-path-touched"]`.                                                                                                                                                              |
| `summary`               | Aggregate counts of tracked, hit, unhit, and untracked functions; the coverage ratio; total trace volume; and the observation window.                                                                                                                                                                                                                                                                                         |
| `findings`              | Cold or unresolved functions. Each finding has a stable `id` (`fallow:prod:<hash>`), `verdict` (`safe_to_delete` / `review_required` / `low_traffic` / `coverage_unavailable` / `active` / `unknown`), and an `evidence` block explaining the verdict.                                                                                                                                                                        |
| `hot_paths`             | Highest-invocation runtime functions, filtered by `--min-invocations-hot`. Each has a stable `id` (`fallow:hot:<hash>`) and `percentile` rank.                                                                                                                                                                                                                                                                                |
| `coverage_intelligence` | Additive decision layer emitted when runtime coverage can be combined with static usage, test coverage, CRAP/complexity, ownership, or change scope. It summarizes `risky-change-detected`, `high-confidence-delete`, `review-required`, and `refactor-carefully` findings with stable `fallow:coverage-intel:<hash>` IDs, compact evidence, related runtime IDs, and actions. Omitted when no combined finding is available. |
| `watermark`             | Present only when trial/license grace rules require visible annotation in the output.                                                                                                                                                                                                                                                                                                                                         |
| `warnings`              | Non-fatal coverage merge diagnostics.                                                                                                                                                                                                                                                                                                                                                                                         |

The `schema_version: 4` envelope was introduced with `fallow-cov-protocol 0.2` and is extended additively as the protocol evolves. Protocol `0.3` added the optional `summary.capture_quality` block. Protocol `0.5` (current) added `HotPath.end_line` so the consumer can do line-range overlap against a `--diff-file`, and split the previous single-verdict surface into a `signals` array alongside `verdict`. Earlier `schema_version: 3` output used a different finding shape (`state` instead of `verdict`, no stable IDs, renamed summary fields); see the [v2.39.0 release notes](https://github.com/fallow-rs/fallow/releases/tag/v2.39.0) for the full migration.

`--runtime-coverage` accepts a V8 directory, a single V8 JSON file, or a single Istanbul coverage map JSON file. A single local capture runs without a license. Use [`fallow coverage setup`](/cli/coverage) for first-run capture instructions; start a trial with [`fallow license`](/cli/license) when you need continuous or multi-capture runtime monitoring. For the conceptual model and trade-offs, see [Runtime coverage](/analysis/runtime-coverage).

## Markdown output

Formatted for PR comments. Pipe directly to `gh pr comment`:

```bash theme={null}
fallow health --file-scores --format markdown | gh pr comment --body-file -
```

```markdown title="$ fallow health --file-scores --format markdown --top 2" theme={null}
## Fallow Health: 24 functions above threshold

### High complexity functions (2 shown)

| File | Function | Line | Cyclomatic | Cognitive | Lines |
|:-----|:---------|-----:|-----------:|----------:|------:|
| src/diff/index.js | diff | 48 | 67 | 138 | 290 |
| src/diff/index.js | diffElementNodes | 381 | 63 | 105 | 200 |

### File health scores (2 files)

| File | Maintainability | LOC | Fan-in | Fan-out | Dead code | Density |
|:-----|---:|---:|-------:|--------:|----------:|--------:|
| src/legacy/handler.ts | 52.3 | 312 | 2 | 18 | 45% | 0.38 |
| src/diff/index.js | 68.4 | 847 | 3 | 12 | 0% | 0.89 |
```

## SARIF output

SARIF format for GitHub Code Scanning and other static analysis tools:

```bash theme={null}
fallow health --format sarif
```

```json title="$ fallow health --format sarif (excerpt)" theme={null}
{
  "$schema": "https://json.schemastore.org/sarif-2.1.0.json",
  "version": "2.102.0",
  "runs": [{
    "tool": { "driver": { "name": "fallow", "version": "2.102.0" } },
    "results": [
      {
        "ruleId": "fallow/high-cyclomatic-complexity",
        "level": "warning",
        "message": { "text": "Function 'diff' has cyclomatic complexity 67 (threshold: 20)" },
        "locations": [{
          "physicalLocation": {
            "artifactLocation": { "uri": "src/diff/index.js" },
            "region": { "startLine": 48, "startColumn": 1 }
          }
        }]
      }
    ]
  }]
}
```

Upload to GitHub Code Scanning in CI:

```yaml theme={null}
- run: fallow health --format sarif > health.sarif
- uses: github/codeql-action/upload-sarif@v3
  with:
    sarif_file: health.sarif
    category: fallow-health
```

## Configuration

Configure default thresholds and ignore patterns in your config file:

<CodeGroup>
  ```jsonc .fallowrc.json theme={null}
  {
    "health": {
      "maxCyclomatic": 20,
      "maxCognitive": 15,
      "maxCrap": 30,
      "crapRefactorBand": 5,
      "ignore": ["**/*.generated.ts", "src/legacy/**"],
      "thresholdOverrides": [
        {
          "files": ["src/legacy/**"],
          "functions": ["parseLegacy"],
          "maxCyclomatic": 40,
          "maxCognitive": 35,
          "reason": "Queued legacy parser cleanup"
        }
      ],
      "suggestInlineSuppression": true
    }
  }
  ```

  ```toml fallow.toml theme={null}
  [health]
  maxCyclomatic = 20
  maxCognitive = 15
  maxCrap = 30
  crapRefactorBand = 5
  ignore = ["**/*.generated.ts", "src/legacy/**"]
  suggestInlineSuppression = true

  [[health.thresholdOverrides]]
  files = ["src/legacy/**"]
  functions = ["parseLegacy"]
  maxCyclomatic = 40
  maxCognitive = 35
  reason = "Queued legacy parser cleanup"
  ```
</CodeGroup>

See [Configuration](/configuration/overview) for the full config reference.

## Inline suppression

Suppress individual functions from complexity findings with inline comments:

```ts theme={null}
// fallow-ignore-next-line complexity
function* parseCsv(text) {
  // ...
}
```

Both cyclomatic and cognitive metrics are suppressed together. File scores and vital signs are unaffected (they reflect actual complexity, not alerting). Use `// fallow-ignore-file complexity` to suppress all functions in a file.

For coverage gaps: `// fallow-ignore-file coverage-gaps` excludes the file from untested-code reporting.

### `suggestInlineSuppression` and baselines

The JSON output for each health finding includes an `actions` array with machine-actionable hints (refactor, add coverage, suppress). The `suppress-line` hint is omitted automatically when:

* `--baseline` or `--save-baseline` is active. The baseline file already suppresses existing findings, so adding `// fallow-ignore-next-line` comments on top would create dead annotations once the baseline regenerates.
* `health.suggestInlineSuppression` is set to `false` in config. Use this when your team manages suppressions exclusively through hand-authored `// fallow-ignore-*` comments and does not want CI-driven inline suppression hints in JSON output.

When the hint is omitted, a top-level `actions_meta: { "suppression_hints_omitted": true, "reason": "baseline-active" | "config-disabled" }` breadcrumb is added to the health JSON envelope so consumers can audit the omission.

### Action selection by coverage tier

For findings triggered by CRAP, the primary action is selected by a formula-aware rule, with the `coverage_tier` field choosing the description:

* **Coverage CAN clear CRAP** (`cyclomatic < maxCrap`): the function's CRAP score can be brought below `maxCrap` by improving coverage, since `CRAP = CC^2 * (1 - cov/100)^3 + CC` bottoms out at `CC` at 100% coverage. The tier picks the description:
  * `none` (file not test-reachable, or Istanbul reports 0%): emits `add-tests` with a "start from scratch" description.
  * `partial` (some coverage exists, Istanbul `(0, 70)`, or estimated 40% band): emits `increase-coverage` with a "targeted branch coverage" description, since the file already has a test path.
  * `high` (Istanbul `>= 70`, or estimated 85% band): emits `increase-coverage` (NOT refactor) because additional coverage can still drop CRAP below threshold for a function whose cyclomatic is small enough.
* **Coverage CANNOT clear CRAP** (`cyclomatic >= maxCrap`): no amount of coverage will bring CRAP under threshold; emits `refactor-function` instead, regardless of tier. Reducing cyclomatic complexity is the only remaining lever.

When CRAP-only and the function's cyclomatic count is within `health.crapRefactorBand` of `maxCyclomatic`, a secondary `refactor-function` action is also emitted alongside the coverage action, but only when cognitive complexity is at or above `maxCognitive / 2`. The default band is `5`; set it to `0` to only add the secondary refactor once cyclomatic already reaches `maxCyclomatic`. The cognitive floor suppresses the secondary refactor on flat type-tag dispatchers and JSX render maps where high cyclomatic comes from a single switch with near-zero cognitive load (refactoring those is wrong-target advice).

See [Inline suppression](/configuration/suppression) for all suppressible issue types.

## See also

<CardGroup cols={3}>
  <Card title="Health metrics explained" icon="chart-line" href="/explanations/health">
    CRAP metric, maintainability index, hotspot scoring, and coverage model details.
  </Card>

  <Card title="Dead code analysis" icon="skull-crossbones" href="/analysis/dead-code">
    Find unused code alongside complexity hotspots.
  </Card>

  <Card title="Configuration" icon="gear" href="/configuration/overview">
    Set default thresholds in your config file.
  </Card>

  <Card title="CI integration" icon="shield-check" href="/integrations/ci">
    Enforce complexity limits in your pipeline.
  </Card>
</CardGroup>
