refactor(mcp): дедупликация конвертер-смежных хелперов — форк node-ops.ts, legacy footnote-*, parse-node-arg #414

Open
opened 2026-07-07 20:57:28 +03:00 by agent_vscode · 0 comments
Collaborator

Мотивация

Аудит при подготовке блочного markdown-редактирования (#413) нашёл реальные дубли конвертер-смежной логики между packages/mcp и packages/prosemirror-markdown. AGENTS.md запрещает per-package копии конвертера (#293) — эти хелперы формально за периметром того правила, но дрейфуют точно так же.

Находки

  1. Форк node-ops: packages/mcp/src/lib/node-ops.ts (963 строки) ↔ packages/prosemirror-markdown/src/lib/node-ops.ts (897 строк; НЕ экспортируется из barrel, используется только тестами пакета). Набор экспортов идентичен (blockPlainText, buildOutline, getNodeByRef, replaceNodeById, deleteNodeById, sanitizeForYjs, findUnstorableAttr, insertNodeRelative, readTable, insertTableRow, deleteTableRow, updateTableCell; в mcp дополнительно assertUnambiguousMatch), уже ~90 разошедшихся строк. Питает patchNode/insertNode/deleteNode/все table-тулы/getOutline/getNode — самый дрейфоопасный форк в репе.
  2. footnote-lex.ts + footnote-analyze.ts (~210 строк, mcp/lib) — диагностика legacy-синтаксиса [^id]:, который канонический импортёр теперь оставляет литералом; их собственные заголовки называют это «vestigial».
  3. footnote-authoring.ts (mcp/lib) — параллельная реализация дедуп-конвенции assembleFootnotes из пакетного footnote.ts (одна идея — content-key дедуп + последовательные id — в двух местах).
  4. parse-node-arg.ts — 17-строчная копия: apps/server/src/core/ai-chat/tools/parse-node-arg.tspackages/mcp/src/lib/parse-node-arg.ts.

Что сделать

  1. node-ops → одна копия в prosemirror-markdown: живая mcp-версия выигрывает; ~90 строк диффа выверить явно (по коммит-истории, не «на глаз»); экспорт из barrel; mcp импортирует; тесты пакета переезжают на ту же копию. Координация: #409 добавляет в node-ops findInvalidNode, #413 добавляет сплайс-варианты — делать вместе или строго после.
  2. footnote-lex/footnote-analyze: удалить, ЛИБО свести к одному короткому предупреждению «reference-style [^id]: не парсится, используйте ^[...]» в результатах create/update/import (решить при реализации; предупреждение полезно ровно для класса инцидента #410).
  3. footnote-authoring: переиспользовать примитивы пакетного footnote.ts (или перенести authoring-хелперы в пакет рядом с ними) — одна реализация конвенции.
  4. parse-node-arg: экспортировать из @docmost/mcp, in-app копию удалить.

НЕ трогать

  • Слой canonicalizeFootnotes внутри markdownToProseMirrorCanonical — задокументированный идемпотентный no-op на импорте, несущий на JSON-путях; ENFORCEMENT RULE #228 в шапке footnote-canonicalize.ts (per-call-site, фрагментные записи не канонизируют, комментарии не канонизируют) остаётся как есть.
  • comment-anchor.ts / json-edit.ts / text-normalize.ts — не дубли конвертера (другая задача: якорение plain-text выделения в живой doc и хирургический find/replace). NB: #408 предлагает вынести из json-edit «closest-block hint» в общий хелпер — независимая работа.

Definition of done

  • Одна копия node-ops, экспортируемая из barrel пакета; в mcp файла-форка нет.
  • Поведение тулов не изменилось: существующие unit/e2e зелёные без правок ожиданий (кроме, возможно, текстов footnote-предупреждений, если выбран вариант «свести к предупреждению»).
  • Порядок сборки по AGENTS.md соблюдён (пакет собирается до tsc/тестов mcp).

Связано

#293 (правило «одна копия»), #409 (findInvalidNode в node-ops — координация), #413 (блочное markdown-редактирование — использует те же node-ops), #408 (hint-хелпер из json-edit — независимо).

## Мотивация Аудит при подготовке блочного markdown-редактирования (#413) нашёл реальные дубли конвертер-смежной логики между `packages/mcp` и `packages/prosemirror-markdown`. AGENTS.md запрещает per-package копии конвертера (#293) — эти хелперы формально за периметром того правила, но дрейфуют точно так же. ## Находки 1. **Форк node-ops**: `packages/mcp/src/lib/node-ops.ts` (963 строки) ↔ `packages/prosemirror-markdown/src/lib/node-ops.ts` (897 строк; НЕ экспортируется из barrel, используется только тестами пакета). Набор экспортов идентичен (`blockPlainText`, `buildOutline`, `getNodeByRef`, `replaceNodeById`, `deleteNodeById`, `sanitizeForYjs`, `findUnstorableAttr`, `insertNodeRelative`, `readTable`, `insertTableRow`, `deleteTableRow`, `updateTableCell`; в mcp дополнительно `assertUnambiguousMatch`), уже ~90 разошедшихся строк. Питает patchNode/insertNode/deleteNode/все table-тулы/getOutline/getNode — самый дрейфоопасный форк в репе. 2. **footnote-lex.ts + footnote-analyze.ts** (~210 строк, mcp/lib) — диагностика legacy-синтаксиса `[^id]:`, который канонический импортёр теперь оставляет литералом; их собственные заголовки называют это «vestigial». 3. **footnote-authoring.ts** (mcp/lib) — параллельная реализация дедуп-конвенции `assembleFootnotes` из пакетного `footnote.ts` (одна идея — content-key дедуп + последовательные id — в двух местах). 4. **parse-node-arg.ts** — 17-строчная копия: `apps/server/src/core/ai-chat/tools/parse-node-arg.ts` ↔ `packages/mcp/src/lib/parse-node-arg.ts`. ## Что сделать 1. **node-ops → одна копия в `prosemirror-markdown`**: живая mcp-версия выигрывает; ~90 строк диффа выверить явно (по коммит-истории, не «на глаз»); экспорт из barrel; mcp импортирует; тесты пакета переезжают на ту же копию. Координация: #409 добавляет в node-ops `findInvalidNode`, #413 добавляет сплайс-варианты — делать вместе или строго после. 2. **footnote-lex/footnote-analyze**: удалить, ЛИБО свести к одному короткому предупреждению «reference-style `[^id]:` не парсится, используйте `^[...]`» в результатах create/update/import (решить при реализации; предупреждение полезно ровно для класса инцидента #410). 3. **footnote-authoring**: переиспользовать примитивы пакетного `footnote.ts` (или перенести authoring-хелперы в пакет рядом с ними) — одна реализация конвенции. 4. **parse-node-arg**: экспортировать из `@docmost/mcp`, in-app копию удалить. ## НЕ трогать - Слой `canonicalizeFootnotes` внутри `markdownToProseMirrorCanonical` — задокументированный идемпотентный no-op на импорте, несущий на JSON-путях; ENFORCEMENT RULE #228 в шапке `footnote-canonicalize.ts` (per-call-site, фрагментные записи не канонизируют, комментарии не канонизируют) остаётся как есть. - `comment-anchor.ts` / `json-edit.ts` / `text-normalize.ts` — не дубли конвертера (другая задача: якорение plain-text выделения в живой doc и хирургический find/replace). NB: #408 предлагает вынести из json-edit «closest-block hint» в общий хелпер — независимая работа. ## Definition of done - Одна копия node-ops, экспортируемая из barrel пакета; в mcp файла-форка нет. - Поведение тулов не изменилось: существующие unit/e2e зелёные без правок ожиданий (кроме, возможно, текстов footnote-предупреждений, если выбран вариант «свести к предупреждению»). - Порядок сборки по AGENTS.md соблюдён (пакет собирается до `tsc`/тестов mcp). ## Связано #293 (правило «одна копия»), #409 (findInvalidNode в node-ops — координация), #413 (блочное markdown-редактирование — использует те же node-ops), #408 (hint-хелпер из json-edit — независимо).
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: vvzvlad/gitmost#414