# ПЛАН: RAGRAF как единственный инструмент — поглощение возможностей XERJ

Статус: план доработки (не реализовано). Версия 1.0, 14 октября 2026 г.
Адресат: разработчик RAGRAF. Основание: задание владельца «не хочу два
инструмента — сделать один, который покроет все задачи», и замеры сессии
14 октября на проекте `z.batteryservice.ru`.

Связанные документы: `docs/02_architecture/TZ.md` (требования),
`docs/02_architecture/ACCEPTANCE.md` (критерии приёмки),
`docs/00_plan/PLAN.md` (общий план), `docs/TZ-2026-10-14-harness-ragraf-usage.md`
(ТЗ по обязательному использованию и производительности — часть его пунктов
является предпосылкой этого плана).

---

## 0. Что считаем успехом

Задача сформулирована как «RAGRAF должен уметь всё, что умеет XERJ». Дословно
это недостижимо и не нужно: XERJ — движок поиска (ES-совместимый) поверх
произвольных папок, а RAGRAF — индекс КОДА с графом, схемой БД и гарантией
свежести. Успех определяется не совпадением списка операций, а **паритетом по
задачам**: любой вопрос, который сегодня закрывается вызовом XERJ, должен
закрываться вызовом RAGRAF — с сохранением главного свойства RAGRAF: факт
приходит со ссылкой `файл:строка`, с провенансом и с пометкой устаревания.

Критерии успеха (измеримые, по `ragraf_usage`/`telemetry`):

| Метрика | Сегодня | Цель |
|---|---|---|
| MCP-инструментов | 43 | ≤ 45 при +12 новых возможностях (дубли схлопываются) |
| Ответов с устаревшими фактами (`stale_served`) | 0 | 0 (сохранить) |
| Доля вызовов, вернувших факты (`hit_rate`) | 0,67 | ≥ 0,80 |
| Задачи «найти / связать / вспомнить», закрытые одним вызовом | — | ≥ 80 % |
| Ответы вне объявленного бюджета размера | нет бюджета | 0 |
| Хиты, потерянные молча | неизвестно | 0 (все перечислены локаторами) |

---

## 1. Факты: что есть сегодня у каждого

### 1.1 RAGRAF (проверено по коду 14.10.2026)

| Слой | Состояние | Где в коде |
|---|---|---|
| Инструменты агента | 43 инструмента, 38 операций фасада, 3 ресурса (`ragraf://status`, `ragraf://catalog`, `ragraf://corpus/{name}/map`) | `src/ragraf/mcp_server.py` (спецификация с ~103, обработчики ~1978–2119), `src/ragraf/facade.py` |
| Хранилище | `control.sqlite`: `meta, rev, rev_history, file, node, edge, text_chunk, cfg_edge, dfg_edge, db_table, db_column, db_fk, web_route, invalidated, coverage, stage_timing, drift_entry, stale_edge, drift_archive, calibration, ack, event_log, note`; `lexvec.sqlite`: `meta, chunk_map, vec_meta, chunk_text` + плотные векторы `vec_chunk` (sqlite-vec) | `src/ragraf/store.py`, `src/ragraf/lexvec.py` |
| Семантика и векторы | есть: fastembed 0.8.1, sqlite-vec 0.1.9, модель `paraphrase-multilingual-MiniLM-L12-v2`; поиск уже гибридный (лексика + вектор) | `src/ragraf/embeddings.py`, `src/ragraf/lexvec.py` |
| Свежесть | сильнейший актив: перед КАЖДЫМ вызовом сверяется `corpus_hash`, индекс пересобирается сам; поле `freshness{stale}` в каждом ответе; журнал расхождений | `src/ragraf/watch.py`, `src/ragraf/drift.py`, `src/ragraf/ensure.py` |
| Граф кода | CPG (Joern), вызовы `callers/callees/impact/slice`, мёртвый код, дубли, точки входа, маршруты | `src/ragraf/cpg.py`, `joern_server.py`, `graph_algo.py`, `analysis.py` |
| База данных | схема из миграций и из живой СУБД, связи, примеры строк, дрейф, сверка «код ↔ база», `write_check` | `src/ragraf/db_index.py`, `db_collect.py`, `db_ops.py`, `db_samples.py` |
| Память агента | таблица `note` (задача, теги, путь, текст) с поиском подстрокой `LIKE`; без эмбеддингов, без namespace, без дедупликации | `src/ragraf/notes.py` |
| Бюджет ответа | только у `repo_map` (`budget_tokens`), в остальных операциях нет | `facade.py:2258` |

