Forge
markdowndeeb25a2
1# ADR 0053: Карта намерений и поток управления на PFD (control flow)
2
3**Статус:** Accepted · Implemented
4**Дата:** 2026-04-17
5
6## Связанные ADR
7
8| ADR / артефакт | Роль |
9|----------------|------|
10| [0021](0021-pfd-mfd-cockpit-attention-model.md) | PFD как приборы, зоны внимания |
11| [0051](0051-intent-based-attention-routing-toml.md) | Маршрутизация внимания |
12| [0055](0055-skia-instrument-composition-pipeline.md) | Skia pipeline |
13| [0056](0056-semantic-map-pipeline-adoption.md) | Внедрение pipeline в карту |
14| [0039](0039-workspace-navigation-affordances.md) | MCP, subgraph |
15| [0065](0065-instrument-categories-domain-taxonomy.md) | Карта намерений кода |
16| `CodeNavigationMapSubgraph*` | Модели subgraph в коде |
17
18## Резюме
19
20- **Карта намерений** на PFD: control flow, subgraph, KISS-навигация вокруг якоря кода.
21- Общий Skia pipeline и cursor semantics ([0056](0056-semantic-map-pipeline-adoption.md)).
22- Не путать с GitMap ([0062](0062-git-submodules-semantic-map-subgraph.md)) и с полным solution tree.
23
24---
25## Контекст
26
27Семантическая карта в зоне PFD сегодня опирается на **граф зависимостей** (символы, вызовы, связи между артефактами). При редактировании **одного метода** `A()` полезно видеть не только «кто с кем связан», но и **маршрут выполнения** внутри метода: условные вызовы, общий хвост после ветвлений.
28
29Пример намерения:
30
31```csharp
32void A() {
33 if (cond) B();
34 else C();
35 D();
36}
37```
38
39На карте (упрощённо): **B** при условии, **C** на альтернативной ветке, **D** как точка схождения / общий продолжение — без превращения экрана в полный CFG или копию текста из редактора.
40
41Аналогия с авиацией: **waypoints** и условия перехода между ними на навигационном дисплее, а не распечатка всех процедурных страниц.
42
43---
44
45## Цели
46
471. Визуализировать **намерение потока управления** внутри выбранного метода там, где это помогает сканированию (Flight / «тонкий» режим), а не синтаксис ради синтаксиса.
482. Сохранить **KISS**: не перегружать PFD текстом условий и декорациями по умолчанию.
493. Подготовить **контракт данных** (subgraph / JSON / MCP), чтобы рёбра и при необходимости узлы несли **тип связи** и опциональные подсказки для hover / drill-down.
50
51## Не-цели (на первом этапе)
52
53- Полная отрисовка **всего** CFG Roslyn на мини-карте.
54- Дублирование **тела** условия `if (…)` постоянной подписью на PFD.
55- Визуализация каждого локального присваивания и мелкого шума внутри веток.
56
57---
58
59## Принципы отображения (чтобы не «засрать» интерфейс)
60
611. **Семантическая компрессия**
62 По умолчанию: **иконка ветвления** (ромб / «?») или **тонкая развилка** на ребре, **без** длинного текста предиката — для **`if` и для `switch` / pattern matching** одинаково (см. [§ «Switch…»](#adr0053-switch)).
63 **Деталь условия** — по задержке взгляда / hover / отдельный слой (Skia tooltip), опционально.
64
652. **Только значимые «внешние» точки маршрута**
66 В приоритете: вызовы **других методов** и тяжёлые операции, которые пользователь ищет глазами. Локальный шум (`i++`, пустой `return` без смысла для карты) — не поднимать на граф без явной политики.
67
683. **Тонкие линии и метафоры**
69 - **Цикл** (`for` / `while`): см. [§ «Циклы: петля на ребре»](#adr0053-loop-edge).
70 - **`try` / `catch`**: позже — стиль «защищённого» участка (например «зонтик» / нестабильный контур узла), когда будет согласована лексика.
71
724. **Вертикальный «полётный план»**
73 Узлы значимых шагов сверху вниз, ветвления — **в сторону**, без блоков «как в IDE» на весь экран.
74
75<a id="adr0053-loop-edge"></a>
76
77### Циклы (for / while): петля на ребре
78
79**Не рисуем** отдельный «граф цикла» как на классической блок-схеме (ромб + стрелка назад, дублирующая узлы). **Рисуем петлю на линии связи** к значимому шагу внутри метода (например вызов `B()` в теле `for`/`while`): линия к узлу **не прямая**, а делает **изящный виток** вокруг направления к этому узлу — по духу как **орбита ожидания / holding** на навигационном дисплее, а не как «ещё один прямоугольник».
80
81Смысл для пользователя: **сразу видно** — «этот вызов здесь крутится по кругу», без чтения кода в Forward и без разворачивания итераций на карте.
82
83**Рендер (направление):** тонкие **неоновые** линии на тёмном фоне PFD, **сглаженные кривые** (Bezier / кубические сплайны в Skia), чтобы визуально быть частью **кокпитного** слоя, а не старой блок-схемы. Центральная линия — условный «основной поток» текущего метода `A()`; виток — **на ребре** к зависимому шагу, не отдельный декоративный слой поверх всего графа.
84
85*Срез реализации (2026-05):* `Views/SkiaKit/Graph/SkiaGraphSceneDrawing.Edges.cs` — для kind с `loop` одна пунктирная кубическая кривая к цели с увеличенным поперечным выносом (в т.ч. когда хорда почти параллельна главному потоку); отдельный эллипс вокруг целевого узла для этого не рисуется. Пунктирный овал группы цикла — `SkiaGraphSceneDrawing.LoopRegions.cs`, только если в `LoopGroupId` **не меньше двух узлов**. При высокой плотности графа пайплайн карты задаёт «компактный» режим (`GraphLayoutEngineOptions.IsDense` + больший минимальный viewport по высоте).
86
87**Опционально (если анализ позволяет эвристику):** «тяжесть» цикла или ожидаемое число итераций — **плотность** витка (ближе спираль, чуть ярче/толще линия и т.п.), без обещания точного `n` на PFD.
88
89**Контракт:** ребро с семантикой цикла несёт признак вроде `Loop` / `LoopCall` + опционально метаданные для стиля (см. таблицу ниже); координаты витка — **производные от layout** (StarGraph / force / иной движок), чтобы кривые не перекрывали соседние узлы — отдельная задача подбора контрольных точек Bezier.
90
91<a id="adr0053-switch"></a>
92
93### Switch, `case` и сопоставление с образцом
94
95**Включаем** в ту же семантику, что и `if` / `else`: это ветвление потока, не «особый случай вне карты». **Не разворачиваем** все ветки как полную таблицу на PFD.
96
97- **Немного значимых исходов (ориентир 2–4):** веер рёбер от одной точки развилки к значимым шагам; подписи **`case` / guard** — опционально, по hover или второму слою.
98- **Много веток или одни «пустые» case:** **сжатие** — один узел/иконка многонаправленного ветвления; наружу только вызовы с смыслом; прочее — агрегат («остальные ветки» / `default`) или скрыто до drill-down. В коде: **glance** — все `MultiBranch` срезаются; **normal** — при веере ≥4 от одной развилки остаются 3 приоритетных исхода + ромб `+N` (`CodeNavigationMapControlFlowDeclutter`); **inspect** — без сжатия.
99- **Pattern matching** (`switch` expression, `when`): не дублировать длинные выражения на постоянной подписи; тот же принцип, что для предиката `if`.
100
101Связь с контрактом: тип вроде **`MultiBranch`** / **`ConditionalCall`** на ребре от общего предка развилки к шагу; детали ветки — в опциональных метаданных.
102
103---
104
105## Данные и контракт (направление)
106
107Существующие модели subgraph (`CodeNavigationMapSubgraphNode`, `CodeNavigationMapSubgraphEdge`) расширяются осмысленно, например:
108
109| Идея | Назначение |
110|------|------------|
111| Тип ребра | `Call`, `ConditionalCall`, `Merge`, `MultiBranch` (несколько исходов: `switch`, цепочка `if`, pattern matching), `Loop` / `LoopCall` (петля на ребре к шагу внутри цикла; не путать с низкоуровневым `LoopBack` CFG, если понадобится отдельно) |
112| Ветви `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](0039-workspace-navigation-affordances.md) для `code_navigation`); `custom` — `condition_branch_positive` / `condition_branch_negative` на `[code_navigation_map]`. |
113| Краткая метка | Опционально; не дублировать полный текст `cond`. |
114| Деталь условия | Опционально, для tooltip / второго слоя; может быть сжата или отложена. |
115
116Точные имена полей и JSON — **зафиксировать** после прототипа генератора (Roslyn / Control Flow Analysis + фильтрация вызовов).
117
118Источник анализа в стеке: **Roslyn** (`ControlFlowAnalysis` и привязка вызовов к веткам), без обязательной привязки к одному только текстовому grep.
119
120---
121
122## Договорённости (черновик)
123
124### Предикат на ребре vs только иконка
125
126Граница «показать **краткий предикат**» и «**только иконка**» задаётся **настройкой пользователя** (приложение / workspace — конкретный ключ и merge с bundle зафиксировать при внедрении), **а не только** жёсткой привязкой к режиму UI вроде Flight.
127
128Контур **агента** (MCP, запрос контекста / subgraph навигации) опирается на **тот же** уровень детализации: в продуктовом смысле агент — тоже **пользователь** этого представления и не живёт в отдельном скрытом режиме в обход настроек, кроме как при **явном** параметре в контракте вызова (override на один запрос).
129
130### Тип карты: `controlFlow` vs «классическая»
131
132Отдельный пресет **UiMode** (например только Flight) **под одну** CF-aware карту не обязателен: **какой вид семантической карты строить** — выбор **пользователя**, ортогонально режиму кабины. Режим **Flight** остаётся полигоном раскладки PFD; **уровень/тип** карты задаётся отдельно.
133
134Черновик формы в TOML (значения и merge с bundle / workspace — при внедрении):
135
136```toml
137[semantic_map]
138level = "controlFlow" # поток управления в методе (CF-aware subgraph)
139# level = "file" # классическая зависимостная / файловый срез (как baseline карты намерений)
140```
141
142Тот же переключатель должен быть **достижим для агента** (поле в MCP / эхо в subgraph-запросе), без отдельной скрытой семантики.
143
144### Версионирование subgraph JSON (MCP / CLI)
145
146**Обратная совместимость при добавлении полей пока не гарантируется:** у продукта **ещё нет пользователей**, JSON subgraph и MCP/CLI могут **менять форму синхронно с кодом** без политики миграции. Когда появятся стабильные потребители вне репозитория (интеграции, долгоживущий контракт агента) — ввести явное **версионирование** схемы и правила совместимости **отдельным решением** (ADR / дополнение к MCP-PROTOCOL).
147
148---
149
150## Следующие шаги (черновик)
151
1521. Зафиксировать минимальный набор **kind** для рёбер и правила фильтрации вызовов.
1532. Прототип: один метод под курсором → упрощённый поток → тот же JSON, который уже потребляет карта намерений на PFD.
1543. Рендер: стили линий и узлов на PFD / Skia без обязательного текста условия на постоянной подписи.
155
156## Backlog intent (добавить поэтапно)
157
158Чтобы карта сохраняла intent и не превращалась в полный CFG, расширения вводим порциями:
159
1601. **Early/abrupt exits**: `ThrowExit`, `Break`, `Continue`.
1612. **Асинхронные границы**: `AwaitBoundary` (точка паузы/возобновления потока).
1623. **Короткое замыкание**: `ShortCircuit` для `&&` / `||` в guard-условиях.
1634. **Pattern guard**: явная семантика для `when` в pattern matching/switch.
1645. **Исключения**: укрупнённый `ExceptionFlow` для `catch`/`finally` без разворота полного exception-CFG.
1656. **Итераторы**: `YieldExit` (`yield return` / `yield break`) как отдельный тип выхода.
1667. **Политика детализации**: разделить «операторный шум» и «внешние шаги» через declutter policy (например, helper-вызовы внутри аргументов).
167
168*(Ниже можно добавлять разделы: примеры JSON, скринмоки, ограничения производительности, ссылки на внешние обсуждения.)*
169
View only · write via MCP/CIDE