# ГЛАВА 16. АРХИТЕКТУРА КОДА, ДРУЖЕСТВЕННАЯ ИИ

В 2026 году ваш код читают не только коллеги — его читают **AI-агенты**. Claude Code, Cursor Agent, Windsurf, Devin — это не «помощники», это полноценные потребители вашей кодовой базы. Они индексируют файлы, парсят функции, строят граф зависимостей. И качество их работы напрямую зависит от того, как ваш код организован.

> **Примечание:** Эта глава — об архитектуре кода, и примеры «до/после» — её основной обучающий инструмент. Фрагменты ниже — иллюстративный псевдокод, показывающий паттерны и анти-паттерны. Для генерации production-кода используйте AI-промпты, приведённые в каждой секции.

Прежде чем разбирать принципы — посмотрите на реальную ситуацию.

### Код, который сломал AI-агента

Команда попросила Cursor Agent добавить скидку для оптовых заказов. Вот что агент увидел:

```python
# app.py — 1200 строк, фрагмент
class App:
    def handle(self, req):
        d = self.db.get(req.args["id"])
        if d and d[7] > 0:  # d[7]? Что это??
            p = d[3] * d[5]  # d[3]? d[5]??
            if self._check(d):  # Что проверяет?
                p = p * 0.9     # Магическое число
            self._do(d, p)      # Что делает?
        return {"ok": True}
```

Агент **не понял**, где цена, где количество, что за `d[7]`, и сгенерировал код со скидкой, применённой не к тому полю. Баг ушёл в прод.

Та же логика, переписанная в стиле, дружественном для ИИ, — файл `order/pricing.py` на 45 строк: типизированная функция `calculate_order_price(items: list[OrderItem], discount_percent: float) -> OrderTotal` с docstring, описывающим аргументы, возврат и семантику. Cursor Agent получил задачу «добавить оптовую скидку 15% при заказе от 100 единиц». Он прочитал контракт, увидел `discount_percent`, добавил одно условие — и всё заработало с первого раза.

> **Промпт для генерации кода:** «Напиши Python-модуль `order/pricing.py` (< 50 строк). Функция `calculate_order_price(items: list[OrderItem], discount_percent: float = 0.0) -> OrderTotal`. Docstring в Google style с Args, Returns. Используй Decimal для денежных расчётов. Dataclasses для OrderItem (quantity, unit_price) и OrderTotal (subtotal, discount_amount, final_total).»

**Вывод**: архитектура кода — это уже не только про людей. Это протокол общения с AI-инструментами, которые пишут, рефакторят и ревьюят ваш код каждый день.

---

## 16.1. Маленькие модули лучше монолита

### Почему модель ошибается в монолитном коде

Представьте кубики LEGO. Каждый кубик — простой: 2×4, красный, с пупырышками. Но из сотни таких кубиков можно собрать что угодно — от домика до космического корабля. А теперь представьте, что вместо кубиков вам дали гипсовый монолит в форме замка. Хотите добавить башню? Нужно ломать стену, надеяться, что крыша не обвалится, и подпирать конструкцию изолентой.

Так же AI-агенты работают с вашим кодом. Маленькие модули — это **LEGO-кубики**: каждый делает что-то одно, его легко понять, заменить или скомбинировать. Монолит — это гипсовый замок: тронешь одно — сломается другое.

LLM обрабатывает код как последовательность токенов с ограниченным окном контекста. В 2026 году это особенно важно: Copilot, Cursor и Windsurf автоматически индексируют вашу кодовую базу, и маленькие файлы помещаются в их контекстное окно целиком. Монолитный файл в 2000 строк создаёт несколько проблем:

**1. Размывание внимания**: в файле на 2000 строк (~6000–10000 токенов) внимание распределяется на множество функций, классов и import'ов. Когда модель модифицирует функцию на строке 1500, она «видит» функцию на строке 50, но уделяет ей минимум внимания (Lost in the Middle).

**2. Неявные зависимости**: в монолите зависимости скрыты. Функция `process_order()` на строке 800 может зависеть от глобальной переменной на строке 12, вспомогательной функции на строке 300 и класса на строке 600. Модель должна отследить все эти связи — Transformer умеет это делать, но каждая дополнительная связь увеличивает вероятность ошибки.

**3. Переполнение контекста**: если файл не помещается в окно контекста — модель работает с фрагментом. Изменение одного фрагмента без видимости другого -> несогласованности.

### Оптимальный размер модуля

| Размер файла | Токены (Python) | Качество генерации | Рекомендация |
|-------------|-----------------|---------------------|-------------|
| <100 строк | ~300–500 | Отлично | **Идеал** |
| 100–300 строк | 500–1500 | Хорошо | Приемлемо |
| 300–500 строк | 1500–2500 | Средне | Рефакторить |
| 500+ строк | 2500+ | Ненадёжно | Разбить обязательно |

**Правило**: один модуль ≤ 100 строк, одна функция ≤ 30 строк, одна ответственность.

### Как это выглядит на практике

**До (монолит):** `order_service.py` — 800 строк. Класс `OrderService` с семью методами: `create_order` (100 строк), `validate_order` (60), `calculate_price` (80), `apply_discount` (40), `process_payment` (120), `send_notification` (50), `update_inventory` (70) + helpers, constants, error handlers.

**После (модули):**
```
order/
├── __init__.py
├── models.py          # 40 строк: Order, OrderItem, OrderStatus
├── validation.py      # 60 строк: validate_order()
├── pricing.py         # 80 строк: calculate_price(), apply_discount()
├── payment.py         # 70 строк: process_payment()
├── notifications.py   # 50 строк: send_notification()
├── inventory.py       # 50 строк: update_inventory()
└── service.py         # 60 строк: create_order() — оркестрация
```

