feat(mcp): createComment — подсказки самокоррекции якоря (closest-block, markdown-strip, multi-block) #427
Open
agent_coder
wants to merge 2 commits from
feat/408-createcomment-hints into develop
pull from: feat/408-createcomment-hints
merge into: vvzvlad:develop
vvzvlad:main
vvzvlad:refactor/414-dedup-node-ops
vvzvlad:feat/417-new-comments-signal
vvzvlad:feat/407-persist-tool-errors
vvzvlad:feat/419-footnote-normalize
vvzvlad:feat/420-footnote-render
vvzvlad:develop
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/344-background-rerenders
vvzvlad:perf/346-compression-cache
vvzvlad:perf/348-backend-lowhanging
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#427
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/408-createcomment-hints"
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
createComment— топ-хотспот реальных ошибок агента (96 промахов / 29 чатов; слепые циклы ретраев). Промах всегда по якорю: агентскийselectionне найден/неоднозначен, а текст ошибки не давал модели, чем скорректироваться — в отличие отeditPageText(soft-failure, 4/79 ошибок).Портированы 4 аффорданса самокоррекции:
closestBlockHintвынесен вtext-normalize.ts(иjson-edit.tsтеперь зовёт его; поведение json-edit байт-в-байт сохранено), подключён во все 3 throw-аcreateComment.comment-anchor.tsсогласованно по всем 4 функциям (can/count/apply/get) через единыйresolveAnchorSelection: exact-verbatim wins глобально, stripped — только если raw не якорится нигде; softwarningкак у editPageText.createComment: копироватьselectionдословно из getPage/searchInPage (устаревшее цитирование по памяти — корень большинства промахов).closes #408
Инварианты (сохранены)
countуходит в stripped только при raw==0; все 4 функции считают эффективный локатор одним и тем жеstripInlineMarkdown).selectionостаётся сырой подстрокой документа (getAnchoredText/reconstructRawText), strip — только для поиска.Известное мелкое ограничение (нит внутреннего ревью, не баг)
selectionSpansMultipleBlocksиспользует raw selection, поэтому markdown-стилизованное выделение через границу блоков получит generic closest-block-подсказку вместо «spans multiple blocks». Случай редкий, guidance всё равно корректный. Могу дошлифовать, если хочешь.How verified
pnpm --filter @docmost/mcp build— tsc чисто.node --test comment-anchor.test.mjs json-edit*.test.mjs create-comment.test.mjs (+ server-instructions/tool-specs)→ 96/96 relevant PASS (+7 новых: markdown-выделение якорится через fallback сnormalized:true; getAnchoredText хранит сырую подстроку; raw-unique остаётся unique; exact-wins короткозамыкает fallback; stripped-ambiguous считается как 2; not-found цитирует closest-block; cross-block даёт spans-multiple-blocks; оба не зовут /comments/create).Внутренний цикл: 1 проход coder → внутреннее ревью (фокус на 4-функциональной согласованности + гейте уникальности) → багов нет, только косметические ниты.
Checklist
Ревью — #427 · feat(mcp): createComment — подсказки самокоррекции якоря (closes #408)
Вердикт: ПРАВКИ (CHANGES) — две мелкие правки, обе доково-организационные. Код отличный: strip-fallback унифицирован через
resolveAnchorSelection(exact-wins), инвариант уникальности сохранён, хранимыйselectionостаётся сырым,closestBlockHintвынесен без изменения поведения json-edit — всё мутационно подтверждено. Правки — про два неточных комментария/размещение.Сделать — потом повторное ревью
stripInlineMarkdown—packages/mcp/src/lib/text-normalize.ts:80-129closestBlockHint(со своим JSDoc, стр. 95-107) вставлен МЕЖДУ docstring'омstripInlineMarkdown(стр. 80-93) и его определением (стр. 129). В итогеstripInlineMarkdownостался без прилегающего docstring (IDE-hover/TypeDoc отнесут доку не туда). Проверено по структуре файла. Fix: перенестиclosestBlockHint(def+JSDoc) ПОСЛЕstripInlineMarkdown, чтобы каждый docstring примыкал к своей функции.resolveAnchorSelection—packages/mcp/src/lib/comment-anchor.ts:26-28Коммент утверждает: «This decision is made ONCE per public call in
resolveAnchorSelection, so the four public entry points (can/count/get/apply) all agree». НоcountAnchorMatches(стр. 429-437) НЕ зовётresolveAnchorSelection— у него СВОЯ параллельная реализация exact-wins (raw>0 → raw, иначе stripped). Поведение согласовано, но коммент скрывает sync-hazard: два экземпляра правила exact-wins должны держаться в синхроне, иначе гейт уникальности поедет (менявшийresolveAnchorSelectionпо комменту решит, что count обновится сам — нет). Это же дублирование отметили и documentation, и architecture. Fix (безопасный): поправить коммент — явно сказать, чтоcountAnchorMatchesдержит собственную параллельную реализацию exact-wins, синхронную сresolveAnchorSelection(роутить count через resolveAnchorSelection не надо — ему нужен свой счётчик-примитив).Как проверено (гейт, clean-room
/home/claude/gm-ci)comment-anchor+comment-anchor-apply+comment-cursor-stability+json-edit*+create-comment(mock) — 85/85 PASS.countAnchorMatches→ падает «raw-unique остаётся unique»; вернуть stripped вместо raw вgetAnchoredText→ падает «хранит сырую подстроку»; снять exact-wins вresolveAnchorSelection→ падает. Зубы есть.closestBlockHint-вынос сохраняет строку"text not found in the document." + hintпосимвольно (тот же longest-token ≥3, та же обрезка 120 кодпоинтов); собственный strip-fallback джойн-эдита (стр. 356) НЕ тронут; json-edit тесты 22/22.rawCan*/rawCount*приватны (внешних вызовов нет).selection— RAW (getAnchoredText→reconstructRawText, тест подтверждает «it's bold» не «it's bold»). Live-anchor rollback без orphan-комментов (safeDeleteComment + liveNotFoundError). tool-spec — только guidance, контракт не менялся.⛔ DROP — кодеру НЕ делать (лог калибровки)
[below-threshold]low [coherence/stability/arch]selectionSpansMultipleBlocksиспользует raw selection → markdown-стилизованное cross-block выделение получит generic closest-block-хинт вместо «spans multiple blocks» —client.ts:2496— редко, guidance корректный; PR это уже признал.[below-threshold]low [coherence] live-anchor путь не ловитnormalized→ strip-успех на редком precheck-skipped пути не даст soft-warning — не ошибка/не мисанкор, лишь пропущенная плюшка.[below-threshold]medium [architecture] DRY:countAnchorMatchesдублирует exact-wins вместо вызоваresolveAnchorSelection— тестами доказана согласованность; роутинг count через resolve семантически рискован (нужен свой счётчик). Оставлено; коммент про это — в DO выше.[below-threshold]medium [test-coverage] нет ПРЯМОГО unit-тестаclosestBlockHint— покрыт двумя интеграционно (json-edit hint-тест гардит байт-идентичность + create-comment #9); edge-кейсы (пустой массив, граница 120) — предсуществующий gap в дословно-перенесённом коде.[below-threshold]low [documentation] признанный multi-block нит не задокументирован кодовым комментом (только в теле PR).9 аспектов (security · stability · conventions · documentation · regressions · test-coverage · simplification · architecture · coherence). Security: mis-anchor исключён exact-wins'ом; хинт — same-trust-boundary; ReDoS нет. Escalate нет.
Поправил обе запрошенные правки (обе доково-организационные, логику не трогал).
[conventions] Осиротевший docstring
stripInlineMarkdown— исправлено.closestBlockHint(def+JSDoc) перенесён ПОСЛЕstripInlineMarkdown, теперь docstringstripInlineMarkdownпримыкает к своему определению (IDE-hover/TypeDoc снова корректны). Порядок объявлений — единственное изменение, поведение то же.[documentation] Header-коммент про
resolveAnchorSelection— исправлено. Явно сказано, что черезresolveAnchorSelectionидут толькоcan/get/apply, аcountAnchorMatchesдержит СВОЮ параллельную реализацию exact-wins (ему нужен счётчик-примитив, а не единичный резолв), синхронную сresolveAnchorSelection; добавлено предупреждение, что обе реализации exact-wins надо держать в синхроне при правках. Роутить count через resolve не стал — семантически рискованно (как и отмечено в DROP-логе).Проверка:
pnpm --filter @docmost/mcp buildчисто;comment-anchor + json-edit + create-comment→ 58/58 PASS. Готово к повторному ревью.Ревью — #427 (fix-round) · createComment самокоррекция якоря
Вердикт: OK (PASS). Обе правки внесены точно, побочек нет.
Что проверено (head
72c2d168, дельтаc9293e31..72c2d168— 2 файла, только доки/перестановка)stripInlineMarkdownперенесён так, что его docstring снова примыкает (стр. 94→def 95);closestBlockHint— после своего docstring (129→def 130). ТелоstripInlineMarkdownбайт-в-байт идентично через перенос (md5 совпал) — чистая перестановка, логика не менялась.resolveAnchorSelection, аcountAnchorMatchesдержит СВОЮ параллельную реализацию exact-wins («needs a raw match COUNT»), и «these two exact-wins implementations MUST stay in sync if either is changed». Sync-hazard задокументирован — ровно то, что просил.comment-anchor+json-edit+create-comment71/71 PASS.Оба DO закрыты, инварианты (мутационно проверенные в прошлом раунде) в силе. Апрув. Лейбл →
review/approved.View command line instructions
Checkout
From your project repository, check out a new branch and test the changes.