Чего в RAGRAF **нет вообще** (проверено поиском по коду, совпадений ноль):
реранк и локальный судья релевантности, любые обращения к внешним моделям,
агрегации и фасеты, план запроса «словами», правила-алерты, индексация
произвольной папки как корпуса, федеративный поиск по нескольким корпусам,
граф связей между документами с двувременными рёбрами.

### 1.2 XERJ (по инструментам и по фактам этой сессии)

| Возможность | Инструменты | Наблюдение 14.10.2026 |
|---|---|---|
| Поиск по проиндексированным папкам | `xerj_search`, `xerj_map` | 32 индекса `ax-*`, включая проект `z.batteryservice.ru` (2034 записи) и отдельные индексы на подпапки (`ax-templates-sections`, `ax-assets-img`) |
| Семантический и векторный поиск, гибрид | `xerj_semantic_search`, `xerj_vector_search`, `xerj_hybrid_search` | работают; в этой сессии семантический запрос по смыслу дал мусор (чанки CSS) — качество зависит от данных |
| Примеры кода из ЧУЖИХ репозиториев | `xerj_code_search` | **не работает: корпуса не проиндексированы.** Две проверки дали `corpus 'peers' is not indexed` и `corpus 'php' is not indexed` |
| Ранжирование второй ступенью | `rerank` (внешняя модель), `judge` (локальный, 0 токенов) | не использовались; требуют настроенного провайдера (внешний) |
| Бюджет ответа | `max_tokens` у поисковых операций | есть, с честным учётом «локатор вместо текста» |
| Память агента | `xerj_memory_store/recall` | namespace `z-batteryservice` существует, индексы `.xerj-memory-*` есть |
| Граф документов («второй мозг») | `xerj_brain_overview/ego/link/unlink` | есть, с `valid_at/invalid_at` и обязательным evidence |
| Правила-алерты | индексы `.xerj_alert_rules`, `.xerj_alert_fires` | есть |
| Сырой ES-доступ | DSL, агрегации, `_cat/indices`, сортировка, пагинация, `_source` | есть (движок ES-совместимый) |

### 1.3 Что этот замер говорит по существу

1. У XERJ **шире охват данных** (любая папка) и **богаче поисковый язык**
   (DSL, агрегации, реранк, бюджет).
2. У RAGRAF **глубже понимание кода** (граф, схема БД, дрейф) и **сильнее
   гарантия** (факт со ссылкой + свежесть). Именно этой гарантии не хватило
   XERJ в этой сессии: его автоиндекс был снят до правок и подтвердить мои
   изменения он не мог — тогда как RAGRAF пересобрался сам (`rev-0245 → rev-0246`).
3. «Примеры кода из чужих репозиториев» — не свойство XERJ «из коробки»,
   а отдельно настраиваемый peer-корпус. Сегодня его нет ни у XERJ, ни у RAGRAF.
4. Пересечение уже есть: память (`note` ↔ `memory_*`), свежесть (`watch` ↔
   отказ по устареванию), схема (`db_*` ↔ индексы таблиц миграций).

---

## 2. Перевод «уметь всё» в классы задач