Каждый файл — это отдельный LEGO-кубик. Он помещается в один промпт с запасом. AI-агент (будь то Claude Code, Cursor или Devin) может модифицировать `pricing.py`, не загружая весь сервис. А при ревью кода автоматические инструменты парсят каждую функцию отдельно — чем чище контракт, тем точнее ревью.

---

## 16.2. Контракт функции как фиксатор смысла

### Зачем нужен контракт

Контракт функции — это **этикетка на продукте**. Когда вы берёте с полки йогурт, вам не нужно его открывать, чтобы понять: состав, калорийность, срок годности — всё на этикетке. Точно так же работает контракт функции: типы аргументов (состав), docstring (описание), тип возврата (результат) — всё видно без чтения тела функции.

Это **семантический якорь** для модели ([Глава 1](01_tokens_vectors_and_semantic_space.md)). Типы, докстроки, pre/post-conditions — всё это фиксирует, что функция делает, не заставляя модель угадывать. В 2026-м это особенно критично: AI-инструменты ревью кода (встроенные в GitHub, GitLab, Cursor) парсят каждую функцию отдельно — чистый контракт = точное автоматическое ревью.

### Минимальный контракт

Минимальный контракт включает: имя функции (семантика), типы аргументов и возврата (формат данных), docstring (спецификация поведения), раздел Raises (граничные условия), пример использования (конкретный I/O для калибровки). Без контракта модель вынуждена угадывать формат, поведение и границы.

> **Промпт для генерации кода:** «Напиши Python-функцию `calculate_order_total(items: list[OrderItem], discount_percent: float, tax_rate: float) -> OrderTotal`. Контракт: items — непустой список, discount_percent и tax_rate — дроби [0.0, 1.0]. Скидка применяется до налога. Добавь Google-style docstring с Args, Returns, Raises (ValueError на невалидные аргументы) и Example с конкретным расчётом. Используй Decimal для точных денежных расчётов.»

### Что даёт контракт модели

| Элемент | Что фиксирует для модели | Без контракта |
|---------|--------------------------|---------------|
| **Имя функции** | Семантика (calculate = вычислить) | Угадывает из контекста |
| **Типы аргументов** | Формат входных данных | Может принять str вместо float |
| **Типы возврата** | Формат выходных данных | Может вернуть dict вместо dataclass |
| **Docstring** | Полная спецификация поведения | Угадывает по имени |
| **Raises** | Граничные условия | Может проигнорировать ошибки |
| **Example** | Конкретный I/O для калибровки | Нет якоря для формата |

### Комментарии: полезен intent, а не пересказ кода

Есть соблазн думать, что для AI чем больше комментариев, тем лучше. Это не совсем так. Современные исследования по code translation показывают, что особенно полезны **комментарии о намерении**: зачем нужен модуль, какова общая цель функции, какой контракт или инвариант должен сохраняться. А вот комментарии уровня «увеличиваем `i` на 1» нередко добавляют шум и даже ухудшают трансформации кода.

Лучший комментарий для AI — это не покадровый пересказ очевидного, а **сжатая спецификация смысла**: что делает функция в системе, почему решение устроено именно так, какие ограничения нельзя нарушать и где безопасно менять код. Поэтому комментарии стоит ставить на границах абстракций — у модулей, публичных функций, сложных инвариантов и точек принятия решений. Если строка кода и без комментария очевидна человеку, почти наверняка не нужно объяснять её и модели.


### GRACE: semantic header как карта файла

Для AI-агента лучший файл — это файл, у которого смысл виден сверху вниз. Перед телом модуля полезно поставить короткий машиночитаемый заголовок: что делает модуль, с чем связан, какой инвариант защищает и какие опасные пути уже отвергнуты.

```md
# [DEF:OrderPricing:Module]
# @PURPOSE: Рассчитать итоговую цену заказа.
# @LAYER: Domain
# @RELATION: DEPENDS_ON -> [DiscountPolicy:Module]
# @INVARIANT: Скидка применяется до налога.
# @RATIONALE: Денежная логика централизована в одном модуле.
# @REJECTED: Не вычислять цену повторно в API-слое.
# [/DEF:OrderPricing:Module]
```

Такой header — не замена коду, а **карта перед входом в здание**. Он помогает модели быстро решить, стоит ли вообще читать файл целиком, а человеку — понять, можно ли безопасно менять модуль.

Почему это имеет смысл. Работы по code generation показывают, что полезен не любой комментарий, а именно слой требований и намерений. В TOSEM-статье **ShortenDoc** DocString рассматривается как носитель пользовательских требований; авторы показывают, что обычные методы prompt compression для code generation начинают заметно деградировать уже около 10% сокращения, тогда как специализированный docstring-подход даёт 25–40% compression без потери качества. Это сильный признак того, что короткая intent-спецификация действительно содержит концентрированную полезную информацию. При этом MSR-исследование про contextual data в code completion показывает более нюансированную картину: помогают прежде всего осмысленные multi-line comments, а не любое дополнительное текстовое украшение.

Отсюда практическое правило: header должен кодировать **purpose / invariants / relations / rejected paths**, а не пересказывать построчно реализацию. Сложностные уровни, лимит 300 строк и прочие governance-правила можно смело использовать как дисциплину команды — но честно: это уже не «физика трансформера», а ваша рабочая политика.

### Semantic markup без валидатора быстро деградирует

Разметка становится инженерной системой только тогда, когда у неё есть автоматическая проверка. Если semantic header существует лишь как красивый комментарий, через несколько спринтов он начинает врать: закрывающие теги расходятся, `@RELATION` указывают на удалённые узлы, а старые `@REJECTED` молча исчезают.

Практический минимум — дешёвый deterministic checker, который проверяет:

- у каждого `[DEF]` есть парный `[/DEF]`;
- обязательные поля header'а не потерялись;
- `@RELATION` указывают на существующие public/shared узлы;
- file-local contracts не утекли в shared graph случайно;
- нарушение правил даёт non-zero exit code в CI.

