| 1 | # ADR 0137: Intercom — соответствие диапазона сообщений (gutter) и якоря кода |
| 2 | |
| 3 | **Статус:** Accepted · In progress |
| 4 | **Дата:** 2026-05-20 |
| 5 | |
| 6 | ## Связанные ADR |
| 7 | |
| 8 | | ADR | Роль | |
| 9 | |-----|------| |
| 10 | | [0136](0136-intercom-feed-gutter-and-slash-namespace.md) | Gutter 1-based в ветке; `/intercom message select`; MCP `ordinal` | |
| 11 | | [0128](0128-intercom-attachment-anchors-and-code-references.md) | `AttachmentAnchor`, reveal, re-resolve; attach ≠ editor line action | |
| 12 | | [0124](0124-slash-parametric-editor-line-commands.md) | Синтаксис `L`, `L R`, `L:R` для строк редактора | |
| 13 | | [0131](0131-editor-slash-select-code-by-bracket-reference.md) | Bracket → select/reveal в буфере | |
| 14 | | [0130](0130-editor-agent-range-reveal-without-selection.md) | Reveal диапазона без selection | |
| 15 | | [0133](0133-commander-cockpit-shared-attention-model-and-instrument-deck.md) | Общая модель внимания Intercom ↔ Editor | |
| 16 | | [0134](0134-intercom-message-prepare-pipeline-v1.md) | Wire `attachments[]` @ commit; проекция ленты | |
| 17 | | [0045](0045-agent-chat-persistence-event-log-and-projections.md) | Event log — source of truth; проекции derived | |
| 18 | | [0135](0135-intercom-attach-symbol-cache-and-hci-sidecar.md) | HCI `index_dir`; colocated SQLite sidecars | |
| 19 | | [0111](0111-editor-linenumber-linerange-value-objects.md) | `LineRange` в домене | |
| 20 | | [0120](0120-primary-work-surface-intercom-or-editor.md) | Forward: Intercom или Editor | |
| 21 | |
| 22 | ## Резюме |
| 23 | |
| 24 | **Мост между двумя шкалами номеров** (traceability Intercom ↔ Editor): |
| 25 | |
| 26 | | Шкала | Где | Смысл | |
| 27 | |-------|-----|--------| |
| 28 | | **Message ordinal** | Gutter detail-ленты | 1-based в **активной ветке** ([0136](0136-intercom-feed-gutter-and-slash-namespace.md)) | |
| 29 | | **Code ref** | Редактор / attach | `AttachmentAnchor` + `(file, LineRange)` ([0128](0128-intercom-attachment-anchors-and-code-references.md)) | |
| 30 | |
| 31 | - **По умолчанию:** авто-infer из `attachments[]` каждого сообщения (экономия внимания IOP-оператора). |
| 32 | - **Явный relate:** только когда **диапазон сообщений** (#3–#5) связывается с кодом, хотя attach есть не в каждом сообщении диапазона. |
| 33 | - **Find:** обратный запрос из редактора → gutter-номера в ветке. |
| 34 | - **Хранение:** explicit relate → **событие event log**; быстрый поиск → **derived index** (SQLite sidecar рядом с HCI, пересборка при re-resolve drift). |
| 35 | |
| 36 | **Не цель:** граф всех связей; разрозненные ordinals `#3` + `#7` в MVP; отдельные `c:…` в IntentMelody. |
| 37 | |
| 38 | --- |
| 39 | |
| 40 | ## Контекст |
| 41 | |
| 42 | `/intercom message select 3:5` — выбор **строк ленты**. Correspondence — «#3–#5 **относятся к** этому коду» и «строки 42–50 в файле → какие сообщения в ветке». |
| 43 | |
| 44 | Без модели остаётся только forward navigation (chip → reveal). Commander в кокпите ([0133](0133-commander-cockpit-shared-attention-model-and-instrument-deck.md)) теряет сквозную прослеживаемость миссии ([0045](0045-agent-chat-persistence-event-log-and-projections.md)). |
| 45 | |
| 46 | --- |
| 47 | |
| 48 | ## Принятые решения (закрытие Q1–Q5) |
| 49 | |
| 50 | | # | Вопрос | Решение | |
| 51 | |---|--------|---------| |
| 52 | | **Q1** | Ручной relate vs авто-infer? | **100% авто-infer по умолчанию** из `attachments[]` @ send ([0134](0134-intercom-message-prepare-pipeline-v1.md)). **Ручной relate** — оверрайд/дополнение для **contiguous message range**, когда контекстный блок (#3 интент, #4 лог, #5 аппрув) относится к одной задаче, а attach только в #4. | |
| 53 | | **Q2** | Contiguous vs разрозненные? | **MVP: только contiguous** (`n`, `n m`, `n:m`) — тот же синтаксис, что [0136](0136-intercom-feed-gutter-and-slash-namespace.md). Разрозненные `#3` + `#7` — не MVP (другой интент или под-тред). | |
| 54 | | **Q3** | Event log vs sidecar vs index? | **Source of truth:** append-only **event log** ([0045](0045-agent-chat-persistence-event-log-and-projections.md)) — событие `message_range_related` с полным **`AttachmentAnchor`** ([0128](0128-intercom-attachment-anchors-and-code-references.md)), не с «голыми» номерами строк. **Derived:** SQLite sidecar @ HCI ([0135](0135-intercom-attach-symbol-cache-and-hci-sidecar.md)) — ускоритель: `(thread_id, file, member_key?)` и опционально line-overlap после **re-resolve**; пересборка при load/reindex/drift. | |
| 55 | | **Q4** | IntentMelody `c:…`? | **Нет.** Только **slash + MCP**; Melody оркеструет, не задаёт синтаксис связей ([0121](0121-intent-oriented-programming-paradigm.md)). | |
| 56 | | **Q5** | Relate на draft? | **Нет в v1** — только committed messages (как wire attach). | |
| 57 | | **Q6** | Cross-file find? | **Да** — по всем attachments + relate-событиям **ветки**. | |
| 58 | | **Q7** | Federated ([0132](0132-intercom-federated-transport-and-multi-client-boundary.md)) | Wire attachments общие; relate-события в логе сессии — общие; derived index — локальный кэш клиента. | |
| 59 | |
| 60 | --- |
| 61 | |
| 62 | ## Решение |
| 63 | |
| 64 | ### 1. Термины |
| 65 | |
| 66 | | Термин | Значение | |
| 67 | |--------|----------| |
| 68 | | **Message range** | Contiguous gutter ordinals в активной ветке | |
| 69 | | **Code ref** | `AttachmentAnchor` / `(file, LineRange)` — канон [0128](0128-intercom-attachment-anchors-and-code-references.md) | |
| 70 | | **Inferred** | Автоматически из `attachments[]` сообщения | |
| 71 | | **Explicit** | Событие `message_range_related` в NDJSON | |
| 72 | |
| 73 | ### 1.1 Идентичность кода (не строки) |
| 74 | |
| 75 | **Номера строк — не ID** (тот же принцип, что [0128 §3](0128-intercom-attachment-anchors-and-code-references.md)): drift ветки/редактора ломает привязку «L42 навсегда». |
| 76 | |
| 77 | | Слой | Что хранится / сравнивается | |
| 78 | |------|-----------------------------| |
| 79 | | **Relate (explicit)** | `code_ref`: полный `AttachmentAnchor` @ declare (`memberKey`, `syntaxScope`, `attachmentShape`, `excerpt`, снимок `lineStart`/`lineEnd` — **hint**) | |
| 80 | | **Inferred** | Из `attachments[]` каждого сообщения — те же поля якоря | |
| 81 | | **Find / relate (ввод)** | `selection` / `[M:…]` / `anchor:<id>` → resolve → якорь; `L:10-20` — только **M0** positional fallback в открытом файле (как slash editor line), не канон для event log | |
| 82 | | **Match** | 1) тот же `file` + `memberKey` (если есть у обеих сторон); 2) иначе overlap **re-resolved** `LineRange`; 3) `excerpt` — для stale UI, не для ключа индекса | |
| 83 | |
| 84 | **Запрещено в v1:** событие или sidecar-запись вида «только `(file, line)` без shape/member» как единственная правда связи. |
| 85 | |
| 86 | ### 2. Команды (ортогональны `select`) |
| 87 | |
| 88 | | Режим | Slash | MCP | |
| 89 | |-------|-------|-----| |
| 90 | | Select | `/intercom message select <range>` | `chat_select_message` (`ordinal`) | |
| 91 | | Find | `/intercom message find <code-ref>` | `intercom.messages_for_code` | |
| 92 | | Relate | `/intercom message <range> relate <code-ref>` | `intercom.message_relate` | |
| 93 | |
| 94 | **Code-ref tail:** `selection` · `L:10-20` · `[M:…]` / bracket · `anchor:<id>` — как §3 [0128](0128-intercom-attachment-anchors-and-code-references.md). |
| 95 | |
| 96 | ### 3. Event log (explicit relate) |
| 97 | |
| 98 | Новый тип события v1 (расширение [0045](0045-agent-chat-persistence-event-log-and-projections.md)): |
| 99 | |
| 100 | ```text |
| 101 | message_range_related |
| 102 | ``` |
| 103 | |
| 104 | Payload (schema_version в событии): |
| 105 | |
| 106 | | Поле | Тип | Смысл | |
| 107 | |------|-----|--------| |
| 108 | | `thread_id` | string | Ветка | |
| 109 | | `start_ordinal` | int | 1-based начало (gutter) | |
| 110 | | `end_ordinal` | int | 1-based конец | |
| 111 | | `code_ref` | object | Сериализованный `AttachmentAnchor` @ declare time — **канон**; поля `lineStart`/`lineEnd` внутри него не дублировать отдельным top-level `line_range` | |
| 112 | | `source` | string | `slash` \| `mcp` \| `agent` | |
| 113 | |
| 114 | Проектор истории **не переписывает** `message_added`; relate — отдельная строка NDJSON (как `message_edited`). При replay explicit relate маппится на ordinals диапазона **по смыслу якоря** (member/syntax), а не по равенству номеров строк в NDJSON. |
| 115 | |
| 116 | ### 4. Derived index (HCI colocation) |
| 117 | |
| 118 | | Аспект | Правило | |
| 119 | |--------|---------| |
| 120 | | Файл | `{hci_index_dir}/intercom-message-code.sqlite` (рядом с symbol sidecar [0135](0135-intercom-attach-symbol-cache-and-hci-sidecar.md)) | |
| 121 | | Построение | Replay event log ветки + текущие `ChatMessages` attachments; explicit — из `message_range_related.code_ref` | |
| 122 | | Ключ запроса (кэш) | `(thread_id, file, member_key?)`; line-bucket — **вторичный** индекс после re-resolve для overlap и editor HUD | |
| 123 | | Значение | `message_id`, `ordinal`, `match_kind`: `inferred` \| `explicit` | |
| 124 | | Drift | Re-resolve якоря → обновление line-bucket; `stale` в find-ответе при `file_missing` / расхождении excerpt | |
| 125 | |
| 126 | Inferred строится **без** отдельного события на каждое сообщение — из wire `attachments[]` при проекции. |
| 127 | |
| 128 | ### 5. Find / overlap |
| 129 | |
| 130 | Сообщение попадает в результат, если якорь сообщения **совпадает по смыслу** с запросом: тот же `memberKey` на том же `file`, иначе overlap **текущего** re-resolved `LineRange` (≥1 строка). Explicit relate разворачивается на все ordinals в `[start..end]` с **одним** `code_ref` (якорь), не с отдельной таблицей строк. |
| 131 | |
| 132 | ### 6. UI |
| 133 | |
| 134 | #### 6.1 Intercom gutter (фаза 2+) |
| 135 | |
| 136 | Тонкий индикатор на строках R при explicit relate; inferred — опционально слабее. |
| 137 | |
| 138 | #### 6.2 Editor gutter — «HUD traceability» (фаза 3) |
| 139 | |
| 140 | При открытом файле IDE запрашивает derived index: *какие сообщения этой ветки связаны с файлом?* |
| 141 | |
| 142 | | Элемент | Поведение | |
| 143 | |---------|-----------| |
| 144 | | Маркер у строк L | Неброская точка / мини-ordinal `[3]` на полях редактора | |
| 145 | | Клик по маркеру | Scroll Intercom к сообщению #3 + рамка выбора ([0136](0136-intercom-feed-gutter-and-slash-namespace.md)) | |
| 146 | | Shift+клик | Опционально reveal range в редакторе | |
| 147 | |
| 148 | ЛКМ по телу сообщения в ленте — без изменения selection ([0136](0136-intercom-feed-gutter-and-slash-namespace.md)). |
| 149 | |
| 150 | --- |
| 151 | |
| 152 | ## Фазы внедрения |
| 153 | |
| 154 | ### Фаза 1 — Inferred index + Find (MVP) |
| 155 | |
| 156 | **Интент:** обратный индекс без event log relate. |
| 157 | |
| 158 | | Шаг | Артефакт | |
| 159 | |-----|----------| |
| 160 | | 1 | `IntercomMessageCodeCorrespondenceProjector` — из ветки: ordinal ↔ message ↔ `AttachmentAnchor` (memberKey + line hint) | |
| 161 | | 2 | `IntercomMessageCodeIndex` — SQLite sidecar @ HCI `index_dir`; `Rebuild(threadId)`, `Query(file, LineRange)` | |
| 162 | | 3 | `ChatPanelViewModel.FindMessagesForCodeRef(codeRef)` → ordinals + select лучший | |
| 163 | | 4 | Slash `/intercom message find` + `IntercomSlashPathBuilder` + handler | |
| 164 | | 5 | MCP `intercom.messages_for_code` | |
| 165 | | 6 | Unit-тесты: overlap, пустой selection, несколько hits | |
| 166 | |
| 167 | **Критерий:** `find selection` в detail-ветке с attach на текущий файл возвращает корректные `#n`. |
| 168 | |
| 169 | ### Фаза 2 — Explicit relate (event log) |
| 170 | |
| 171 | | Шаг | Артефакт | |
| 172 | |-----|----------| |
| 173 | | 1 | `ChatHistoryEventKind.MessageRangeRelated` + payload record | |
| 174 | | 2 | `PersistEventAsync` из slash/MCP relate | |
| 175 | | 3 | Проектор лога → дополнение derived index (`match_kind: explicit`) | |
| 176 | | 4 | Slash `message <range> relate …` + MCP `intercom.message_relate` | |
| 177 | | 5 | Gutter Intercom: индикатор explicit | |
| 178 | |
| 179 | ### Фаза 3 — Editor gutter markers |
| 180 | |
| 181 | Маркеры на line gutter редактора + click → scroll Intercom (§6.2). |
| 182 | |
| 183 | --- |
| 184 | |
| 185 | ## Следующий интент в кодовой базе (фаза 1) |
| 186 | |
| 187 | **Один вертикальный срез** — без relate-событий, без editor gutter: |
| 188 | |
| 189 | ``` |
| 190 | Services/Intercom/IntercomMessageCodeIndex.cs # SQLite + Query/Rebuild |
| 191 | Services/Intercom/IntercomMessageCodeIndexCoordinator.cs # hook после message_completed / thread open |
| 192 | Features/Chat/IntercomMessageCodeCorrespondenceProjector.cs # pure: messages → entries |
| 193 | Features/Chat/ChatPanelViewModel.IntercomCorrespondence.cs # FindMessagesForCodeRef |
| 194 | Features/Chat/ChatSlashIntercomHandlers.cs # message_find |
| 195 | Features/Chat/IntercomSlashPathBuilder.cs # path /intercom message find |
| 196 | IntentMelody/intent-catalog.toml # entry + help |
| 197 | Services/IdeCommands.Intercom.cs # intercom.messages_for_code |
| 198 | ``` |
| 199 | |
| 200 | **Порядок работ:** projector + unit-тесты overlap → in-memory index (без SQLite) → find slash → SQLite colocation → MCP. |
| 201 | |
| 202 | Это минимальный «обратный индекс», на который ляжет и HUD редактора (фаза 3). |
| 203 | |
| 204 | --- |
| 205 | |
| 206 | ## Последствия |
| 207 | |
| 208 | - [0136](0136-intercom-feed-gutter-and-slash-namespace.md): help — раздел find/relate; координаты ordinal. |
| 209 | - [0045](0045-agent-chat-persistence-event-log-and-projections.md): новый kind `message_range_related` (фаза 2). |
| 210 | - [0135](0135-intercom-attach-symbol-cache-and-hci-sidecar.md): ещё один sidecar в `index_dir`, не путать с symbol L2. |
| 211 | - Тесты: парсер `find selection`; index; MCP JSON schema. |
| 212 | |
| 213 | ## Критерии приёмки |
| 214 | |
| 215 | ### Фаза 1 |
| 216 | |
| 217 | 1. Attach на `Foo.cs` L10–20 → `find L:10-20` (файл открыт) → ordinals включают сообщение с attach. |
| 218 | 2. `find selection` без selection — явная ошибка. |
| 219 | 3. Документация: `Intercom/intercom-help.ru.md`, `MCP-PROTOCOL.md`. |
| 220 | |
| 221 | ### Фаза 2 |
| 222 | |
| 223 | 4. `message 3:5 relate selection` → строка в `*.events.ndjson`; find учитывает #3–#5. |
| 224 | |
| 225 | ### Фаза 3 |
| 226 | |
| 227 | 5. Маркер у строки редактора → scroll + select сообщения в Intercom. |
| 228 | |
| 229 | --- |
| 230 | |
| 231 | ## Статус реализации |
| 232 | |
| 233 | | Компонент | Состояние | |
| 234 | |-----------|-----------| |
| 235 | | ADR | Accepted · In progress | |
| 236 | | `IntercomMessageCodeCorrespondenceProjector` | In-memory; match по `memberKey`, fallback line overlap | |
| 237 | | `IntercomMessageCodeIndex` (SQLite) | — (фаза 1 без SQLite) | |
| 238 | | Slash `message find` | Implemented | |
| 239 | | MCP `intercom.messages_for_code` | Implemented | |
| 240 | | Event `message_range_related` | Implemented | |
| 241 | | Slash `message relate` | Implemented | |
| 242 | | MCP `intercom.message_relate` | Implemented | |
| 243 | | Editor gutter HUD | — (фаза 3) | |
| 244 | |