| Класс задачи | Кто закрывает сегодня | Что должно появиться в RAGRAF | Приоритет |
|---|---|---|---|
| Найти по смыслу в своём коде/доках | оба | — (уже есть) | — |
| Проиндексировать произвольную папку (не проект) и искать по ней | XERJ | корпуса вида `folder/docs/reference` из любого каталога | **P0** |
| Искать сразу по нескольким корпусам | XERJ (`ax-*`) | федеративный поиск с полем `corpus` в каждом хите | **P0** |
| Запомнить знание между сессиями и вспомнить по смыслу | XERJ (семантика) | память поверх `note`: эмбеддинги, namespace, дедупликация, давность | **P0** |
| Ответ фиксированного размера с честным учётом потерянного | XERJ | единый бюджет во ВСЕХ операциях + перечисление локаторов | **P0** |
| Связать факты/документы и спросить окружение | XERJ (`brain_*`) | граф документов с evidence и двувременными рёбрами | P1 |
| Пример реализации из чужого репозитория с учётом лицензии | XERJ (`code_search`, не настроен) | корпуса `kind: reference` + лицензионная политика | P1 |
| Отчёты «сколько/где/когда» | XERJ (агрегации) | агрегаты: `terms`, `date_histogram`, `stats`, `cardinality` | P1 |
| Понять, какие поля есть в данных и что в них | XERJ (`map`) | профиль: кардинальность, покрытие, примеры, диапазоны | P1 |
| Слить две выдачи (лексика + вектор) с весами | XERJ (`hybrid_search`) | гибрид с явным `rrf|linear` и весами (сейчас гибрид есть, но без управления) | P1 |
| Оценить релевантность после первого этапа | XERJ (`judge`, `rerank`) | локальный судья (обязательно), внешний реранк (опционально, off) | P2 |
| Спросить словами, получить структурированный фильтр | XERJ (`plan`) | детерминированный планировщик с отказом на непонятой фразе | P2 |
| Следить за изменениями и оповещать | XERJ (алерты) | правила поверх `event_log` (основа уже есть) | P2 |
| Сырой ES-доступ для внешних клиентов | XERJ | тонкий read-only фасад — **только если подтверждена потребность** | P2 |

---

## 3. Принципы, которые нельзя нарушить при слиянии

1. **Нет факта без ссылки.** Любой новый слой (память, граф, чужие корпуса)
   обязан возвращать `path:line` либо явную пометку `source` («заметка»,
   «утверждение агента») — как это уже делает `notes.py`.
2. **Одна свежесть.** Ответ не может содержать смесь «свежий код» + «память
   из прошлого» без пометки: у памяти своё время (`ts`), у кода — `rev_id`.
3. **Fail-closed на неподдержанном.** Если часть языка запроса не реализована —
   это ошибка с перечислением поддерживаемого, а не «поиск без фильтра».
4. **Локальность по умолчанию.** Внешние модели — только по явному флагу и с
   предупреждением; ничего не уходит с машины само.
5. **Один агентский контур.** Одна серия имён (`ragraf_*`), одна инструкция
   `initialize`, одни ресурсы. Старые имена не удаляются, а становятся
   алиасами: чужие промпты и навыки не должны ломаться.

---

## 4. Решение по архитектуре

### Варианты

| Вариант | Суть | Плюсы | Минусы |
|---|---|---|---|
| A. RAGRAF как фасад над движком XERJ | XERJ остаётся движком поиска, RAGRAF — только оболочка/граф | ES-DSL, агрегации, реранк «бесплатно» | Два процесса и два хранилища остаются; зависимость от чужого движка; свежесть и провенанс снова придётся собирать вручную; «один инструмент» только по имени |
| B. Всё своё на SQLite | FTS5 + sqlite-vec, свой поисковый слой | Один процесс, ноль новых зависимостей, свежесть и провенанс уже наши | Меньше готового: DSL, агрегации, реранк пишем сами |
| **C. RAGRAF владеет всем; поисковый слой — SQLite; ES-DSL — тонкий транслятор подмножества (рекомендуется)** | Пункты B + read-only совместимость там, где она нужна, + peer-корпуса как обычные корпуса RAGRAF с метаданными лицензий | Сохраняется главный актив (граф, схема, свежесть, провенанс); контракт — MCP-инструменты, а не движок, поэтому бэкенд можно подменить позже | Требует дисциплины: не тащить DSL целиком |

