| 1 | # ADR 0052: CLI для контракта агента (паритет с MCP) и снапшот-тесты |
| 2 | |
| 3 | **Статус:** Accepted · Implemented |
| 4 | **Дата:** 2026-04-17 |
| 5 | **Реализация:** [`Services/AgentContract/AgentContractRunner.cs`](../../Services/AgentContract/AgentContractRunner.cs), [`Services/AgentContract/AgentContractHeadlessRuntime.cs`](../../Services/AgentContract/AgentContractHeadlessRuntime.cs), вход `CascadeIDE.exe --agent-contract [--workspace <dir>] <command>` в [`Program.cs`](../../Program.cs); тесты [`CascadeIDE.Tests/AgentContractRunnerTests.cs`](../../CascadeIDE.Tests/AgentContractRunnerTests.cs), [`CascadeIDE.Tests/AgentContractRunnerHeadlessTests.cs`](../../CascadeIDE.Tests/AgentContractRunnerHeadlessTests.cs) (в т.ч. снапшот-срез `cockpit_surface`: [`CascadeIDE.Tests/AgentContractCockpitContractSlice.cs`](../../CascadeIDE.Tests/AgentContractCockpitContractSlice.cs), [`CascadeIDE.Tests/TestData/AgentContract/cockpit_surface_contract_slice.approved.json`](../../CascadeIDE.Tests/TestData/AgentContract/cockpit_surface_contract_slice.approved.json)). Паритет с MCP: `ide_get_ui_modes_diagnostics`, `ide_get_supported_editor_languages`, `ide_get_ide_state` (полная сводка — команда CLI `get_ide_state`), `ide_get_cockpit_surface` / только CDS — `get_cockpit_surface` (тот же JSON, что поле `cockpit_surface` в `get_ide_state`), `ide_git_*` (те же JSON-поля, что и в `MainWindowViewModel` / `GitCommandBuilder` + `GitCommandRunner`). |
| 6 | ## Связанные ADR |
| 7 | |
| 8 | | ADR | Роль | |
| 9 | |-----|------| |
| 10 | | [0002](0002-debug-human-agent-parity.md) | паритет «человек ↔ агент» | |
| 11 | | [0008](0008-mcp-contracts-and-testable-infrastructure.md) | MCP-контракты и тестируемая инфраструктура | |
| 12 | | [0043](0043-mcp-transport-recovery-human-agent-parity.md) | транспорт MCP и паритет | |
| 13 | | [0047](0047-cockpit-instrument-descriptor-and-slot-composition.md) | Инструмент кабины (`Instrument`) — дескриптор композиции слота, не `Control` | |
| 14 | |
| 15 | ### Вне ADR |
| 16 | |
| 17 | | Документ | Роль | |
| 18 | |----------|------| |
| 19 | | [MCP-PROTOCOL.md](../MCP-PROTOCOL.md) | команды IDE / stdio MCP | |
| 20 | |
| 21 | ### Снимок реализации |
| 22 | |
| 23 | | Элемент | Значение | |
| 24 | |---------|----------| |
| 25 | | — | `get_ui_modes_diagnostics`, `get_supported_editor_languages`, `get_solution_info`, `get_cockpit_surface`, `get_ide_state`, read-only `git_*` с `--workspace` | |
| 26 | | — | CI: `.gitlab-ci.yml` — `dotnet test` + smoke `--agent-contract` | |
| 27 | | — | **golden slice** CDS: `CascadeIDE.Tests/TestData/AgentContract/cockpit_surface_contract_slice.approved.json` + `AgentContractCockpitContractSlice` | |
| 28 | |
| 29 | --- |
| 30 | ## Контекст |
| 31 | |
| 32 | Сегодня Cascade IDE отдаёт агенту **обогащённый JSON** (layout, состояние workspace, диагностики, проекция кабины/инструментов и т.д.) через **MCP-инструменты** (`ide_*`, см. контракт в [MCP-PROTOCOL.md](../MCP-PROTOCOL.md)). Часть полей — **тот же семантический слой**, на который опирается UI (размещение, видимость слотов, раскладка в смысле CDS/host surface), а не «параллельная выдумка для агента». Это удобно в интерактиве, но: |
| 33 | |
| 34 | - **Регрессии** в форме ответа сложно ловить без запуска IDE и сценария MCP. |
| 35 | - **CI** хочется иметь **детерминированные** проверки контракта: «после изменения сборщика снимка ответ не сломался». |
| 36 | - Отдельная реализация «как CLI» рискует **разойтись** с реальным MCP — тесты будут зелёными, агент в Cursor — нет. |
| 37 | |
| 38 | ## Решение |
| 39 | |
| 40 | **Принять** отдельную поставку — **headless CLI** (или подкоманда существующего host’а), которая: |
| 41 | |
| 42 | 1. **Вызывает те же** публичные/внутренние **сборщики данных и сериализацию JSON**, что и обработчики MCP для выбранных `ide_*` команд — **без второй копии логики** (общий слой: сервисы, `IdeMcpCommandExecutor` хелперы, билдеры снимков и т.д., вынесенные так, чтобы их можно было дернуть и из процесса IDE, и из CLI). |
| 43 | 2. Принимает на вход **явный контекст**: как минимум `--workspace` (корень workspace), при необходимости путь к решению, флаги «какой тул эмулировать» и параметры args (по схеме MCP). |
| 44 | 3. Пишет **в stdout** тот же JSON (или тот же **нормализованный** вид), что вернул бы соответствующий тул в MCP. |
| 45 | |
| 46 | **CI:** в корне репозитория — [`.gitlab-ci.yml`](../../.gitlab-ci.yml): `dotnet build` / `dotnet test` и последовательный smoke `dotnet run --project CascadeIDE -- --agent-contract …` (ожидается **Windows** runner с .NET 10 SDK и тег `windows`; при другом окружении — поправить `default.tags`). Дополнительно в репозитории принят **`dotnet script`** (глобальный `dotnet-script`, см. `Financial/finplan/update-finplan-pdf.csx`, `agents-and-humans-book/update-agents-humans-pdf.csx`). Для вызова `--agent-contract` из пайплайна с собранным `CascadeIDE.exe`: [`docs/samples/agent-contract-ci.csx`](../samples/agent-contract-ci.csx) — `ProcessStartInfo`, код выхода, без сюрпризов с `$LASTEXITCODE`. Альтернатива на **PowerShell**: [`docs/samples/agent-contract-ci.ps1`](../samples/agent-contract-ci.ps1) (`pwsh` 7+ или `Start-Process -PassThru.ExitCode` в Windows PowerShell 5.1). |
| 47 | |
| 48 | **Тесты:** |
| 49 | |
| 50 | - **Интеграционные / контрактные:** запуск CLI (или прямой вызов общего API) на **фикстурном workspace** (минимальный `.sln` / файлы уже есть в тестах) → сравнение с **ожидаемым JSON** или с **снапшотом** с осознанным обновлением. |
| 51 | - **CDS (кабина):** узкий **golden slice** — `schema_version` + `topology` + отсортированный `instruments` (`TestData/AgentContract/cockpit_surface_contract_slice.approved.json`); поля вроде `ui_mode` и `presentation_effective_line` в снапшот не входят (зависят от пользовательского `settings.toml`). При смене дефолтного placement или схемы CDS — обновить approved-файл осознанно. |
| 52 | - **Нормализация для стабильности:** абсолютные пути → относительные к фикстуре; вырезание времени/версий при необходимости; либо assert по **подмножеству полей** (schema-shaped), если полный снапшот слишком шумный. |
| 53 | - **«Отрисовка» на уровне данных:** для сценариев, где MCP отдаёт **состояние кабины / layout / инструменты** (то, что консистентно с тем, что рисует UI), снапшот того же JSON в CI проверяет **регрессию представления** — не пиксели и не Skia/Avalonia-пайплайн, а **что именно должно оказаться в каком слоте и с какими идентификаторами**. Это осмысленный слой проверки «UI vs агент видят одно и то же», без дублирования отдельной модели «только для тестов». |
| 54 | |
| 55 | **Негативно:** |
| 56 | |
| 57 | - **Пиксели, шрифты, визуальные регрессии Skia/Avalonia** — по-прежнему отдельно (скриншоты, UI-тесты, ручной просмотр). CLI+MCP-снапшоты не заменяют рендер-движок. |
| 58 | - Не дублировать **бизнес-логику** «с нуля» — только тонкая оболочка над общим кодом. |
| 59 | |
| 60 | ## Последствия |
| 61 | |
| 62 | | Плюсы | Минусы / риски | |
| 63 | |--------|----------------| |
| 64 | | Регрессии контракта в CI без GUI | Нужно один раз вынести общий слой и следить, чтобы MCP и CLI шли в одну точку входа | |
| 65 | | Документирование «что именно» отдаёт тул (через тесты) | Фикстуры и нормализация — поддержка при смене ОС/путей | |
| 66 | | Быстрый feedback при рефакторинге сборщиков JSON | Скоуп по тулам расти поэтапно — иначе большой взрыв работы | |
| 67 | | Проверка согласованности **семантики кабины/UI** с тем, что видит агент (один источник в JSON) | Риск смешать слои: держать в снапшотах поля, которые реально shared с UI, а не весь ответ целиком если он шумный | |
| 68 | |
| 69 | ## Этапы внедрения (рекомендация) |
| 70 | |
| 71 | 1. Выбрать **один** тул с устойчивым JSON (например снимок layout или один read-only сценарий). |
| 72 | 2. Вынести **единую** функцию «построить payload» + «в JSON string». |
| 73 | 3. Добавить минимальный **CLI** (`dotnet run --project …` или отдельный `net10.0` tool). |
| 74 | 4. Покрыть **одним** снапшот-тестом; затем расширять список команд. |
| 75 | |
| 76 | ## Принятие и открытые вопросы |
| 77 | |
| 78 | **Открытых вопросов нет** — направление зафиксировано для реализации. |
| 79 | |
| 80 | Дальше: добавлять команды в `AgentContractRunner.TryGetJson`, вызывая те же сборщики, что и `IIdeMcpActions` / MCP; при существенном расширении — обновить таблицу здесь и в [MCP-PROTOCOL.md](../MCP-PROTOCOL.md). |
| 81 | |