Пока MCP-сервер работал прямо из src\...\bin, он держал открытым kompas-mcp.exe
и Kompas.Mcp.Core.dll — dotnet build падал с MSB3027 от каждой живой сессии.
Лаунчер копирует сборку в %LOCALAPPDATA%\kompas-mcp\dev\<отпечаток> и запускает
её оттуда, поэтому bin свободен всегда.
- tools/dev/KompasMcpDev.psm1 — отпечаток каталога сборки, теневая копия через
атомарный Directory.Move, уборка старых копий мимо занятых живым процессом,
сессионный mutex на одновременный старт нескольких сессий;
- tools/dev/launch-dev-mcp.ps1 — точка входа для .mcp.json, инвариант «в stdout
до старта сервера не уходит ни байта»;
- tools/dev/rebuild-mcp.ps1 — -Check компилирует мимо bin (~2 с при живом
сервере), обычный режим отказывается заранее и называет блокирующие PID;
- tools/tests/DevLauncher.Tests.ps1 — Pester на все три файла.
Раздел «The rebuild cycle» в CLAUDE.md (2c56511) уже описывал эту схему.
Три канала обратной связи (патч в src, новый общий тул, правка навыков),
правило «навыки отлаживаются как код» с source of truth в plugin/skills,
запрет утечки внутреннего в публикуемые навыки и ловушка с залоченным
kompas-mcp.exe при пересборке.
Корневой CLAUDE.md грузится в каждую сессию, но большая его часть нужна только
при доработке сервера. 37 476 → 12 203 символов (~6 300 токенов на сессию).
Перенесено в .claude/skills/kompas-mcp-dev/reference/:
- com-implementation-facts.md — проверенные COM-цепочки («Key implementation facts»);
- plugin-and-release.md — устройство plugin/, ветка dist, релиз, CI, обновление RAG-базы.
Релизная кухня положена в навык, а не в plugin/CLAUDE.md: plugin/ раздаётся
пользователям, внутренностям сборки там не место. В корневом файле оставлены два
правила, которые нельзя откладывать до загрузки навыка — обязательный бамп версии
в plugin.json и UTF-8 с BOM для PowerShell плагина.
Удалено как выводимое из репозитория: раскладка каталогов, стек и счётчики тестов,
список режимов sync-agent-assets.ps1, перечень содержимого каталога SDK.
Секция «Navigating the SDK base» удалена как дубль §«Порядок поиска» из определения
агента kompas-sdk-research — вдобавок она описывала инструменты, доступные только
субагенту. Уникальная процедура обновления базы перенесена в навык.
Документация переписана под состояние без каталога docs/
Каталог docs/ удалён в b022a91, но CLAUDE.md и README ссылались на
ARCHITECTURE.md, TODO.md, OPEN_QUESTIONS.md, IMPLEMENTATION_PLAN.md и
presentation.html — все ссылки были битыми.
- CLAUDE.md: явно зафиксировано, что прозаическая документация — только он и
README, docs/ воссоздавать не нужно; бэклог (чертёж: базы/допуски/привязка,
сопряжения сверх coincidence и distance) и известный подводный камень с
направлением по нормали грани перенесены из удалённых TODO/OPEN_QUESTIONS
- убран субагент docs-maintainer: он удалён вместе с docs/, доки правятся руками
- ссылка на спеку по параметрическим эскизам заменена на память
- README: структура репозитория приведена к реальной, добавлены --version,
run-ps-tests.ps1 и оговорка про .slnx на SDK 8
- kompas-mcp-dev: справка SDK описана как RAG-база kompas-sdk вместо
несуществующего docs/Kompas3D_SDK/, добавлен порядок работ по новому
инструменту, команды проверки и границы CI
Счётчики сверены по коду: 84 инструмента, 250 unit + 131 integration, 41 Pester.
@
Продолжение 4d4e114 (сборки КОМПАС убраны из поставки).
- kompas_status проверяет интероп до обращения к сессии и отдаёт причину текстом:
иначе MCP SDK показывает агенту только «An error occurred invoking …»
- провайдер сносит негодный остаток кеша перед Move: повтор после оборванной
распаковки падал IOException вместо внятного сообщения
- README/plugin/README/doctor/CLAUDE.md: требование компонента SDK,
KOMPAS_INTEROP_DIR, кеш в %LOCALAPPDATA%, счётчики тестов 381 (250 unit)
finally в Install-KompasMcpServer спасает только от штатного throw/return.
После принудительного убийства процесса посреди установки каталог
.tmp\<guid> оставался навсегда — по ~70 МБ на оборванный запуск, сервер
публикуется self-contained.
Remove-KompasMcpStaleTemp удаляет подкаталоги .tmp старше суток. Возраст
считается как максимум LastWriteTimeUtc по всему поддереву: пока сосед
льёт байты в один и тот же server.zip, время записи самого каталога не
меняется, и по нему можно было бы снести живую установку.
Вызов стоит в начале Install-KompasMcpServer, до раннего выхода по
Test-KompasMcpInstall — иначе мусор от оборванных попыток лежал бы до
следующего релиза. Если каталога .tmp нет, всё сводится к одному
Test-Path.
Функция не бросает исключений и не пишет в success stream: запуск
сервера важнее мусора, а stdout занят JSON-RPC.
При переключении на ветку или коммит без plugin/skills junction'ы навыков
повисают битыми, и git checkout падает на середине: часть рабочего дерева
уже удалена, а HEAD остаётся на прежней ветке.
Remove-AgentSkillLink удаляет сам reparse point через Directory.Delete
(без -Recurse), поэтому в target не заходит и содержимое plugin/skills не
трогает. Junction опознаётся по атрибуту ReparsePoint, а не по резолву
target — иначе режим не работал бы ровно в том случае, ради которого сделан.
Настоящий каталог не удаляется, а возвращает Skipped.
Режимы скрипта разведены parameter set'ами: -Remove -Check вместе теперь
дают ошибку разбора параметров вместо тихого выигрыша одного из ключей.
Покрыто пятью кейсами в tools/tests/AgentAssets.Tests.ps1 (включая junction
с исчезнувшим источником); порядок действий и восстановление после падения
чекаута описаны в CLAUDE.md.
База docs/Kompas3D_SDK/ (2465 статей) переехала в приватный kompas-sdk-docs
и проиндексирована в RAG на CT 127; субагент kompas-sdk-research переведён
на MCP-инструменты сервера kompas-sdk.
Спек дополнен фактами прогона: 2465 документов / 13 755 точек, проверка
grep/search/фильтров, живой прогон CI с инкрементальной переиндексацией.
Отдельно зафиксирована находка — реранк падал 400 на длинных чанках
(слот 1024 токена против обрезки в 2000 символов); починено в rag-node.
Гейт «версия не должна уменьшаться» отвергал и равную версию безусловно, что
делало невозможным повторный прогон на том же теге (Task 10 Step 4 плана).
Теперь сравнение version+sourceSha читается из plugin/server.lock.json ветки
dist: равная версия проходит дальше только если sourceSha из dist совпадает с
текущим github.sha (ретрай уже опубликованного релиза — ниже по пайплайну он
и так идемпотентен: upsert релиза/ассета, повторная сверка sha256, force-push
dist тем же содержимым). Другой sourceSha при той же версии — попытка
подменить уже опубликованный релиз другим кодом — отвергается с явным
сообщением, какой коммит уже занимает эту версию. Меньшая версия отвергается
как и раньше.
ci.yml: сборка + unit-тесты на Linux-раннере (EnableWindowsTargeting), плюс
проверка согласованности plugin/.claude-plugin/plugin.json и
plugin/server.lock.json (версии совпадают; для опубликованного релиза —
sha256 непустой и 64 hex).
release.yml: на тег vX.Y.Z — публикация self-contained win-x64,
идемпотентный upsert релиза/ассета через Gitea API, сверка SHA256
опубликованного ассета перед обновлением, и force-push ветки dist поверх
коммита с тегом (server.lock.json + plugin.json). Отличия от черновика в
плане: устойчивый разбор "версия не должна уменьшаться" (не полагается на
особенность jq на пустом stdin), явные HTTP-коды вместо curl -f для
различения "релиза нет" от прочих ошибок API, persist-credentials: false
на checkout, чтобы не конфликтовать с собственным токеном на пуше.
Не прогонялось на раннере — раннер ещё не готов (отдельная работа).
server.lock.json вида "{}" (валидный JSON, ноль ключей) — реальный вход
(placeholder до заполнения релизным пайплайном), но под
Set-StrictMode -Version Latest член-перечисление .PSObject.Properties.Name
бросает PropertyNotFoundException, когда коллекция .Properties пуста, — Read-
KompasMcpLock падала сырым .NET-трейсом вместо дружественного сообщения.
Тесты этого не ловили: любой объект хотя бы с одним свойством (даже без
version) уже не задевает пустую коллекцию.
Фикс: PSObject.Properties['имя'] (индексатор) вместо .Properties.Name
-contains — он возвращает $null и не бросает независимо от того, пуста
коллекция или нет. Применено ко всем трём проверкам (version/url/sha256).
Отдельно исключён случай $lock -eq $null (JSON "null" и пустой файл
разбираются в $null, а $null.PSObject тоже бросает под StrictMode).
Добавлены тесты: лок "{}", лок "null", лок "" (пустая JSON-строка вместо
объекта) — все дают сообщение с именем файла, а не .NET-трейс. Известные
пробелы покрытия (IOException-гонка вокруг Directory.Move,
AbandonedMutexException) зафиксированы комментарием в тестовом файле — не
автотестируются осознанно (нужен настоящий межпроцессный тайминг).
- Test-Path -PathType Leaf: путь-каталог в KOMPAS_MCP_EXE больше не проходит
проверку молча — раньше падение случалось на голом & $exe сырым
CommandNotFoundException в обход диагностической обёртки.
- запуск сервера (& $exe @args) обёрнут в try/catch: сбой самого запуска
(битый бинарь, недостающая зависимость, отказ в доступе) теперь тоже даёт
чистое сообщение 'kompas-mcp launcher: ...' и код 1, а не сырой трейс;
ненулевой код возврата уже запущенного сервера по-прежнему пробрасывается как есть.
- тесты: три новых сценария (каталог вместо файла, ноль байт в stdout на
пути отказа, повреждённый exe) + первый тест переписан на побайтовую
проверку stdout через Start-Process без фильтрации пустых строк.
Три предметных замечания ревью качества:
- Resolve-KompasMcpExecutable: WaitOne() перенесён в try/finally, отдельно
ловится AbandonedMutexException (владелец умер посреди установки — владение
переходит к нам, это не ошибка); ReleaseMutex вызывается только если mutex
реально захвачен, иначе он бросает SynchronizationLockException и маскирует
первопричину. Добавлен -Downloader passthrough для тестируемости.
- Read-KompasMcpLock: разбор JSON обёрнут в try/catch с сообщением, называющим
файл; наличие полей version/url/sha256 проверяется через
PSObject.Properties.Name (обращение к отсутствующему свойству под
Set-StrictMode -Version Latest бросает PropertyNotFoundException); sha256
валидируется на 64 hex-символа.
- Добавлены тесты на конкурентную логику: Resolve-KompasMcpExecutable
(повторный вызов не скачивает), гонка "сосед выиграл" (целевой каталог уже
содержит годную/негодную установку) в Install-KompasMcpServer, счастливый
путь и все новые ветки валидации в Read-KompasMcpLock.
Мелочи: & $Downloader ... | Out-Null (внедряемый загрузчик не должен писать
в success stream); уточнён комментарий Test-KompasMcpInstall — оно намеренно
трактует любую ошибку чтения маркера (не только битый JSON, но и блокировку
антивирусом) как "установка негодная".
AbandonedMutexException-ветка вручную воспроизведена и проверена: процесс-
владелец mutex убит, пока другой процесс уже блокирован в WaitOne() —
тот получает AbandonedMutexException и корректно продолжает работу.
MD-база docs/Kompas3D_SDK/ (2465 статей, 10.4 МБ) переехала в приватный
kompas-sdk-docs и проиндексирована в RAG на CT 127. Доступ — MCP-сервер
kompas-sdk (порт 8092); субагент kompas-sdk-research переведён с Grep/Read
на его инструменты и теперь ищет по базе, которой в репозитории нет.
Порядок поиска в инструкции агента: grep_knowledge для точных имён,
search_knowledge с фильтрами type/tags для задачных вопросов,
get_chunk_context вместо get_document (геометрия.md весит 261 КБ).
Обновление при новой версии КОМПАС — update_sdk_docs.ps1 в репозитории
доков: zip справки → генератор → diff → тег → push → переиндексация
через Gitea Actions. Спек и фактические результаты —
docs/superpowers/specs/2026-07-31-sdk-docs-rag-migration-design.md.
Прежний комментарий утверждал, что Global\ требует отдельной привилегии —
ревью опровергло это эмпирически (мьютекс создался без ошибок под тем же
непривилегированным пользователем). Настоящая причина: плагин ставится на
чужие незнакомые машины, где политика безопасности может отличаться, и отказ
Global\ был бы фатальным для запуска сервера, тогда как остаточный риск
сессионного mutex (лишняя повторная загрузка при параллельных сессиях)
ограничен и не портит установку.
- AgentAssets.psm1 и sync-agent-assets.ps1 пересохранены в UTF-8 с BOM:
powershell.exe читает файлы без BOM в системной ANSI-кодировке и ломает
парсинг на кириллических строковых литералах (pwsh 7 работал за счёт
автодетекта и маскировал баг).
- Copy-Item в ветке -AllowCopy получил -ErrorAction Stop: без него ошибка
копирования писалась как non-terminating и функция возвращала 'Copied'
при частично скопированном каталоге, если вызывающий не выставил
$ErrorActionPreference='Stop' сам.
- Test-AgentSkillLink сравнивает пути через
[string]::Equals(...,OrdinalIgnoreCase) явно, а не через регистронезависимый -eq.
- Добавлены тесты: отсутствующий SourcePath, перелинковка на другой
источник (старый target остаётся нетронутым) и AllowCopy-регрессия
(мок Copy-Item с [CmdletBinding()], чтобы корректно эмулировать
ErrorAction реального cmdlet).
Инкремент 9. 83 инструмента, 331 тест. + заметка о ревью реализации в спеке
(Codex + pi/glm-5.1 + pi/kimi-k2.6).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- проверка возврата AddBranchByPoint/SetBranchTextPosition (bool) — ранняя диагностика
вместо позднего RPC_E_SERVERFAULT (Codex, glm)
- RU-алиасы в сообщении ShelfDirections.Parse и в описании инструмента (glm, kimi)
- интеграционный тест направления полки параметризован Right/Left/Up/Down — покрывает
всю поверхность ToKompas на реальном COM (glm, kimi)
- отклонено (обоснование): unit ToKompas (граница проекта), e2e tool-обёртки (тонкая),
angleDeg (нет Angle у ILeader), default text у AddText (вне объёма)
331 тест зелёный, сборка Release чистая.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>