feat(mcp): getTree — иерархия пространства/поддерева одним вызовом (#443, часть 2/3) #469
Reference in New Issue
Block a user
Delete Branch "feat/443-get-tree"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Вторая из трёх частей #443. Новый тул
getTree— иерархия пространства или поддерева ОДНИМ вызовом (для агентов). Раньше единственный способ —listPages tree:true: BFS по sidebar-эндпоинту, десятки-сотни HTTP-вызовов и молчаливая потеря страниц (#442). Только чтение.Стоит стопкой на части 1 (
feat/443-search, PR #468) — база указывает на ветку search, в диффе только getTree. Порядок: … #415 → #443-search → #443-get_tree. issue #443 закрою финальной частью (get_page_context).Реализация
Бэкенд форка уже отдаёт всё одним
POST /pages/tree(getSidebarPagesTree, merged #442/#451) — серверных изменений нет.lib/tree.ts:buildPageTree(nodes)аддитивно расширен доbuildPageTree(nodes, options?)c{shape?: "lean"|"getTree"; maxDepth?}. Дефолт/{}— байт-идентичный lean{id,slugId,title,children?}(существующие вызыватели не тронуты, есть регрессионный тест на точный набор полей).shape:"getTree"→{pageId, title, children?, hasChildren?}(id→pageId; slugId/icon/position/parentPageId не утекают).client.ts:getTree(spaceId, rootPageId?, maxDepth?)— тот же код-путь, чтоlistPages tree:true(enumerateSpacePages→buildPageTree), без второго fetch/BFS.listPages tree:trueпомечен DEPRECATED (BFS-фолбэк для stock upstream на месте).maxDepth:1→ только корни;hasChildren:trueТОЛЬКО на срезанном узле с сервернымhasChildren, опущен у листьев/раскрытых. Схема тулаmin(1).getTreeна ОБОИХ хостах (MCP + in-app) через цикл спеков;mcpName === inAppKey === "getTree"(camelCase);DocmostClientLike/DocmostClientMethod+= getTree; compile-time contract.How verified
mcptscчисто;node --test— 718/718;prosemirror-markdowntsc чисто; server jest (shared-tool-specs.contract+ai-chat-tools.service) 263/263 (getTree заведён in-app под inAppKey с совпадающей схемой/описанием)./pages/tree(#442/#451), здесь не пере-проверяется; вместо этого полное юнит-покрытие чистой tree-логики.Checklist
Часть 2 из 3 по #443 (getTree). Стоит стопкой на части 1 (PR #468, feat/443-search — уже с фиксом мёртвого индекса) — база указывает на search-ветку, в диффе только getTree. Мержить после #468. issue #443 закрою финальной частью (get_page_context).
Что сделал: getTree — иерархия пространства/поддерева одним вызовом (POST /pages/tree, бэкенд уже готов). buildPageTree аддитивно расширен (lean-выход существующих вызывателей байт-идентичен), listPages tree:true депрецирован. Наружу только pageId.
Проверка: mcp node --test 718/718, tsc чисто, server jest (contract + ai-chat) 263/263. Циклы/orphan безопасны, покрыты тестами.
Внутренний цикл: 1 проход (адверсарно, 7 рисков — главные: регрессия shared buildPageTree и cycle-safety project). Блокеров нет; поправил 1 nit (JSDoc называл getTree-shape «lean», хотя lean в кодовой базе — это {id,slugId,...}). Ветка ребейзнута на пофикшенную search-ветку (файлы не пересекаются, конфликтов нет).
Ревью — #469
feat(mcp): getTree — иерархия пространства/поддерева одним вызовом (#443, часть 2/3)Вердикт: PASS ✅
Полный веер из 9 аспектов по изолированной дельте (база — ветка #443-search/#468; ревьюил
f794ac6d..bfb4c8d8= 8 файлов, +380/−16, mcp-only, серверных изменений нет). Гейт зелёный.Проверено (клик)
Новый тул
getTree— иерархия за ОДИНPOST /pages/tree(вместоlistPages tree:true= BFS по sidebar с десятками HTTP + тихой потерей #442).buildPageTreeрасширен АДДИТИВНО опциями{shape?, maxDepth?}.{}/shape:"lean"→{id, slugId, title, children?}без изменений; единственный вызыватель (listPages tree:true) опций не передаёт. Регресс-тест пиннит точный набор ключей. Адверсариально: подмешал поле в lean-shape → tree.test КРАСНЕЕТ (тип/тест отвергают лишнее поле).shape:"getTree"→{pageId, title, children?, hasChildren?}, БЕЗ slugId/icon/position/parentPageId. Тест проходит по всему выводу и ассертит отсутствие этих ключей на КАЖДОЙ ноде.depth>=maxDepth,hasChildren:trueТОЛЬКО на обрезанной ноде с серверными детьми;<=0/NaN/Infinity → no-cut (defensive + схема.int().min(1)). Off-by-one нет. Сорт детерминирован (code-unit, как серверный collate "C").enumerateSpacePages→ один POST /pages/tree (наследует permission-фильтр сервера + fix тихой потери #442/#451), НЕ переписан. Cycle-safe (итеративная сборка, циклы недостижимы от корней — тест: 2-node cycle → пусто, JSON-serializable).⛔ DROP — кодеру НЕ делать (лог калибровки)
[below-threshold]low[documentation] нет CHANGELOG-энтри на новый тул getTree. Аддитивная фича, задокументирована в tool-description + ROUTING_PROSE + doc-комменте buildPageTree; проектного правила «CHANGELOG на каждый PR» нет (как #468 ч.1 / #465 — там тоже DROP). Рекомендую строку в [Unreleased] Added (#443), но не блок.DO/ESCALATE нет. lean байт-идентичен (регресс-тест + адверсариал), pageId-only-инвариант enforced+tested, backend-путь переиспользован (permission-фильтр + no-loss наследуются), maxDepth корректен и DoS-safe, cycle-safe. Гейт зелёный. Approve.
Часть 2/3 #443, на #468. 9 аспектов. Байт-идентичность lean + slugId-non-leak адверсариально/тестом пришпилены; maxDepth/сорт/цикл-безопасность/permission-наследование подтверждены. CHANGELOG — below-threshold DROP. Escalate нет. Merge-order: после #468.