Для таких semantic envelopes полный AST не всегда лучший первый инструмент. AST отлично понимает структуру кода, но плохо подходит для comment-level contracts и machine-readable headers. Поэтому на практике хорошо работает гибрид: **регулярные выражения и построчный валидатор для semantic boundaries** плюс AST только там, где действительно важно проверить imports, callable names или topology.

```text
$ python tools/check_semantics.py
ERROR app/services/pricing.py: missing closing [/DEF:OrderPricing:Module]
ERROR appgraph.xml: unknown relation target [DiscountPolicy:Module]
WARN  tests/test_pricing.py: helper block has no anchor
```

Это полезное инженерное правило для любой кодовой базы, дружественной ИИ: semantic markup должен жить рядом с тестами и линтерами, а не только в агентных инструкциях. Иначе у вас появляется красивый язык контрактов без механизма, который удерживает его от распада.

### Pre/Post-conditions и Design by Contract

Ещё мощнее — контракт с явными pre/post-conditions и инвариантами. Для функции `transfer_funds(from_account, to_account, amount)` pre-conditions фиксируют: `amount > 0`, достаточный баланс, разные счета, оба активны. Post-conditions гарантируют: баланс отправителя уменьшился, получателя увеличился, транзакция залогирована. Инвариант: суммарный баланс не изменился. Pre/post-conditions записываются и в docstring, и как executable assertions в теле функции.

Модель видит pre/post-conditions и генерирует код, который **по конструкции** их соблюдает. Без них — каждое ограничение нужно угадывать.

> **Промпт для генерации кода:** «Напиши Python-функцию `transfer_funds(from_account: Account, to_account: Account, amount: Decimal) -> TransferResult` с Design by Contract. Добавь в docstring: Pre-conditions (amount > 0, достаточный баланс, разные ID, активные счета), Post-conditions (балансы изменены, транзакция залогирована), Invariant (суммарный баланс неизменен). Реализуй pre- и post-conditions как executable assertions.»

---

## 16.3. Zero-Context Survival: код должен быть понятен без промпта

### Тест на автономность

Вспомните **карточку экстренной инструкции** в самолёте или на огнетушителе. Она работает, даже если вы никогда не видели этот аппарат раньше: пиктограммы, нумерация шагов, цветовая маркировка — нулевой контекст, полная ясность. Ваш код должен быть такой же карточкой: когда AI-агент открывает файл впервые, он должен понять всё без дополнительных объяснений.

Код, сгенерированный ИИ, должен быть **полностью самодокументированным** — работать и быть понятным без оригинального промпта. Это критерий качества:

**Вопросы для проверки:**
1. Может ли новый разработчик понять функцию, прочитав только код?
2. Все ли зависимости явны (imports, типы, конфигурация)?
3. Понятны ли границы ответственности (что функция делает и чего НЕ делает)?
4. Есть ли обработка ошибок для реальных сценариев?

### Антипаттерн: контекстно-зависимый код

Функция `process(data)` без типов, с обращением к `item[2]`, неизвестным `threshold` и загадочной `transform` — требует промпт для понимания. В противоположность — самодокументированная `filter_high_temperature_readings` с typed dataclass `SensorReading`, явным порогом `threshold_celsius` и однострочной реализацией. Второй вариант не требует контекста: новый разработчик или AI-агент поймёт всё из сигнатуры.

> **Промпт для генерации кода:** «Рефакторинг: есть функция `process(data)`, которая фильтрует элементы по неявному порогу `threshold` и применяет неявную `transform`. Перепиши в Zero-Context Survival стиле: создай frozen dataclass `SensorReading` (sensor_id, timestamp, temperature_celsius), напиши функцию `filter_high_temperature_readings(readings: Sequence[SensorReading], threshold_celsius: float = 40.0) -> list[SensorReading]`. Все зависимости явные, типы на всех аргументах.»

---

## 16.4. "Small Simple Blocks": линейный код лучше переусложнённого DRY

### Проблема чрезмерной абстракции

