|
| 1 | +# Отчёт: правки MCP-инструментов и резолвера символов |
| 2 | + |
| 3 | +Период: 2026-07-08 — 2026-07-09. |
| 4 | + |
| 5 | +Свод изменений, сделанных в рамках диагностики и доработки MCP-сервера: |
| 6 | +исправление ошибок в резолюции символов и call-графа, повышение |
| 7 | +достоверности ответов инструментов, снижение расхода токенов. |
| 8 | + |
| 9 | +## 1. Memory-gate: корректная обработка `available_memory == 0` |
| 10 | + |
| 11 | +**Файлы:** `memory.rs`, `mcp/engine.rs`. |
| 12 | + |
| 13 | +- **Было:** проверка «хватает ли памяти для загрузки ONNX-модели |
| 14 | + эмбеддингов» была продублирована в двух местах с одинаковым порогом |
| 15 | + `1_500_000_000` байт. Показание `available_memory == 0` (что `sysinfo` |
| 16 | + реально отдаёт под memory pressure) трактовалось как «памяти нет», и |
| 17 | + сервер гарантированно уходил в graph-only режим без семантического |
| 18 | + поиска. |
| 19 | +- **Стало:** логика вынесена в `should_skip_embeddings_for_available_memory`, |
| 20 | + используется из обоих мест. `0` теперь трактуется как «показание |
| 21 | + неизвестно», а не «памяти нет» — сервер пробует загрузить модель вместо |
| 22 | + автоматического отказа. |
| 23 | +- **Эффект:** на машине с нестабильными показаниями `sysinfo` (382–482 MB, |
| 24 | + иногда ровно 0) сервер стал грузить модель эмбеддингов вместо |
| 25 | + гарантированной деградации до graph-only. |
| 26 | + |
| 27 | +## 2. `nodeId` как число ломал резолюцию узла |
| 28 | + |
| 29 | +**Файл:** `mcp/server.rs`. |
| 30 | + |
| 31 | +- **Было:** `args.get("nodeId").and_then(|v| v.as_str())` — если клиент |
| 32 | + передавал ID числом (`{"nodeId": 1306}`, ровно так, как его отдаёт |
| 33 | + `codegraph_symbol_search`), парсинг молча проваливался в `None`, и |
| 34 | + инструмент отвечал «узел не найден», хотя он существовал в графе. |
| 35 | +- **Стало:** добавлена `arg_node_id(v) -> Option<NodeId>`, принимающая и |
| 36 | + число, и строку. Применена во всех 5 местах (`get_callers`, |
| 37 | + `get_callees`, `traverse_graph`, `get_symbol_info`, |
| 38 | + `get_detailed_symbol`). |
| 39 | +- **Эффект:** инструменты, принимающие `nodeId`, теперь работают |
| 40 | + независимо от того, в каком JSON-типе клиент передал идентификатор. |
| 41 | + |
| 42 | +## 3. Рассинхронизация форматов путей (относительный vs `file://`) |
| 43 | + |
| 44 | +**Файлы:** `node_resolution.rs`, `mcp/server.rs`, `handlers/ai_context.rs`. |
| 45 | + |
| 46 | +- **Было:** индексатор хранит путь относительно workspace-root |
| 47 | + (`./crates/foo/bar.rs`), а обработчики требовали `file://` URI, |
| 48 | + превращали его в абсолютный путь и сравнивали через `==` — строки |
| 49 | + никогда не совпадали. Наихудший случай: при ошибке парсинга URI путь |
| 50 | + молча превращался в пустую строку, которая случайно совпадала с path |
| 51 | + нескольких синтетических узлов — инструмент возвращал **случайный, |
| 52 | + не относящийся к запросу символ** без явной ошибки. |
| 53 | +- **Стало:** добавлены `paths_match()` (сравнение путей по границе |
| 54 | + компонента, устойчивое к relative/absolute; пустые строки не совпадают |
| 55 | + ни с чем) и `resolve_uri_to_path()` (принимает и `file://`, и обычный |
| 56 | + путь). Применены во всех местах, где раньше было строгое `==` по path. |
| 57 | +- **Эффект:** `get_ai_context`, `get_edit_context`, `get_callers`, |
| 58 | + `get_callees`, `traverse_graph`, `get_symbol_info`, `get_detailed_symbol`, |
| 59 | + `find_related_tests`, `analyze_complexity` перестали как не находить |
| 60 | + проиндексированные файлы, так и молча подменять символ на неверный. |
| 61 | + |
| 62 | +## 4. `codegraph_reindex_workspace`: ложный `status: "success"` на пустом графе |
| 63 | + |
| 64 | +**Файл:** `mcp/server.rs`. |
| 65 | + |
| 66 | +- **Было:** мягкий (`force=false`) реиндекс мог отрапортовать успех, даже |
| 67 | + если граф после реиндекса фактически пуст. |
| 68 | +- **Стало:** после индексации сверяется `node_count` с количеством |
| 69 | + обработанных файлов; при явном расхождении статус — `degraded`. |
| 70 | + Дополнительно: если мягкий реиндекс дал `degraded`, инструмент |
| 71 | + автоматически повторяет его с `force=true` один раз (поле |
| 72 | + `auto_retried_with_force`), прежде чем возвращать ответ клиенту. |
| 73 | +- **Эффект:** типовой случай рассинхронизации инкрементального состояния |
| 74 | + теперь устраняется без ручного повторного вызова с `force=true`. |
| 75 | + |
| 76 | +## 5. Структурированные причины «symbol not found» |
| 77 | + |
| 78 | +**Файлы:** `node_resolution.rs`, `mcp/server.rs`. |
| 79 | + |
| 80 | +- **Было:** все инструменты при неудачной резолюции возвращали одну и ту |
| 81 | + же общую фразу без указания причины (неверный URI? неиндексированный |
| 82 | + файл? пустой workspace?). |
| 83 | +- **Стало:** `enum SymbolNotFoundReason` (`InvalidUri`, `WorkspaceNotIndexed`, |
| 84 | + `FileNotIndexed`) с кодом и actionable-подсказкой на каждый случай, |
| 85 | + плюс машиночитаемый `suggested_next_queries()` — подключены во всех |
| 86 | + 8 инструментах, резолвящих `uri+line`/`nodeId`. |
| 87 | +- **Эффект:** агент получает конкретное объяснение причины отказа и |
| 88 | + готовую подсказку, что вызвать дальше, вместо непрозрачной общей |
| 89 | + ошибки. |
| 90 | + |
| 91 | +## 6. `match_confidence` в ответах uri+line-инструментов |
| 92 | + |
| 93 | +**Файлы:** `node_resolution.rs`, `callers.rs`, `symbol_info.rs`, |
| 94 | +`call_graph.rs`, `impact.rs`. |
| 95 | + |
| 96 | +- **Было:** уровень доверия к резолюции виден только опционально |
| 97 | + (`used_fallback`, поле присутствует лишь при `true`). |
| 98 | +- **Стало:** добавлено поле `match_confidence: "exact" | "fallback"`, |
| 99 | + присутствующее в ответе всегда. |
| 100 | +- **Эффект:** единообразная, всегда доступная проверка достоверности |
| 101 | + ответа без реконструкции состояния из опционального поля. |
| 102 | + |
| 103 | +## 7. `codegraph_probe_symbol` — новый лёгкий инструмент |
| 104 | + |
| 105 | +**Файлы:** `domain/probe_symbol.rs` (новый), `mcp/server.rs`, `mcp/tools.rs`. |
| 106 | + |
| 107 | +- **Задача:** проверить «попал ли запрос в правильный символ» без |
| 108 | + full-context ответа. |
| 109 | +- **Реализация:** принимает `uri+line`/`nodeId`, возвращает только |
| 110 | + `name`/`type`/`node_id`/`uri`/`line_start`/`line_end`/`used_fallback`/ |
| 111 | + `match_confidence` — без source, без callers/callees. |
| 112 | +- **Эффект:** на порядок более дешёвая по токенам проверка резолюции по |
| 113 | + сравнению с полным `get_symbol_info`. |
| 114 | + |
| 115 | +## 8. `get_detailed_symbol`: дублирование callers/callees + `compact` |
| 116 | + |
| 117 | +**Файлы:** `symbol_info.rs`, `mcp/server.rs`, `mcp/tools.rs`, |
| 118 | +`handlers/ai_query.rs`. |
| 119 | + |
| 120 | +- **Было:** `get_detailed_symbol` считал callers/callees дважды — один раз |
| 121 | + внутри `get_symbol_info` (вложенно в `symbol.callers`/`symbol.callees`), |
| 122 | + второй раз отдельным вызовом `get_callers`/`get_callees` с теми же |
| 123 | + аргументами — итоговый JSON содержал один и тот же список каждого |
| 124 | + caller'а дважды, плюс лишний повторный обход графа на сервере. |
| 125 | +- **Стало:** `DetailedSymbolMeta` — вариант без вложенных callers/callees; |
| 126 | + top-level `callers`/`callees` берутся из уже посчитанного результата. |
| 127 | + Добавлен opt-in `compact: bool` (default `false`) — обрезает списки до |
| 128 | + 5 записей с полями `*_truncated`. |
| 129 | +- **Эффект:** устранено дублирование данных в ответе и лишний обход |
| 130 | + графа; для символов с большим числом caller'ов доступен компактный |
| 131 | + режим ответа. |
| 132 | + |
| 133 | +## 9. Секционный `get_edit_context` |
| 134 | + |
| 135 | +**Файлы:** `edit_context.rs`, `mcp/server.rs`, `mcp/tools.rs`. |
| 136 | + |
| 137 | +- **Было:** все 5 секций (`symbol`/`callers`/`tests`/`memories`/ |
| 138 | + `recentChanges`), включая дорогое поле `code`, вычислялись безусловно. |
| 139 | +- **Стало:** добавлены переключатели `includeSymbol`/`includeCallers`/ |
| 140 | + `includeTests`/`includeMemories`/`includeRecentChanges` и лимиты |
| 141 | + `maxCallers`/`maxTests` — секции пропускаются **на этапе вычисления**, |
| 142 | + а не только сериализации. По умолчанию поведение не изменилось. |
| 143 | +- **Эффект:** клиент может запросить только нужные секции и снизить |
| 144 | + нагрузку на сервер, а не только объём ответа. |
| 145 | + |
| 146 | +## 10. `find_nearest_node` резолвился в `CodeFile`-узел вместо символа |
| 147 | + |
| 148 | +**Файл:** `node_resolution.rs`. |
| 149 | + |
| 150 | +- **Было:** узел файла (`NodeType::CodeFile`) создаётся без |
| 151 | + `line_start`/`line_end`; чтение через не-`Option` геттеры молча |
| 152 | + подставляло `0`, что давало диапазон `0..0`. При запросе с |
| 153 | + `target_line == 0` (частый дефолт, если клиент не указал строку) такой |
| 154 | + узел формально «выигрывал» у любой реальной функции/класса как |
| 155 | + «самое тесное совпадение» (`range_size = 0`) — резолвер возвращал файл |
| 156 | + вместо символа, без пометки fallback, с `code: "<source not available>"` |
| 157 | + и пустыми callers/tests. |
| 158 | +- **Стало:** `find_nearest_node` использует `line_start_opt`/`line_end_opt` |
| 159 | + и пропускает узлы без явно проставленного диапазона строк. Если в файле |
| 160 | + нет ни одного символа с реальным диапазоном, резолвер честно возвращает |
| 161 | + `None` вместо фейкового совпадения на файл. |
| 162 | +- **Эффект:** запрос с `line: 0` (или любой строкой вне размеченных |
| 163 | + символов) теперь резолвится в ближайший реальный символ файла, а не в |
| 164 | + бесполезный узел файла. |
| 165 | + |
| 166 | +## 11. Вызовы `self.method()`/`cls.method()` не попадали в call-граф (Python) |
| 167 | + |
| 168 | +**Файлы:** `codegraph-python/src/extractor.rs`, |
| 169 | +`codegraph-python/src/parser_impl.rs`. |
| 170 | + |
| 171 | +- **Было:** два независимых дефекта одновременно исключали из графа |
| 172 | + подавляющее большинство вызовов внутри Python-класса: |
| 173 | + 1. `node_map` при добавлении методов регистрировался только под голым |
| 174 | + именем (`"method_name"`), а вызовы извлекались с квалифицированным |
| 175 | + именем caller'а (`"ClassName.method_name"`) — поиск `caller` в |
| 176 | + `node_map` всегда проваливался, вызов уходил в межфайловое |
| 177 | + разрешение вместо локального ребра. |
| 178 | + 2. `extract_callee_name` для `self.helper()` возвращала весь текст узла |
| 179 | + (`"self.helper"`) вместо голого имени (`"helper"`) — такая строка не |
| 180 | + совпадала ни с одной записью `node_map`. |
| 181 | +- **Стало:** методы регистрируются в `node_map` дополнительно под |
| 182 | + квалифицированным ключом; `extract_callee_name` для `self.`/`cls.` |
| 183 | + возвращает голое имя метода. Заодно исправлено связывание |
| 184 | + `Contains`-рёбер класс→метод при одноимённых методах в разных классах |
| 185 | + одного файла (раньше могли связаться с чужим методом). |
| 186 | +- **Эффект:** вызовы вида `self.method()`/`cls.method()` — то есть |
| 187 | + большинство вызовов внутри Python-класса — теперь становятся |
| 188 | + `Calls`-рёбрами в графе; `get_callers`/`get_callees`/`get_call_graph`/ |
| 189 | + `analyze_impact` на методах перестали систематически недооценивать |
| 190 | + связи. |
| 191 | + |
| 192 | +## 12. `codegraph_index_health` — новый инструмент диагностики индекса |
| 193 | + |
| 194 | +**Файлы:** `mcp/server.rs`, `mcp/tools.rs`. |
| 195 | + |
| 196 | +- **Задача:** дать агенту дешёвый способ проверить, не устарел ли индекс, |
| 197 | + и увидеть состояние всех известных namespace одним вызовом (вместо |
| 198 | + нескольких MCP-сессий). |
| 199 | +- **Реализация:** переиспользует уже существующую инфраструктуру |
| 200 | + (`_registry:<slug>` записи в общей `graph.db`, `graph_db_generation()`, |
| 201 | + `GitExecutor::head_commit()`, `IndexState::all_hashes()`). Отдаёт |
| 202 | + текущее состояние графа, generation, git HEAD, число изменившихся |
| 203 | + файлов с момента индексации, список других namespace и |
| 204 | + `suggested_next_queries` при обнаруженном дрейфе. |
| 205 | +- **Эффект:** агент может проверить актуальность индекса и увидеть все |
| 206 | + проиндексированные проекты без дополнительных round-trip'ов. |
| 207 | + |
| 208 | +## Проверка |
| 209 | + |
| 210 | +- `cargo test -p codegraph-server` — 369 unit-тестов, 0 упавших. |
| 211 | +- `cargo test -p codegraph-python` — 35 unit-тестов, 0 упавших. |
| 212 | +- Живые прогоны через `--run-tool` на реальном многофайловом workspace |
| 213 | + подтвердили каждый фикс отдельно (см. историю коммитов для деталей). |
0 commit comments