feat(mcp): drawio стадия 2 — guide, каталог фигур, ELK-лейаут, quality-warnings (#424) #440
Open
agent_coder
wants to merge 3 commits from
feat/424-drawio-rules into develop
pull from: feat/424-drawio-rules
merge into: vvzvlad:develop
vvzvlad:main
vvzvlad:refactor/448-generate-inventory
vvzvlad:refactor/445-execute-mapping
vvzvlad:refactor/446-derive-client-types
vvzvlad:fix/447-registry-stamp-ci
vvzvlad:fix/444-agent-loop-guards
vvzvlad:fix/396-sendnow-detached-run
vvzvlad:fix/452-immutable-cache-control
vvzvlad:develop
vvzvlad:docs/footnote-authoring-comment-cleanup
vvzvlad:feat/437-error-diagnostics
vvzvlad:fix/442-cursor-pagination
vvzvlad:feat/419-footnote-normalize
vvzvlad:perf/399-comment-resolve-async
vvzvlad:perf/435-collab-token-cache
vvzvlad:perf/344-background-rerenders
vvzvlad:perf/348-backend-lowhanging
vvzvlad:feat/423-drawio-crud
vvzvlad:perf/401-collab-hotpath
vvzvlad:feat/430-live-reconnect
vvzvlad:perf/400-collab-session
vvzvlad:refactor/414-dedup-node-ops
vvzvlad:feat/417-new-comments-signal
vvzvlad:feat/408-createcomment-hints
vvzvlad:feat/407-persist-tool-errors
vvzvlad:feat/420-footnote-render
vvzvlad:dummy-review/mcp-hang-fix
vvzvlad:dummy-review/mcp-hang-base
vvzvlad:feat/381-resumable-sse-pr1
vvzvlad:feat/git-sync-2
vvzvlad:docs/agents-workspace-build-order
vvzvlad:feature/offline-sync
vvzvlad:perf/342-code-splitting
vvzvlad:perf/346-compression-cache
vvzvlad:feat/196-multi-cursor
vvzvlad:feat/370-page-versioning
vvzvlad:perf/343-typing-latency
vvzvlad:docs/how-to-test
vvzvlad:fix/ai-sdk-partial-output-oom
vvzvlad:test/351-generative-converter
vvzvlad:feat/371-roles-catalog
vvzvlad:refactor/345-server-converter
vvzvlad:refactor/294-spec-registry-cont
vvzvlad:fix/363-migration-order
vvzvlad:fix/e2e-callout-and-gate-build
vvzvlad:fix/docker-re2-toolchain
vvzvlad:feat/git-sync
vvzvlad:fix/media-roundtrip-stability
vvzvlad:fix/340-comment-panel-perf
vvzvlad:fix/332-deferred-tools
vvzvlad:fix/329-ephemeral-suggestions
vvzvlad:fix/330-search-in-page
vvzvlad:fix/328-resolved-anchor-spam
vvzvlad:fix/331-intraline-diff
vvzvlad:fix/324-coverage-gate
vvzvlad:fix/325-mobile-390
vvzvlad:feat/293-A-git-sync-package
vvzvlad:feat/300-avatar-oklch
vvzvlad:fix/321-banner-mobile
vvzvlad:feat/300-avatar-colors
vvzvlad:feat/315-comment-suggestions
vvzvlad:feat/scroll-restore-stable-wait
vvzvlad:feat/300-agent-avatar-stack
vvzvlad:feat/300-avatar-polish
vvzvlad:refactor/294-tool-spec-registry
vvzvlad:feat/scroll-restore-ux
vvzvlad:fix/responsive-tablet-sidebar
vvzvlad:feature/ai-chat-page-change-observability
vvzvlad:image-inline-center
vvzvlad:fix/283-short-remap-title
vvzvlad:fix/283-slash-layout
vvzvlad:image-inline-row
vvzvlad:feat/276-ai-chat-dock
vvzvlad:fix/269-table-menu-refocus
vvzvlad:docs/dev-stand-guide
vvzvlad:feat/266-scroll-position
vvzvlad:fix/260-collab-docname-slugid
vvzvlad:test/244-phase2-tail
vvzvlad:fix/262-reindex-progress-realtime
vvzvlad:fix/258-changelog-compare-links
vvzvlad:fix/244-dataloss-bugs
vvzvlad:feat/246-spoiler
vvzvlad:feat/221-image-captions
vvzvlad:test/244-part-b
vvzvlad:feat/251-intentional-clear
vvzvlad:fix/embeddings-reindex-progress
vvzvlad:refactor/193-tool-spec-registry
vvzvlad:fix/255-ws-redis-adapter-leak
vvzvlad:fix/252-e2e-open-handles
vvzvlad:feat/229-catalog-yaml
vvzvlad:feat/243-blob-sandbox
vvzvlad:feat/228-inline-footnotes
vvzvlad:fix/qa-ui-bugs-216-218
vvzvlad:feature/agent-roles-catalog
vvzvlad:fix/share-alias-rename
vvzvlad:fix/ai-chat-empty-render
vvzvlad:feat/191-chat-doc-binding
vvzvlad:feat/201-temporary-notes
vvzvlad:feat/198-interrupt-agent
vvzvlad:feat/ai-chat-full-history
vvzvlad:feat/199-ai-generate-title
vvzvlad:feat/205-share-aliases
vvzvlad:batch/issues-189-187-170
vvzvlad:feat/170-mcp-test-button
vvzvlad:feat/189-context-badge
vvzvlad:feat/198-interrupt-agent-send-now
vvzvlad:fix/issues-190-159
vvzvlad:fix/ai-chat-new-chat-during-stream
vvzvlad:fix/ai-chat-stream-perf
vvzvlad:batch/issues-2026-06-25
vvzvlad:feat/ai-chat-persistent-history
vvzvlad:fix/ai-chat-copy-chat-wysiwyg
vvzvlad:fix/ai-stream-reset-resilience
vvzvlad:fix/ai-stream-undici-timeout
vvzvlad:fix/footnote-review-1227-followup
vvzvlad:fix/ai-chat-token-counter-realtime
vvzvlad:docs/manual-qa-test-plan
No Reviewers
Labels
Clear labels
epic
needs-human
review/approved
review/changes-requested
review/needs
возможно, баг
Large multi-phase effort spanning many changes
эскалация: нужно решение человека
в последнем ревью нет открытых blocking-находок
последнее ревью оставило открытые blocking-находки
head не ревьюился (head != reviewed_head)
Похоже на баг, но требует подтверждения/воспроизведения; чиним, если повторится
No Label
review/approved
Milestone
No items
No Milestone
Projects
Clear projects
No project
No Assignees
Notifications
Due Date
No due date set.
Dependencies
No dependencies set.
Reference: vvzvlad/gitmost#440
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Delete Branch "feat/424-drawio-rules"
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
Draw.io стадия 2 — надстройка над стадией-1 (сырой mxGraph XML, #434), помогающая агенту рисовать корректные диаграммы без бэкенд-рендеринга:
closes #424
drawio_create/drawio_update(геометрия, parent-relative координаты, стили);drawio_guide— прогрессивный справочник, 5 секций (skeleton / layout / containers / icons-aws / icons-azure), каждая ≤4KB, по требованию (не раздувает контекст);drawio_shapes— реальный jgraph shape-index (10446 фигур, gzip 437KB, ленивыйnode:zlibgunzip при первом вызове) + курируемый оверлей: service-level паттерны AWS/Azure, ребренды (OpenSearch→elasticsearch_service, MSK→…), блоклист сломанных стенсилов с note-заменой, палитра категорий; ранжирование aws4>aws3;escapeReв score (не ReDoS);layout:"elk"через elkjs (чистый JS,dependencies:{}) вdrawio_create/drawio_update— compound-nesting, best-effort (на сбое ELK возвращает нормализованный вход), 73→0 warnings на 12-узловом графе;Оба новых инструмента в
SHARED_TOOL_SPECS(tier:deferred) +SERVER_INSTRUCTIONS. In-app-паритет: пока PR висел, стадия-1 была довяжена in-app в develop (f46d89ea); чтобы contract-parity не покраснел, довязал такую же in-app-обвязку для двух новых тулов — но это ЧИСТЫЕ read-only функции без сетевого вызова, поэтому не client-методы (DocmostClientLike/HOST_CONTRACT_METHODSне тронуты), а прямой вызовsearchShapes/getGuideSectionиз пакета.How verified
Прогон на стенде (worktree; packages/mcp —
node --test, apps/server — jest):pnpm --filter @docmost/mcp build→ чисто (tsc);tsc --noEmit -p apps/server→ EXIT 0.node --test): drawio-guide 4, drawio-layout 4, drawio-quality-warnings 9, drawio-shapes 9, drawio-stage2-registration 4, mock/drawio-tools 15 → 45/45 passed.pnpm install --frozen-lockfile --lockfile-only --offline→ EXIT 0 (elkjs ^0.11.1 — единственный новый рантайм-деп; lockfile синхронизирован).git check-ignoreподтверждает!packages/mcp/data/**перебивает игнор → gz-индекс добавляется; читается изpackages/mcp/data/(не из build/).shared-tool-specs.contract→ 211/211 (было 6 падений на shapes/guide);client-host-contractdrift-guard 3/0 (вайтлист не тронут).Внутренний цикл: 2 прохода (стадия-2 логика — APPROVE WITH SUGGESTIONS; in-app-обвязка — APPROVE, 0 находок). Замечания ревью были некритичны и подтверждены как предсуществующие: до in-app-обвязки contract-спека краснела на shapes/guide тем же долгом, что стадия-1 (теперь закрыт). Опциональную двойную нормализацию в ELK-пути сознательно не трогал — оставил форком.
Checklist
Надстройка над стадией-1 (сырой mxGraph XML) — помогает агенту рисовать корректные диаграммы без бэкенд-рендеринга: - hard-rules в описаниях drawio_create/drawio_update (геометрия, parent- relative координаты, стили); - drawio_guide (5 секций: skeleton / layout / containers / icons-aws / icons-azure, каждая ≤4KB) — по требованию, не раздувает контекст; - drawio_shapes — реальный jgraph shape-index (10446 фигур, gzip 437KB, ленивый node:zlib gunzip) + курируемый оверлей (service-level паттерны AWS/Azure, note-подсказки на пустые resIcon, палитра категорий); ранжирование aws4>aws3; escapeRe в score (не ReDoS); - layout:"elk" через elkjs (чистый JS, dependencies:{}) — compound-nesting, best-effort (на сбое ELK возвращает нормализованный вход), 73→0 warnings на 12-узловом графе; - 6 типов quality-warnings в линтере (overlap, out-of-bounds, edge-cross, и т.п.), геометрия Liang-Barsky; warnings НИКОГДА не блокируют write. Оба новых инструмента в SHARED_TOOL_SPECS (tier:deferred) + SERVER_ INSTRUCTIONS; drift-guards зелёные. elkjs ^0.11.1 — единственный новый рантайм-деп; lockfile синхронизирован (--frozen-lockfile --offline EXIT 0). data/ едет с воркспейсом (.gitignore-негация !packages/mcp/data/). Внутренний цикл: 1 проход внутреннего ревью (APPROVE WITH SUGGESTIONS); все 59 профильных тестов зелёные. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>Ревью — #440
feat(mcp): drawio стадия 2 — guide, каталог фигур, ELK-лейаут, quality-warnings (#424)Вердикт: ПРАВКИ (CHANGES) — 1 правка (безлимитный ELK-лейаут → DoS).
Полный веер из 9 аспектов по всему диффу PR (merge-base
ee33a293, +1841). Гейт зелёный (clean-room/home/claude/gm-ci):elkjsвстал на чистой установке, mcp build (tsc) 0, node --test 591/591 (guide/layout/shapes/quality-warnings/stage2-registration), server tsc 0.Правка (сделать, потом ре-ревью)
packages/mcp/src/lib/drawio-layout.ts:135(await elk.layout(graph)).applyElkLayoutвызывается на mxGraph-XML от LLM (layout:"elk"в drawio_create/update) БЕЗ ограничения числа узлов/рёбер и БЕЗ таймаута. try/catch (стр. 136) ловит только БРОШЕННУЮ ошибку → возвращает исходный XML; но НЕ ловит зависание/OOM на большом графе. elkjs крутит layout В ПРОЦЕССЕ (JS) → большой граф (тысячи узлов; ~1МБ XML легко проходит stage-1 cap 16МБ) блокирует event-loop MCP-сервера на секунды-минуты. Проверил напрямую: гарда/Promise.race/таймаута нет. Плюс большой-граф путь НЕ тестируется (покрыт только empty/degenerate — layout.test.mjs:81). Fix: кап числа узлов/рёбер (напр. ~500) +Promise.race([elk.layout(graph), timeout(5s)])→ на превышение/таймаут вернуть исходный XML (best-effort, как уже делает try/catch); + тест на большой граф.Что проверено и чисто (клик)
setAttributeтолько числами; вывод ELK/стили фигур не инъектятся в XML без эскейпа. ReDoS: регекспы warnings (/
/gi,/<br\s*\/?>/gi, и т.д.) без катастрофического бэктрекинга; shape-lookup черезescapeRe, пустой запрос →[], crash-safe.drawio-shape-index.json.gz: доверенный бандл-ассет, есть инструкция регенерации в доке; .gitignore корректно re-includes. elkjs pinned^0.11.1. Documentation/conventions/simplification — LGTM (модули well-separated, без дублей со stage-1).⛔ DROP — кодеру НЕ делать (лог калибровки)
[speculative/defense-in-depth]low[security]gunzipSyncна shape-index без size-гарда (427КБ→4.7МБ, one-time cached) — это ДОВЕРЕННЫЙ бандл-ассет (не user-input); decompression-bomb гард нужен для НЕдоверенного (stage-1 капит untrusted pako 16МБ). Подмена коммитнутого ассета = уже game-over. Не эксплойт; опц. харднинг, не требует раунда.DO/ESCALATE нет сверх ELK-лимита. Ядро стадии-2 (каталог фигур, guide, quality-warnings, ELK-обёртка) корректно и покрыто (warnings мутационно-держатся), инъекции/ReDoS безопасны, stage-1 не тронут; но ELK-лейаут на untrusted-графе без лимита/таймаута — DoS. После правки — PASS.
9 аспектов. ELK-безлимитность/большой-граф-непокрыт, XML-гарды, quality-warnings-мутация, drift/паритет проверены (частично мной напрямую — testcov-субагент упёрся в лимит сессии, покрыл его аспект сам). gunzip = trusted-asset DROP. Escalate нет.
Поправил единственную правку.
[stability/security] ELK-лейаут без лимита/таймаута → DoS — исправлено.
applyElkLayoutкрутит elkjs СИНХРОННО в процессе на mxGraph-XML от LLM без ограничения размера графа и без таймаута; try/catch ловил только брошенную ошибку, но не зависание/OOM на большом графе.drawio-layout.ts):>500узлов или>1000рёбер → возврат исходного XML (best-effort, тот же контракт, что уже делает catch). Поскольку elkjs синхронный, именно кап и есть реальная защита event-loop'а.Promise.race([elk.layout, 5s])— defense-in-depth на случай async-варианта elkjs; таймер гасится вfinally. Проверил взаимодействие: если лейаут выигрывает — timeout-промис остаётся pending с уже погашенным таймером (нет утечки хендла, нет unhandled-rejection); если выигрывает таймаут — уходит в тот же catch → исходный XML.ELK_MAX_NODES/ELK_MAX_EDGES/ELK_TIMEOUT_MSименованные, с обоснованием в комментарии.Проверку про
gunzipSyncбез size-гарда на shape-index сознательно не делал — это ДОВЕРЕННЫЙ бандл-ассет (не user-input; ты сам пометил DROP), в отличие от untrusted-графа.Гейт:
pnpm --filter @docmost/mcp buildчисто; полный drawio-свип 79/79.Ревью (раунд 2) — #440
feat(mcp): drawio стадия 2 — guide, каталог фигур, ELK-лейаут, quality-warnings (#424)Вердикт: PASS ✅ — правка раунда 1 (безлимитный ELK) закрыта.
Полный веер из 9 аспектов. Гейт зелёный (clean-room
/home/claude/gm-ci): mcp build (tsc) 0,drawio-layout5/5. Дельта раунда 2 — толькоdrawio-layout.ts(+39) + его тест (+23); остальное со стадии-2 не менялось.Закрытие DO раунда 1 (ELK DoS) — проверено адверсариально
ELK_MAX_NODES=500,ELK_MAX_EDGES=1000—if (vertices.length > MAX || edges.length > MAX) return modelXml;ДО построения графа → на превышении возвращает исходную (валидную) модель, best-effort. Мутация: убрал кап → тест «DoS guard: over-node-cap returned unchanged quickly» (600 узлов) падает. Держит.Promise.race([elk.layout(graph), timeout(5s)])→ на таймаут/ошибкуcatchвозвращает modelXml;finally { clearTimeout(timer) }чистит таймер на ВСЕХ путях (success/timeout/elk-throw) — утечки таймера нет (проверено).⛔ DROP — кодеру НЕ делать (лог калибровки)
[below-threshold]low[test-coverage] путь 5с-таймаута не тестируется отдельно (покрыт только node-cap). Таймаут — ВТОРИЧНЫй safety-net; первичный гард (кап) мутационно-доказан; код-путь звучный (catch+finally). Тест на реальный 5с флейки/медленный; опц. — fake-timer тест. Не отдельный раунд.DO/ESCALATE нет. ELK-DoS закрыт (кап мутационно-держится, таймаут+чистка таймера корректны, best-effort сохранён, нормальный лейаут не задет); гейт зелёный. Approve.
9 аспектов. Кап проверен адверсариально (реверт → тест падает), timer-cleanup/timeout-race/нет-регрессии — консолидированным субагентом. Таймаут-тест — below-threshold DROP (кап = первичный доказанный гард). Escalate нет.
View command line instructions
Checkout
From your project repository, check out a new branch and test the changes.