---
id: ADR-023
title: "File modularity discipline: 800 LOC warn / 1500 fail / 3000 fail-with-LOC-EXEMPT-allowlist"
status: accepted
date: "2026-04-16"
phase: "Phase B — hygiene foundation"
deciders: ["@ThibautMelen"]
tags: ["hygiene", "file-loc", "modularity", "discipline"]
affects_crates: ["all"]
affects_layers: ["L0", "L0.5", "L1", "L2", "L3", "L4", "L5"]
supersedes: []
superseded_by: []
related: ["ADR-018", "ADR-021", "ADR-022", "ADR-026", "ADR-038"]
requires: []
enables: []
amends: ["ADR-004"]
fci: []
inv: []
shadow_zones: []
nika_codes: []
timeline: "v0.81"
follow_ups: ["implement scripts/hygiene/check-file-loc.sh as vector 27"]
---

# ADR-023: File modularity discipline

## Context

Current rule: max 1500 LOC per file (hard cap). 4-agent audit (2026-04-16)
revealed:
- Current P90 ≈ 600 LOC (well under cap)
- nika-catalog/build.rs at 1386 LOC and data/models.rs at 1248 LOC are
  uncomfortably close
- rust-analyzer P90 = 900 LOC, helix P90 = 700 LOC, oxc P90 = 500 LOC
- Mature workspaces routinely ship 2k+ LOC files (codegen output, generated
  lookup tables)

Two opposing views:
- Agent 1 (rust-architect): tighten to 800/1200 — match SOTA Rust P90
- Agent 2 (web-researcher SOTA): NO workspace enforces hard limits — relax to
  1500/3000

Both have merit. Cap discipline matters; codegen reality matters.

## Decision

**Three-tier file LOC policy:**

| Threshold | Action | Mode |
|---|---|---|
| **800 LOC** | warning | hygiene vector 27 emits YELLOW |
| **1500 LOC** | hard fail | hygiene vector 27 fails RED — cannot commit |
| **3000 LOC** | absolute hard fail with allow-list exemption only | hygiene vector 27 fails CRITICAL — exempt ONLY via per-file marker |

**`// LOC-EXEMPT: <reason> [<owner>]` marker** at top of file allows bypassing
the 1500 cap up to 3000 LOC. Reasons must be one of:
- `codegen` — auto-generated by build.rs / proc-macro
- `lookup-table` — large compile-time static data table
- `enum-mega` — single mega-enum (e.g., 200+ variants of EventKind aggregator)

**Per-function cap**: 100 LOC soft warn (`clippy::too_many_lines = "warn"`).
Currently unenforced — enable in workspace lints.

## Consequences

- ✅ 800 warn discipline keeps cohesion tight (we're already there at P90 ~600)
- ✅ 1500 fail catches genuine sprawl
- ✅ 3000-with-exemption preserves codegen reality
- ✅ Marker enforces conscious decision (not silent growth)
- ❌ +1 hygiene vector (27) — implementation cost ~1h
- ❌ One-time refactor of catalog/build.rs (1386) + data/models.rs (1248) to
  honor 800 warn — handled in Phase B'

## Implementation

```bash
# scripts/hygiene/check-file-loc.sh (vector 27)
#  - For each .rs file in crates/*/src/ and crates/*/build.rs:
#    - count LOC
#    - if > 3000:
#        check for // LOC-EXEMPT: marker; fail if absent
#    - elif > 1500:
#        fail (no exemption above 1500 except via 3000 path)
#    - elif > 800:
#        warn (yellow)
```

Workspace `Cargo.toml` lint section:
```toml
[workspace.lints.clippy]
too_many_lines = { level = "warn", priority = -1 }
```

## Alternatives considered

- **Relax to 1500/3000** (Agent 2's pure recommendation) — loses discipline
- **Tighten to 800/1200** (Agent 1's pure recommendation) — fails under codegen
- **Per-crate cap** instead of per-file — moves the problem one level up
- **Hard 1500 with no exemptions** — current rule, breaks under generated code
