Один логический тул жил под двумя именами: внешний MCP snake_case
(edit_page_text), in-app camelCase (editPageText) — дублирование доков, путаница
при переносе промптов/скиллов, помеха шарингу спек (#294). Решение владельца:
единый camelCase везде, включая внешний MCP. После этого mcpName === inAppKey.
- tool-specs.ts: mcpName ВЫВЕДЕН из ключа спеки (mcpName == inAppKey) для всех
43 shared-спек — раньше divergent snake, теперь равен ключу (проверено: mcpName
читается только структурно — цикл регистрации, генератор <tool_inventory>,
TOOL_FAMILY). +5 inline-регистраций (tableGet/updateComment/deleteComment/
docmostTransform; search без изменений). Рантайм: 47 тулов, все camelCase, ноль
подчёркиваний.
- Контракт-конвенция ИНВЕРТИРОВАНА: shared-tool-specs.contract.spec
`mcpName === toSnake(inAppKey)` → `mcpName === inAppKey`; tool-specs.test
и tool-inventory.test обновлены.
- ROUTING_PROSE/TOOL_FAMILY/INLINE_MCP_INVENTORY (server-instructions.ts) →
camelCase (105 замен). ai-chat.prompt/guard уже на in-app camelCase-ключах —
без изменений (guard прошёл). comment-signal EXCLUDED_TOOLS схлопнут с
дублей snake+camel до camelCase.
- Некоторое неочевидное: assertUnambiguousMatch(op: "patch_node"|"delete_node")
в prosemirror-markdown/node-ops — op интерполируется в model-facing ошибку;
литерал-юнион + call-sites → "patchNode"|"deleteNode".
- Все snake-имена в описаниях/error-строках/комментах/тестах/доках → camelCase
(whole-token, longest-match-first). CHANGELOG: BREAKING-таблица 46 строк +
миграция (allowlists mcp__gitmost-*__get_node→__getNode, промпты/скиллы,
.mcp.json, метрики по tool-label); релизится вместе с #411.
Внутренние имена методов (PageService.updatePageContent и т.п.) НЕ тронуты —
переименованы только ИМЕНА ТУЛОВ.
Гейт: mcp node --test 677/677; tsc -p apps/server чисто; jest ai-chat-tools.
service + shared-tool-specs.contract + tool-tiers + ai-chat.prompt +
comment-signal-inapp → 323. Второй линк breaking-окна (#411→ЭТОТ→#413→#415).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
edit_page_text reported "success" when asked to change formatting (e.g. remove
strikethrough): the markdown-strip fallback matched the bare text, the replace
preserved marks, and the tool returned success — so the agent believed it had
fixed something that never changed.
Two fixes, both in the shared @docmost/mcp DocmostClient so they reach BOTH the
standalone MCP server and the in-app AI chat (which loads @docmost/mcp):
- Verifiable result for every content mutator: mutatePageContent now computes a
`verify` change-report (text inserted/deleted, blocks changed, per-mark-type
delta, integrity/structure delta) via summarizeChange() and returns it on all
mutators (incl. replaceImage via mutateLiveContentUnlocked). diffDocs is
text-only, so the mark/structure delta is what surfaces formatting changes.
- edit_page_text hard-refuses formatting edits: applyTextEdits rejects an edit
whose find/replace differ only in markdown markers (via stripBalancedWrappers,
which strips balanced wrappers/links without trimming whitespace/emoji, so
plain-text edits like trailing-space trims, snake_case, math are NOT refused).
A fully-refused batch errors instead of silently succeeding.
Also updated the model-facing edit_page_text descriptions in BOTH tool layers
(packages/mcp/src/index.ts and ai-chat-tools.service.ts) to drop the misleading
"strip-and-retry tolerated" wording and point formatting changes to patch_node.
New unit tests: test/unit/diff-verify.test.mjs, test/unit/json-edit-refuse.test.mjs.