### Почему C

* Ценность RAGRAF для агента — не «поиск», а **проверяемый факт в текущем
  состоянии кода**. Пока это свойство соблюдается, инструмент один, и он
  сильнее обоих сегодняшних.
* Порог данных у пользователя — сотни тысяч чанков на десятки корпусов.
  SQLite + sqlite-vec это держит; ES-масштаб (10⁷+) не заявлен.
* Если однажды понадобится ES-масштаб, меняется **бэкенд**, а не контракт:
  MCP-инструменты и формат ответа остаются, агентские промпты не переписываются.

### Точки расширения (файлы)

| Что | Где |
|---|---|
| Новые инструменты и их обработчики | `src/ragraf/mcp_server.py` — спецификация `TOOLS_SPEC` (с ~103), словарь обработчиков (~1978–2119), `INSTRUCTIONS` |
| Новые операции | `src/ragraf/facade.py` (сейчас 38 операций `op_*`) |
| Схема: память, граф документов, алерты, профиль полей | `src/ragraf/store.py` (control-хранилище) |
| Эмбеддинги для памяти и графа | `src/ragraf/lexvec.py`, `src/ragraf/embeddings.py` (модель уже есть) |
| Память | `src/ragraf/notes.py` → расширить до `memory` (совместимость `note` сохранить) |
| Реестр корпусов, федеративный поиск | `src/ragraf/catalog.py`, `src/ragraf/corpus.py` |
| Свежесть для нескольких корпусов сразу | `src/ragraf/watch.py`, `src/ragraf/ensure.py` |
| Конфигурация (тип корпуса, лицензии, включения/исключения) | `config/*.yaml` + `src/ragraf/config.py` |
| Метрики эффекта | `src/ragraf/telemetry.py` |
| Паспорт (по правилу AGENTS.md — обновляется тем же изменением) | `docs/AGENTS-context.md` |

---

## 5. Фазы

Оценки — планировочные, в инженерных днях одного разработчика, знающего код;
это не замеры. Порядок фаз обязателен: P1 опирается на память и корпуса из P0.

### P0. Фундамент «одного инструмента» — 6–9 дней

| № | Работа | Критерий приёмки |
|---|---|---|
| P0-1 | Единый контур свежести для НЕСКОЛЬКИХ корпусов: один вызов проверяет все активные корпуса, отставшие пересобирает, ответ содержит `freshness` по каждому | `ensure` с двумя корпусами → `stale_served = 0`, по каждому корпусу поле с ревизией; долгий проход отдаёт прогресс, а не таймаут |
| P0-2 | Память: таблицы `memory`, `memory_edge` в control-хранилище; эмбеддинг текста через существующую модель; операции `memory_store`, `memory_recall`, `memory_list`, `memory_forget`; namespace, дедупликация по близости, учёт давности, `as_of` | `memory_recall` по смыслу находит запись БЕЗ совпадения слов; у каждой записи `source`, `ts`, namespace; заметка без эмбеддинга читается как `memory(legacy=true)` с пометкой `source=note` |
| P0-3 | Корпуса из произвольного каталога: `kind: folder` (документы, выгрузки) и `kind: reference` (чужой код); автоопределение типа; включения/исключения, лимит размера, понятный отказ вместо таймаута | `ensure(root = папка с 200 md/html)` → `status = created`, поиск по ней отвечает с `path:line`; повторный вызов → `actual` |
| P0-4 | Федеративный поиск: `search` принимает список корпусов (или `*`), каждый хит несёт `corpus`, порядок объясним | Один вызов по двум корпусам даёт ≥ 20 хитов, в каждом поле `corpus`; время ответа ≤ 2× от одиночного |
| P0-5 | Единый бюджет ответа во всех операциях: `max_tokens`, честный учёт `kept / locator_only / deduped / dropped`, ни один хит не исчезает молча | Ответ не превышает объявленный бюджет; число хитов в бюджете + в локаторах = найденному; поле `_budget` в каждом ответе |

