Трендовые github проекты в нашем телеграм канале. Подпишись → Проверяемый контекст для изменений в большом проекте
AI-агент может быстро добавить функцию, поправить тест или изменить API-клиент. Локальная правка выглядит убедительно: код читается, типы сходятся, ближайшая проверка проходит. Ошибка часто проявляется дальше по цепочке — в другом слое приложения, фоновом обработчике или пользовательском сценарии.
Причина в том, что кодовая база живёт не набором файлов, а сетью зависимостей. Сигнал от одного вызова проходит через внедрение зависимостей, сервисы, HTTP-границы, очередь, хранилище и frontend. При изменении публичной сигнатуры или настройки провайдера важно знать не только места с тем же именем, но и реальные пути, которые могут быть затронуты.
Почему поиска по символу недостаточно
Обычный поиск хорошо отвечает на вопрос «где встречается строка». Для анализа влияния этого мало. У метода save может быть несколько реализаций, а получатель вызова способен приходить из конструктора, фабрики, DI-контейнера или конфигурации во время запуска.
Рассмотрим упрощённый сервис:
class OrderService:
def __init__(self, repository):
self.repository = repository
def create_order(self, order):
return self.repository.save(order)
Из строки self.repository.save(order) наблюдается факт вызова метода save у поля repository. Конкретная цель пока не доказана. В проекте могут существовать OrderRepository, PaymentRepository, тестовый mock и несколько зарегистрированных провайдеров.
Чтобы связать вызов с методом реализации, анализатору нужны дополнительные свидетельства: тип параметра конструктора, присваивание поля, binding в контейнере, правила выбора реализации и объявление целевого метода. Если этих данных нет или они противоречат друг другу, результатом должен быть список кандидатов либо неизвестная граница. Уверенная стрелка без основания создаёт для разработчика и агента ложный маршрут.
Разделяйте факт, вывод и наблюдение
Полезный граф кода хранит происхождение каждой связи. Для ребра между сервисом и репозиторием достаточно следующей структуры:
{
"from": "OrderService.create_order",
"to": "OrderRepository.save",
"kind": "CALLS",
"resolution_status": "resolved",
"evidence_class": "static_inferred",
"validation_status": "not_validated",
"confidence": 0.86,
"evidence": [
"параметр конструктора repository: OrderRepository",
"self.repository = repository",
"self.repository.save(order)"
]
}
Здесь стоит различать три слоя. Факт извлекается напрямую из исходного кода: объявление класса, import, вызов, декоратор маршрута, присваивание. Семантический вывод связывает эти факты правилами языка и фреймворка. Наблюдение времени выполнения показывает, что путь действительно прошёл в конкретном тесте, окружении и конфигурации.
Высокий confidence описывает силу цепочки доказательств, а не гарантию исполнения при каждом запуске. Runtime-трасса подтверждает один сценарий, однако не исключает альтернативную ветку, feature flag, другой provider или неисполненный async-процесс. Храните эти свойства отдельно: так отчёт не превращает частичное знание в абсолютное.
Конвейер построения графа
Анализ удобно строить как последовательность стадий с устойчивыми артефактами:
- Inventory определяет языки, manifests, локальные модули, тесты и внешние зависимости.
- Scan plan исключает generated-код,
node_modules, build-артефакты, vendor-каталоги и бинарные файлы. - Extraction собирает raw facts через AST и parser-backed извлекатели: декларации, импорты, вызовы, маршруты, поля и присваивания.
- Normalization назначает стабильные идентификаторы и сохраняет позицию в файле, исходный факт и provenance.
- Semantic binding формирует кандидатов: связывает импорты, типизированные receivers, конструкторы, return values и провайдеры зависимостей.
- Precision resolution подтверждает единственную цель либо сохраняет статусы
ambiguous,unresolvedиunsupported_semantics. - Impact query строит ограниченный обход вверх и вниз от изменённого символа.
Такое разбиение помогает обновлять граф инкрементально. После изменения файла можно пересчитать только зависимые факты, рёбра и resolver-ы, а неизменённую часть переиспользовать. Стабильные идентификаторы позволяют сравнить incremental-результат с полным rebuild и заметить расхождение.
Роль правил фреймворка
Языкового синтаксиса недостаточно для полного маршрута в современном сервисе. Например, в FastAPI цепочка включает декоратор маршрута, APIRouter, вложенный include_router с префиксом, Depends и сервисный слой. Во frontend похожая проблема возникает из-за aliases, barrel exports, hooks, HTTP-wrapper-ов и динамически собираемых путей.
Для таких случаев нужны версионируемые support packs. Каждое правило должно фиксировать пакет, версию правила, совпавший паттерн и доказательства. Это позволяет ответить на простой, но критичный вопрос: почему граф считает конкретный endpoint связанным с handler-ом?
Правила полезно проверять на положительных и запрещённых fixtures. Положительный сценарий требует найти ожидаемую связь. Запрещённый фиксирует похожую конструкцию, которую анализатор не имеет права соединять. Например, маршрут пользователей не должен попасть в цепочку заказа только из-за одинакового имени метода save.
Как проверять точность
Количество узлов и стрелок почти ничего не говорит о качестве графа. Нужны измеримые проверки:
- true positive, false positive и false negative на размеченных semantic edges;
- число forbidden violations — запрещённых связей, появившихся в результате;
- покрытие разрешения eligible callsites;
- доля неизвестных областей, которые действительно требуют ручной проверки;
- стабильность canonical IDs и логических рёбер между повторными запусками;
- эквивалентность инкрементальной обработки полному анализу.
Особенно полезно mutation testing. Удалите binding конструктора, замените provider, добавьте второго кандидата, уберите import, смените тип receiver-а или route prefix. Корректный анализатор после такой мутации изменит цель, снизит уверенность либо сообщит о неоднозначности. Сохранённое «подтверждённое» ребро показывает, что система опирается на совпадение имён вместо доказательств.
Что получает команда и AI-агент
Ценность графа появляется в impact slice — компактном срезе для конкретной задачи. В него входят изменённый узел, вызывающие и вызываемые элементы, endpoints, фоновые задачи, релевантные тесты, цепочки evidence, альтернативные кандидаты и неизвестные границы.
Для pull request такой срез превращается в список проверяемых действий: какие тесты затронуты, где требуется review, через какой dynamic provider проходит рискованная ветка. При инциденте он связывает симптом, handler, сервис, внешний клиент, событие и consumer. При рефакторинге помогает оценить влияние на DTO, публичный API или слой хранения до внесения правок.
AI-агенту не нужно отправлять весь репозиторий в контекст. Ему можно передать подграф, доказательства и список границ, которые нельзя считать установленными. Это сокращает поиск и снижает вероятность того, что агент дополнит отсутствующую связь правдоподобным предположением.
Полностью «зелёный» граф недостижим для любой production-системы: остаются динамическая загрузка, рефлексия, плагины и конфигурации окружения. Явно обозначенная неизвестность здесь полезна. Она направляет внимание на место, которое стоит проверить вручную, тестом или runtime-наблюдением, и сохраняет доверие к подтверждённой части анализа.