Документация. Само это слово стало синонимом нерешаемой проблемы.
Цитата одного менеджера:
“Разработчики держат знание на кончиках пальцев”.
Цитата меня:
“Одних нужно заставлять писать,
других нужно заставлять читать,
третьих нужно заставлять заставлять”.
Итоговое соотношение цена/скорость/качество это то, о чём не принято говорить вслух.
ИИ пока не спасает ситуацию. На больших кодовых базах он ожидаемо упускает контекст и галлюцинирует.
Напротив: Вайбкодинг ведёт нас в эпоху кода, работоспособность которого легко потерять и невозможно понять в гораздо большей степени чем недоразобранные монолиты из десятых годов.
Решение | Содержание:
Исходник инструментария на github.
К статье прилагается видео и поскольку графы это очень наглядно, я рекомендую его к просмотру.
1. Готовый пример.
То, что получаю сейчас, после прохождения всех описанных ниже этапов. Формат диаграммы - экспериментальный.
Перед вами фрагмент кода из исходника Claude Code, чья утечка случилась весной двадцать шестого года.
Это голова функции queryModel, с которой начинается отправка запроса из агента в модель.
Текстовые блоки с пояснениями, я называю аннотациями. Они сгенерированы агентом, а остальная структура получена скриптами.

Исходный код
if ( !isClaudeAISubscriber() && isNonCustomOpusModel(options.model) && ( await getDynamicConfig_BLOCKS_ON_INIT<{ activated: boolean }>( 'tengu-off-switch', { activated: false, }, ) ).activated ) { logEvent('tengu_off_switch_query', {}) yield getAssistantMessageFromError( new Error(CUSTOM_OFF_SWITCH_MESSAGE), options.model, ) return }
Гекс это условное ветвление, синий цвет означает вызов функции. Здесь вычисляется пользователь, подключившийся не по ванильной подписке, а через API-ключ Anthropic или стороннего провайдера.
При срабатывании условия опциональный процесс смещается вправо, когда закончится - вернётся влево в основную ветку.Далее вызов проверки, не выбрал ли юзер модель семейства Opus.
Жёлтым цветом отображается всё что связано с данными. Через паззловое соединение с точкой показан передаваемый объект и его поле. Здесь мы видим выделением что поток вернулся налево, подробности чуть ниже.Для прошедшего через две проверки в этой ветке применяется третья: не отключена ли сейчас модель аварийно.
Спойлер: отключена она может быть по причине перегрузки из-за высокого спроса.
Для этого вычисляется значение представленное в виде гекса, то есть оно булевое. \ Отсутствие имени и диагональная штриховка означают что это переменная которой нет в оригинальном коде, как и метода set, её создавшего. Эти сущности введены в граф и диаграмму моими скриптами для удобства визуализации и движению к единой модели отображения любого кода в графе/диаграмме. Я буду называть их виртуальными, пока не столкнулся с языками где есть ключевое слово virtual, тогда придётся придумать другой термин.Вычисление этого значения происходит через await вызов функции в который передаются два параметра.
Системные ключевые слова покрашены в фиолетовый.
Несколько параметров раскладываются по вертикали.В вызове указан ожидаемый формат ответа который рисуется ниже семейства параметров (чтобы не растягивать диаграмму в ширину) и из его поля activated значение вернётся в ожидающую переменную, и в зависимости от которой выполнение пойдёт вниз или вернётся влево.
Если доступ отключён, то такое событие логируется, а для пользователя формируется сообщение с предложением сменить модель.
После чего выполняется глобальный return, то есть из функции и из диаграммы мы уходим.
2. Логирование динамики.
Обратите внимание что некоторые пути выделены более жирно. Эта подсветка трейса работает эта благодаря тому, что в код внедряется логирование нужных узлов.
На этапе экстракции в графе размечаются места в коде, которые нужны для трейсинга. В основном это развилки. В данном случае мы видим трейс захода через API-токен, но выбора не самой модной модели, которую нужно оберегать
При сборке проекта с соответствующими параметрами добавляется логирующий код. К сохранённым логам имеет доступ расширение VS Code, которое и отрисовывает их на схеме.
В итоге у нас не просто диаграммы draw.io, а интерактивная связка.
Доп. материал: по ссылке доступна полная диаграмма функции, которая срабатывает после ввода промпта.
3. А теперь посмотрим как код преобразуется в граф.
Claude Code это React-подобное приложение на TypeScript, весом чуть больше полумиллиона строчек кода. Чтобы получить граф из такого объёма исходников нужен конвейер ETL.

