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(); виток — на ребре к зависимому шагу, не отдельный декоративный слой поверх всего графа.
Срез реализации (2026-05): Views/SkiaKit/Graph/SkiaGraphSceneDrawing.Edges.cs — для kind с loop одна пунктирная кубическая кривая к цели с увеличенным поперечным выносом (в т.ч. когда хорда почти параллельна главному потоку); отдельный эллипс вокруг целевого узла для этого не рисуется. Пунктирный овал группы цикла — SkiaGraphSceneDrawing.LoopRegions.cs, только если в LoopGroupId не меньше двух узлов. При высокой плотности графа пайплайн карты задаёт «компактный» режим (GraphLayoutEngineOptions.IsDense + больший минимальный viewport по высоте).
Опционально (если анализ позволяет эвристику): «тяжесть» цикла или ожидаемое число итераций — плотность витка (ближе спираль, чуть ярче/толще линия и т.п.), без обещания точного n на PFD.
Контракт: ребро с семантикой цикла несёт признак вроде Loop / LoopCall + опционально метаданные для стиля (см. таблицу ниже); координаты витка — производные от layout (StarGraph / force / иной движок), чтобы кривые не перекрывали соседние узлы — отдельная задача подбора контрольных точек Bezier.
Switch, case и сопоставление с образцом¶
Включаем в ту же семантику, что и if / else: это ветвление потока, не «особый случай вне карты». Не разворачиваем все ветки как полную таблицу на PFD.
- Немного значимых исходов (ориентир 2–4): веер рёбер от одной точки развилки к значимым шагам; подписи
case/ guard — опционально, по hover или второму слою. - Много веток или одни «пустые» case: сжатие — один узел/иконка многонаправленного ветвления; наружу только вызовы с смыслом; прочее — агрегат («остальные ветки» /
default) или скрыто до drill-down. В коде: glance — всеMultiBranchсрезаются; normal — при веере ≥4 от одной развилки остаются 3 приоритетных исхода + ромб+N(CodeNavigationMapControlFlowDeclutter); inspect — без сжатия. - Pattern matching (
switchexpression,when): не дублировать длинные выражения на постоянной подписи; тот же принцип, что для предикатаif.
Связь с контрактом: тип вроде MultiBranch / ConditionalCall на ребре от общего предка развилки к шагу; детали ветки — в опциональных метаданных.
Данные и контракт (направление)¶
Существующие модели subgraph (CodeNavigationMapSubgraphNode, CodeNavigationMapSubgraphEdge) расширяются осмысленно, например:
| Идея | Назначение |
|---|---|
| Тип ребра | Call, ConditionalCall, Merge, MultiBranch (несколько исходов: switch, цепочка if, pattern matching), Loop / LoopCall (петля на ребре к шагу внутри цикла; не путать с низкоуровневым LoopBack CFG, если понадобится отдельно) |
Ветви if |
edge_provenance: cf_branch_true / cf_branch_false; подпись — condition_branch_label_preset = id; слои merge: бандл CodeNavigation/condition-branch-label-presets.toml → .cascade/workspace.toml [[code_navigation_map.condition_branch.presets]] → %LocalAppData% settings (как 0039 для code_navigation); custom — condition_branch_positive / condition_branch_negative на [code_navigation_map]. |
| Краткая метка | Опционально; не дублировать полный текст 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, скринмоки, ограничения производительности, ссылки на внешние обсуждения.)