RAGRAF — локальная база знаний о коде: индекс репозитория, который отвечает LLM-агенту фиксированными операциями: где определён символ, кто его вызывает, что сломается при правке, какие колонки есть в таблице. Каждый факт приходит со ссылкой файл:строка, с пометкой происхождения (stated — доказано анализатором, derived — эвристика) и с указанием ревизии индекса, по которой он получен. Граф, полнотекстовый индекс и векторы лежат в трёх файлах SQLite внутри каталога .ragraf/ самого проекта — ни сервера, ни облака, ни подписки.

RAGRAF — база знаний о коде для LLM-агентов
Обложка: RAGRAF 0.3.0 — 52 операции, 59 инструментов MCP, свежесть индекса в каждом ответе

Ниже — разбор продукта целиком: какая проблема решается, что именно он умеет (52 операции фасада, 59 инструментов MCP, разбор деревом на 29 языках), почему его ответам можно верить, где границы, а затем установка и настройка по шагам — от чистой машины до первого запроса агента. Всё проверяемое: версия 0.3.0, команды и числа взяты из репозитория и с сайта продукта, а не пересказаны по памяти.

TL;DR: что это и как проверить за десять минут

  1. Задача. Больше половины отказов агента в чужом коде — это не анализ, а навигация: найти файл, найти строку. RAGRAF выносит навигацию из модели во внешний индекс, который отвечает фактом, а не списком кандидатов.
  2. Форма ответа. 52 фиксированные операции (symbol, callers, impact, slice, schema, write_check, policy_trace, search…) и 59 инструментов MCP. Языка запросов к графу нет намеренно: по замеру LLMxCPG (USENIX Security ’25) базовые модели дают валидный запрос к графу кода в 19–132 случаях из 1278.
  3. Доказательство в каждом факте. path:line, provenance, confidence, index_rev. Эвристические рёбра помечены отдельно и в один блок с доказанными не смешиваются.
  4. Устаревание видно, а не скрыто. Ревизия иммутабельна, правки копятся в append-only журнале расхождений, затронутые факты получают stale в самом ответе, сторож контрольных сумм переиндексирует при расхождении.
  5. Локально и без инфраструктуры. Три файла SQLite в .ragraf/ проекта, эмбеддинги считаются на месте (модель 384 измерения, ~240 МБ, без torch и без облака). Удалили каталог — удалили индекс.
  6. Экономия измерена. На 14 навигационных задачах реального PHP-портала: 14 вызовов против 28 (реалистично 43), 0 полных чтений файлов против 14, попадание в файл-цель с первого запроса 92%.
  7. Десять минут на проверку — после установки:
ragraf ensure C:\work\myproject            # найти/создать корпус, пересобрать отставший индекс
ragraf -p <конфиг> status                  # ревизия, покрытие, деградация, размер журнала
ragraf -p <конфиг> query symbol --name corpus_hash
ragraf -p <конфиг> query write_check --table doc_payments --columns "amount,AuthorUserID"
ragraf -p <конфиг> check                   # разошёлся ли индекс с кодом

Если symbol вернул путь и строки, write_check назвал ближайшие существующие колонки, а check сказал, что суммы совпадают, — индекс работает. Дальше можно подключать агента.

Проблема: агент ищет место вместо того, чтобы думать

Три симптома, знакомые каждому, кто поручал модели правку в существующем проекте.

Не находит место. rg по имени функции даёт десятки кандидатов, и нужный — не первый. В замерах VulnGym в цепочках длиннее девяти шагов 56,5% провалов приходится именно на навигацию: в 26,8% агент не нашёл файл, в 29,7% нашёл файл, но не строку. Это отказы не про качество анализа, а про поиск.

Не знает схему базы. Колонку назвали «как переменную в соседнем модуле» — и узнали об этом при первом сохранении: SQLSTATE[42S22] Unknown column. Схему из миграций нужно проверять до запуска кода, а не после.

Не знает, что устарел. Индекс построили в понедельник, во вторник переименовали функцию, в среду агент получил честный, доказанный и уже неверный ответ. Отличить «такой функции нет» от «она появилась после индексации» по обычному RAG-ответу невозможно, а решение уже принято.

Свободный язык запросов к графу эту проблему не решает: он её перекладывает на модель. Именно поэтому в RAGRAF нет Cypher и CPGQL — только фиксированный набор операций.

Что такое RAGRAF

Три хранилища на корпус, в каталоге .ragraf/ внутри индексируемого проекта:

