LSP-бекенд для graph
Типово плагін graph відповідає на symbol, callers, impact, outline і
explore зі свого індексу tree-sitter-tags у SQLite (backend = "tags"). Налаштування
backend = "lsp" спрямовує ті самі п'ять MCP-інструментів — ті самі назви, ті самі виклики —
натомість через мовний сервер, з яким rtok спілкується через stdio. Індекс tags тоді
для цього виклику не використовується. Кожна відповідь LSP записує один рядок Measurement з
plugin = "graph" і kind = "lsp.symbol" | "lsp.callers" | "lsp.impact" | "lsp.outline" | "lsp.explore", тож rtok stats враховує її як будь-яку іншу економію.
Кожен із п'яти інструментів приймає необов'язковий project (id або каталог). Без нього виклик
відповідає за проєктом робочого каталогу та пов'язаними з ним проєктами; мовний сервер відповідає
за один корінь, тому за backend = "lsp" відповідає лише перший проєкт цієї області, і відповідь
про це повідомляє (auto питає кожен проєкт, див. нижче).
Сервер обирається за коренем робочого простору — rtok ніколи не лінкує й не викликає через оболонку
нічого іншого (D6); він запускає один із цих серверів із PATH:
| Маркер кореня | Команда сервера |
|---|---|
Cargo.toml | rust-analyzer |
compile_commands.json | clangd |
tsconfig.json | typescript-language-server --stdio |
pubspec.yaml | dart language-server |
Покрокові інструкції нижче охоплюють Rust і Dart.
Rust: rust-analyzer
Встановіть один раз через rustup, потім перевірте, що бінарник відповідає:
rustup component add rust-analyzer
rust-analyzer --version
rustup додає rust-analyzer у PATH; крім того, rtok розв'язує його через
rustup which rust-analyzer, якщо rustup є.
Dart: Dart SDK
Встановіть SDK (https://dart.dev/get-dart), потім перевірте, що він відповідає:
dart --version
Сервер — це власна підкоманда SDK language-server: rtok запускає
dart language-server, тож жодного додаткового кроку встановлення, крім самого SDK, немає.
Як налаштувати проєкт
Файл-маркер визначає корінь робочого простору: Cargo.toml для крейта Rust,
pubspec.yaml для пакета Dart. Задайте бекенд або для окремого проєкту в
<git root>/.rtok.toml:
[plugins.graph]
backend = "lsp"
або для окремого виклику через змінну середовища:
RTOK_PLUGINS_GRAPH_BACKEND=lsp rtok config get plugins.graph.backend
яка виводить lsp (типово — tags). З --sources рядок виглядає як
plugins.graph.backend = lsp (env):
rtok config show --sources | grep plugins.graph.backend
Як переконатися, що все працює
Викличте MCP outline для зразкового файлу, потім callers для одного з його символів.
tests/graph_lsp_gate.rs — це версія цієї перевірки, яку можна запустити: вона отримує
outline двосимвольного lib/main.dart через dart language-server, перевіряє,
що посилання OnlyTyped знаходиться через rust-analyzer (від T52.5 воно
знаходиться й через tags; закріплений промах для tags тепер — це посилання
macro_callee у тілі макроса), і перевіряє наявність одного рядка вимірювання lsp*:
cargo test --test graph_lsp_gate
Усі шість тестів проходять, коли обидва сервери є в PATH; інакше тести Dart і LSP
пропускаються.
Бекенд auto
backend = "auto" (T329.9) вмикається за бажанням; типово лишається tags. Він обирає режим для кожного
проєкту й мови, а не один раз на процес. Проєкт, мова якого має сервер (таблиця вище), питається першим, і
відповідь отримує заголовок (lsp). Мова без сервера дає відповідь tags із заголовком (tags); сервер,
якого немає, що не готовий, відповідає довше за lsp_timeout_ms (типово 40000) або помер посеред сесії,
дає відповідь tags із заголовком (tags; lsp: <причина>), тож збій — це сповіщення, а не помилка. Кожна
відповідь tags записує рядок виміру tags.symbol | tags.callers | tags.impact | tags.outline | tags.explore поруч із рядками lsp.*, тому rtok stats показує, як часто відповідав кожен режим.
В області з кількох проєктів, де сервер встановлено для частини з них, кожен проєкт відповідає сам, а його
частину позначено [ім'я] і забезпечено заголовком із режимом: проєкт на Rust може прийти від
rust-analyzer, а проєкт на Go — з tags в одній відповіді. Викликачі зі зв'язаного проєкту — це відповідь
самого проєкту; обхід зв'язків за tags працює лише тоді, коли для жодного проєкту області сервер не
встановлено, і тоді режим кожного проєкту — один рядок [ім'я] (tags) на початку.
[plugins.graph.backend_by_language] закріплює режим для однієї мови, яку визначає файл-маркер проєкту
(rust, c, typescript, dart, go, python, javascript, java, ruby, php, elixir,
swift): go = "tags" лишає Go на індексі, поки решта працює в auto.
Текстовий пошук
backend = "text" (T329.10), а також auto для проєкту без мовного сервера й без файлів, які розбирає
граматика (наприклад, проєкт на Java або Ruby), відповідають текстовим пошуком. Він працює у власному
процесі rtok із правилами обходу та ігнорування інструмента search (.gitignore, include і exclude
графа, plugins.read.search_max_bytes): rg, grep і ssh не запускаються, корені ssh:// не
підтримуються. Кожна відповідь має заголовок (text), а відповідь зі списком збігів закінчується рядком
про те, що в ній можуть бути коментарі, рядки та однойменні символи.
symbolдрукує рядок кожного визначення,path:line kind, знайдений заfn,def,function,func,class,struct,enum,trait,interface,typeта ще кількома ключовими словами перед цілим словом. Тіло не друкується: текстовий пошук не знає, де визначення закінчується.callersдрукує файли, що згадують ім'я поза рядком визначення,path xN (Lline).impact— один рівень тих самих файлів; ланцюжкиtoпотребують ребер викликів і відповідають «not available in text mode».outlineперелічує ключові слова визначень одного файлу;exploreзбирає визначення й однорівневий impact без шляхів викликів.deadвідповідає «not available in text mode» замість здогаду.
Текстова відповідь записує рядок виміру text.symbol | text.callers | text.impact | text.outline | text.explore (витрачений час, а не заощадження). Проєкт, корінь якого не можна прочитати, виключається
з відповіді рядком no graph backend available: <причина>.
Перевіряється один раз
У режимах lsp і auto (T329.11) rtok перевіряє кожен проєкт один раз: файл-маркер, бінарник сервера в
PATH і перший запит, що запускає сервер. Результат — запис проєкту, і наступні запити користуються ним,
не перевіряючи знову. Відсутній сервер не шукають при кожному виклику, а сервер, що зламався (помер,
закрив канал або не відповів вчасно), не перезапускають при кожному виклику: його один раз виключають із
відповідей цього проєкту з указанням причини, і проєкт відповідає з tags. Відповідь сервера з помилкою на
один запит поломкою не вважається. Одночасні перші запити ділять одну перевірку.
Запис зберігається в пам'яті й дзеркалиться в сховище, тому rtok graph projects --json (і вебсторінка)
показують його в полі backend: backend (lsp або tags), language, server (чи є сервер для мови),
reason, config (значення backend, з яким зроблено запис), checked_at і next_probe_at
(unix-секунди; не раніше цього моменту перевірка здоров'я повторить невдалий запис). Зміна backend або
backend_by_language перевіряє лише проєкти, у яких значення змінилося. Новий процес користується записом
іншого процесу до next_probe_at, потім перевіряє сам, тож перезапуск rtok після встановлення сервера
підхоплює його. Запити ніколи не перевіряють знову: це робить перевірка здоров'я нижче.
Перевірка здоров'я та сповіщення
Кожен процес, у якому працює graph (rtok mcp, rtok web), раз на [plugins.graph] health_check_interval_s (60 с; 0 вимикає) легко перевіряє проєкти своєї області. Лише цей код дивиться
наново. У кожному раунді перевіряється, що корінь проєкту існує, що посилання маніфестів проєкту вказують
на наявні каталоги, і запис про можливості. Сервер, установлений після створення запису, підхоплюється в
цьому ж раунді. Сервер, що зламався, пробується знову через 60 с, потім через 120 с, з подвоєнням до 15
хвилин; повтор — та сама перевірка, що й перша (бінарник є в PATH), тому випробуванням слугує наступний
запит, і сервер, що його не пройшов, знову виключається, а очікування далі росте. Повторюється лише запис,
який тримає сам процес, тож сторінка rtok web не перебиває сесію rtok mcp.
Проблема піднімає сповіщення, коли її побачили дві перевірки поспіль, а сповіщення знімається після двох
добрих перевірок, тож коротке відмонтування не блимає ([plugins.graph] alerts = false вимикає
сповіщення):
| Сповіщення | Коли піднімається |
|---|---|
missing | корінь видалено або переміщено (його батько існує) |
unreachable | кореня чи його батька немає (відмонтований диск), помилка, або stat не відповідає 2 с |
backend down | мовний сервер працював, потім зламався, і проєкт відповідає з tags |
link broken | посилання маніфесту (шлях Cargo, npm file:, go replace, шлях Python) вказує на неіснуючий каталог |
Сповіщення одного виду, підняті разом, читаються одним рядком («b, c missing since 14:02 (2 projects)»), а проєкт, пов'язаний лише через інші, називає ланцюжок («a to b to c: c missing»). Сервер, який просто не встановлено, сповіщенням не є. Проєкт, видалений із реєстру, не сповіщає. Коли відсутній чи недоступний проєкт повертається і сповіщення знімається, його індексують наново.
Сповіщення дзеркаляться в сховище, тому їх видно в rtok doctor (розділ graph alerts і graph_alerts у
--json), у rtok graph projects --json (alerts у проєкта) і рядком notice: на початку відповіді
graph-інструмента, чия область містить проєкт («notice: b missing since 14:02 (directory does not exist);
results exclude b»).
Оцінка здоров'я
Кожен проєкт має оцінку від 0 до 100, побудовану з того, що вже знають перевірка здоров'я та запис про можливості (заради неї нічого не запускається). Три компоненти, кожна від 0 до 1, важать 40, 30 і 30:
| Компонента | Вага | Дорівнює 1, коли | Нижча, коли |
|---|---|---|---|
| freshness (свіжість) | 40 | немає файлів, що чекають | є файли, що чекають: 0 при п'ятій частині файлів або коли індексу понад 24 год і після нього були зміни |
| backend (бекенд) | 30 | налаштований бекенд працює (tags і text працюють завжди) | за auto і lsp сервер не відповідає: 0,6 на запасному tree-sitter, 0,3 на текстовому, 0 якщо не відповідає ніщо |
| links (зв'язки) | 30 | кожен зв'язок веде до наявного, досяжного, проіндексованого проєкту (без зв'язків теж 1) | ціль зв'язку відсутня, недосяжна або не проіндексована, або посилання в маніфесті зламане |
Мова без мовного сервера не штрафується. До кожної причини, що знизила компоненту, додано виправлення
(«rust-analyzer not on PATH, using tree-sitter»: «install the server; it is picked up within one
health-check interval, or restart»). Відсутній проєкт отримує 0 («missing»); проєкт на першій індексації
ще не має оцінки (level: "indexing").
У rtok graph projects є стовпці health (оцінка) і scope, а --json містить health (score,
level: good від 80, warn 50-79, bad, indexing або missing, components, reasons з їхнім
fix) і scope_health - найменшу оцінку в області проєкту: вирішує найслабша ланка, а не середнє.
Область з оцінкою нижче 80 додає на початок відповіді graph-інструмента один рядок
notice: graph health ... (проєкт, уже названий рядком сповіщення, пропускається; alerts = false
вимикає обидва рядки), а rtok doctor перелічує всі проєкти нижче 80 у розділі graph health
(graph_health у --json). Оцінки перераховуються не частіше за раз на секунду на проєкт.
Без сервера
backend = "lsp" не падає, коли сервер не може відповісти. Якщо бінарника немає,
над запитаним файлом не знайдено жодного файлу-маркера, сервер не готовий або
помер, чи він відповідає «нічого» на ім'я, яке знає індекс tags, інструмент
віддає відповідь tags із заголовком (tags; lsp: <причина>) — наприклад,
(tags; lsp: rust-analyzer not on PATH) або
(tags; lsp: no Cargo.toml / compile_commands.json / tsconfig.json / pubspec.yaml in <root>) —
тож видно, який бекенд відповів і чому. Кожен такий відкат записує рядок
вимірювання lsp_fallback (його before_bytes — втрачений час). Щоб повернутися до
вбудованого індексу, знову задайте backend = "tags" (типове значення); відповіді tags
побайтово ідентичні з вимкненим прапорцем, згідно з контрактним тестом graph_lsp_gate.
Типовим залишається tags: зміна типового значення потребує нового гейта
із записаними рядками затримки lsp.*.