# Scope analysis & configuration

The `izri/scope` signal compares a PR's changed files against a
declarative **scope config** — the team vocabulary of what counts as
a module, a category of work, and a sensitive area. The analyzer
turns the file diff plus the intent (PR title + body) into an
alignment score, a bucketed view of changed files, and per-rule
findings.

## Two sources of scope config

Scope config can live in two places:

1. **Dashboard** — `project_scope_configs` rows seeded by the LLM
   `/derive-scope` job on project connect, edited later from
   `/organizations/<slug>/projects/<id>/scope`. Versioned.
2. **In repo** — `.izri/scope.yml` at the PR head, checked into the
   customer's repository and code-review-able.

**In-repo wins when valid.** The file at the PR head SHA reflects
what the team is intentionally shipping with this delta; the
dashboard config can lag (someone forgot to update it) but the
checked-in file can't. If the in-repo file is missing or fails
validation we fall back to the dashboard config, and if neither is
available the analyzer uses baked-in defaults.

The active source for a given delta is surfaced as a small badge on
the scope card of the delta report — *Using `.izri/scope.yml`* when
the in-repo file took precedence.

## File location

```
<repo-root>/.izri/scope.yml
```

Place exactly at `.izri/scope.yml`. Other paths and other extensions
(`.yaml`, `.json`) are not recognized.

## Schema

The file is validated against the same Zod schema that gates
dashboard configs (`packages/trpc/src/types/scopeCheck.ts ::
ScopeConfigSchema`). All three top-level arrays are required, even
when empty.

```yaml
# Logical modules in your codebase. The analyzer uses these to group
# changed files into "what part of the system did this PR touch?".
# `paths` are globs — minimatch syntax.
modules:
  - name: auth
    paths:
      - "packages/auth/**"
  - name: web
    paths:
      - "apps/web/**"

# Categories of work the PR intent should resolve to (parsed from the
# PR title + body). Each category can optionally require a stated
# intent or a test change. `expects` and `forbids` are advisory hints
# the analyzer surfaces in its rationale.
categories:
  - label: feature
    requires_intent_statement: true
  - label: bugfix
    requires_test_change: true
  - label: docs

# Paths whose modification produces a soft `sensitive_path` finding.
# Use for areas where review attention matters more than usual —
# auth, billing, migrations. Sensitive findings are informational by
# default (they don't block the umbrella).
sensitive:
  - name: env_files
    paths:
      - ".env*"
  - name: db_migrations
    paths:
      - "packages/database/migrations/**"
```

## Override semantics

| Situation                                            | Source recorded | Behaviour                                                                                                          |
| ---------------------------------------------------- | --------------- | ------------------------------------------------------------------------------------------------------------------ |
| `.izri/scope.yml` parses + passes schema validation  | `in_repo`       | The in-repo config is used. Dashboard config is ignored for this delta.                                            |
| `.izri/scope.yml` missing                            | `dashboard`     | Dashboard config used. Falls through to `defaults` if no dashboard config exists.                                  |
| `.izri/scope.yml` malformed YAML or schema mismatch  | `dashboard`     | Dashboard config used (or defaults). One `info`-severity scope finding is emitted naming the file and the failure. |
| Neither in-repo nor dashboard config                 | `defaults`      | Analyzer falls back to baked-in defaults — useful findings on day one for first-time projects.                     |

A malformed in-repo file is **never fatal**. The analyzer always
produces a verdict; the misconfiguration shows up as an info finding
on the scope card so a reviewer can see what happened without the PR
being blocked.

## Where the source is recorded

The resolved source is persisted in two places:

- `scope_checks.scope_config_source` — source of truth, per analysis.
- `delta_reports.scope_config_source` — denormalized cache of the
  latest scope_check's source, so dashboard / API reads don't need
  a join.

Both columns take one of `'dashboard'`, `'in_repo'`, `'defaults'`.
Rows written before the column was added are nullable.

## Why in-repo wins

The reverse default ("dashboard wins, in-repo file is a hint")
would mean a customer who checks in a config file might see the
analyzer disagree with what's actually in their repo, with no clear
signal which one ran. Inverting later (in-repo wins → dashboard
wins) would silently change behaviour for every project that already
relies on it. In-repo as the override is the safer default to ship
first: the file someone reviewed with the PR is the one that drives
the analysis.

See issue [#234](https://github.com/legendify-dev/izri/issues/234).