Файл Что внутри
graph.sqlite узлы (класс, функция, метод), рёбра (вызовы, наследование, импорты) с доказательствами, схема БД, маршруты
lexvec.sqlite лексика FTS5 (BM25) и векторы vec0 в одном файле, плюс сырой текст чанков — фрагменты выдаются без чтения диска
control.sqlite ревизии, append-only журнал расхождений, архивы переиндексаций, телеметрия обращений
flowchart LR
  P["Проект:
код, миграции, документы"] --> I["Индексация"] I --> G[("graph.sqlite
узлы и рёбра
с доказательствами")] I --> V[("lexvec.sqlite
BM25 + векторы")] I --> C[("control.sqlite
ревизии, дрейф,
телеметрия")] G --> AG["Агент
MCP или CLI"] V --> AG C --> AG AG -- правка кода --> P P -. расхождение идёт
в журнал, а не в индекс .-> C

Индексация — шесть стадий, каждая называется в отчёте и в метриках прогона: scan (обход, sha256 каждого файла, контрольная сумма корпуса) → ast (tree-sitter: символы, вызовы, SQL-литералы) → load → resolve (разрешение имён: точное совпадение, затем имя+файл, затем честный отказ) → schema (таблицы и колонки из миграций, без подключения к СУБД) → cpg (межпроцедурные вызовы через Joern, если он есть) → lexvec (токены и векторы; неизменённые чанки переиспользуются по хешу).

flowchart LR
  subgraph IDX["Индексация — одна команда"]
    direction TB
    S["scan
sha256 каждого файла"] --> A["ast
tree-sitter: символы, вызовы, SQL"] A --> L["load"] L --> R["resolve
разрешение имён"] R --> SC["schema
таблицы из миграций"] SC --> CPG["cpg
Joern, опционально"] CPG --> LV["lexvec
FTS5 + векторы 384"] end LV --> REV[("ревизия
corpus_hash")] subgraph ANS["Ответ на каждый запрос"] direction TB Q["операция агента"] --> G{"сторож:
corpus_hash совпал?"} G -- да --> F["факты со ссылкой path:line"] G -- нет --> ST["stale_warnings
+ запись в журнал"] F --> ENV["конверт: index_rev,
capability, drift, limits"] ST --> ENV end REV --> G

Ответ на каждый запрос собирается в конверт — и конверт здесь важнее результата:

{
  "op": "impact",
  "index_rev": "rev-0007",
  "capability": "full",
  "drift": { "files": 3, "symbols": 7, "entries": 4 },
  "provenance": "index",
  "limits": ["статический анализ; динамика не покрыта"],
  "stale_warnings": [
    { "path": "src/Pipeline/PipelineEngine.php", "reason": "modify после rev-0007", "suggest": "live_read" }
  ],
  "result": ["…"]
}
Конверт ответа RAGRAF: ревизия, режим, происхождение факта, дрейф, границы и результат
Конверт ответа: результат — только последняя строка, остальное — условия, при которых ему можно верить

Три решения, каждое выведено из измеренного дефекта, а не из вкуса:

  1. Индекс неизменяем в пределах ревизии. Факты разных моментов времени не смешиваются в одном ответе.
  2. Агенту не даётся язык запросов. Он выбирает, что спросить, а не как.
  3. Ребро без доказательства не считается фактом. Иначе оно выглядит достоверным только потому, что пришло «из базы».

Возможности: 52 операции вместо свободного поиска

Навигация и структура

Операция На какой вопрос отвечает
search где об этом написано: гибридно, BM25 + вектор, с выдачей текста фрагментов
search_multi где встречается сразу несколько строк; какие файлы — копии друг друга
duplicates какие файлы дублируют друг друга и чем доказана активность главного
symbol где определён символ: вид, файл, строки, сигнатура, контракт параметров из docblock
references где ещё используется
code тела символов — только по явному запросу, а не «на всякий случай»
repo_map карта репозитория в заданном бюджете символов
entrypoints точки входа: маршруты, HTTP-обработчики, CLI, endpoint’ы разметки
dead_code файлы и подпрограммы без ссылок и вне достижимости
neighbors есть ли готовая реализация в соседних корпусах того же стека
live_read актуальный файл с выделением отличий от индекса
catalog реестр корпусов: что проиндексировано и с какими суммами
route куда идти за ответом: какой корпус отвечает и почему (без цитирования)
diff что изменилось между ревизиями: файлы, символы, рёбра, регрессии

Граф и влияние правки

Операция На какой вопрос отвечает
callers / callees кто вызывает / кого вызывает; --mode hard только доказанное, all — с эвристикой
impact что затронет изменение: транзитивный конус, доказанные и эвристические связи раздельно
slice структурный срез: узлы, рёбра, строки — без тел функций (нужен слой Joern)
routes маршруты приложения и их обработчики
policy_trace кто и по какому условию получает право; красный флаг — default_behavior: allow
class_usage класс оформления: где описан, где применён, что используется без правила

Данные, схема и живая база

Операция На какой вопрос отвечает
schema что в таблице по миграциям проекта
table_usage кто читает и кто пишет таблицу (--mode write — только запись)
write_check можно ли так записать: сверка плана записи со схемой до запуска SQL
db_schema / db_table что в живой базе: таблицы, колонки, типы, индексы, ключи
db_relations связи таблицы: stated (внешний ключ, достоверность 1.0) и derived (вывод по имени, 0.4) раздельно
db_sample пример строк; персональные колонки закрыты, а не выброшены
db_drift / db_cross что изменилось в структуре между ревизиями; сверка «код ↔ база»

Свежесть, дрейф, качество

Операция На какой вопрос отвечает
freshness актуален ли индекс: сверка контрольной суммы корпуса
index_status ревизия, покрытие, деградация, размер журнала расхождений
drift_report / drift_ack журнал расхождений индекса и кода; подтверждение разобранных записей
calibrate калибровка порогов переиндексации по фактическому накоплению правок
quality_suite / quality_diff / quality_prune контур качества: прогон проверок индекса, сравнение прогонов, прореживание журнала прогонов с копией и подтверждением

Учёт и след решений

Операция На какой вопрос отвечает
usage телеметрия обращений и оценка эффекта: saved_calls, hit_rate, avg_ms, misses, stale_served
task_stats отчёт по задаче пользователя: обращения к индексу, факты, доля проверяемых ссылок
task_set привязать вызовы к метке задачи
gate решение по вызову инструмента: allow / ask / deny
note / notes записать и прочитать заметку о решении — знание между сессиями

Ещё девять операций — экспорт в SIEM, единый вход, офлайн-лицензия, аттестат поставки и общий индекс команды — поставляются отдельными сборками: в открытой сборке Community их файлов нет, и вызов отвечает отказом с названной причиной, а не пустым результатом. Для обычной работы над кодом они не нужны.

Отдельно стоит write_check: он сверяет план записи (INSERT/UPDATE) со схемой и при ошибке называет ближайшие существующие колонки. Замер на пилоте: 44 таблицы и 453 колонки, 14 проверок за одну доработку, четыре дефекта предотвращены до запуска кода.

Языки, форматы и документы

29 языков разбираются деревом — то есть дают узлы и рёбра графа. У шестнадцати карта узлов описана профилем: PHP, Python, JavaScript, TypeScript, Java, C#, C, C++, Ruby, Lua, Go, Perl, bash, PowerShell, CSS, HTML. У тринадцати (Rust, Kotlin, Swift, Scala, Dart, R, F#, OCaml, Solidity, Fortran, MATLAB, Elm, Lisp) она берётся из tags-запроса самой грамматики: имена узлов у них не совпадают с привычными, и профиль на каждый язык означал бы выдумывание имён. Каждый язык проверяется на пробном файле — минимум один найденный символ.

Ещё 40 подключаются текстом: файлы находятся поиском, но узлов не дают. Это Julia, Elixir, Erlang, Haskell, Clojure, Groovy, Visual Basic, Pascal/Delphi, Objective-C, Zig, Nim, Crystal, Tcl, Prolog, Ada, Verilog, VHDL, ассемблер, HCL/Terraform, Dockerfile, Makefile, CMake, SCSS, Less, Vue, Svelte, GraphQL, Protobuf, SQL, batch, Markdown, RST, YAML, JSON, TOML, INI, XML, обычный текст и язык document.

Документы индексируются целиком, включая старые версии офисных форматов: Word (.docx, .docm, .dotx, .doc — собственный разбор OLE), Excel (.xlsx, .xlsm, .xltx, .xls), PowerPoint (.pptx, .pptm, .ppsx, .potx, .ppt), PDF, OpenDocument (.odt, .ods, .odp, .odg), EPUB, RTF, письма .eml и .msg, файлы WPS. Форматы без разборщика не выдают себя за пустой документ — они помечаются причиной.

Всего в реестре форматов 84 записи и 186 расширений, плюс четыре формата, опознаваемых по имени файла: Dockerfile, Containerfile, Makefile, CMakeLists.txt. Что индексировать — выбирается в панели, вкладка «Форматы»; значение * означает «все, включая добавленные позже».

Свежесть и дрейф: почему индексу можно верить

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

1. Иммутабельная ревизия. corpus_hash = sha1(отсортированный список (path, sha256)) фиксирует точное состояние корпуса. Внутри ревизии факты не правятся и не дописываются — только новая ревизия. Следствие: любой ответ воспроизводим, index_rev однозначно определяет набор фактов.

2. Append-only журнал расхождений. Пока агент работает, код правится — и каждая правка регистрируется журналом, а не индексом: entry_id, rev, change (create/modify/delete), path, old_sha256/new_sha256, затронутые узлы и символы, dedupe_key с уникальным индексом (повторная регистрация того же события не создаёт дубль). Журнал не удаляется и не перезаписывается.

3. Сторож контрольных сумм в каждом запросе. corpus_hash пересчитывается или сверяется по кэшу состояния и сравнивается с ревизией; расхождение дополняет журнал и попадает в конверт: drift и stale_warnings с полем suggest — live_read (перечитать файл) или reindex.

4. Архив и отчёт «что изменилось в знаниях». При переиндексации журнал не исчезает: он архивируется вместе с ревизией, которая его закрыла, и превращается в отчёт — какие файлы разошлись, какие символы затронуты, что индекс узнал заново. Вопрос «что изменилось в знаниях о коде между понедельником и средой» получает ответ не по git-истории, а по истории фактов.

flowchart LR
  Q["Запрос агента"] --> H{"corpus_hash
совпал?"} H -- да --> OK["Ответ: drift.files = 0"] H -- нет --> W{"затронут ли
запрошенный символ?"} W -- нет --> OK2["Ответ + drift в конверте"] W -- да --> ST["Метка stale + suggest:
live_read или reindex"] ST --> CAP{"операция возможна
в текущем режиме?"} CAP -- да --> OK3["Ответ с пометкой stale"] CAP -- нет --> REF["Отказ: код 3,
причина в конверте"]

В ответе агент видит одно из трёх состояний, и четвёртого нет:

Состояние Что видит агент
Свежо drift.files = 0, предупреждений нет — обычный ответ
Разошлось stale_warnings со suggest и метки stale у затронутых фактов: «функция найдена, но её файл правился после ревизии — перечитай»
Невозможно отказ с причиной (код 3): операция несовместима с текущим режимом

Чего механика не делает — и это тоже часть контракта: не следит за файлами в реальном времени (файлового вотчера нет, сторож срабатывает на запросе или по команде); не пересобирает CPG-слой инкрементально (у Joern её нет — после правок, затрагивающих межпроцедурные связи, slice честнее пересобрать целиком); не защищает от правок самого каталога .ragraf/ — это ответственность файловой системы, поэтому каталог и не попадает в репозиторий.

Замеры: что меняется в цифрах

Замер на реальном проекте — PHP-портал, 14 навигационных задач, артефакты прогонов лежат в bench/results/:

Показатель Без индекса С индексом
Вызовов инструментов на 14 задач 28 (реалистично 43) 14
Среднее на задачу 2,0 / 3,1 1,0
Кандидатов к просмотру до 34 файлов 1–5 точных
Файлов, прочитанных целиком 14 (минимум) 0
Попадание в файл-цель с первого запроса — 92% (12 из 13)
Проверяемость: факты со ссылкой файл:строка — 100%

Второй замер — журнал телеметрии доработки подсистемы согласований на том же портале: 46 обращений к операциям за всю доработку, 0 полных чтений файлов, 0 ошибок выполнения, 14 вызовов write_check перед SQL-записями, четыре дефекта предотвращены до появления кода. Доминирующий класс пойманного — «работает, но не так»: страница открывается, исключений нет, а отклонённый документ оказывается тупиком, потому что маршрут согласования обрывается. Ни компилятор, ни тест «страница открывается» такое не ловят; ловят policy_trace и routes.

Честная оговорка, которую обычно опускают: выигрыш в 2–3 раза относится к задачам локализации. В цикле «прочитал → изменил → проверил» экономии по числу вызовов почти нет — там выигрыш в другом: правки не ломают индекс, а спорный SQL проверяется до запуска.

Ориентиры по времени на 323-файловом PHP-корпусе (процессор): полная индексация 71 с, из них 64 с — построение графа свойств кода через Joern; без Joern — единицы секунд. Первая индексация с векторами дополнительно скачивает модель (~240 МБ, один раз).

Почему это круто

  1. Проверяемость вместо доверия. Ответ без path:line — догадка; в RAGRAF догадка помечена как догадка. Проверить факт можно в редакторе, а не в вере к модели. Это меняет жанр: агент не «объясняет», а ссылается.
  2. Он отказывается отвечать, когда не имеет права. capability=degraded и limits в конверте — это редкое свойство: инструмент сообщает о своей неполноте раньше, чем её заметит человек.
  3. Ноль инфраструктуры. Три файла SQLite рядом с кодом, переносится копированием каталога, удаляется rm -rf .ragraf. Ни демонов, ни Docker, ни внешней БД, ни API-ключей.
  4. Данные не уходят. Индексация, эмбеддинги и ответы — локально; единственный сетевой трафик: PyPI при установке и разовая загрузка модели. Файлы из secrets_exclude не открываются вовсе.
  5. Целый класс дефектов исчезает до запуска. write_check — 3 мс против ошибки драйвера в рантайме, impact — против «а кто ещё это вызывает?».
  6. Один контракт для любого агента. MCP по stdio: 59 инструментов, одна форма конверта, одна телеметрия. Подключение к AutoClaw и DeepSeek Harness отлажено замерами сессий; к средам без MCP-клиента — через CLI и навык.
  7. Видно, кто работает, а кто grep-ит. Телеметрия пишет agent и agent_kind, поэтому «агент игнорирует индекс» — это строка в отчёте, а не догадка. Лечится регламентом, а не заменой модели.
  8. Документы в том же индексе. PDF, DOCX, XLSX, PPTX, ODT, письма .eml — «где это описано в ТЗ» перестаёт быть отдельной задачей.

Установка

Что Значение
Python 3.13 или новее (проверено на 3.14)
Права администратора не нужны, всё ставится в пользовательский каталог
Проверенная платформа Windows + PowerShell 7.6
Общий путь через pip Linux и macOS — не верифицирован разработчиком сквозным прогоном
Диск ~2,5 ГБ на окружение; индекс — порядка 1 МБ на 1–2 файла исходников
Опционально JDK 21 + Joern (~5–6 ГБ) для межпроцедурного слоя; GPU — для эмбеддингов
Сеть PyPI при установке и один раз ~240 МБ модели эмбеддингов

Лицензия — Business Source License 1.1: исходный код открыт, использование внутри организации разрешено, предоставлять продукт третьим лицам как управляемый сервис — нельзя; через четыре года после выпуска версия переходит на Apache-2.0. Java и Joern ставятся отдельно и остаются под Apache-2.0.

Windows: установщик одной командой

iwr -OutFile install.ps1 https://raw.githubusercontent.com/paulmann/RAGRAF/main/install.ps1
pwsh -NoLogo -NoProfile -File .\install.ps1

Установщик проверяет окружение (PowerShell 7.6+, Python 3.13+, место на диске, доступность GitHub), спрашивает каталог установки (по умолчанию C:\RAGRAF), получает пакет (git clone, а без git — архив ветки), ставит виртуальное окружение и зависимости тем же путём, что CI продукта, и в конце печатает инструкцию по запуску панели. Полезные ключи:

pwsh -File .\install.ps1 -InstallDir C:\RAGRAF -Yes            # без диалогов
pwsh -File .\install.ps1 -SourceDir D:\dist\RAGRAF             # из локальной копии, без сети
pwsh -File .\install.ps1 -WithTruststore                       # сеть с TLS-инспекцией
pwsh -File .\install.ps1 -SkipDeps -SkipClients                # только развернуть дерево
pwsh -File .\install.ps1 -SummaryJson C:\temp\install.json      # машиночитаемый итог

Чего он осознанно не делает: не пишет настройки в AutoClaw (тот принимает MCP-серверы только через свой интерфейс, а файлового способа регистрации нет) — вместо этого печатает точные значения для формы «Add tool»; не индексирует корпуса; не ставит Java и Joern; не перезаписывает найденную установку без согласия.

Из дерева проекта

git clone https://github.com/paulmann/RAGRAF.git
cd RAGRAF
pwsh -NoLogo -NoProfile -File scripts/install.ps1

Виртуальное окружение создаётся в ~/.ragraf/venv, транскрипт пишется в ~/.ragraf/logs/install-<время>.log, каждый шаг заканчивается проверкой, а итог печатается таблицей. Ключи этого установщика: -Project (сразу подготовить корпус), -Index и -NoVectors (индексация, с моделью или без), -Venv, -Python, -DependenciesOnly, -WithTruststore, -Force, -MinFreeGb, -TimeoutScale, -SummaryJson.

pwsh -File scripts/install.ps1 -Project C:\work\myproject -Index
pwsh -File scripts/install.ps1 -Project C:\work\myproject -Index -NoVectors

Коды возврата установщика — контракт: 0 успех, 1 ошибка установки, 2 неверные аргументы, 3 неподходящее окружение.

Общий путь через pip

git clone https://github.com/paulmann/RAGRAF.git
cd RAGRAF
python -m venv .venv
. .venv/bin/activate            # Windows: .venv\Scripts\Activate.ps1
pip install -e .
ragraf --version

Без установки пакета продукт тоже работает — через обёртку, которая сама добавляет src/ в путь:

python tools/ragraf.py --version

Слой Joern, если нужен slice

Для межпроцедурного графа нужны JDK 21 и Joern. Без них всё работает: индекс строится, поиск и структура доступны, slice отказывает с причиной.

pwsh -File scripts/install-joern.ps1

Архив кладётся в .tools/downloads/, скрипт идемпотентен, распакованный комплект занимает около 5 ГБ.

Настройка корпуса

Первый корпус и что делает ensure

ragraf ensure C:\work\myproject --json

ensure — команда «приведи в порядок»: находит существующий корпус, для незнакомого каталога создаёт конфигурацию, при расхождении контрольных сумм переиндексирует. Статус в ответе: actual, reindexed, created, not_a_project (каталог не похож на проект — корпус не создаётся) или error с причиной. Индекс ложится в <проект>/.ragraf/, и каталог надо добавить в .gitignore того репозитория:

echo ".ragraf/" >> .gitignore
git check-ignore -v .ragraf     # правило действует

Если корпус нужен вручную и по шагам:

ragraf init C:\work\myproject      # осмотреть каталог и собрать конфигурацию, напечатать путь
ragraf -p <конфиг> register        # зарегистрировать разработку в реестре
ragraf -p <конфиг> index           # полная индексация

Файл конфигурации

Конфигурация корпуса — YAML. init заполняет её по фактическому содержимому каталога; ниже — рабочий пример с пояснениями:

project: myproject
root: C:/work/myproject

languages: [php, javascript, markdown]        # языки, файлы которых попадают в индекс
include:                                      # что индексировать
  - "**/*.php"
  - "**/*.js"
  - "**/*.md"
exclude:                                      # ДОБАВЛЯЕТСЯ к базовому списку, не заменяет его
  - "**/node_modules/**"
  - "**/build/**"

secrets_exclude:                              # не индексируются и не читаются вовсе
  - "**/*.local.php"
  - "**/.env"
  - "**/*.pem"

db:
  enabled: false                              # схема БД из migrations/*.sql

embedding:
  model: sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2
  dim: 384
  batch_size: 64
  use_gpu: false
  device: auto                                # auto | cpu | cuda:0

chunking:
  by_symbol: true
  max_chunk_lines: 120
  min_chunk_lines: 3

index:
  retain_revs: 3                              # сколько ревизий хранить
  batch_size: 2000
  joern_enabled: true                         # нет Joern — capability=degraded
  joern_server: true
  joern_server_port: 9500
  joern_heap_gb: 8
  reindex_after_changes: 5                    # быстрая переиндексация по накоплению правок
  full_reindex_after_changes: 20              # полный пересбор с графом свойств
  reindex_interval_minutes: 15                # индекс старше 15 минут — полный пересбор

Плотность правок и пороги — не украшение: при полном пересборе с графом свойств индексация кратно дороже, поэтому до 5 правок считается быстрая переиндексация, от 20 — полная. Эти пороги видны в ответе любой операции полем accumulation вместе с возрастом индекса.

Что индексировать и что исключать

Базовый список исключений (28 шаблонов) применяется всегда и не заменяется списком из конфига: история и зависимости (.git, node_modules, vendor, bower_components), служебные каталоги (.ragraf, .zwork, .pi, .venv, __pycache__, кеши), выход сборки (dist, build, target), образцы имён (*_bak*, *backup*, *.min.js). Правила из конфига добавляются к нему.

flowchart LR
  F["Файлы проекта"] --> X{"под exclude или
под базовыми 28 правилами?"} X -- да --> SKIP["не индексируются"] X -- нет --> SEC{"в secrets_exclude?"} SEC -- да --> NOREAD["файл не читается вовсе"] SEC -- нет --> LANG{"язык в списке
languages?"} LANG -- "дерево: 29 языков" --> GRAPH["узлы и рёбра графа"] LANG -- "текст: 40 языков" --> LEX["FTS5 + векторы"] GRAPH --> LEX

Три вещи, которые нельзя исключать «на всякий случай»: bin, cache, obj, logs, publish — по имени назначение неразличимо. В одном проекте bin — шесть рабочих PHP-скриптов, в другом — выход сборки. Такие каталоги исключаются в конфиге конкретного корпуса, где назначение видно.

Сопоставление шаблонов регистрозависимо (**/cache/** не поймает src/Cache/), поэтому после правки исключений проверяйте, сколько файлов выпало, — ошибка в списке молча выкидывает рабочий код из индекса.

Векторы и GPU

Векторы считаются локально моделью paraphrase-multilingual-MiniLM-L12-v2 (384 измерения) через fastembed и ONNX Runtime — без torch и без облака. Модель скачивается один раз в ~/.ragraf/models (каталог переопределяется RAGRAF_MODEL_CACHE). Индексация без векторов: ragraf -p <конфиг> index --no-vectors — остаётся лексический поиск BM25.

GPU ускоряет только кодирование текста в векторы, и включается по порогу окупаемости: подготовка видеокарты занимает десятки секунд разово, поэтому на малом объёме она проигрывает. Настройки: use_gpu, gpu_min_texts (по умолчанию 1800 текстов), gpu_batch_size, в конфиге корпуса — device. Для графовых расчётов есть отдельный порог graph_gpu_min_nodes (5000 вершин). Какой путь выбран и почему — пишется в метаданные прогона, а не подразумевается.

Настройки панели

Часть параметров живёт не в конфиге корпуса, а в настройках продукта (51 ключ, правится в панели): пороги переиндексации и хранения ревизий, index_formats (все 84 формата), dedupe_enabled/dedupe_scope/dedupe_mode/dedupe_keep (схлопывание побайтовых копий документации — по умолчанию выключено, потому что меняет состав корпуса и сдвигает все числа отчётов), ragraf_mode (cli, mcp-stdio, mcp-http, both), блок secrets_* для хранилища секретов. Изменяющие запросы требуют токен, который печатается при запуске панели (или задаётся RAGRAF_ADMIN_TOKEN).

Подключение к агенту (MCP)

Конфигурация сервера

{
  "mcpServers": {
    "ragraf": {
      "command": "python",
      "args": [
        "C:/RAGRAF/tools/ragraf-mcp.py",
        "--project", "C:/RAGRAF/config/mycorpus.yaml",
        "--agent", "my-agent",
        "--agent-kind", "external"
      ]
    }
  }
}

--project указывает корпус. --agent и --agent-kind нужны только для раздельной статистики: без них вызовы попадут в журнал как unknown. Клиенты, которые запускают сервер без --project, переключают корпус на лету инструментом ragraf_use_project. Значения --agent-kind: autoclaw (среда автора, расширенная атрибуция), external (сторонний агент), internal.

Сервер отдаёт 59 инструментов ragraf_* по stdio (JSON-RPC) — это те же операции, что в CLI, с теми же конвертами. Проверить, что сервер поднялся и отвечает:

sequenceDiagram
  autonumber
  participant A as Агент
  participant M as ragraf-mcp.py
stdio, JSON-RPC participant F as Фасад: 52 операции participant S as .ragraf
три SQLite A->>M: initialize M-->>A: 59 инструментов ragraf_* A->>M: ragraf_symbol name=corpus_hash M->>F: op symbol F->>S: граф + сверка corpus_hash S-->>F: узлы, рёбра, свежесть F-->>M: конверт: index_rev, capability, drift, limits M-->>A: факт + path:line A->>M: ragraf_write_check table=Users columns=... M-->>A: unknown_columns + ближайшие имена
python tools/ragraf-mcp.py --project config/mycorpus.yaml --agent probe --agent-kind external
ragraf -p config/mycorpus.yaml usage

Регламент для агента

Инструменты без регламента не работают — агент возвращается к grep. Регламент живёт там, где агент читает правила: в AGENTS.md проекта или в пользовательских правилах клиента. Блок ниже переносится как есть.

# Регламент работы с RAGRAF
1. Навигация — только через ragraf_symbol / ragraf_search. Полный файл — только
   ragraf_live_read и только по факту, найденному в индексе.
2. Проверяй конверт, а не только result: stale_warnings → перечитай затронутое
   (live_read); capability=degraded → не планируй задачи со slice.
3. Перед любой записью в БД — ragraf_write_check. Всегда, без исключений.
4. Перед правкой символа — ragraf_impact --depth 3.
5. «Не найдено» — валидный ответ: сообщай limits из конверта, не додумывай.

Проверка и телеметрия

ragraf -p <конфиг> usage                    # сводка по всем агентам
ragraf -p <конфиг> usage --agent my-agent   # только этот агент
ragraf -p <конфиг> usage --task <метка>     # только эта задача
ragraf -p <конфиг> query task_stats --task <метка>

Телеметрия пишет, кто именно вызвал операцию, — поэтому видно, соблюдается ли правило первого шага: ensure и обращения к операциям попадают в сводку наравне друг с другом. Задача помечается меткой (--task или переменная RAGRAF_TASK), и тогда task_stats отдаёт готовый отчёт: обращения, факты, доля фактов со ссылкой файл:строка, объём ответов и оценку того, сколько чтения файлов они заменили.

Первые запросы: шпаргалка

# где определено и как выглядит сигнатура
ragraf -p cfg.yaml query symbol --name corpus_hash

# кто вызывает (только доказанное) и что затронет правка
ragraf -p cfg.yaml query callers --name audit_log --mode hard
ragraf -p cfg.yaml query impact --target "Facade::query" --depth 3

# гибридный поиск с выдачей текста фрагментов — без второго вызова
ragraf -p cfg.yaml query search "порог переиндексации" --with-text --limit 8

# схема и проверка плана записи ДО выполнения SQL
ragraf -p cfg.yaml query schema --table doc_payments
ragraf -p cfg.yaml query write_check --table doc_payments --columns "amount,AuthorUserID"

# кто получает право и по какому условию
ragraf -p cfg.yaml query policy_trace acl_can_act

# свежесть и «что изменилось в знаниях»
ragraf -p cfg.yaml check
ragraf -p cfg.yaml query drift_report
Вывод команды ragraf query symbol на собственном корпусе RAGRAF
Настоящий вывод на корпусе RAGRAF: ревизия, режим, происхождение факта и ссылка на строки

Порядок флагов — самая частая ошибка: глобальные -p/--json ставятся до подкоманды (ragraf -p cfg query search …), собственные ключи подкоманды — после неё. Исключение — ensure: у неё свой --json, он идёт после (ragraf ensure <каталог> --json).

Коды возврата:

Код Значение
0 успех; для selftest — «отчёт сформирован», даже если компоненты отсутствуют
1 ошибка аргументов или корпуса: флаг после подкоманды, нет конфига, корпус не найден
2 внутренняя ошибка: повреждённое хранилище, баг
3 операция невозможна в этом состоянии: slice при capability=degraded

Код 3 — не «ничего не найдено». «Не найдено» — успешный ответ с пустым result и объявленными limits. Различие принципиально, и на нём держится вся честность отчётов агента.

Админ-панель

python tools/ragraf-admin.py --no-browser

Открывается на 127.0.0.1:8777 и слушает только этот адрес. Показывает корпуса и состояние их индексов, размеры трёх хранилищ, качество индекса (чанки сверх бюджета, обрезанные сигнатуры, доступность векторного поиска), статистику обращений и настройки порогов. Отдельные страницы: /sources — разрешённые корни источников (добавление, скан, включение, удаление; содержимое файлов не читается, секретоносные видны только числом и правилом) и /value — польза за 30 дней, сложенная по корпусам.

Токен мутаций печатается при старте в консоль; его можно задать заранее переменной RAGRAF_ADMIN_TOKEN. Панель не является обязательной: всё то же делается командами, а сама панель наружу не доступна.

Подводные камни

  • Флаг после подкоманды. ragraf query search -p cfg.yaml — ошибка «не распознан аргумент», код 1. Глобальные ключи идут первыми.
  • selftest всегда возвращает 0. Это отчёт по 11 компонентам, а не гейт: читать надо тело отчёта, а joern/java в нём опциональны.
  • certificate verify failed при скачивании модели. Корпоративный TLS-прокси: install.ps1 -WithTruststore или pip install truststore.
  • slice отвечает кодом 3. Нет Joern — capability=degraded. Либо scripts/install-joern.ps1, либо жить без срезов: остальные операции работают.
  • .ragraf попал в git. Каталог индекса — производные данные на сотни мегабайт; правило .ragraf/ в корневом .gitignore обязательно до первой индексации, а не после.
  • Ожидание «индекс сам заметит». Файлового вотчера нет: сторож срабатывает на запросе или по check. При длинной сессии переиндексация — отдельное действие, и drift_report покажет, что изменилось.
  • reindex после правок в межпроцедурных связях. Joern не умеет инкрементальную пересборку: слой CPG пересобирается целиком, иначе impact/slice опираются на старые рёбра.
  • Расширение списка исключений без замера. Ошибка в маске молча выбрасывает рабочий код из индекса; проверяйте число файлов до и после.
  • Включённое схлопывание копий меняет числа. dedupe_enabled по умолчанию выключено; после включения состав корпуса и все отчёты сдвигаются — это надо делать осознанно и один раз.
  • Путь через pip на Linux и macOS не проверен разработчиком. На Windows прогон сквозной; на других платформах сначала прогоняйте selftest и первый ensure на тестовом каталоге.

Чек-лист внедрения

  • [ ] Python 3.13+ в PATH, PowerShell 7.6+ на Windows; прав администратора не требуется.
  • [ ] Установка завершилась кодом 0, ragraf --version отвечает 0.3.0.
  • [ ] .ragraf/ добавлен в .gitignore того репозитория, который индексируется.
  • [ ] ensure по каталогу проекта вернул actual, reindexed или created — не error.
  • [ ] status показывает capability и покрытие; degraded — известное состояние, а не поломка.
  • [ ] query symbol по известному имени возвращает path:line; search находит фразу из README проекта.
  • [ ] secrets_exclude заполнен: .env, ключи, локальные конфиги не индексируются.
  • [ ] MCP-сервер зарегистрирован в клиенте и отдаёт инструменты; регламент добавлен в правила агента.
  • [ ] usage --agent <имя> показывает вызовы агента, а не unknown.
  • [ ] Панель открывается на 127.0.0.1:8777, токен мутаций не хранится в репозитории.

FAQ

Чем это отличается от обычного RAG по коду?

Тремя обязательствами. Индекс иммутабелен в пределах ревизии, а расхождения с кодом копятся в append-only журнале: устаревший факт помечается stale в каждом ответе, а не выдаётся молча за актуальный. Каждый факт несёт доказательство — path:line, provenance (stated/derived), confidence. Вместо свободного retrieval-ответа — фиксированные операции с явными limits и состоянием capability.

Нужен ли сервер, облако или подписка?

Нет. RAGRAF — локальный инструмент: индексация и ответы идут на вашей машине, состояние лежит в трёх файлах SQLite внутри проекта. MCP-сервер — процесс stdio, который запускает ваш агент. Единственный внешний компонент — Joern с сервером на localhost во время индексации CPG, и он останавливается автоматически.

Что попадает наружу?

Ничего, кроме установки из PyPI и разовой загрузки модели эмбеддингов. Ни API-ключей, ни отправки кода: secrets_exclude означает, что файл не открывается вовсе, а телеметрия хранит хеш аргумента и короткий дайджест вместо полного текста запросов.

Что будет без Java и Joern?

Продукт работает: индекс строится, поиск, символы, схема и все операции чтения доступны. Недоступен межпроцедурный слой — slice и часть impact по межпроцедурным связям; capability при этом degraded, и отказ называет причину. Захотели слой — scripts/install-joern.ps1, около 5 ГБ.

Можно ли добавить свою операцию или запрос к графу?

Языка запросов к графу нет и не планируется: это архитектурное решение, основанное на замере (19–132 валидных CPGQL-запроса из 1278 у базовых моделей). Набор из 52 операций фиксирован; расширение обсуждается в issues репозитория.

Сколько места занимает индекс и сколько строится?

Окружение ~2,5 ГБ (ONNX Runtime, tree-sitter, зависимости). Сам индекс — порядка 1 МБ на 1–2 файла исходников; на 323-файловом PHP-корпусе полная индексация с CPG заняла 71 с, из них 64 с — Joern; без него — единицы секунд.

Итог

RAGRAF решает узкую, но дорогую задачу: убрать из модели ту работу, которую она делает плохо, — поиск места в незнакомом коде. Взамен агент получает фиксированный контракт из 52 операций, три файла SQLite рядом с кодом и обязанность смотреть на конверт ответа: ревизия, состояние возможностей, расхождения, границы. Числа на пилоте скромные на вид и важные по сути: один вызов вместо двух-трёх, ноль полных чтений файлов, 92% попаданий с первого запроса, 100% фактов со ссылкой на строку.

Главное же свойство продукта — не скорость, а право отказаться отвечать. Агент, который говорит «этот файл правился после ревизии, перечитай», полезнее агента, который уверенно называет функцию, удалённую во вторник.

Цитата: Индекс кода — это не ускорение поиска, а перенос части работы из модели во внешнюю структуру, которая знает, чего она не знает.

Связанные материалы

LLM Summary

  • Тема: RAGRAF — локальная графово-векторная база знаний о коде для LLM-агентов; разобрана версия 0.3.0 (25.10.2026).
  • Что делает: индексирует репозиторий и отвечает фиксированными операциями (symbol, callers, impact, slice, schema, write_check, policy_trace, search) фактами со ссылкой файл:строка, с provenance, confidence и index_rev.
  • Масштаб: 52 операции фасада, 59 инструментов MCP по stdio, 21 подкоманда CLI, 29 языков разбором деревом, ещё 40 текстом, 84 формата в реестре.
  • Хранение: три файла SQLite в .ragraf/ внутри проекта — graph.sqlite, lexvec.sqlite (FTS5 + векторы 384), control.sqlite (ревизии, журнал расхождений, телеметрия); внешней инфраструктуры нет.
  • Свежесть: иммутабельная ревизия (corpus_hash), append-only журнал расхождений с dedupe_key, сторож контрольных сумм в каждом запросе, stale_warnings с suggest, архив и drift_report.
  • Замеры (PHP-портал, 14 навигационных задач): 28 (реалистично 43) → 14 вызовов; 2,0–3,1 → 1,0 на задачу; 34 → 1–5 кандидатов; 14 → 0 полных чтений файлов; 92% попаданий с первого запроса; 100% фактов со ссылкой.
  • Установка: Windows — iwr корневого install.ps1 или scripts/install.ps1 (ключи -Project, -Index, -NoVectors, -WithTruststore); общий путь — python -m venv + pip install -e .; Joern опционально (scripts/install-joern.ps1, ~5 ГБ).
  • Настройка: ensure/init/register/index; конфигурация корпуса (languages, include, exclude, secrets_exclude, embedding, chunking, index); базовые 28 исключений применяются всегда; .ragraf/ — в .gitignore; MCP-сервер tools/ragraf-mcp.py с --project, --agent, --agent-kind; панель tools/ragraf-admin.py на 127.0.0.1:8777.
  • Границы: статический анализ (динамика не покрыта), нет файлового вотчера, Joern не пересобирается инкрементально, slice требует CPG, pip-путь на Linux/macOS разработчиком сквозным прогоном не проверен.
  • Ссылки: https://ragraf.ru/ru/, https://github.com/paulmann/RAGRAF, https://github.com/paulmann/RAGRAF/releases/tag/v0.3.0

Промт для агента (установка и настройка)

Готовый промт: скопируйте блок целиком и отдайте ассистенту. Шаги и команды взяты из README.ru.md репозитория и с сайта продукта, а регламент — из раздела «Регламент для агента» этой статьи: его нужно положить в AGENTS.md вашего агента, иначе он вернётся к grep.

Задача: поставить RAGRAF 0.3.0, проиндексировать один проект, подключить индекс к агенту
и доказать замерами, что агент работает через индекс, а не через grep.

Материалы:
- сайт продукта: https://ragraf.ru/ru/
- вики: установка (https://ragraf.ru/ru/wiki/getting-started/), CLI
  (https://ragraf.ru/ru/wiki/cli/), свежесть и дрейф
  (https://ragraf.ru/ru/wiki/freshness-and-drift/)
- репозиторий: https://github.com/paulmann/RAGRAF
- установка и требования: README.ru.md, раздел «Установка»

1. Установка (Windows, PowerShell 7.6+; Python 3.13+ должен быть в PATH):
   iwr -OutFile install.ps1 https://raw.githubusercontent.com/paulmann/RAGRAF/main/install.ps1
   pwsh -NoLogo -NoProfile -File .\install.ps1 -Yes
   Альтернатива из дерева проекта:
   git clone https://github.com/paulmann/RAGRAF.git
   cd RAGRAF
   pwsh -NoLogo -NoProfile -File scripts/install.ps1 -Project C:\work\myproject -Index
   Без модели эмбеддингов (только лексический поиск): добавить -NoVectors.
   Сеть с TLS-инспекцией: -WithTruststore.

2. Корпус:
   ragraf ensure C:\work\myproject
   Ожидаемые статусы: actual, reindexed или created. Любой другой — остановиться.
   Добавить .ragraf/ в .gitignore индексируемого репозитория.

3. Настройка (файл, который напечатала команда init или ensure):
   - languages/include: убедиться, что .ps1, .sh, .py, .cs, .pl попали (иначе файлы не индексируются);
   - exclude: служебные каталоги проекта (не добавлять в общий список bin/cache/logs);
   - secrets_exclude: .env, ключи, локальные конфиги — файлы не читаются вовсе;
   - index.reindex_after_changes (5), full_reindex_after_changes (20),
     reindex_interval_minutes (15) — оставить по умолчанию до первых замеров;
   - joern_enabled: false, если JDK 21 и Joern не ставились (тогда slice будет отказывать с причиной).

4. Подключение к агенту (MCP, stdio):
   команда: python
   аргументы: C:/RAGRAF/tools/ragraf-mcp.py --project <конфиг корпуса>
              --agent <имя агента> --agent-kind external
   В правила агента добавить регламент: навигация только через symbol/search, полный
   файл только через live_read по найденному факту, проверка конверта (stale_warnings,
   capability), write_check перед любой записью в БД, impact --depth 3 перед правкой,
   «не найдено» — валидный ответ.

5. Проверка (что считать успехом):
   ragraf --version                                        → 0.3.0
   ragraf -p <конфиг> status                               → capability, покрытие, размер журнала
   ragraf -p <конфиг> query symbol --name <известный символ> → path:line
   ragraf -p <конфиг> query search "<фраза из README>"     → фрагменты с путями
   ragraf -p <конфиг> check                                → суммы совпадают
   ragraf -p <конфиг> usage --agent <имя>                  → вызовы атрибутированы агенту
   python tools/ragraf-admin.py --no-browser               → панель на 127.0.0.1:8777

Ограничения:
- глобальные флаги -p и --json ставятся ДО подкоманды (исключение: ragraf ensure <dir> --json);
- selftest всегда возвращает код 0 — читать тело отчёта, а не код;
- код 3 у slice означает отсутствие CPG, а не «ничего не найдено»;
- не менять пороги переиндексации и не включать схлопывание копий до первых замеров;
- если команда падает — остановиться и показать команду, вывод и код возврата.