Code Structural Search

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

Суть

RAG по документам ищет фрагменты, похожие на запрос. Для кода это плохая мера близости: функция, которая вызывает нужную, может не иметь с ней ни одного общего слова, а два несвязанных файла с одинаковыми именами переменных будут выглядеть соседями.

Структурный подход отвечает на другой вопрос. Не «какой фрагмент похож на этот текст», а «кто вызывает эту функцию», «что сломается при смене сигнатуры», «где реализация этого интерфейса». Такие вопросы не приблизительные — у них есть точный ответ, и он выводится из грамматики языка, а не угадывается по близости векторов.

Почему для кода это работает, а для прозы нет

Сравнение с GraphRAG здесь важнее любых чисел, потому что объясняет, откуда берётся выигрыш.

GraphRAG по документам Структурный индекс по коду
Кто строит граф модель извлекает сущности и связи из текста грамматика: tree-sitter разбирает файл в дерево
Чем платится индексация вызовами LLM на каждый документ разбором, то есть процессорным временем
Что бывает с точностью извлечение вероятностное — сущность можно пропустить или выдумать разбор детерминирован: foo() вызывает foo, и вариантов нет
Что не даётся даром ничего сверх этого разрешение типов: какой именно foo при перегрузке и наследовании

Самый дорогой шаг GraphRAG для кода бесплатен. Вот и весь механизм. У прозы связей в синтаксисе нет — «Иванов руководит проектом» надо извлечь смыслом; у кода вызов записан вызовом.

Остаётся вторая половина, которая даром не даётся: связать имя с определением. Одноимённых методов в проекте десятки, и решает это не парсер, а разрешение типов — то, чем занимается языковой сервер (LSP). Отсюда двухслойная конструкция во всех реализациях: tree-sitter для синтаксиса, вывод типов поверх — для семантики.

Что показывают замеры и чего они не показывают

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

Graft (MIT, TypeScript) — надстройка над агентом: строит граф и выдаёт его связанными markdown-файлами.

Прогон Что мерили Результат
Контролируемый замер: 2 репозитория, 162 прогона против «холодного» агента, исследующего с нуля вызовов инструментов −46%, токенов −42%, времени −60%
SWE-bench Verified: 50 задач из 500 корректность и расход 27 → 33 задачи (54% → 66%); токенов −23%, вызовов −25%, времени −32%, стоимости −19%

Эти две строки нельзя ставить рядом, и это не придирка. Экономия в контролируемом замере вдвое больше, чем на SWE-bench: на подобранных репозиториях приём выглядит сильнее, чем на общем стенде. Плюс прирост корректности — шесть задач из пятидесяти. Двенадцать пунктов на выборке в 50 задач — величина того порядка, который дают перестановка задач и случайность прогона; относиться к ней надо как к направлению, а не как к измеренной разнице.

Codebase-Memory (MIT, реализация на C) — MCP-сервер, держащий постоянный граф проекта.

  • На 31 репозитории: качество ответов 83% против 92% у агента, исследующего файлы, при десятикратно меньшем расходе токенов и в 2.1 раза меньшем числе вызовов.
  • На графовых запросах (поиск хабов, ранжирование вызывающих) сравнивается или выигрывает на 19 наборах из 31.
  • Отдельный замер на пяти структурных запросах: ~3 400 токенов против ~412 000 при обходе файлов грепом — экономия 99.2%, то есть в сто с лишним раз.

Главное в первой строке — не 83%, а то, что рядом стоит 92%. Качество ответов у структурного индекса ниже, и это размен: девять пунктов качества за десятикратную экономию. Инструмент, поданный как «83% качества», читается ровно наоборот тому, что измерено.

Форма запроса решает больше, чем инструмент

Два числа экономии у одной системы отличаются в двенадцать раз: 10× на общем наборе вопросов и 125× на пяти структурных. Разрыв между ними — и есть содержательный результат.

Структурный индекс выигрывает там, где вопрос выражается через отношения символов: кто вызывает, что наследует, где реализация, что сломается. Он не выигрывает там, где вопрос про намерение — «почему здесь ретрай», «что этот модуль делает вообще», — потому что намерение в синтаксисе не записано, оно в комментариях, коммитах и головах.

Практический вывод: это не замена обхода файлов, а другой слой. Агент, у которого есть только граф, будет плохо отвечать на вопросы «зачем»; агент, у которого только чтение файлов, будет платить сотнями тысяч токенов за вопросы «кто вызывает».

Порядок обхода: искать, потом читать

У свежего агента на реальной задаче наблюдается устойчивая последовательность: прочитать манифест, прочитать конфигурацию, прочитать точку входа, прочитать все файлы в каталоге исходников — и через двадцать шагов приступить к делу. Стратегия «прочитать всё, потом действовать» выглядит основательной, а на деле забивает контекст и расходует внимание до того, как началась работа.

Правило простое и переносимое: сначала поиск, потом чтение того, что будешь менять. И вторая половина, без которой первая не работает: не читать файлы «на всякий случай».

Тонкость в том, где это правило записывать. Оно не идёт в описание инструмента поиска — это не свойство инструмента, а политика поведения агента, и место ей в системном промпте, в разделе про действия (Prompt Engineering). Инструменты при этом не меняются вовсе.

Симметричная проверка, что правило не переусердствовало: задача, в которой файл назван прямо, должна идти сразу в чтение, минуя поиск. Если агент начинает грепать и там, правило сформулировано слишком широко.

Цена и границы

Индекс надо строить и держать в согласии с кодом. Заявленная скорость разбора высокая — ядро Linux (28 млн строк, 75 тысяч файлов) за три минуты, Django примерно за шесть секунд, — но это разовая индексация, а не поддержание. Вопрос «что происходит при каждом коммите» инструменты решают по-разному, и это первое, что стоит проверять при выборе.

Разрешение типов покрывает не все языки. У Codebase-Memory разбор заявлен для 161 языка, а собственная реализация вывода типов — для двенадцати. Это ровно то расслоение, которое ожидаемо: синтаксис дёшев, семантика дорога. Для языка вне второго списка получается граф вызовов по именам, то есть с ошибками на перегрузках.

Материал разошёлся с работой, на которую ссылается. Препринт от 28 марта 2026 описывает 66 языков, репозиторий сейчас заявляет 161. Числа из препринта относятся к более ранней версии, и переносить их на сегодняшний инструмент нельзя.

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

Тот же принцип без всякого индекса

Крайний случай структурного поиска — запрос по синтаксическому дереву без построения индекса вообще: ast-grep разбирает файлы на лету и отвечает на вопрос формы «вызов такой-то без такого-то аргумента». Индекса нет, значит нечему устаревать и нечего поддерживать; платой становится полный обход при каждом запросе.

Это и есть механизм, на котором стоят правила базы в .claude/rules/: rg отвечает на «есть ли такое», а вопрос проверки обычно противоположный — «нет ли этого хоть где-то», и на него текстовый поиск ответить не может. Для разовой проверки по всем вхождениям индекс не нужен; для интерактивной работы агента, где запросы идут десятками, — нужен.

Связано с

  • RAG — поиск по документам, где мерой близости служит смысл; здесь показано, где эта мера не работает
  • GraphRAG — граф знаний, который строит модель; ключевое различие разобрано выше
  • Embeddings — почему векторная близость по коду вводит в заблуждение
  • Agent Retrieval Policy — извлекать надо то, что помогает решению сейчас, а не всё похожее
  • Context Compaction — экономия контекста как цель, ради которой этот слой и заводят
  • MCP — форма поставки: оба инструмента отдаются агенту как MCP-сервер