Слева направо:
Допустим у в исходнике есть функция queryModel которая вызывает две другие isOpusModel и getDynamicConfig. *Я упростил оригинальные названия.
Пока это просто тексты в которых одинаковые слова.Парсер TypeScript строит AST - абстрактное синтаксическое дерево и тогда эти связи становятся явными.
Скрипт “Экстрактор” через компилятор обходит AST и получает знания про все связи кода.Сначала они сохраняются в промежуточную базу как пары “вызывающий - адресат”, я буду называть их Звеньями.
Их можно записывать в граф, но если делать это в лоб, то в скольких звеньях будет встречаться функция queryModel - столько раз она и запишется как вершина. Никакого связного графа не получится, а все звенья будут по отдельности.
В графовой базе Neo4J есть оператор Merge, который при каждой записи проверяет наличие уже записанной вершины, чтоб прикрепить к ней новую связь без повторов.
Но тогда следующая проблема в том, что операция с проверкой очень дорогая, не может быть распараллелена и на больших объёмах такой подход быстро положит весь конвейер.
Решение: делим звенья на два набора: узлы и связи.
В таком разборе узел queryModel окажется в наборе дважды, а значит нужно провести дедупликацию.
Обратите внимание что связи в наборе содержат только идентификаторы своих концов, а не всю их мету. Это не полная копия звеньев, а их декомпозированная часть.
Подготовленные наборы сохраняются в Parquet — компактные колоночные файлы. Их можно читать партиями и повторно импортировать, не разбирая все исходники заново.Первыми в базу (без всяких проверок на уникальность) записываются вершины.
После этого они связываются через запись рёбер которые находят айдишники своих адресатов в индексированном пространстве.Расширение для VS Code читает граф, присваивает стили и расставляет элементы по форку draw.io внутри расширения. В результате получаются диаграммы отдельных функций, а ещё я генерирую высокоуровневые sequence и на этом список потенциальных возможностей не заканчивается.
Такую систему не обязательно строить на Neo4J, можно использовать другие компоненты, в том числе open-source.
Описываемый концепт не ограничивается TypeScript и уровнем кода приложения. Он пригоден для любого языка, а так же можно подняться на уровень архитектуры, можно спуститься до регистров и байтов.
4. Теперь сделаем диаграммы говорящими.

