Browse docs
Docs /configure/config-yml
Configure

.izri/config.yml

.izri/config.yml holds your in-repo Izri settings, one top-level section per concern. The scope: section is the vocabulary the scope analyzer reasons against: what your modules are called, what kinds of change your PR types allow, and which paths deserve a second look.

It lives in your repository, at the PR head. That's the important part — it is checked in, code-reviewable, and versioned alongside the code it governs.

Both .izri/config.yml and .izri/config.yaml are read; .yml wins if you somehow have both. Sections resolve independently, so a file carrying only one section leaves the others on their dashboard values.

Sections

Section Read by What it does
runtime: runner How to start the app and run its tests.
services: runner Runtime dependencies to provision (Postgres, Redis, …).
signals: runner Which optional signals execute (hallucination, visual).
testSelection: runner Diff → spec mapping, so a small PR runs a small suite.
scope: analysis Your modules, PR categories, and sensitive paths.
policy: analysis The merge policy: threshold, floors, weights, block rules.
tests: analysis Where izri/tests gets its evidence: ci_upload, runner, or none.

Every section is optional, and each is validated only by the component that owns it. A malformed runtime: cannot downgrade your merge policy, and a malformed policy: cannot break a test run.

The two readers do differ in one way worth knowing: the runner treats a bad runtime: as fatal, because it cannot start your app without one. Analysis treats a bad scope: or policy: as non-fatal, falls back to your dashboard settings, and leaves an info finding on the PR. A YAML typo never blocks a merge.

Moving from the old files

.izri/izri.yml

The runner config used to live in its own file, with quality: where signals: now is. It still works: when .izri/config.yml carries none of the runner's sections, the legacy file is read instead. Move runtime:, services: and quality: (renaming that last one to signals:) into .izri/config.yml and delete it.

A legacy file carrying both quality: and signals: is rejected rather than silently resolved — pick one.

.izri/test-scope.json

The diff → spec mapping used to live in its own JSON file. It is still read when .izri/config.yml has no testSelection: section. Move its body under testSelection:, drop the version and _doc keys, and delete it. See scoped test selection for the schema.

.izri/scope.yml

.izri/scope.yml was the previous name for the scope section, and the whole file was the scope body. It still works: when there is no .izri/config.yml, the legacy file is read as the scope section and every delta carries one info finding (scope_config_deprecated_path) asking you to migrate. If both files exist, config.yml wins and the legacy file is ignored.

To migrate, nest the old body one level under scope: and delete the old file:

# .izri/config.yml
scope:
  modules: [...]      # ← the old .izri/scope.yml body, indented
  categories: [...]
  sensitive: [...]

Resolution precedence

Four steps, in order:

# Condition Source
1 Valid scope: section in .izri/config.yml at the PR head in_repo
2 No scope: section, but a valid legacy .izri/scope.yml in_repo, and emits a deprecation info finding
3 Malformed file, or a scope: section that fails validation falls back, and emits an info finding
4 No repo config, dashboard config exists dashboard
5 Neither defaults

A scope: section that is present but invalid does not fall through to the legacy file. An explicit, broken section reports its error rather than quietly resolving from somewhere else.

In-repo wins on purpose. If the dashboard could override a checked-in file, the file in your repository would silently disagree with what actually ran — and the file is the thing your team reviews.

A malformed file degrades, it does not fail

A .izri/config.yml that doesn't parse falls back to the dashboard config or defaults and emits an info-level finding saying so. Your build does not break because of a YAML typo.

The tradeoff: a broken config is easy to miss, because info findings don't block. If scope results look wrong, check the resolved source first — the delta report records which case applied.

Schema

The scope: section takes three keys: modules, categories, sensitive.

scope:
  modules:
    - name: billing
      paths:
        - src/billing/**
        - packages/payments/**
    - name: auth
      paths:
        - src/auth/**

  categories:
    - label: feature
      expects:
        - src/**
      requires_test_change: true
    - label: docs
      forbids:
        - src/**
        - migrations/**
      requires_intent_statement: false

  sensitive:
    - name: payment handling
      paths:
        - src/billing/charge.ts
    - name: migrations
      paths:
        - migrations/**

modules

Named groups of paths. This is the vocabulary findings are phrased in — without it, scope can only talk about file paths.

Field Required Type
name yes string
paths yes string[]

categories

What each kind of change is allowed to touch. label matches the PR's declared type, typically from its conventional-commit prefix.

Field Required Type Effect
label yes string The PR type this rule applies to.
expects no string[] Paths this type normally touches.
forbids no string[] Paths this type must not touch. Emits pr_type_forbidden_category at error — a hard rule that fails the umbrella.
requires_intent_statement no boolean Require a body explaining the change.
requires_test_change no boolean Require the diff to include test changes.

forbids is the only user-configurable hard gate. Everything else in scope is advisory. If you want a rule that genuinely blocks a merge, express it as forbids on a category — not as a sensitive path.

sensitive

Paths worth a human glance.

Field Required Type
name yes string
paths yes string[]

Touching one emits sensitive_path at info. It does not block, and it is not meant to — it points a reviewer at something. Teams reliably assume this gates a merge. It doesn't. Use forbids for that.

tests

tests:
  evidence: ci_upload   # ci_upload | runner | none

Where izri/tests gets its run. ci_upload means your CI uploads its JUnit results and coverage with izri upload; runner means Izri executes the tests itself (Playwright only); none means no tests verdict. When the key is present it is the setting — every push writes it to every project connected to this repository, and the dashboard shows it; two projects on one repository that need different sources leave the key out and set each in the dashboard. It is read from your project's default branch — never from a pull request's head or base, otherwise anyone who can open a PR could switch the project to none and merge with red tests. A change takes effect on the first push after it lands on that branch. When absent, the dashboard value stands, and a project's first accepted upload switches it from none to ci_upload on its own.

Auto-derivation

You don't have to write this by hand. Izri derives an initial config from your repository's file tree and metadata. The stored config records its origin:

Source Meaning
derived Generated by Izri.
user Edited in the dashboard.
merged Derived, then hand-adjusted.

Read the current one with the get_scope_config MCP tool, or from the dashboard. Export it into the scope: section of .izri/config.yml when you want it under version control.

Worked example: a minimal config

Start here. Sensitive paths alone are useful before you formalize categories:

scope:
  modules: []
  categories: []
  sensitive:
    - name: auth
      paths:
        - src/auth/**
    - name: migrations
      paths:
        - migrations/**

Worked example: enforcing a boundary

Stopping a docs PR from carrying a schema migration:

scope:
  categories:
    - label: docs
      forbids:
        - migrations/**
        - src/**

A PR titled docs: update readme that also edits migrations/0042_add_column.sql now fails the umbrella, regardless of how clean every other signal is.

Reading this with an agent? /docs/configure/config-yml.md serves the raw markdown.

Edit this page on GitHub →All docs