# Project configuration and ignore handling

Mori can load one strict JSON configuration named `.mori.json`. It searches
from the current working directory upward. Use `--config <path>` to select a
different file or `--no-config` to disable configuration loading.

Unknown fields, malformed JSON, non-regular files, and files larger than one
MiB are errors. Relative `baseline` paths resolve from the configuration
file's directory, and baseline documents are bounded to 16 MiB. A selected profile supplies defaults, explicit configuration
fields override profile values, and explicit command-line values override
both. Repeated `--exclude` and `--language-pair` values are additive. A
command-line `--comparison-domain` overrides the configured domain. `--sql-dialect`
overrides the configured SQL parser dialect.

Create a review-oriented config with `mori init [directory]`. The command
writes deterministic, explicit values so the file remains reviewable if a
future Mori version changes a profile. It never replaces an existing
`.mori.json` unless `--force` is supplied. `mori init --stdout` writes the
template without changing the filesystem, and `--profile review|explore|sql`
selects the template.

## Profiles

`profile` accepts:

- `review`: same-language code, threshold `0.85`, 40-token floor, at most 250
  groups and 10 retained occurrences per fingerprint, review ranking,
  generated-source exclusion, and required aggregate coverage;
- `explore`: the broad compatibility defaults—threshold `0.70`, 12-token
  floor, at most 100 groups, structural ranking, every comparison domain, and
  no required coverage or generated-source exclusion; or
- `sql`: SQL-query comparison, threshold `0.70`, 12-token floor, at most 250
  groups and 10 retained occurrences per fingerprint, review ranking,
  generated-source exclusion, and required aggregate coverage.

Profiles deliberately do not guess project-specific test, migration, generated
router, or framework exclusions. Add those only after auditing the repository's
source categories and the first report.

The selected profile is recorded in schema-18 and later reports. Omitting `profile`
retains the legacy defaults. A CLI `--profile` replaces a configured profile;
explicit fields from the config are then applied, followed by explicit CLI
flags. An explicit language-selection mode replaces the profile's mode, so
`--profile review --cross-language-only` does not require a separate
`--same-language-only=false` flag.

## Fields

```json
{
  "profile": "review",
  "threshold": 0.85,
  "min_tokens": 40,
  "max_groups": 250,
  "max_occurrences": 10,
  "max_pairs": 5000000,
  "max_file_bytes": 2097152,
  "workers": 8,
  "format": "json",
  "comparison_domain": "code",
  "fragment_selection": "all",
  "sql_dialect": "generic",
	"embedded_sql": false,
	"statement_blocks": false,
	"block_statements": 3,
	"max_blocks_per_function": 64,
  "ranking": "review",
  "priority_paths": ["**/auth/**=25", "**/security/**=25"],
  "same_language_only": true,
  "cross_language_only": false,
  "language_pairs": [],
  "fail_on_match": false,
  "require_coverage": true,
	"min_file_coverage": 0.95,
	"max_zero_fragment_files": 2,
	"fail_on_warning": false,
	"fail_on_parse_diagnostic": true,
  "exclude_generated": true,
  "baseline": "mori-baseline.json",
  "baseline_scope": "content",
  "exclude": ["**/*_test.go"],
  "respect_ignore": true
}
```

## Named scopes

Monorepositories can keep reviewed scan surfaces in `scopes` and select one
with `--scope NAME`:

```json
{
  "profile": "review",
  "scopes": {
    "backend": {
      "roots": ["cmd", "internal"],
      "exclude": ["**/*_test.go"],
      "priority_paths": ["**/auth/**=25"]
    },
    "ios": {
      "roots": ["Sources", "Packages"],
      "same_language_only": true
    }
  }
}
```

Scope roots must be relative paths inside the configuration project. Top-level
settings apply first, the selected scope overrides or adds its settings, and
explicit CLI flags remain last. Positional scan paths override the scope's
default roots without discarding its other policy. The selected name and roots
participate in baseline scan-profile digests and appear as
`configuration.scope` and `configuration.scope_roots`.

`comparison_domain` accepts one case-insensitive domain ID printed by
`mori languages`. The selected domain is applied before parsing, so other
domains do not affect file counts, warnings, focus, or pair limits. An empty or
omitted value selects every registered domain.

`sql_dialect` accepts `generic` or `postgresql` and defaults to `generic`.
It selects the parser for every discovered `.sql` file and does not affect
non-SQL files. Use separate scans when one repository contains multiple SQL
dialects.