DRY (Don't Repeat Yourself) — полезный принцип, но его чрезмерное применение создаёт проблемы для AI-генерации.

**Переабстрагированный код** — это иерархия `PricingStrategy(ABC)` → `StandardPricing` → `DiscountPricing` + `DiscountFactory` → `BulkDiscount` / `NoDiscount`. Шесть классов, три уровня косвенности — чтобы посчитать `subtotal × (1 - discount)`. Модель должна отследить всю цепочку, чтобы понять, что делает одна операция.

**Линейный код** — одна функция `calculate_order_price(items, discount_percent) -> Decimal`: посчитать subtotal, применить скидку, вернуть результат. Три строки логики, нулевая косвенность.

> **Промпт для генерации кода:** «Напиши Python-функцию `calculate_order_price(items: list[OrderItem], discount_percent: float = 0.0) -> Decimal`. Линейный стиль, без Strategy pattern. Посчитай subtotal как сумму quantity × unit_price, примени скидку, верни результат. Документируй в Google-style docstring.»

### Почему линейный код лучше для AI

1. **Меньше косвенности**: модель не должна отслеживать цепочку `Strategy -> Factory -> Discount -> apply()`.
2. **Все шаги видимы**: в линейном коде каждый шаг — одна строка. В абстрактном — каждый шаг спрятан за интерфейсом.
3. **Модификация проще**: чтобы изменить расчёт, достаточно изменить одну функцию, а не всю иерархию.
4. **Тестирование проще**: один вход → один выход, без моков и dependency injection.

### Когда абстракция оправдана

| Критерий | Линейный код | Абстракция |
|----------|-------------|------------|
| Вариантов < 3 | if/elif | Strategy pattern |
| Код используется 1–2 раза | Дублирование | Helper function |
| Логика < 30 строк | Inline | Отдельный класс |
| Требования стабильны | Прямой код | Зависит |
| Требования часто меняются | Зависит | Абстракция с контрактом |
| Вариантов > 5 | Спагетти | Strategy/Registry |

**Правило WET (Write Everything Twice)**: дублируйте код до тех пор, пока не увидите три одинаковых фрагмента. Тогда — и только тогда — абстрагируйте. Преждевременная абстракция опаснее дублирования.

---

## 16.5. Как писать код, который AI легко модифицирует

### Принципы кода, дружественного ИИ

**1. Явные зависимости через аргументы (Dependency Injection простыми словами):**

Dependency Injection звучит сложно, но идея простая: вместо того чтобы функция сама доставала себе всё нужное из глобальных переменных, она **получает всё через аргументы**. Представьте: вместо «я сам пойду в магазин за молоком» — «передайте мне молоко в руки». Для AI-агента это означает: он видит все зависимости прямо в сигнатуре, не рыская по всему проекту. Пример: вместо `get_user()`, которая обращается к глобальным `db` и `current_user_id` — `get_user(db: Session, user_id: int) -> User | None`.

**2. Один файл = одна концепция:**
```
# ✗ models.py — 500 строк с User, Order, Product, Payment, Notification

# ✓ 
models/
├── user.py       # User, UserRole
├── order.py      # Order, OrderItem, OrderStatus  
├── product.py    # Product, Category
├── payment.py    # Payment, PaymentMethod
└── notification.py # Notification, NotificationType
```

**3. Конфигурация через переменные, не через код:**

Вместо magic numbers (`if retry_count > 3 and elapsed > 30`) — именованные константы `MAX_RETRIES = 3`, `TIMEOUT_SECONDS = 30` с использованием в условии и информативным сообщением об ошибке. AI-агент сразу понимает семантику и может безопасно изменить значение.

**4. Тесты рядом с кодом (почему это критично для AI-агентов):**

AI-агенты (Claude Code, Cursor Agent, Devin) работают циклично: написал код → запустил тест → увидел ошибку → исправил. Если тест лежит рядом с кодом, агент находит его мгновенно. Если тесты в отдельной папке `tests/` с другой структурой — агент тратит токены на поиск и может обновить не тот файл. Кроме того, агент использует тесты как **спецификацию**: тест показывает, что функция должна делать, какие краевые случаи важны.
```
pricing/
├── __init__.py
├── calculator.py
├── test_calculator.py    # Рядом с кодом, а не в отдельной папке tests/
└── conftest.py
```

Модель видит тест рядом с реализацией → может обновить оба одновременно.

---

## 16.6. Как AI-агенты читают ваш код

### Что видит агент, когда открывает проект

В 2026 году AI-агенты — это не просто автокомплит. Claude Code, Cursor Agent, Windsurf, Devin — это полноценные «разработчики», которые навигируют по проекту, читают файлы, запускают тесты и коммитят код. Понимание того, **как** они это делают, позволяет писать код, который они обрабатывают эффективно.

**Шаг 1: Индексация структуры.** Агент начинает с дерева файлов. Он видит имена файлов и папок — это его первая карта. `order/pricing.py` сообщает больше, чем `utils/helpers2.py`.

**Шаг 2: Чтение ключевых файлов.** Агент читает конфигурационные файлы (README, pyproject.toml, package.json), затем — файлы, релевантные задаче. У каждого агента ограниченное контекстное окно, поэтому он выбирает, какие файлы загрузить.

**Шаг 3: Семантический поиск.** Современные IDE (Cursor, Windsurf, Copilot в VS Code) создают эмбеддинги вашего кода. Когда агент ищет «где вычисляется цена заказа», он находит `calculate_order_price()` по семантическому сходству — но только если имя функции осмысленное.

**Шаг 4: Навигация по зависимостям.** Агент переходит от функции к её зависимостям через import'ы и type hints. Явные типы = быстрая навигация. Неявные globals = слепое блуждание.

**Шаг 5: Цикл редактирования.** Агент вносит изменение, запускает тесты, анализирует ошибки. Чем быстрее этот цикл — тем лучше результат.

### Что помогает, а что мешает агенту

| Помогает | Мешает |
|----------|--------|
| Осмысленные имена файлов и функций | `utils.py`, `helpers.py`, `misc.py` |
| Type hints на всех публичных функциях | `def process(data)` без типов |
| README с описанием архитектуры | Нет документации проекта |
| Тесты рядом с кодом | Тесты в отдельной иерархии `tests/` |
| Маленькие файлы (< 100 строк) | Файлы на 1000+ строк |
| Явные import'ы | Магические `__getattr__`, метапрограммирование |
| `.cursorrules` / `AGENTS.md` с инструкциями | Неявные конвенции «у нас так принято» |


### Overview graph, AST graph и raw code: почему нужен гибрид

Спор «семантический обзор vs AST-граф» неверно ставить как выбор одного победителя. Разные слои решают разные задачи.

**AST/dataflow-слой** полезен, когда агенту нужно пройти многошаговую цепочку зависимостей: controller -> service -> repository, интерфейс -> реализация, producer -> consumer. Здесь структурный retrieval действительно выигрывает. DraCo строит repo-specific context graph через dataflow analysis и улучшает repository-level code completion в среднем на 3.43% exact match и 3.27% identifier F1. Работа **Reliable Graph-RAG for Codebases** показывает похожий вывод для архитектурных и code-tracing запросов: детерминированный AST-derived graph даёт более надёжное покрытие и multi-hop grounding, чем vector-only baseline и LLM-extracted graph.

Но production-практика важна не меньше. Cursor документирует codebase indexing через embeddings for each file, а Kilo Code — через Tree-sitter semantic blocks + embeddings + Qdrant. То есть реальные агенты часто используют AST и парсинг **как основу индексирования и chunking**, а не как единственный интерфейс, с которым модель потом «разговаривает».

**Intent/overview-слой** решает другую задачу: быстро понять, что это за модуль, ради чего он существует и какие ограничения уже известны. Здесь особенно полезны `@PURPOSE`, `@INVARIANT`, `@RATIONALE`, `@REJECTED` и верхнеуровневые relations. SpecRover показывает, что specification inference внутри LLM-агента повышает качество генерации патчей: в статье сообщается о росте более чем на 50% относительно AutoCodeRover на полном SWE-Bench. А работа **Your Coding Intent is Secretly in the Context** показывает, что намеренное восстановление авторского замысла перед completion даёт более 20% относительного выигрыша на задачах уровня целого репозитория.

Для дружественной ИИ кодовой базы лучше работает многослойный паттерн:

1. **Слой обзора и намерения** — что это за модуль и чего нельзя ломать.
2. **Структурный слой** — AST, imports, dataflow, call edges.
3. **Слой привязки к коду** — чтение реального кода перед финальной правкой.
4. **Слой тестов и проверки** — тесты и postconditions после изменения.

Это именно тот класс практик, куда логично помещать GRACE. Он не отменяет AST и не заменяет raw code; он даёт модели короткую карту местности перед тем, как она войдёт в детали.


### Public truth vs private truth: двухуровневый интерфейс репозитория

Одна из самых полезных идей, которая хорошо оформлена в `grace-marketplace`, — жёсткая граница между **shared/public truth** и **file-local/private truth**.

**Shared/public truth** — это артефакты уровня проекта: `requirements.xml`, `development-plan.xml`, `knowledge-graph.xml`, `verification-plan.xml`. Они отвечают на вопросы:

- какие модули вообще существуют;
- каковы их публичные контракты и зависимости;
- какие verification-entry и critical flows к ним привязаны;
- в каком порядке их безопасно реализовывать или менять.

**File-local/private truth** — это уже заголовок и внутренняя разметка конкретного файла: `MODULE_CONTRACT`, `MODULE_MAP`, локальные контракты функций, semantic blocks, `CHANGE_SUMMARY`, а в варианте GRACE-подобной markdown-разметки — `[DEF]`, `@PURPOSE`, `@RATIONALE`, `@REJECTED` и т. п.

Почему такое разделение важно:

1. **Shared-артефакты не распухают.** Если knowledge graph начинает перечислять каждый private helper, он превращается из карты страны в снимок всех кухонь сразу.
2. **Снижается drift.** Публичная граница меняется реже, чем внутренняя реализация. Значит, общий слой синхронизировать проще.
3. **Агенту легче выбрать глубину чтения.** Сначала он читает boundary-level truth, и только потом — implementation detail в целевом файле.

Практическое правило: shared docs должны отвечать на вопрос **«что за модуль и как он связан с другими?»**, а file-local markup — на вопрос **«что именно здесь можно безопасно менять и чего делать нельзя?»**.

### Репозиторий как интерфейс запросов для агента

Важный сдвиг в поздних версиях `grace-marketplace` — появление слоя запросов, понимающего схему. CLI и навыки репозитория разделяют два режима чтения:

- `grace module find/show` — доступ к **shared/public** модульной картине;
- `grace file show --contracts --blocks` — доступ к **local/private** контрактам и semantic blocks конкретного файла.

Это очень сильная идея для агентной разработки. Вместо того чтобы каждый раз гнать модель через голый `grep`, полезно дать ей **два разных API чтения**:

1. найти релевантный модуль и его публичный контекст;
2. после этого открыть только нужный файл и только его важные секции.

Такой слой запросов делает из репозитория не просто папку с кодом, а **контекстный интерфейс**. Для модели это снижает избыточное чтение, уменьшает вероятность схватиться не за тот файл и помогает не путать факты уровня границы модуля с локальными деталями реализации.

Минимальный рабочий паттерн для кодового агента выглядит так:

```text
grace module find auth
→ grace module show M-AUTH --with verification
→ grace file show src/auth/index.ts --contracts --blocks
→ только потом чтение/редактирование сырого кода
```

Это не отменяет embedding search и AST traversal. Скорее наоборот: semantic search помогает найти кандидатов, structural graph помогает пройти multi-hop зависимости, а слой запросов даёт **короткую проверенную выжимку**, с которой агент начинает работу.


### Практика 2026: патч не в один выстрел, а через короткий цикл поиска и проверки

Метаобзор 2026 года по механике LLM полезен именно тем, что соединяет проектирование запросов, обучение в контексте и масштабирование вычислений на этапе инференса в одну инженерную картину. Для кодовых задач из этого следует очень прикладной вывод: качество патча определяется не красотой одной формулировки, а качеством **цикла** — как агенту показали формат задачи, сколько вариантов он пробует и чем эти варианты проверяются.

**1. Стабилизируйте каркас промпта.** Не переписывайте системный промпт заново под каждую задачу. Держите постоянный шаблон: `задача -> целевые файлы -> ограничения -> инварианты -> чем проверяем -> формат ответа`. Для репозитория это важнее, чем художественные формулировки. Если вы даёте несколько демонстрационных примеров, пусть они фиксируют именно форму результата: список файлов, дифф, тест-план, чек-лист приёмки.

**2. Порождайте несколько патчей только там, где поиск действительно нужен.** Для переименования, мелкого CRUD и однотипных правок хватит одного прохода и тестов. Для миграции схемы, неоднозначного багфикса, сложного SQL, оптимизации алгоритма или многофайлового рефакторинга лучше сразу закладывать 3–5 кандидатов патча и выбирать лучший по верификатору. Это дешевле, чем десять раз просить одну и ту же модель «подумать ещё» в том же контексте.

**3. Пусть побеждает верификатор, а не риторика модели.** Для кода внешний верификатор почти всегда сильнее внутреннего самообъяснения. Базовый стек: `pytest`, типизация (`mypy` или `pyright`), линтер (`ruff`/`eslint`), проверка схемы, контрольный дифф, интеграционный smoke-тест. Практическое правило простое: выбирайте самый маленький дифф, который проходит проверки и не ломает инварианты.

**4. Давайте модели ровно столько рассуждения, сколько нужно для аудита.** Для сложной задачи полезен краткий план в 3–5 пунктов: какие файлы менять, какие риски проверить, чем валидировать итог. Но длинный многостраничный монолог редко даёт дополнительную ценность. Если после короткого плана патч и проверки уже согласованы, дальнейшее «размышление вслух» обычно только расходует токены и повышает шанс уйти в сторону.

> **Промпт для генерации кода:** «Собери пайплайн `generate_patch_candidates(task, repo_context)` на Python. Вход: описание задачи, список целевых файлов, инварианты, команды верификации. Шаги: (1) сгенерировать 3 кандидата патча в едином формате, (2) прогнать для каждого тесты, линтер и типизацию, (3) выбрать минимальный дифф, прошедший все проверки, (4) вернуть `best_patch`, `verification_report`, `rejected_candidates`. Используй asyncio для параллельного прогона проверок.»

### Калибруйте шаблон на истории реального репозитория

Лучший промпт для кода не угадывают, а подбирают на собственных задачах. Возьмите 20–30 реальных тикетов из истории проекта и сравните 2–3 версии каркаса: например, «короткий план + diff» против «только диффа», или «один кандидат» против «3 кандидата + верификатор». Смотрите не на красоту объяснения, а на инженерные метрики:

- долю задач, где патч проходит тесты с первой попытки;
- число ручных правок после ответа агента;
- точность выбора файлов;
- среднюю стоимость одного успешно закрытого тикета.

Именно так стоит относиться к контуру запросов в коде: как к конфигурации, которую можно оптимизировать по метрике, а не как к магическому тексту.

### Иерархия инструкций защищает агента от заражённого контекста

Ещё один практический вывод из свежих работ по inference и безопасности: кодовый агент нельзя учить одинаково доверять всем кускам контекста. В репозитории всегда есть шум — старые комментарии, устаревшие README, треды в issue-трекере, случайные примеры, чужие SQL-сниппеты. Поэтому полезно зафиксировать явную иерархию:

1. **Задача пользователя и критерии приёмки.**
2. **Правила репозитория:** `AGENTS.md`, `.cursorrules`, контракты модулей, тесты.
3. **Структурные факты:** типы, AST, imports, dataflow.
4. **Нестабильный контекст:** issue-описания, старые комментарии, внешние вставки и примеры.

Если README противоречит тесту, доверяйте тесту. Если комментарий спорит с контрактом функции, доверяйте контракту. Если внешний фрагмент кода предлагает «срезать угол», но нарушает инвариант из `@INVARIANT`, агент должен отклонить такой путь, а не послушно продолжать. Для production-команды это одна из самых дешёвых и полезных защит: она одновременно улучшает качество патчей и снижает риск того, что агент подхватит вредное указание из неавторитетного контекста.

### Пример: как имена файлов влияют на агента

```
# ✗ Агент не понимает, где искать
src/
├── utils.py          # 400 строк всего подряд
├── helpers.py        # Ещё 300 строк
├── core.py           # «Ядро» из 800 строк
└── main.py

# ✓ Агент находит нужное за секунды
src/
├── auth/
│   ├── login.py
│   ├── tokens.py
│   └── permissions.py
├── orders/
│   ├── pricing.py
│   ├── validation.py
│   └── fulfillment.py
└── notifications/
    ├── email.py
    └── sms.py
```

---

## 16.7. Конфигурация AI на уровне проекта

### Новый стандарт: инструкции для AI рядом с кодом

В 2026 году в корне проекта наряду с `.gitignore` и `README.md` появился новый тип файлов — **инструкции для AI-агентов**. Это не опция; это production-практика, которую используют команды от стартапов до FAANG.

| Файл | Инструмент | Назначение |
|------|-----------|-----------|
| `.cursorrules` (или `.cursor/rules/` в новых версиях) | Cursor | Правила для Cursor Agent и автокомплита |
| `.clinerules` | Cline | Инструкции для Cline-агента |
| `AGENTS.md` | Claude Code | Инструкции для Claude Code по папкам и стилю |
| `.github/copilot-instructions.md` | GitHub Copilot | Инструкции для Copilot в VS Code |
| `.windsurfrules` | Windsurf | Правила для Windsurf Cascade |

### Что писать в этих файлах

Эти файлы — **контракт между командой и AI-агентом**. Они фиксируют то, что раньше передавалось устно: стиль кода, архитектурные решения, запреты.

```markdown
# .cursorrules (пример для Python-проекта)

## Стиль кода
- Python 3.12+, type hints обязательны на всех публичных функциях
- Docstrings в формате Google style
- Максимум 100 строк на файл, 30 строк на функцию

## Архитектура
- Бизнес-логика в domain/, HTTP в api/, хранение в storage/
- Никаких глобальных переменных — dependency injection через аргументы
- Каждый модуль имеет свой test_*.py рядом

## Запреты
- НЕ использовать ORM (мы используем raw SQL через asyncpg)
- НЕ добавлять новые зависимости без обсуждения
- НЕ использовать print() — только structlog

## Тестирование
- pytest, тест рядом с модулем
- При изменении функции — обновить тест
```

```markdown
# AGENTS.md (пример для Claude Code)

## Общие правила
При изменении любого файла в domain/ — обязательно запустить тесты.

## Структура проекта
- api/ — FastAPI роуты, тонкий слой, без бизнес-логики
- domain/ — чистая бизнес-логика, без зависимостей от фреймворков
- storage/ — работа с БД, все SQL-запросы здесь

## Code review
- Перед коммитом проверь: нет ли файлов > 100 строк
- Все новые функции должны иметь docstring с примером
```

### Почему это работает

AI-агент читает эти файлы **первыми** — до того, как смотрит ваш код. Это как дать новому сотруднику документ «Как мы здесь работаем» в первый рабочий день. Без него агент будет угадывать ваш стиль (и угадает неправильно). С ним — сразу пишет код по вашим правилам.

---

## 16.8. Миграция: от монолита к коду, дружественному ИИ

### Пошаговый план рефакторинга

Вы не можете переписать весь проект за день. Но вы можете мигрировать постепенно — и каждый шаг будет давать немедленный эффект для AI-агентов.

**Шаг 1: Добавьте конфигурацию AI (1 час).** Создайте `.cursorrules` или `AGENTS.md` с описанием текущей архитектуры и правил. Это самый быстрый способ улучшить качество AI-генерации — агент сразу поймёт контекст.

**Шаг 2: Нарежьте на файлы самые большие модули (1–2 дня).** Найдите файлы > 300 строк: `find . -name "*.py" | xargs wc -l | sort -rn | head -20`. Разбейте каждый по принципу «один файл = одна концепция». Не меняйте логику — только перемещайте.

**Шаг 3: Добавьте контракты к ключевым функциям (постепенно).** Начните с публичных функций: type hints + docstring с примером. Не нужно описывать всё сразу — начните с функций, которые AI-агенты меняют чаще всего.

**Шаг 4: Переместите тесты к коду (1 день).** Вместо `tests/test_pricing.py` → `order/test_pricing.py`. Обновите `testpaths` в `pyproject.toml`, чтобы pytest искал тесты прямо в `src/`.

**Шаг 5: Замените implicit globals на аргументы (постепенно).** Каждый раз, когда AI-агент (или вы) трогаете функцию с глобальной зависимостью — превращайте её в аргумент. Не нужно менять всё сразу.

### Чек-лист миграции

- [ ] `.cursorrules` / `AGENTS.md` / `copilot-instructions.md` создан
- [ ] Нет файлов > 300 строк (`find . -name "*.py" | xargs wc -l | sort -rn`)
- [ ] Все публичные функции имеют type hints
- [ ] Ключевые функции имеют docstring с примером
- [ ] Тесты рядом с кодом (`test_*.py` в том же каталоге)
- [ ] Нет глобальных переменных в бизнес-логике
- [ ] README описывает структуру проекта

### От недружественного ИИ кода к дружественному: ещё примеры

**Пример 1: Неявная конфигурация → явная.** Анти-паттерн: `import config` с обращением к `config.SMTP_HOST`, `config.SMTP_PORT` — AI-агент не знает, где этот файл и что в нём. AI-friendly вариант: frozen dataclass `SmtpConfig(host, port, user, password)` передаётся в `send_email()` как аргумент. Все зависимости видны в сигнатуре.

**Пример 2: Магические строки -> enum'ы.** Анти-паттерн: `update_order(order, new_status: str)` с проверкой `if new_status == "paid"` — агент не знает полный список статусов. AI-friendly вариант: `OrderStatus(str, Enum)` с явными членами (PENDING, PAID, SHIPPED, DELIVERED, CANCELLED) и типизированный `update_order(order: Order, new_status: OrderStatus) -> Order`.

> **Промпт для генерации кода:** «Рефакторинг: есть функция `send_email(to, body)`, которая использует глобальный `import config` для SMTP-настроек. Перепиши: создай frozen dataclass SmtpConfig (host, port, user, password), передай в send_email как аргумент. Добавь также OrderStatus(str, Enum) с пятью статусами заказа и типизированную update_order.»

---

## Практический вывод

### Чек-лист AI-friendly кода

| # | Правило | Метрика |
|---|---------|---------|
| 1 | **Модули < 100 строк** | `wc -l` на каждый файл |
| 2 | **Функции < 30 строк** | Cyclomatic complexity < 10 |
| 3 | **Явные контракты** | Типы + docstring + примеры |
| 4 | **Zero-Context Survival** | Новичок поймёт без промпта? |
| 5 | **Линейный > абстрактный** | WET до 3-х повторений |
| 6 | **Явные зависимости** | Через аргументы, не globals |
| 7 | **Тесты рядом** | `test_*.py` в том же каталоге |
| 8 | **Нет magic numbers** | Именованные константы |
| 9 | **AI-конфигурация** | `.cursorrules` / `AGENTS.md` в корне проекта |
| 10 | **Semantic header на сложных модулях** | `@PURPOSE` + `@INVARIANT` + `@RELATION` + при необходимости `@RATIONALE/@REJECTED` |
| 11 | **Осмысленные имена** | Файлы и функции названы по смыслу, не `utils.py` |
### Ментальная модель

> **Пишите код не для себя сегодняшнего, а для AI-агента, который увидит его впервые.** Если через год Cursor Agent сможет изменить функцию, не сломав соседние — ваша архитектура успешна. Контракты, маленькие модули, явные зависимости и AI-конфигурация в корне проекта — это не теория, а production-практика 2026 года.

### Задания

1. **Аудит кодовой базы по размеру файлов.** Выполните `find . -name "*.py" | xargs wc -l | sort -rn | head -20` в вашем проекте. Файлы > 300 строк — кандидаты на разбиение по принципу «один файл = одна концепция». Ожидаемый результат: список файлов для рефакторинга с приоритизацией по частоте изменений (чем чаще AI-агент касается файла, тем выше приоритет).

2. **Создайте AGENTS.md или .cursorrules для проекта.** Опишите: структуру каталогов, стиль кода, архитектурные решения, запреты. Проверьте: попросите AI-агента добавить новый endpoint или функцию — результат должен соответствовать вашим конвенциям без дополнительных инструкций. Ожидаемый результат: файл в корне проекта, который AI-агент читает при первом обращении.

3. **Добавьте контракты к 5 ключевым функциям.** Выберите 5 публичных функций, которые AI-агенты изменяют чаще всего (git history → frequency of changes). Добавьте type hints, Google-style docstring с Args/Returns/Raises и Example. Ожидаемый результат: 5 функций с полным контрактом, которые AI-агент может менять с первого раза без ошибок.

---

## Источники
- Martin, R. C. (2008). "Clean Code: A Handbook of Agile Software Craftsmanship." (Принципы, адаптированные для AI-эры)
- Fowler, M. (2018). "Refactoring: Improving the Design of Existing Code." 2nd Ed.
- Cursor. "Rules for AI." Cursor Documentation (2025–2026).
- Anthropic. "Best practices for agentic coding." Claude Documentation (2025).
- Anthropic. "AGENTS.md: A standard for AI agent project configuration." (2026).
- GitHub. "Copilot coding agent: customization with copilot-instructions.md." GitHub Docs (2026).
- Cognition. "Devin: AI Software Engineer." Best Practices (2025–2026).
- Gan, Z., Ren, R., Yao, W., et al. (2026). "Beyond the Black Box: A Survey on the Theory and Mechanism of Large Language Models." arXiv:2601.02907. Метаобзор по inference-stage практикам: prompt engineering, in-context learning, inference-time scaling и evaluation.
- Zhao, Z., Wallace, E., Feng, S., Klein, D., & Singh, S. (2021). "Calibrate Before Use: Improving Few-shot Performance of Language Models." ICML 2021. Формат и порядок примеров существенно влияют на качество; полезна калибровка prompt scaffold.
- Min, S., Lyu, X., Holtzman, A., Artetxe, M., Lewis, M., Hajishirzi, H., & Zettlemoyer, L. (2022). "Rethinking the Role of Demonstrations: What Makes In-Context Learning Work?" EMNLP 2022. В few-shot важны формат, пространство меток и распределение входов, а не только «правильные» ответы в примерах.
- Khattab, O., Singhvi, A., Maheshwari, P., Zhang, Z., Santhanam, K., Vardhamanan, S., Haq, S., Sharma, A., Joshi, T. T., Moazam, H., Miller, H., Zaharia, M., & Potts, C. (2024). "DSPy: Compiling Declarative Language Model Calls into Self-Improving Pipelines." ICLR 2024 / TMLR. Пайплайны с LLM стоит оптимизировать по метрике, а не править вручную вслепую.
- Yang, C., Yue, X., Zhang, Y., et al. (2024). "Large Language Models as Optimizers." ICLR 2024. Prompt pipeline можно улучшать итеративно, как оптимизируемый артефакт.
- Gupta, M., et al. (2026). "Revisiting the Role of Natural Language Code Comments in Code Translation." arXiv:2601.16661.
- Yang, G., et al. (2026). "Less is more: DocString compression in code generation." ACM TOSEM 35(2). DocString как носитель требований; 25–40% compression без потери качества.
- van Dam, T., Izadi, M., & van Deursen, A. (2023). "Enriching Source Code with Contextual Data for Code Completion Models: An Empirical Study." MSR 2023. Multi-line comments помогают умеренно; не вся дополнительная разметка одинаково полезна.
- Cheng, W., Wu, Y., & Hu, W. (2024). "Dataflow-Guided Retrieval Augmentation for Repository-Level Code Completion." ACL 2024. Repo-specific context graph и улучшение repository-level completion.
- Chinthareddy, M. R. (2026). "Reliable Graph-RAG for Codebases: AST-Derived Graphs vs LLM-Extracted Knowledge Graphs." arXiv:2601.08773.
- Ruan, H., Zhang, Y., & Roychoudhury, A. (2024). "SpecRover: Code Intent Extraction via LLMs." arXiv:2408.02232 / ICSE 2025.
- Li, Y., et al. (2025). "Your Coding Intent is Secretly in the Context and You Should Deliberately Infer It Before Completion." arXiv:2508.09537.
- Yao, S., Yu, D., Zhao, J., Shafran, I., Griffiths, T., Cao, Y., & Narasimhan, K. (2023). "Tree of Thoughts: Deliberate Problem Solving with Large Language Models." NeurIPS 2023. Для сложных задач полезен поиск по нескольким траекториям вместо одного ответа.
- Snell, C., et al. (2025). "Scaling LLM Test-Time Compute Optimally Can Be More Effective Than Scaling Model Parameters." arXiv:2408.03314. Бюджет размышления стоит распределять адаптивно по сложности задачи.
- Setlur, A., et al. (2025). "Scaling Test-Time Compute Without Verification or RL is Suboptimal." arXiv:2506.14495. Для роста качества нужен verifier, а не просто больше попыток.
- Swamy, G., et al. (2025). "All Roads Lead to Likelihood: The Value of Reinforcement Learning in Fine-Tuning." arXiv:2505.14864. Генерацию выгодно ограничивать пространством решений, которое хорошо отделяется верификатором.
- Sprague, Z., et al. (2025). "To CoT or not to CoT? Chain-of-Thought Helps Mainly on Math and Symbolic Reasoning." arXiv:2503.16411. Длинное рассуждение полезно применять выборочно.
- Wang, X., & Zhou, D. (2024). "Chain-of-Thought Reasoning Without Prompting." arXiv:2402.10200. Не вся полезная reasoning-траектория обязана быть явно выписана в ответе.
- Ivanov, V. `osovv/grace-marketplace` README, CHANGELOG v3.5–3.7, `grace-explainer`, `grace-plan`, `grace-cli`, semantic validator patterns (`check_semantics.py`) (2026). Shared/public vs file-local/private boundary; schema-aware query layer (`module find/show`, `file show`); enforceable semantic markup.

---

**Навигация:**
- Назад: [Глава 15. Безопасность LLM-систем](15_llm_system_security.md)
- Далее: [Глава 17. Наблюдаемость и эксплуатация LLM-продукта](17_observability_and_operations.md)
