ADR 0156: Страница Correspondence (MFD) и Reverse Anchors через CodeAnchor¶
Статус: Accepted
Дата: 2026-05-28
Резюме¶
- Correspondence Surface (CRS) — отдельная страница оболочки Mfd (
MfdShellPage.Correspondence), по образцу ERS (0023): один экран вторичного контура только для слоёв correspondence L0–L4 (0155), без смешения со списком related-файлов. - PFD остаётся компактным: бейдж слоёв + переход на CRS; детали — на MFD.
- Reverse Anchors — направление док → код: какие ADR/KB явно привязаны к текущему файлу/диапазону через CodeAnchor (канон из 0128, не дублируя второй словарь координат).
- L1 path map (0061) — forward (путь → ADR); reverse — дополнение, не замена.
Связанные ADR¶
| ADR | Роль |
|---|---|
| 0155 | Слои L0–L4, каталог kinds, feature registry |
| 0061 | L1: [workspace.adr.map] |
| 0023 | Образец: dedicated MFD page + refresh + MCP show_*_page |
| 0021 | PFD = внимание; MFD = вторичный контур |
| 0039 | Якорь карты; PFD граф/CF; MFD RelatedFiles |
| 0128 | CodeAnchor / AttachmentAnchor — file + line range + member |
| 0137 | L4 discourse ↔ code |
| 0029 | TOML для экземпляров L1; не онтология kinds |
| 0157 | Magic Link cide:// из браузера → IDE |
Контекст¶
После MVP correspondence (0061, 0155) на PFD/MFD появились:
- строка Feature (L1′),
- строка ADR (L1),
- бейдж Correspondence: L0 · L1′ · …,
- страница RelatedFiles со смешанным контентом (related-код + те же строки correspondence).
Это работает как индикатор, но:
- Нет «кабины» для correspondence — как ERS для окружения: один экран, куда сводятся лампы/слои, refresh, действия.
- Forward-only: map говорит «для этого файла читай ADR X», но не «ADR Y ссылается на этот файл в §Implementation».
- Дублирование UX: correspondence размазан между PFD,
RelatedFilesи Markdown Preview.
Идея Reverse Anchors: при открытии Foo.cs показать не только ADR по префиксу пути, но и документы, в тексте которых есть устойчивый якорь на Foo.cs (или на символ в нём).
Решение¶
1. Correspondence Surface (CRS) — страница Mfd¶
1.1. Размещение¶
| Элемент | Решение |
|---|---|
| Enum | MfdShellPage.Correspondence (новое значение после Editor) — [F:Models/MfdShellPage.cs] |
| View | CorrespondenceMfdPageView — DataContext = NavigationMap (тот же WorkspaceNavigationMapViewModel, что карта PFD) — [F:Views/CorrespondenceMfdPageView.axaml] |
| Allowance | всегда true (как HybridIndex, RelatedFiles) |
Порядок в PageOrder |
после RelatedFiles, перед HybridIndex |
Паттерн как у ERS (0023):
show_correspondence_page/close_correspondence_page(MCP + intent-catalog),- при входе на страницу —
ScheduleWorkspaceNavigationMapRefresh()(актуальный якорь), EnsureMfdShellSurfaceForLayout()при навигации.
1.2. Содержимое CRS (v1)¶
Одна прокручиваемая колонка (не TabControl):
┌─ Correspondence · workspace ─────────────────┐
│ Якорь: Features/Chat/ChatPanelViewModel.cs │
│ Correspondence: L0 · L1′ · L1 · L2 │ ← tooltip = расшифровка слоёв
├──────────────────────────────────────────────┤
│ L1′ Feature: intercom.chat (+ docs) [open] │
│ L1 ADR: 0119 (+3) [open] │
│ Docs coverage / templates hint │
├──────────────────────────────────────────────┤
│ Reverse anchors (preview) │
│ · ADR 0155 — «…Features/Chat…» (line ~42) │
│ · ADR 0061 — link to path in map table │
├──────────────────────────────────────────────┤
│ Linked ADRs (forward, полный список) │
│ · docs/adr/0119-… │
│ · docs/adr/0150-… │
└──────────────────────────────────────────────┘
Не входит в CRS v1: related-файлы Roslyn (остаются на RelatedFiles); control-flow граф (PFD).
1.3. PFD после CRS¶
| Было | Станет |
|---|---|
| Feature / ADR / layers / docs на PFD и в RelatedFiles | PFD: краткий бейдж слоёв + клик → CRS; опционально одна строка «ADR: … (+n)» |
| RelatedFiles: дубли correspondence | RelatedFiles: только related list + якорь |
Клик по бейджу слоёв или ADR: на PFD → MfdShellPage.Correspondence (не сразу Markdown Preview).
1.4. Данные и refresh¶
- Источник истины для UI —
WorkspaceNavigationMapViewModel(уже есть refresh pipeline). - Новые поля VM (направление реализации):
WorkspaceCorrespondenceDocPaths— полный forward-список ADR;WorkspaceReverseAnchorItems— коллекция для reverse-секции;ShowCorrespondencePageCommand— navigate CRS (show_correspondence_page).- Слои L0–L4: логика как в
CorrespondenceLayersProjection+ каталогwire/correspondence/correspondence-kinds.v1.json.
2. Reverse Anchors и CodeAnchor¶
2.1. Термины¶
| Термин | Направление | Смысл |
|---|---|---|
| Forward map (L1) | код → док | Префикс пути → ADR (0061) |
| Reverse anchor | док → код | Документ упоминает участок кода устойчивым якорем |
| CodeAnchor | — | Минимальный контракт привязки: repoRelativeFile, опционально lineStart/lineEnd, опционально memberKey / syntaxScope |
CodeAnchor для correspondence согласуется с полями AttachmentAnchor (0128) там, где есть file + диапазон; Intercom-специфика (ordinal, excerpt в prose) остаётся в L4.
2.2. Источники reverse (приоритет)¶
| Источник | provenance |
v1 |
|---|---|---|
| Explicit registry | workspace.correspondence.anchors в .cascade/workspace.toml |
опционально, позже |
| Markdown scan | doc_body |
да — эвристики по телу ADR/KB |
| Forward map inverse | adr_map |
нет — map не хранит обратных ссылок на строки |
| Intercom event log | discourse (L4) |
отдельно 0137 |
2.3. Эвристики markdown-scan (v1)¶
По каждому forward-linked ADR (и при необходимости KB из feature.docs):
- Inline
`Features/Chat/Foo.cs`и`src/...`(repo-relative, нормализация/). - Markdown-ссылки
[label](path/to/file.cs)— неhttp. - Упоминания
file.cs:10-20,lines 10–20,L10-L20рядом с путём в той же строке/абзаце. - Ссылки на ADR не считаются code anchor.
Сопоставление с якорем карты: файл якоря = navigationPath; диапазон — optional filter (если в доке указаны строки вне файла — anchor всё равно показывается с пометкой «range»).
Не v1: полный Roslyn resolve memberKey в ADR; structural picker.
2.4. Модель записи (IDE / JSON)¶
{
"docPath": "docs/adr/0155-documentation-code-correspondence-and-architectural-drift.md",
"docTitle": "ADR 0155",
"kind": "documents",
"codeAnchor": {
"file": "Features/WorkspaceNavigation/Application/WorkspaceCorrespondenceResolver.cs",
"lineStart": 19,
"lineEnd": null,
"memberKey": null
},
"excerpt": "…WorkspaceCorrespondenceResolver.Resolve…",
"provenance": "doc_body"
}
Канонический kind — из 0155 §6 (documents).
2.5. Explicit registry (v2, TOML)¶
Опциональная секция в .cascade/workspace.toml для ручных reverse без парсинга:
[[workspace.correspondence.code_anchors]]
doc = "docs/adr/0155-documentation-code-correspondence-and-architectural-drift.md"
file = "Features/WorkspaceNavigation/Presentation/WorkspaceNavigationMapViewModel.cs"
line_start = 470
line_end = 490
kind = "documents"
Merge с markdown-scan: explicit перекрывает эвристику для того же (doc, file).
2.6. Как устроены Anchors: та же модель, три поверхности записи¶
Ответ на вопрос «как в чате [F,M,L,S] или иначе»: семантика та же, что в 0128 §5.1 (F | M | L | S → поля AttachmentAnchor). В correspondence это называется CodeAnchor — тот же shape без Intercom-полей (messageOrdinal, chip id в ленте).
| Ось | В чате | В ADR / correspondence |
|---|---|---|
| File | F: или active file |
F: обязателен (док не привязан к «текущему файлу») |
| Member | M: |
M: — re-resolve у читателя ADR |
| Lines | L: fallback |
L: — снимок в тексте; при клике из CRS — hint, не контракт навсегда |
| Scope | S: |
S: — «второй for в Run», как в чате |
Не дублируем отдельный «ADR-алфавит»: bracket-parser (0131 / BracketCodeReferenceParser) переиспользуется для тела ADR и sidecar.
Три способа разместить якоря (рекомендуемый стек)¶
Точность / устойчивость ──────────────────────────────────────►
(1) inline bracket в ADR (2) sidecar у ADR (3) workspace TOML
лучший для людей bulk / codegen repo-wide overlay
| # | Где | Формат | Когда использовать |
|---|---|---|---|
| A | В тексте ADR (inline) | L1: [F:Features/Chat/Foo.cs M:RunAsync] · L2 в front-matter: code_refs = ["F:…; M:…"] |
Норматив: «реализация — вот этот метод»; читается в GitHub без IDE |
| B | Файл-спутник рядом с ADR | docs/adr/0156-….code-anchors.toml (или .json) — массив CodeAnchor + doc implicit |
Много ссылок, генерация из Roslyn; не засорять narrative |
| C | Workspace | [[workspace.correspondence.code_anchors]] в .cascade/workspace.toml |
Связь без правки ADR (аудит, миграция map), feature-level glue |
Merge при индексации (reverse): explicit(C) > explicit(B) > parsed(A) > markdown-эвристики (backticks, :line) из §2.3.
A. Inline bracket в ADR (предпочтительно для авторов)¶
Пример в теле ADR:
Реализация refresh — [F:Features/WorkspaceNavigation/Presentation/WorkspaceNavigationMapViewModel.Refresh.cs M:RunWorkspaceNavigationMapRefreshAsync].
Правила как в 0128:
- Парсить только вне fenced code blocks (иначе примеры кода ломают индекс).
@fileне используем — только[…].
Сосуществование с Markdown ([] / ())¶
Markdown-ссылка в CommonMark — только пара [текст](url): после ] сразу (допускается пробел) идёт (…).
| В ADR | Markdown-рендер (GitHub / Markdig) | Code anchor parse |
|---|---|---|
[M:Run] |
Обычный текст в квадратных скобках, не ссылка | да — префикс M: |
[F:Features/Chat/Foo.cs M:Bar] |
то же | да |
[ADR 0061](docs/adr/0061-….md) |
кликабельная MD-ссылка | нет — нет осей F:/M:/L:/S: внутри [] |
[M:Run](docs/adr/foo.md) |
MD-ссылка с подписью M:Run |
не использовать — URL в () перехватывает markdown |
Итого: не ломается, потому что code-bracket не использует ](…) сразу после ]. ADR-перекрёстные ссылки [title](path) остаются как сейчас; code anchor — отдельный вид [F:… M:…].
Дисциплина авторов: не писать [M:Foo](relative.md) — либо markdown-ссылка на док, либо code anchor [F:… M:Foo] без скобок-URL.
Парсер: BracketCodeReferenceParser срабатывает только если внутри […] есть ось F: / M: / L: / S: (как в 0128); сегменты внутри inline-code и fenced code block (ограждение из трёх grave-accent на отдельной строке) не сканируются. В narrative не вставлять непарные grave-accent-тройки — иначе ломается MarkdownProseSegments.
Опционально для параноидной совместимости с чужими рендерерами: обернуть в `code` — тогда в prose не парсить (как fenced); не рекомендуем как канон, только для примеров в списках.
- Для агента в front-matter (опционально, v2):
---
code_refs:
- "F:Features/Chat/ChatPanelViewModel.cs; M:TryCommitCockpitCommandLineAsync"
---
YAML front-matter — не канон wire; канон после parse — CodeAnchor JSON / C# record.
B. Sidecar (файл-спутник)¶
Соглашение об имени (предложение):
docs/adr/0156-correspondence-mfd-surface-and-reverse-code-anchors.md
docs/adr/0156-correspondence-mfd-surface-and-reverse-code-anchors.code-anchors.toml
# schema: correspondence.code-anchors.v1
[[anchor]]
bracket = "F:Features/WorkspaceNavigation/Application/WorkspaceCorrespondenceResolver.cs; M:Resolve"
kind = "documents"
note = "§2.1 entry point"
[[anchor]]
file = "Features/WorkspaceNavigation/Presentation/WorkspaceNavigationMapViewModel.cs"
line_start = 470
line_end = 490
kind = "documents"
Поле bracket — если задано, parse → те же поля, что у file/line_*. Sidecar не обязателен, если всё уже в prose.
Плюс: CI может валидировать anchors без парсинга всего Markdown. Минус: второй файл на ADR — держать в sync дисциплиной или codegen.
C. Workspace TOML (уже §2.5)¶
Для связей «этот ADR ↔ этот файл» без указания member/строк — достаточно doc + file. Для точных участков — дублировать bracket в bracket = "F:…; M:…; S:…" или line_start/line_end.
Что не делаем¶
| Идея | Почему |
|---|---|
Отдельные префиксы D: doc-only |
F/M/L/S уже покрывают; doc — контейнер reverse-записи |
| Только эвристики без bracket | Остаются как bootstrap v1, не как долгосрочный канон |
Дублировать полный AttachmentAnchor wire в ADR |
Event log — L4; ADR — L1 documents |
Реализация (направление)¶
- v1: scan §2.3 + explicit TOML (C).
- v1.1:
BracketCodeReferenceParserпо телу ADR (A) + optional sidecar (B). - CRS UI: показывать
displayLabelкак в чате (Run › for (2)), клик →intercom.reveal_attachment-совместимый reveal (open + range), без открытия Intercom.
3. MCP и команды¶
Реализация CRS и reverse (v1): навигация — [F:Features/WorkspaceNavigation/Presentation/WorkspaceNavigationMapViewModel.Correspondence.cs M:ShowCorrespondencePage]; reverse scan — [F:Features/WorkspaceNavigation/Application/DocReverseAnchorResolver.cs M:Resolve]; JSON для агента — [F:Features/WorkspaceNavigation/Application/WorkspaceCorrespondenceContextBuilder.cs M:BuildJson]; bracket в prose — [F:Services/Intercom/BracketCodeReferenceParser.cs M:EnumerateInProse].
| Команда | Поведение |
|---|---|
show_correspondence_page |
TryNavigateToMfdShellPage(Correspondence) + expand Mfd |
close_correspondence_page |
первая другая разрешённая страница |
open_workspace_adr_correspondence |
v1: открыть первый ADR в preview; v1.1: фокус CRS + highlight строки в списке |
get_correspondence_context (новая, v1.1) |
JSON: layers, feature, forward docs[], reverse anchors[] для file? |
Не дублировать get_code_navigation_context — другой слой (L2).
4. Политика (IOP)¶
| Уровень | Reverse anchors |
|---|---|
| Inform | список на CRS, tooltip excerpt |
| Advisory | агент: «ADR 0155 ссылается на этот метод» |
| Verify | не v1 — не блокировать merge из-за «ADR не упоминает новый файл» |
Эвристики markdown могут ложноположительно матчить путь в примере кода внутри ADR — показывать provenance и не трактовать как доказательство.
Альтернативы (отклонены)¶
| Альтернатива | Почему нет |
|---|---|
| Оставить correspondence только на RelatedFiles | Смешение L1/L2; нет «кабины» как ERS |
Отдельный CorrespondenceViewModel |
Дублирует refresh якоря карты; достаточно расширить NavigationMap |
| Второй граф «док ↔ код» в TOML | Дорого, drift; reverse = scan + optional explicit |
Новый wire-тип CodeAnchor без связи с 0128 |
Два контракта координат |
| Reverse только через L1 map | Map не знает строки и member в ADR |
Дорожная карта¶
| Этап | Работа | Критерий |
|---|---|---|
| C1 | ADR 0156 Accepted | Этот документ |
| C2 | MfdShellPage.Correspondence + CorrespondenceMfdPageView + MCP show/close |
Навигация как ERS |
| C3 | CRS UI: layers, feature, forward ADR list | PFD slim; RelatedFiles без дубля correspondence |
| C4 | DocReverseAnchorResolver + markdown-scan v1 |
Секция reverse на CRS; тесты на sample ADR |
| C5 | get_correspondence_context |
Done — JSON layers/forward/reverse |
| C6 | TOML [[workspace.correspondence.code_anchors]] |
Done — merge explicit > scan |
C2–C3 не блокируются C4 — reverse-секция может быть пустой с подписью «scan pending».
Открытые вопросы¶
- Имя страницы в UI: Correspondence vs Doc correspondence vs аббревиатура CRS в chrome only.
- Индексировать reverse при refresh (дорого) vs lazy при открытии CRS.
- Включать ли KB (
knowledge/) в scan наравне сdocs/adr/. - Связь reverse anchor click → open doc + scroll to line (Markdown Preview) vs side-by-side.
- Codegen: общий C# record
CodeAnchorиз schema, shared с Intercom wire subset. - Sidecar: обязательный
.code-anchors.tomlдля Accepted ADR vs только inline bracket. - Front-matter
code_refs— поддерживать в IDE preview или только body parse.
Последствия¶
- Плюс: correspondence читается как подсистема с одной точкой входа (CRS), симметрично ERS.
- Плюс: reverse закрывает вопрос «какие ADR реально говорят про этот файл», не только «какой ADR назначен папке».
- Плюс: переиспользование CodeAnchor / 0128 снижает drift координат.
- Минус: markdown-scan требует тестов и оговорок про false positives.
- Минус: ещё одна страница Mfd — нужен discoverability (PFD badge, palette, MCP).
Статус реализации¶
| Компонент | Статус |
|---|---|
| L0–L4 badge + каталог kinds | Implemented (частично, до CRS) |
[workspace.adr.map] + feature registry |
Implemented |
MfdShellPage.Correspondence + CRS UI |
Implemented (C2–C3) |
| Reverse anchors scan + explicit TOML | Implemented (C4, C6) |
get_correspondence_context |
Implemented (C5) |