> ## 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.

<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`.                                                                                                                                                            |

### 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, explanations are always shown. 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).                                                                                                                                                                                                                                                                                                                                                                                                                           |
| `--file-scores`                   | Show only per-file maintainability scores. Requires the full analysis pipeline (graph + dead code detection). File scores are sorted by maintainability index ascending (worst 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.                                                                                                                                                                                                                                                                                                                                           |
| `--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`.                                                                                                                                                                                                |
| `--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`.                                                                                                                                                                                                                                                                      |
| `--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 if the health score is below this threshold (exit code 1). Implies `--score`. Use as a CI quality gate. |

### 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`).                                                                                                                                                                                                                                                                                                                                                          |
| `--min-commits <N>`         | Minimum number of commits for a file to be included in hotspot ranking (default: 3).                                                                                                                                                                                                                                                                                                                                                                                                       |
| `--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 drift, 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), `hash` (stable `xxh3:` pseudonym), or `raw` (full email). Implies `--ownership`. Use `hash` 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

| Code | Meaning                                       |
| :--- | :-------------------------------------------- |
| `0`  | No functions exceed the configured thresholds |
| `1`  | One or more functions exceed a threshold      |

## Maintainability index formula

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 hash --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`/`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.                                                                                                                                                                               |
| `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` | `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                            | Most projects. Balances readability and privacy.              |
| `hash`             | `xxh3:<16hex>` non-cryptographic pseudonym | Regulated environments where author identities are sensitive. |
| `raw`              | Full email address                         | Public OSS repositories where git history is already exposed. |

The 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>

## 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.75.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 maintainability index
  fallow health --file-scores

  # Worst 20 files by maintainability
  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)
  src/diff/index.js
    :48 diff
          67 cyclomatic  138 cognitive  290 lines
    :381 diffElementNodes
          63 cyclomatic  105 cognitive  200 lines
  src/utils/parser.ts
    :15 parseExpression
          25 cyclomatic   31 cognitive   98 lines
  Functions exceeding cyclomatic or cognitive complexity thresholds — https://docs.fallow.tools/explanations/health#complexity-metrics

● File health scores (3 files)

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

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

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

  Composite file quality scores based on complexity, coupling, and dead code. Risk: low <15, moderate 15-30, high >=30. — 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.75.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"
    }
  ]
}
```

### 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.75.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`). 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.75.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.75.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.75.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.75.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.75.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.                                                                                                                   |
| `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.75.0",
  "runs": [{
    "tool": { "driver": { "name": "fallow", "version": "2.75.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,
      "ignore": ["**/*.generated.ts", "src/legacy/**"],
      "suggestInlineSuppression": true
    }
  }
  ```

  ```toml fallow.toml theme={null}
  [health]
  maxCyclomatic = 20
  maxCognitive = 15
  maxCrap = 30
  ignore = ["**/*.generated.ts", "src/legacy/**"]
  suggestInlineSuppression = true
  ```
</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 5 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 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>