### P1. Паритет по возможностям — 7–10 дней

| № | Работа | Критерий приёмки |
|---|---|---|
| P1-6 | Граф документов: `doc_node`, `doc_edge` с `evidence` (цитата + смещение), `valid_at` / `invalid_at`; операции `graph_link`, `graph_unlink`, `graph_ego`, `graph_overview`; ретроспектива `as_of`; узлы-хабы | Снятая связь не удаляется: запрос с `as_of` до снятия её видит; связь без evidence помечена «утверждена, не обнаружена» |
| P1-7 | Чужие репозитории: `kind: reference`, лицензионная политика `warn|strict`, поиск по ОПРЕДЕЛЕНИЮ (не по окну файла) с `path:line` | Корпус из двух репозиториев: 5 хитов с лицензиями; при `strict` текст AGPL/SSPL не отдаётся, остаётся локатор и предупреждение |
| P1-8 | Агрегаты: `terms`, `date_histogram`, `stats`, `cardinality` в поисковых операциях | Отчёт «языки/папки/типы по корпусу» одним вызовом; числа сходятся с `repo_map` |
| P1-9 | Профиль данных (`map`): кардинальность, покрытие, примеры, диапазоны, признак «оценка по выборке» | Профиль по индексу с ≤ 5 примерами на поле и пометкой о выборке; поля с высокой кардинальностью помечены |
| P1-10 | Гибрид с управлением: `rrf|linear`, веса подзапросов, объяснение порядка | Один и тот же запрос воспроизводим; в ответе видно, какой подзапрос дал хит |

### P2. То, без чего можно жить — 5–8 дней

| № | Работа | Критерий приёмки |
|---|---|---|
| P2-11 | Локальный судья релевантности (лексический, 0 токенов, без сети) + опциональный внешний реранк: off по умолчанию, требует флага и предупреждения, отправляет только поля из `_source` | При выключенном флаге ни один байт не уходит наружу (проверяется отсутствием сетевых обращений); с флагом — вероятность на хит и порог отсечения |
| P2-12 | Планировщик «словами → фильтр»: детерминированный, только по известным полям и значениям, отказ называет нераспознанную фразу | Запрос с неизвестным полем → ошибка с названием поля; одинаковый ввод даёт одинаковый фильтр |
| P2-13 | Алерты: правила на `event_log` + наблюдение (исчезновение файла, рост дрейфа, устаревание корпуса) | Правило срабатывает один раз на событие; срабатывания видны в отчёте |
| P2-14 | Тонкий read-only ES-совместимый фасад | **Только по подтверждённой внешней потребности.** Без неё не делать: это цена без выгоды |

---

## 6. Что осознанно НЕ тащим из XERJ

| Не берём | Почему |
|---|---|
| Полную совместимость ES DSL и агрегаций | Пользователь не пишет DSL руками; для отчётов хватает четырёх агрегатов из P1-8. Полная совместимость — это движок, а не функция |
| Второй процесс/движок поиска | Ровно то, от чего уходим: две свежести, два реестра, две правды |
| Внешний реранк по умолчанию | Отправка текста документов наружу; в портале есть персональные данные и пароли. Только явный флаг |
| Семантический поиск «вместо» лексического | Он уже дважды показал себя хуже на точных идентификаторах (в этой сессии — мусор по CSS). Гибрид, а не замена |
| Универсальный поисковик без провенанса | Тогда RAGRAF теряет то, за что его держат: «факт = файл:строка + свежесть» |

---

## 7. Совместимость и миграция

