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

# Configuration

> Configure roe with roe.json or roe.yaml.

roe reads an optional config file named `roe.json`, `roe.yaml`, or
`roe.yml`. It's resolved by walking up from the analysis root to the nearest
directory containing one (like `.eslintrc`/`tsconfig.json`), or pointed to
explicitly with `--config`:

```bash theme={null}
roe dead-code --config path/to/roe.json
```

If a directory contains more than one candidate, roe warns and `roe.json`
wins. An explicit `--config` path that's missing or malformed is a hard
error.

## Fields

```json roe.json theme={null}
{
  "aggressive": true,
  "roots": ["MyApp.Program.Main"],
  "entryPoints": ["Jobs/", "Api/Handlers/**/*.cs"],
  "libraryProjects": ["MyLib"],
  "ignore": ["Migrations/**", "Generated/", "**/*.designer.cs"],
  "deadCode": {
    "ignore": ["Plugins/**"]
  },
  "dupes": {
    "mode": "semantic",
    "minTokens": 100,
    "minLines": 10,
    "minOccurrences": 2,
    "ignore": ["**/InsurerUnionQuery.cs"]
  },
  "health": {
    "maxComplexity": 15,
    "maxCognitive": 20,
    "maxMethodLines": 60,
    "maxParameters": 6,
    "maxFileLines": 750,
    "maxTypeMembers": 25,
    "excludeTests": true,
    "baseline": "roe-baseline.json",
    "ignore": ["**/GeneratedModels.cs"]
  }
}
```

| Field             | Type      | Description                                                                                                                                                  |
| ----------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `aggressive`      | boolean   | Also flag enum members and public settable auto-properties. Default for the `--aggressive` flag.                                                             |
| `roots`           | string\[] | Fully-qualified symbol names to treat as extra entry-point roots. Default for `--root`.                                                                      |
| `entryPoints`     | string\[] | Glob patterns naming files to treat as entry points: every declaration in a matching file is a root, so the file and everything it references count as used. |
| `libraryProjects` | string\[] | Project names to always treat in library mode (public API counts as used). Default for `--library`.                                                          |
| `ignore`          | string\[] | Glob patterns; every finding in a matching file is dropped. Applies to all three commands; each command's section can add its own.                           |
| `deadCode`        | object    | Extra `ignore` globs applied only to [`roe dead-code`](/commands/dead-code).                                                                                 |
| `dupes`           | object    | Matching mode, thresholds, and extra `ignore` globs for [`roe dupes`](/commands/dupes). Every key is optional.                                               |
| `health`          | object    | Thresholds, the baseline path, and extra `ignore` globs for [`roe health`](/commands/health). Every key is optional.                                         |

Unknown fields are rejected, so a typo fails loudly instead of being
silently ignored.

## Dupes options

Every field under `dupes` is optional and corresponds to a flag on
[`roe dupes`](/commands/dupes):

| Field            | Type                      | Flag                | Default   |
| ---------------- | ------------------------- | ------------------- | --------- |
| `mode`           | `"exact"` or `"semantic"` | `--mode`            | `"exact"` |
| `minTokens`      | number                    | `--min-tokens`      | `50`      |
| `minLines`       | number                    | `--min-lines`       | `5`       |
| `minOccurrences` | number                    | `--min-occurrences` | `2`       |
| `ignore`         | string\[]                 | —                   | none      |

This block is what calibrates a combined run: [`roe check`](/commands/check)
takes no dupes flags of its own, so raising `minTokens` here is how a one-line
CI gate learns the threshold your codebase needs.

## Health thresholds

Every field under `health` is optional and corresponds to a flag on
[`roe health`](/commands/health):

| Field            | Type      | Flag                 | Default |
| ---------------- | --------- | -------------------- | ------- |
| `maxComplexity`  | number    | `--max-complexity`   | `10`    |
| `maxCognitive`   | number    | `--max-cognitive`    | `15`    |
| `maxMethodLines` | number    | `--max-method-lines` | `40`    |
| `maxParameters`  | number    | `--max-parameters`   | `5`     |
| `maxFileLines`   | number    | `--max-file-lines`   | `750`   |
| `maxTypeMembers` | number    | `--max-type-members` | `20`    |
| `excludeTests`   | boolean   | `--exclude-tests`    | `false` |
| `baseline`       | string    | `--baseline`         | none    |
| `ignore`         | string\[] | —                    | none    |

