refactor(ai-chat): move patch_node/insert_node into the shared tool-spec registry (#294) #305
Reference in New Issue
Block a user
Delete Branch "refactor/294-tool-spec-registry"
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
Продвигает #294 (дублирование определений инструментов). Общий transport-agnostic реестр
SHARED_TOOL_SPECSуже дедуплицирует 14 инструментов; этот PR переносит ещё два действительно идентичных —patch_node/patchNodeиinsert_node/insertNode.Почему только два (а не все): описания инструментов — это model-facing промпты, слияние с потерей формулировки меняет поведение агента. Поэтому мигрирую только там, где схема идентична И описание можно слить БЕЗ потери guidance ни с одной стороны. Каноническое описание — строгий СУПЕРСЕТ обоих: сохранены и MCP-«without resending the whole document» + table-структура/anchor-гайд, и in-app-«reversible via page history» / «exactly one of anchorNodeId or anchorText». Схема идентична (in-app-сторона лишь получает
.min(1)на id — безопасное ужесточение). Каждый транспорт держит свой execute/auth; in-appparseNodeArg(node как JSON-строка) не тронут.Осознанно НЕ слито (задокументировано):
table_insert_row/delete_row/update_cell) — реальное расхождение имени параметраtable(MCP) vstableRef(in-app); force-переименование сломало бы контракт одной стороны. Комментарий на обеих сторонах.search/sharePage/createComment/transformPage/listPages— намеренная per-transport дивергенция (гибрид RRF, security-подтверждение, current-page контекст и т.п.). Короткий коммент на каждом, объясняющий почему остаётся раздельно (как issue и просил «flag as intentional divergence»).DocmostClientLike— рукописное зеркало: ESM/CJS-граница не даёт compile-time type-import (пакет без.d.ts, нет path-mapping); runtime drift-guard уже пиннит имена методов.Заодно фикс латентного бага в
shared-tool-specs.contract.spec.ts: деривацияrequiredчерезinstanceof z.ZodOptional(совпадает с эмиттеромz.toJSONSchema) вместоisOptional(), который ошибочно считалz.any()-поля опциональными.How verified
tool-specs.test.mjs7/7 (новые проверки: каноническое описание содержит ключевые фразы ОБОИХ оригиналов) + client-host-contract drift-guard 3/3; полный MCP unit+mock 432/432.tsc0 по затронутым;jest src/core/ai-chat/tools— 51/51 (incl. contract-spec).pnpm --filter @docmost/mcp buildчисто; коммитнут пересобранныйbuild/(он в репо трекается).Note
Partially addresses #294 — оставшийся долг (унификация конвертера) — это отдельный #293; здесь закрыта дедупликация метаданных для мигрируемого подмножества, остальное осознанно оставлено раздельным с документацией.
Ревью — #305 (refactor ai-chat: move patch_node/insert_node в shared tool-spec registry, продвигает #294), round 1, head
f720151c6, base develop (merge-basee648771ab)Вердикт: PASS — behavior-sensitive рефактор корректен: описания слиты БЕЗ потери guidance, схемы идентичны, исполнение/auth per-transport целы. Все 9 аспектов LGTM. Do-list пуст. Готов к мержу.
Полный 9-аспектный веер. Объективка запущена мной (детач
f720151c): serverjest shared-tool-specs.contract tool-specs→ 86 passed; mcpnode --test tool-specs.test.mjs→ 9 passed; servertsc --noEmit→ 0.Подтверждено по коду
{pageId,nodeId,node}/ insert_node{pageId,node,position,anchorNodeId,anchorText}совпадают на всех трёх (old MCP / old in-app / new); ничего не переименовано/выпало. Единственное изменение —.min(1)на in-apppageId/nodeId(MCP уже имел) — безопасное ужесточение (отвергает только пустую строку, ранее-невалидную).node: z.any()на обеих.{description, schema}вынесено; execute-тела байт-идентичны (parseNodeArg+client.patchNode/insertNode); in-app CASL-loopback-JWT и MCP-auth не тронуты. Ни один security-confirmation-инструмент (sharePage/transformPage-deleteComments-guardrail/searchPages-ACL/createComment) НЕ слит и не ослаблен.build/tool-specs.js+build/index.jsрегенерированы (используютregisterShared(SHARED_TOOL_SPECS.patchNode/insertNode), старыхregisterToolнет) — dist не отстал, MCP-рантайм отдаёт новые определения. tool-specs.test.mjs импортит из build/ → двойной guard регенерации.!(field instanceof z.ZodOptional)вместоisOptional?.()— load-bearing: новыеnode: z.any()-поля репортятisOptional()===true, ноz.toJSONSchemaкладёт их вrequired; старая деривация дала бы ложный mismatch. В реестре нет.default()/.nullable()→ instanceof-форма корректна для всех полей.tablevstableRef— реальный param-rename), search (RRF+ACL vs plain REST), sharePage/createComment/transformPage/listPages — коммент «intentional divergence» на ОБЕИХ сторонах, причина верна. DocmostClientLike hand-mirror (не тронут PR) — drift-guard бидирекционален (пиннит имена методов обе стороны).Do — apply these, then re-review
(нет)
⛔ DROP — кодеру НЕ делать · калибровочный лог (для оператора)
[out-of-scope]info[test-coverage] MCP-регистрация shared-спеков не покрыта исполняемым тестом (нет теста, чтоcreateDocmostMcpServerреально регистрирует patch_node/insert_node) — ПРЕД-СУЩЕСТВУЮЩИЙ паттерн для всех 14registerShared-инструментов, не внесён этим PR.[speculative]low[test-coverage/stability] contract-specinstanceof z.ZodOptional-деривация мис-классифицировала бы будущее.default()-поле (ZodDefault) — сегодня таких нет; латентная хрупкость test-only.[note]info[coherence/docs] каноническое описание генерализовало MCP-сайд ссылки на сиблинг-инструменты (update_page_json→«replacing the whole document») — задокументировано как намеренная transport-neutral проза (сиблинги под snake_case vs camelCase); superset сохранён, поведение-нейтрально.[style/linter]info[documentation] секция-хедерtool-specs.ts:122-130иллюстрирует слияние только patch-специфичными фразами, не упоминает insert-специфику; опц. +1 предложение.