Скрипт, который я называю Аннотатор получит задание описать код нашего алгоритма.
Он запрашивает граф и видит что верхняя функция queryModel вызывает две другие.\ В таком случае задача описания верхней функции откладывается в стек ожидания, а аннотатор спускается по связям графа к вызываемым.Теперь задача описать isOpusModel. Предположим что функция конечная и не вызывает ничего другого, определённого разработчиками. В реальности обход будет более сложным особенно если внутри узлов маршрута будет обнаружено использование глобальных компонентов: модулей, пакетов или переменных, тогда нужно будет сходить наверх за их определениями.
А сейчас примем что код isOpusModel полностью понятен LLM, для его контекста не нужно больше никуда проваливаться и подниматься.
Обходчик передаёт код в модель, получает описание и сохраняет его в граф и свой кэш.Теперь ветка getDynamicConfig. Мы уже знаем про сохранение Аннотаций и нужно проговорить что сначала аннотатор проверяет в своём кэше и в графе нет ли уже готового описания для актуального узла. И на это раз давайте схалявим и предположим что это описание уже было получено в предыдущих проходах.
Теперь можно вернуться к отложенной задаче описания queryModel .
Для этого в модель передаются готовые контексты нижних веток, а так же, если функция большая, то задача декомпозируется и сначала аннотации добываются к её отдельным шагам, как к самостоятельным элементам.
Получилось движение в две стороны: сначала спуск за недостающими смыслами, затем подъём накопленного контекста.
Таким челночно-рекурсивным способом собирается контекст для всех узлов.
Готовые аннотации можно редактировать и таким образом вынести слой комментариев из кода.
Чем это отличается от того, что агенты делают сейчас?
Сохранение проделанной работы. Сейчас агенты каждый раз извлекают контекст повторно.
“Ленивый барьер”: для экономии ресурсов агенты не идут собирать смыслы до самых корней. На сколько уровней они готовы проваливаться это почти всегда решение из “чёрной коробки”.
На задачах документирования кода это приводит к буквально следующим результатам: “Функция queryModel вызывает функции isOpusModel и getDynamicConfig”.
Что делают вызываемые функции агент не выясняет, потому что “копать команды не давали”.Сейчас агенты всецело доверяют, а значит зависят от имён функций, переменных, а ещё комментариев. Эти семантики слишком часто не соответствуют действительности на 100 процентов, и это пространство для галлюцинаций.
Не слишком ли жадный получается алгоритм?
Не начнёт ли он потянув за одну вершину описывать весь граф?
Да, глубина обхода может оказаться очень большой.
Здесь возникает развилка: может быть это не баг, а фича и нам действительно нужно описать весь граф? Поговорим об этом варианте позже, а пока останемся в опциях обработки по частям.
Ещё раз скажу что мы сохраняем все результаты и избегаем повторной работы, в том числе в рамках одной сессии.
Не делегируем агенту самостоятельно гулять по коду. Максимально жёстко определяем логику обхода.
Начинаем с разметки на этапе экстракции из исходника соответствующих типов компонентов и зависимостей. Граф сам должен указывать как собрать его контекст. \ Используем очереди задач и продуманные паттерны обработки.Там, где логика задана скриптами, можно поставить жёсткие лимиты на глубину и ширину обхода, на количество токенов. Это не идеальный приём: появляется риск потерять часть контекста.
Мягкие лимиты: на критических расширениях графа явным образом задаваться вопросом: “нужно ли идти дальше?”

Как видите кодекс уже может останавливаться сам когда дело пахнет комбинаторным взрывом.
Хорошо что он так умеет, но это ,конечно, не повод расслабляться.
Если описать весь граф кода…
…, а потом по регламенту обрабатывать входящие изменения, тогда и решится поставленная задача:
диаграммы с аннотациями становятся Документацией вместо прежней кунсткамеры.
Драмеди с системными аналитиками заменяется автоматизацией.
Кроме того, граф с аннотациями становится не просто местом, куда кто-то иногда заходит что-то почитать, а системой на которую регулярно опирается разработка в том числе агенты и может заметно улучшить скорость, точность и стоимость их работы.
По аннотациям можно построить полнотекстовый индекс, сформировать эмбеддинги, создать вектора для семантического поиска и улучшить ранжирование кандидатов за счёт их близости в графе зависимостей.
А ещё напоминаю про опцию сохранять проходы по коду (графовые пути). И это огромный пласт возможностей для исследования и использования в т.ч. агентами.
В стадии разработки сравнение утекшего Claude Code, обвешанного логами на все его грепы и регулярки с graph-first агентом на одних задачах для одной кодовой базы.
Подписывайтесь на youTube если интересно.
В данный момент я ищу компанию, в которой смогу развивать это направление. Заинтересован в реферальных ссылках https://t.me/a1oleg