`fragment_selection` accepts `all` (the default), `production`, or `tests`.
The equivalent CLI option is `--fragment-selection`; named scopes can override
it. It selects syntax-classified fragments after parsing, independently of
file roots and exclusions. Conventional test paths and explicit Rust test
attributes or unambiguously test-only conditional modules supply classification.
See the [exact conventions](reference/languages-and-parser-limits.md#test-selection).
Unclassified fragments remain production/unknown; `tests` is therefore not a
universal test detector. Ordinary production closures remain eligible. Excluded fragment counts stay visible in file coverage, and a file
with only excluded fragments is not evidence of analyzed comparison coverage.
The selection participates in baseline and receipt compatibility.

Keep the base `all` selection for inclusive staged gates; choose a separate
named scope for production-only exploration. This field advances the recorded
configuration contract to schema 2; `.mori.json` still has no schema-version
field and previously valid files remain readable.

### Classification overrides

Use `production_paths` to retain a shipped library such as `django/test` in
production selection. Use `test_paths` for test code with unconventional names.
Both accept arrays of exact file or directory paths, with directory-boundary
prefix matching, not globs. Config paths resolve relative to the configuration
file. Named scopes can replace either array, including with an empty array.
Repeated `--production-path PATH` and `--test-path PATH` flags add command-line
paths relative to the current working directory. Opposing overlapping rules
are rejected. Overrides supersede conventional path and inline test rules,
but do not bypass discovery, ignores, generated exclusions or parse failures.

```json
{
  "fragment_selection": "production",
  "production_paths": ["django/test"],
  "test_paths": ["tools/test-support"]
}
```

Effective configuration reports both optional arrays. Per-file excluded counts
remain available for inspecting the result. The paths participate in baseline,
receipt and staged-cache identity, and are redacted with other report paths.
The configuration contract is now 3. Project-contract schema stays 1 and old
contracts remain strictly readable and eligible for managed upgrade. Report 22
and baseline 4 retain their versions: the optional arrays are additive and
omitted when unused, preserving existing default-profile digests. Consumers
using strict JSON Schema validation should refresh the current schema file.

`embedded_sql` is an explicit opt-in for direct string arguments to recognized
Go database methods. It requires `comparison_domain` to be `sql-query` and uses
the selected `sql_dialect`. Arbitrary strings, variables, concatenations, and
receiver-type inference remain outside extraction. Fixed safety limits skip a
host file above 1,000 recognized calls and skip a decoded query above 256 KiB,
with coverage warnings.

`statement_blocks` enables fixed-size statement windows inside code functions.
`block_statements` accepts 2 through 10 and defaults to 3.
`max_blocks_per_function` accepts 1 through 256 and defaults to 64. Exceeding
the cap skips block extraction for that function with a visible coverage
warning instead of returning a partial hidden sample. Embedded SQL and
statement blocks are mutually exclusive scan modes.

`ranking` accepts `structural` or `review` and defaults to `structural`.
Structural ordering sorts by similarity score, shared evidence mass,
represented location-pair count, and stable identity. Review ordering first
uses explicit location signals, then the existing structural order. It favors
distinctively named candidates across directories, other cross-directory and
cross-file candidates, and identities representing repeated location pairs. It does not
change scores, fingerprints, candidate membership, focus priority, or baseline
suppression. The equivalent command-line form is `--ranking review`.

`priority_paths` contains at most 32 deterministic `GLOB=WEIGHT` rules. Each
weight must be an integer from 1 to 100. When review ranking is selected, a
group receives a rule's weight once if either side of any retained location
pair matches the doublestar glob. Repeat the equivalent `--priority-path`
option for command-line use. Rules change presentation order only: they do not
change similarity, fingerprints, baselines, or whether a group qualifies.
Use them for reviewed project categories such as authorization or security
paths; a matching path is not evidence that a finding is risky or actionable.

`same_language_only` compares within review families. TypeScript and TSX are
one family and remain comparable, as do Bash/POSIX shell and Zsh, and PHP and
Hack. It is mutually exclusive with
`cross_language_only` and `language_pairs`. `language_pairs` accepts language
IDs or families; the `typescript` selector includes both TypeScript and TSX,
while `php-hack` includes both PHP and Hack. The concrete `php` selector still
selects only PHP.
Explicit pairs must belong to a selected comparison domain.

`max_groups` bounds distinct content-pair identities. `max_occurrences` bounds
the locations shown for each fingerprint while retaining the exact occurrence
count. Zero opts into an unlimited value and should be used deliberately.
`--max-matches` remains a deprecated alias for `--max-groups`.

`require_coverage` requires at least one supported file and one extracted
comparison fragment. When the requirement is not met, Mori still writes the
deterministic report and its `coverage` warning, then exits with status `4`.
This distinguishes an unsupported or inapplicable scan from a clean scan with
no qualifying similarity groups. The command-line override is
`--require-coverage`; use `--require-coverage=false` to disable a configured
requirement for an explicitly exploratory invocation.

`min_file_coverage` accepts `0` through `1`; zero disables the policy. Its
numerator is the number of analyzed files producing at least one comparison
fragment, and its denominator is every analyzed supported file. Files with
status `excluded_generated` remain visible evidence but do not enter the
denominator. The command-line form is `--min-file-coverage`.

`max_zero_fragment_files` accepts `-1` or greater; `-1` disables the policy.
It limits analyzed supported files that produced no comparison fragments. The
command-line form is `--max-zero-fragment-files`.

`fail_on_warning` fails on any discovery, parsing, resource, focus, or coverage
warning. `fail_on_parse_diagnostic` is narrower and fails only when a file has
Tree-sitter parse diagnostics. Their command-line forms are
`--fail-on-warning` and `--fail-on-parse-diagnostic`. Every strict coverage
policy writes the report first and then exits with status `4`. Coverage policy
failure takes precedence over finding status `3`, and baseline update or prune
checks the policies before changing its baseline file.

`exclude_generated` excludes supported files only when Mori recognizes a
conservative generated-source comment marker in the first 8 KiB, including
`Code generated ... DO NOT EDIT`, `@generated`, and common automatically
generated-file headers. The exact `Generated by typeshare` header is also
recognized without treating arbitrary `Generated by` comments as generated
source. The equivalent command-line flag is
`--exclude-generated`. Excluded files remain visible in `file_coverage` with
status `excluded_generated`, the recognized marker class, and zero fragments.
The default remains `false`, so upgrading never silently removes source from a
scan.

## Ignore files

Directory scans honor `.gitignore` and `.moriignore` files from the working
directory through nested scan directories. Rules are evaluated in order;
`.moriignore` is read after `.gitignore` in the same directory, so it can add
or negate repository rules. Common VCS, dependency, and build directories are
still excluded by Mori's built-in policy. A linked worktree's `.git` control
file is VCS metadata and is not included in unsupported-extension counts.

Ignore files support the Git-style rules Mori needs for repository discovery:
comments, negation, directory suffixes, anchoring, and `*`, `?`, and `**`
wildcards. An ignored directory is still traversed when an applicable negation
may re-include a descendant. Invalid, oversized, symlinked, or non-regular
ignore files fail the scan instead of silently weakening exclusions.

Ignore files affect directory traversal only. A file passed explicitly remains
visible. Repeated `--exclude` globs remain additive and continue to exclude an
explicitly requested file. Use `--no-ignore` to disable both ignore-file types.

Schema-18 JSON reports record the effective profile, named scope, strict coverage policies,
options, and every loaded ignore file with its SHA-256 content evidence under
`configuration` so a scan can be
reproduced. Review focus remains
CLI-only: `--focus-path` is repeatable, `--changed-since` always requires an
explicit locally available Git revision, and repeatable
`--changed-worktree PATH=REVISION` values give every additional Git worktree
its own revision. Dynamic Git revisions are intentionally not project config.

`--staged` is also CLI-only. It reads tracked sources, ignore files,
`.mori.json`, and any configured or explicit baseline from one immutable
Git-index snapshot and records the HEAD and index digest under
`configuration.input`. A staged baseline must be a tracked regular file inside
that worktree and no larger than 16 MiB; Mori fails closed rather than reading
an untracked, external, or working-tree copy. Combine staged mode with `--include-focused`
to bypass ordinary ignore rules for changed files while retaining explicit
excludes, generated-source policy, and resource bounds. Add
`--require-focused-coverage` when every supported non-deleted focused path must be
analyzed; failures still write the report and exit with status `4`.

See [Scan selection controls](scan-selection.md) for the complete validation,
execution, compatibility, and reporting contract.
