Forge
markdowndeeb25a2
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
421. **Вызывает те же** публичные/внутренние **сборщики данных и сериализацию JSON**, что и обработчики MCP для выбранных `ide_*` команд — **без второй копии логики** (общий слой: сервисы, `IdeMcpCommandExecutor` хелперы, билдеры снимков и т.д., вынесенные так, чтобы их можно было дернуть и из процесса IDE, и из CLI).
432. Принимает на вход **явный контекст**: как минимум `--workspace` (корень workspace), при необходимости путь к решению, флаги «какой тул эмулировать» и параметры args (по схеме MCP).
443. Пишет **в 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
711. Выбрать **один** тул с устойчивым JSON (например снимок layout или один read-only сценарий).
722. Вынести **единую** функцию «построить payload» + «в JSON string».
733. Добавить минимальный **CLI** (`dotnet run --project …` или отдельный `net10.0` tool).
744. Покрыть **одним** снапшот-тестом; затем расширять список команд.
75
76## Принятие и открытые вопросы
77
78**Открытых вопросов нет** — направление зафиксировано для реализации.
79
80Дальше: добавлять команды в `AgentContractRunner.TryGetJson`, вызывая те же сборщики, что и `IIdeMcpActions` / MCP; при существенном расширении — обновить таблицу здесь и в [MCP-PROTOCOL.md](../MCP-PROTOCOL.md).
81
View only · write via MCP/CIDE