1. **Имена.** Всё новое — под `ragraf_*`. Для чужих промптов и навыков,
   написанных под XERJ, добавить алиасы: `map`, `semantic_search`,
   `vector_search`, `hybrid_search`, `code_search` → `reference_search`,
   `memory_store`, `memory_recall`, `brain_*`, `plan`. Старые имена не удалять
   ни в P0, ни в P1; удаление — отдельное решение после замера.
2. **Данные.** `note` не переписывается: таблица `memory` создаётся рядом,
   старые заметки читаются как память с `legacy` и остаются доступны по
   старому имени `ragraf_notes`.
3. **Ресурсы.** `ragraf://status`, `ragraf://catalog`, `ragraf://corpus/{name}/map`
   расширяются: сводка по нескольким корпусам, профиль, память.
4. **Инструкция `initialize`.** После P0 в неё добавляется правило: «память —
   это утверждение агента, а не факт о коде; проверяй её индексом».
5. **Существующее ТЗ** `docs/TZ-2026-10-14-harness-ragraf-usage.md`
   (регистрация корпуса, отказ от таймаутов на больших деревьях) — не
   дублируется, а выполняется как предусловие P0-1 и P0-3: без регистрации
   корпусов федеративный поиск бессмыслен.
6. **Паспорт** `docs/AGENTS-context.md` и `docs/00_plan/PLAN.md` обновляются
   тем же изменением, что и код (правило AGENTS.md).

---

## 8. Риски

| Риск | Проявление | Как снимаем |
|---|---|---|
| Размывание гарантий | «Умный» поиск начинает отвечать без ссылок | Принцип 1–3 из раздела 3 + проверка в чекере: новый инструмент без поля провенанса не проходит приёмку |
| Дублирование смысла `note` и `memory` | Два хранилища одного знания, расходящиеся ответы | Одна сущность: `note` — частный случай памяти |
| Стоимость эмбеддингов | Индексация произвольных папок растягивается | Инкрементально: только новые чанки; ленивая индексация памяти; лимит размера корпуса с понятным отказом |
| Размер ответов | Токены агента уходят на простыни | Бюджет P0-5 по умолчанию, локаторы вместо текста |
| Таймауты на больших деревьях | Уже наблюдалось (BWO: 38 675 файлов, 2,3 ГБ) | Исключения по расширениям/размерам, предварительная оценка объёма, разбиение на этапы с прогрессом |
| Потеря скорости | Свежесть по нескольким корпусам на каждом вызове | Кэш по `corpus_hash` + дешёвая сверка по путям/размерам/времени (как сейчас), полная сверка только при расхождении |
| Регресс агентских промптов | Чужие инструкции ссылаются на имена XERJ | Алиасы (раздел 7.1), старые имена живут до отдельного решения |

---

## 9. Что нужно от владельца до начала P1

1. **Чужие репозитории:** нужны ли примеры кода из внешних проектов, каких
   именно и с какой лицензионной политикой. Без списка корпусов P1-7 не имеет
   смысла (сейчас не проиндексирован ни один).
2. **Память:** на корпус или глобальная с namespace по проекту.
3. **Внешний реранк:** разрешён ли вообще выход текста документов наружу, и
   если да — только по явному флагу?
4. **Предел числа инструментов** для агента (предлагается ≤ 45).
5. **Нужен ли внешним клиентам ES-совместимый доступ** (P2-14). Ответ «нет»
   экономит примерно треть работы P2 и снимает риск «движка внутри движка».

---

## 10. Порядок работ

1. Подтвердить решения раздела 9 (полдня обсуждения).
2. Выполнить предусловия из ТЗ 2026-10-14 (регистрация корпуса, отсутствие
   таймаутов) — они уже поставлены.
3. P0 целиком, с замером метрик раздела 0 до и после.
4. Отдельным решением — продолжать ли P1 (по факту: стало ли одним вызовом
   закрываться больше задач).
5. После P0/P1 — обновить паспорт, `PLAN.md`, навык `ragraf-core` (операции и
   рецепты) и прогнать чекер конвенций репозитория.