Committing these is usually better than passing six flags on every CI
invocation, and it keeps local runs and CI in agreement.

`baseline` names a [baseline file](/commands/health#baselines) and is
resolved relative to the config file's own directory, the way `ignore` globs
are — so `"roe-baseline.json"` means the one sitting next to `roe.json`, no
matter which subdirectory the run starts in. Setting it here rather than
passing `--baseline` is also what makes a bare `roe` and
[`roe check`](/commands/check) honour it, since neither takes health flags of
its own. A path that doesn't exist is a hard error, not a silent
full-backlog run.

## Entry-point files

`entryPoints` is the file-level counterpart to `roots`: where `roots` names
one symbol by its fully-qualified name, an entry-point glob keeps a whole
file's contents alive. Every declaration in a matching file becomes a root,
so the file is never flagged and everything it references counts as used —
unlike an `ignore` glob, which only drops the findings in the matching file
itself.

Use it for files that are consumed in ways roe can't see: a plugin host
loading them by path, string-based reflection, or an external tool reading
the file directly.

```json roe.json theme={null}
{
  "entryPoints": ["Jobs/", "Api/Handlers/**/*.cs"]
}
```

Patterns resolve relative to the config file's own directory with the same
rules as `ignore` globs — a trailing `/` matches the whole directory, and
`..` is unsupported. A pattern that matches no file at all is reported as a
note in the output, so a stale path can't silently drop the protection it
promised.

## Ignore globs

`ignore` patterns are resolved relative to the config file's own directory.
A trailing `/` matches the whole directory, so `"Generated/"` needs no `**`.
Patterns containing `..` are unsupported and produce a warning.

The top-level `ignore` list applies to all three commands — including
`roe dupes` and `roe health`'s circular dependencies, which span multiple
files and don't map cleanly onto a single-line
[inline suppression comment](/suppressing-findings).

Each command's section takes an `ignore` list of its own with the same
rules, unioned with the top-level list and applied only to that command.
That keeps a suppression as narrow as the exception it accepts: ignoring an
intentional duplicate through `dupes.ignore` doesn't cost the file its
dead-code and health coverage.

```yaml roe.yaml theme={null}
ignore:
  - "Generated/**"
deadCode:
  ignore:
    - "Plugins/**"
dupes:
  ignore:
    - "**/InsurerUnionQuery.cs"
health:
  ignore:
    - "**/GeneratedModels.cs"
```

A scoped list only ever adds patterns — it can't re-include a file the
top-level list ignores — and an empty one is a no-op.

## Precedence

Every setting follows the same order: an explicit command-line flag wins,
otherwise the config file's value applies, otherwise the built-in default.

For `aggressive`, `roots`, and `libraryProjects` that means an explicit
`--aggressive`, `--root`, or `--library` flag beats the config, falling back
to `false` / no extra roots / no extra library projects.

Two nuances: `--aggressive` can only turn the setting on — there is no
`--no-aggressive` to override a config's `"aggressive": true`. And passing
`--root` or `--library` replaces the config's list wholesale rather than
merging with it.

Health thresholds work the same way, per field: `--max-complexity 12`
overrides `"maxComplexity"` for that run, while every threshold you don't
pass still comes from the config. Like `--aggressive`, `--exclude-tests` can
only turn the setting on.

Dupes settings follow the same per-field order: `--min-tokens 100` overrides
`"minTokens"` for that run, and the rest still come from the config. `--mode`
carries a value, so unlike `--aggressive` and `--exclude-tests` it has no
on-only nuance — `--mode exact` overrides a config's `"semantic"` cleanly.

Ignore lists and `entryPoints` sit outside this order entirely: neither has
a CLI flag, and a command's own `ignore` list is unioned with the top-level
one rather than replacing it.

To confirm a config-only `excludeTests` or `ignore` actually applied, read the
`roe health` footer: the scanned counts narrow by whatever was excluded, and
an `excluded:` line names it. See
[What "scanned" counts](/commands/health#what-scanned-counts).
