Forge
markdown4405de34
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
101message_range_related
102```
103
104Payload (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
126Inferred строится **без** отдельного события на каждое сообщение — из 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```
190Services/Intercom/IntercomMessageCodeIndex.cs # SQLite + Query/Rebuild
191Services/Intercom/IntercomMessageCodeIndexCoordinator.cs # hook после message_completed / thread open
192Features/Chat/IntercomMessageCodeCorrespondenceProjector.cs # pure: messages → entries
193Features/Chat/ChatPanelViewModel.IntercomCorrespondence.cs # FindMessagesForCodeRef
194Features/Chat/ChatSlashIntercomHandlers.cs # message_find
195Features/Chat/IntercomSlashPathBuilder.cs # path /intercom message find
196IntentMelody/intent-catalog.toml # entry + help
197Services/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
2171. Attach на `Foo.cs` L10–20 → `find L:10-20` (файл открыт) → ordinals включают сообщение с attach.
2182. `find selection` без selection — явная ошибка.
2193. Документация: `Intercom/intercom-help.ru.md`, `MCP-PROTOCOL.md`.
220
221### Фаза 2
222
2234. `message 3:5 relate selection` → строка в `*.events.ndjson`; find учитывает #3–#5.
224
225### Фаза 3
226
2275. Маркер у строки редактора → 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
View only · write via MCP/CIDE