Перейти к содержанию

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 и условия перехода между ними на навигационном дисплее, а не распечатка всех процедурных страниц.


Цели

  1. Визуализировать намерение потока управления внутри выбранного метода там, где это помогает сканированию (Flight / «тонкий» режим), а не синтаксис ради синтаксиса.
  2. Сохранить KISS: не перегружать PFD текстом условий и декорациями по умолчанию.
  3. Подготовить контракт данных (subgraph / JSON / MCP), чтобы рёбра и при необходимости узлы несли тип связи и опциональные подсказки для hover / drill-down.

Не-цели (на первом этапе)

  • Полная отрисовка всего CFG Roslyn на мини-карте.
  • Дублирование тела условия if (…) постоянной подписью на PFD.
  • Визуализация каждого локального присваивания и мелкого шума внутри веток.

Принципы отображения (чтобы не «засрать» интерфейс)

  1. Семантическая компрессия
    По умолчанию: иконка ветвления (ромб / «?») или тонкая развилка на ребре, без длинного текста предиката — для if и для switch / pattern matching одинаково (см. § «Switch…»).
    Деталь условия — по задержке взгляда / hover / отдельный слой (Skia tooltip), опционально.

  2. Только значимые «внешние» точки маршрута
    В приоритете: вызовы других методов и тяжёлые операции, которые пользователь ищет глазами. Локальный шум (i++, пустой return без смысла для карты) — не поднимать на граф без явной политики.

  3. Тонкие линии и метафоры

  4. Цикл (for / while): см. § «Циклы: петля на ребре».
  5. try / catch: позже — стиль «защищённого» участка (например «зонтик» / нестабильный контур узла), когда будет согласована лексика.

  6. Вертикальный «полётный план»
    Узлы значимых шагов сверху вниз, ветвления — в сторону, без блоков «как в 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 (switch expression, 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).


Следующие шаги (черновик)

  1. Зафиксировать минимальный набор kind для рёбер и правила фильтрации вызовов.
  2. Прототип: один метод под курсором → упрощённый поток → тот же JSON, который уже потребляет карта намерений на PFD.
  3. Рендер: стили линий и узлов на PFD / Skia без обязательного текста условия на постоянной подписи.

Backlog intent (добавить поэтапно)

Чтобы карта сохраняла intent и не превращалась в полный CFG, расширения вводим порциями:

  1. Early/abrupt exits: ThrowExit, Break, Continue.
  2. Асинхронные границы: AwaitBoundary (точка паузы/возобновления потока).
  3. Короткое замыкание: ShortCircuit для && / || в guard-условиях.
  4. Pattern guard: явная семантика для when в pattern matching/switch.
  5. Исключения: укрупнённый ExceptionFlow для catch/finally без разворота полного exception-CFG.
  6. Итераторы: YieldExit (yield return / yield break) как отдельный тип выхода.
  7. Политика детализации: разделить «операторный шум» и «внешние шаги» через declutter policy (например, helper-вызовы внутри аргументов).

(Ниже можно добавлять разделы: примеры JSON, скринмоки, ограничения производительности, ссылки на внешние обсуждения.)