ADR 0053: Карта намерений и поток управления на PFD (control flow)¶
Статус: Accepted · Implemented
Дата: 2026-04-17
Связанные ADR¶
| ADR / артефакт | Роль |
|---|---|
| 0021 | PFD как приборы, зоны внимания |
| 0051 | Маршрутизация внимания |
| 0055 | Skia pipeline |
| 0056 | Внедрение pipeline в карту |
| 0039 | MCP, subgraph |
| 0065 | Карта намерений кода |
CodeNavigationMapSubgraph* |
Модели subgraph в коде |
Резюме¶
- Карта намерений на PFD: control flow, subgraph, KISS-навигация вокруг якоря кода.
- Общий Skia pipeline и cursor semantics (0056).
- Не путать с GitMap (0062) и с полным solution tree.
Контекст¶
Семантическая карта в зоне PFD сегодня опирается на граф зависимостей (символы, вызовы, связи между артефактами). При редактировании одного метода A() полезно видеть не только «кто с кем связан», но и маршрут выполнения внутри метода: условные вызовы, общий хвост после ветвлений.
Пример намерения:
void A() {
if (cond) B();
else C();
D();
}
На карте (упрощённо): B при условии, C на альтернативной ветке, D как точка схождения / общий продолжение — без превращения экрана в полный CFG или копию текста из редактора.
Аналогия с авиацией: waypoints и условия перехода между ними на навигационном дисплее, а не распечатка всех процедурных страниц.
Цели¶
- Визуализировать намерение потока управления внутри выбранного метода там, где это помогает сканированию (Flight / «тонкий» режим), а не синтаксис ради синтаксиса.
- Сохранить KISS: не перегружать PFD текстом условий и декорациями по умолчанию.
- Подготовить контракт данных (subgraph / JSON / MCP), чтобы рёбра и при необходимости узлы несли тип связи и опциональные подсказки для hover / drill-down.
Не-цели (на первом этапе)¶
- Полная отрисовка всего CFG Roslyn на мини-карте.
- Дублирование тела условия
if (…)постоянной подписью на PFD. - Визуализация каждого локального присваивания и мелкого шума внутри веток.
Принципы отображения (чтобы не «засрать» интерфейс)¶
-
Семантическая компрессия
По умолчанию: иконка ветвления (ромб / «?») или тонкая развилка на ребре, без длинного текста предиката — дляifи дляswitch/ pattern matching одинаково (см. § «Switch…»).
Деталь условия — по задержке взгляда / hover / отдельный слой (Skia tooltip), опционально. -
Только значимые «внешние» точки маршрута
В приоритете: вызовы других методов и тяжёлые операции, которые пользователь ищет глазами. Локальный шум (i++, пустойreturnбез смысла для карты) — не поднимать на граф без явной политики. -
Тонкие линии и метафоры
- Цикл (
for/while): см. § «Циклы: петля на ребре». -
try/catch: позже — стиль «защищённого» участка (например «зонтик» / нестабильный контур узла), когда будет согласована лексика. -
Вертикальный «полётный план»
Узлы значимых шагов сверху вниз, ветвления — в сторону, без блоков «как в IDE» на весь экран.
Циклы (for / while): петля на ребре¶
Не рисуем отдельный «граф цикла» как на классической блок-схеме (ромб + стрелка назад, дублирующая узлы). Рисуем петлю на линии связи к значимому шагу внутри метода (например вызов B() в теле for/while): линия к узлу не прямая, а делает изящный виток вокруг направления к этому узлу — по духу как орбита ожидания / holding на навигационном дисплее, а не как «ещё один прямоугольник».
Смысл для пользователя: сразу видно — «этот вызов здесь крутится по кругу», без чтения кода в Forward и без разворачивания итераций на карте.
Рендер (направление): тонкие неоновые линии на тёмном фоне PFD, сглаженные кривые (Bezier / кубические сплайны в Skia), чтобы визуально быть частью кокпитного слоя, а не старой блок-схемы. Центральная линия — условный «основной поток» текущего метода A(); виток — на ребре к зависимому шагу, не отдельный декоративный слой поверх всего графа.
Опционально (если анализ позволяет эвристику): «тяжесть» цикла или ожидаемое число итераций — плотность витка (ближе спираль, чуть ярче/толще линия и т.п.), без обещания точного n на PFD.
Контракт: ребро с семантикой цикла несёт признак вроде Loop / LoopCall + опционально метаданные для стиля (см. таблицу ниже); координаты витка — производные от layout (StarGraph / force / иной движок), чтобы кривые не перекрывали соседние узлы — отдельная задача подбора контрольных точек Bezier.
Switch, case и сопоставление с образцом¶
Включаем в ту же семантику, что и if / else: это ветвление потока, не «особый случай вне карты». Не разворачиваем все ветки как полную таблицу на PFD.
- Немного значимых исходов (ориентир 2–4): веер рёбер от одной точки развилки к значимым шагам; подписи
case/ guard — опционально, по hover или второму слою. - Много веток или одни «пустые» case: сжатие — один узел/иконка многонаправленного ветвления; наружу только вызовы с смыслом; прочее — агрегат («остальные ветки» /
default) или скрыто до drill-down. - Pattern matching (
switchexpression,when): не дублировать длинные выражения на постоянной подписи; тот же принцип, что для предикатаif.
Связь с контрактом: тип вроде MultiBranch / ConditionalCall на ребре от общего предка развилки к шагу; детали ветки — в опциональных метаданных.
Данные и контракт (направление)¶
Существующие модели subgraph (CodeNavigationMapSubgraphNode, CodeNavigationMapSubgraphEdge) расширяются осмысленно, например:
| Идея | Назначение |
|---|---|
| Тип ребра | Call, ConditionalCall, Merge, MultiBranch (несколько исходов: switch, цепочка if, pattern matching), Loop / LoopCall (петля на ребре к шагу внутри цикла; не путать с низкоуровневым LoopBack CFG, если понадобится отдельно) |
| Краткая метка | Опционально; не дублировать полный текст cond. |
| Деталь условия | Опционально, для tooltip / второго слоя; может быть сжата или отложена. |
Точные имена полей и JSON — зафиксировать после прототипа генератора (Roslyn / Control Flow Analysis + фильтрация вызовов).
Источник анализа в стеке: Roslyn (ControlFlowAnalysis и привязка вызовов к веткам), без обязательной привязки к одному только текстовому grep.
Договорённости (черновик)¶
Предикат на ребре vs только иконка¶
Граница «показать краткий предикат» и «только иконка» задаётся настройкой пользователя (приложение / workspace — конкретный ключ и merge с bundle зафиксировать при внедрении), а не только жёсткой привязкой к режиму UI вроде Flight.
Контур агента (MCP, запрос контекста / subgraph навигации) опирается на тот же уровень детализации: в продуктовом смысле агент — тоже пользователь этого представления и не живёт в отдельном скрытом режиме в обход настроек, кроме как при явном параметре в контракте вызова (override на один запрос).
Тип карты: controlFlow vs «классическая»¶
Отдельный пресет UiMode (например только Flight) под одну CF-aware карту не обязателен: какой вид семантической карты строить — выбор пользователя, ортогонально режиму кабины. Режим Flight остаётся полигоном раскладки PFD; уровень/тип карты задаётся отдельно.
Черновик формы в TOML (значения и merge с bundle / workspace — при внедрении):
[semantic_map]
level = "controlFlow" # поток управления в методе (CF-aware subgraph)
# level = "file" # классическая зависимостная / файловый срез (как baseline карты намерений)
Тот же переключатель должен быть достижим для агента (поле в MCP / эхо в subgraph-запросе), без отдельной скрытой семантики.
Версионирование subgraph JSON (MCP / CLI)¶
Обратная совместимость при добавлении полей пока не гарантируется: у продукта ещё нет пользователей, JSON subgraph и MCP/CLI могут менять форму синхронно с кодом без политики миграции. Когда появятся стабильные потребители вне репозитория (интеграции, долгоживущий контракт агента) — ввести явное версионирование схемы и правила совместимости отдельным решением (ADR / дополнение к MCP-PROTOCOL).
Следующие шаги (черновик)¶
- Зафиксировать минимальный набор kind для рёбер и правила фильтрации вызовов.
- Прототип: один метод под курсором → упрощённый поток → тот же JSON, который уже потребляет карта намерений на PFD.
- Рендер: стили линий и узлов на PFD / Skia без обязательного текста условия на постоянной подписи.
Backlog intent (добавить поэтапно)¶
Чтобы карта сохраняла intent и не превращалась в полный CFG, расширения вводим порциями:
- Early/abrupt exits:
ThrowExit,Break,Continue. - Асинхронные границы:
AwaitBoundary(точка паузы/возобновления потока). - Короткое замыкание:
ShortCircuitдля&&/||в guard-условиях. - Pattern guard: явная семантика для
whenв pattern matching/switch. - Исключения: укрупнённый
ExceptionFlowдляcatch/finallyбез разворота полного exception-CFG. - Итераторы:
YieldExit(yield return/yield break) как отдельный тип выхода. - Политика детализации: разделить «операторный шум» и «внешние шаги» через declutter policy (например, helper-вызовы внутри аргументов).
(Ниже можно добавлять разделы: примеры JSON, скринмоки, ограничения производительности, ссылки на внешние обсуждения.)