Forge
markdowndeeb25a2
1# ADR 0128: Intercom — якоря вложений (code references) и канонический attach
2
3**Статус:** Accepted · Implemented
4**Дата:** 2026-05-19
5
6## Связанные ADR
7
8| ADR | Роль |
9|-----|------|
10| [0080](0080-intercom-naming-and-multi-party-channel-model.md) | Intercom как канал; deep links; multi-party |
11| [0119](0119-chat-slash-commands-intercom-surface.md) | Slash в composer; autocomplete; local execution |
12| [0124](0124-slash-parametric-editor-line-commands.md) | `/editor line …` — **действие** в редакторе; binders строк 1-based |
13| [0125](0125-slash-workspace-file-commands-and-dynamic-completion.md) | `/file open` — **навигация**; dynamic completion путей |
14| [0045](0045-agent-chat-persistence-event-log-and-projections.md) | Event log; вложения в теле сообщения |
15| [0030](0030-command-ids-hotkeys-and-ui-registry-layers.md) | `command_id`; паритет MCP |
16| [0058](0058-agent-roslyn-mcp-coupling-settings-toml.md) | Resolve member / semantic |
17| [0111](0111-editor-linenumber-linerange-value-objects.md) | `LineRange` в домене |
18| [0053](0053-semantic-map-control-flow-pfd.md) | Structural picker внутри метода (v2+) |
19| [0120](0120-primary-work-surface-intercom-or-editor.md) | Composer в Forward |
20| [0123](0123-intercom-full-skia-surface-evolution.md) | Skia-лента |
21| [0129](0129-intercom-message-body-markdown-and-fenced-code.md) | **Fenced code** и markdown в `content` — **не** этот ADR |
22| [0130](0130-editor-agent-range-reveal-without-selection.md) | MCP **reveal** диапазона без selection (агент); общий presentation mode с §8 |
23| [0131](0131-editor-slash-select-code-by-bracket-reference.md) | `/editor select code [M:…]` — bracket → select в редакторе, не attach |
24| [0137](0137-intercom-message-code-correspondence.md) | `anchor:<id>` в find/relate; message ordinals |
25| [0138](0138-cockpit-command-line-and-parametric-ranges.md) | CCL — единственный REPL Forward; anchor list/peek без hit-test |
26
27### Вне ADR (playbook)
28
29| Документ | Роль |
30|----------|------|
31| [intercom-ux-reference-slack-mattermost-v1.md](../design/intercom-ux-reference-slack-mattermost-v1.md) | Slack/MM границы, flat feed, макеты — **продуктовый чертёж** |
32| [intercom-design-hub-v1.md](../design/intercom-design-hub-v1.md) | Домены D1–D9 для дизайнера |
33
34## Резюме
35
36Зафиксировать **единую модель «упомянуть кусок кода в репозитории»** в Intercom: не путь/строки в голове пользователя, а **смысловой якорь** (выделение, member, синтаксический scope, файл), с **производным** снимком `file` + `LineRange` @ send и **re-resolve** у получателя.
37
381. **Attach** (`/attach …`, `[…]`, chips) — payload к **черновику/сообщению**; ортогонален `/file open` и `/editor line`.
392. **Канон в wire:** `AttachmentAnchor` (shape, optional `memberKey`, optional `syntaxScope`, `excerpt`, resolved `file` + lines @ send, offsets в prose).
403. **Человек:** приоритет **H0** selection, **H0b** scope @ caret, **H1** `[M:…]`; **M0** path/lines — fallback для агента.
414. **Клик из ленты:** `intercom.reveal_attachment` — open + scroll + **рамка** (не selection); Shift → select.
425. **Строки @ send** — не контракт между участниками; устойчивее **excerpt** и **member** / **syntaxScope**.
43
44**Не входит:** vision/policy для image в prompt агента; полный structural picker (v2); `@file` inline (запасной); реализация composer chips (отдельные задачи); **fenced code / markdown в теле** — [0129](0129-intercom-message-body-markdown-and-fenced-code.md).
45
46**Два вида «кода» в чате:** (1) **якорь на workspace** — этот ADR; (2) **цитата в `content`** (`` ``` ``) — [0129](0129-intercom-message-body-markdown-and-fenced-code.md).
47
48---
49
50## Контекст
51
52Оператор хочет в одной реплике сказать: *«этот метод здесь, а регрессия — там»*, в том числе **в середине предложения**. Кажется простым, но смешивает:
53
54| Поверхность | Пример | Эффект |
55|-------------|--------|--------|
56| Attach | `/attach selection`, `[M:Foo]` | Payload агенту + deep link |
57| Editor action | `/editor line select 5 10` | Меняет selection в буфере |
58| Open file | `/file open Foo.cs` | Только вкладка |
59| Agent MCP | `go_to_position` | Select в редакторе (правка) |
60| Fenced в ответе | `` ```csharp … ``` `` | Только текст в ленте — [0129](0129-intercom-message-body-markdown-and-fenced-code.md) |
61
62Без ADR неизбежны: `@cc:`-стиль, пузыри, «строка 50» как единственная правда, случайное удаление после клика из чата, рассинхрон строк у отправителя и получателя.
63
64**Процесс:** домен проговаривали с агентом как с собеседником до кода (ветвления attach/клик/re-resolve) → этот ADR и [intercom-design-hub](../design/intercom-design-hub-v1.md); капитан и ревью — у человека — [philosophy §8](../design/cascadeide-philosophy-v1.md#8-агент-как-партнёр-для-проектирования-до-кода).
65
66---
67
68## Проблема
69
701. **Когнитивная нагрузка:** требовать path + line numbers в prose.
712. **Две правды по строкам:** ветка, локальные правки, другой монитор/контекст — L50 у отправителя ≠ L50 у получателя.
723. **Гранулярность:** `[M:Foo]` на 500 строк не покрывает «второй `for`» без имени.
734. **Разрыв входов:** slash, скобки, chips, MCP должны сходиться в **один** wire-тип для [0045](0045-agent-chat-persistence-event-log-and-projections.md).
74
75---
76
77## Решение
78
79<a id="adr0128-p1"></a>
80
81### 1. Термины
82
83| Термин | Значение |
84|--------|----------|
85| **Attachment** | Ссылка на workspace-артефакт в **теле** реплики (не отдельная «полоса вложений» только сверху) |
86| **AttachmentAnchor** | Каноническая структура в event log |
87| **Chip** | UI-токен в composer; после send — inline-метка в flat feed |
88| **Resolve @ send** | Roslyn/редактор вычисляют `file`, `LineRange`, `excerpt` в момент отправки |
89| **Reveal** | Навигация по клику из ленты (рамка, не selection по умолчанию) |
90
91<a id="adr0128-p2"></a>
92
93### 2. `attachmentShape` (обязательное поле)
94
95| Shape | Когда | Строки в resolve |
96|-------|--------|------------------|
97| `selection` | Выделение в редакторе | да |
98| `text-range` | Текстовый файл + диапазон / positional M0 | да |
99| `whole-file` | Медиа, бинарники, «весь файл» | нет |
100| `member` | Resolve по `memberKey` (весь член или уточнённый scope) | да (снимок @ send) |
101| `syntax-scope` | Innermost `ForStatement` / `IfStatement` @ caret (H0b) | да (снимок @ send) |
102
103Для `.png`, `.dll` и т.д. — только `whole-file`; slash `[path] [start] [end]` для binary → **ошибка** (как I3 у [0124](0124-slash-parametric-editor-line-commands.md)).
104
105<a id="adr0128-p3"></a>
106
107### 3. Канон `AttachmentAnchor` (wire / event log)
108
109Минимальный контракт для [0045](0045-agent-chat-persistence-event-log-and-projections.md) (имена полей — при реализации в JSON/schema):
110
111| Поле | Обязательность | Смысл |
112|------|----------------|--------|
113| `id` | да | Стабильный id внутри сообщения |
114| `attachmentShape` | да | см. §2 |
115| `displayLabel` | да | Короткая метка в ленте (`GetUserAsync`, `Foo › for (2)`) |
116| `file` | почти всегда | Workspace-relative path после resolve |
117| `lineStart`, `lineEnd` | если применимо | 1-based inclusive @ send — **hint**, не контракт между участниками |
118| `memberKey` | опционально | Roslyn-qualified или согласованный stable key |
119| `syntaxScope` | опционально | `{ kind: "for", indexInParent: 2, parentMemberKey }` — v2 |
120| `excerpt` | рекомендуется | Текст фрагмента @ send — **устойчивый** для всех участников |
121| `proseStart`, `proseLength` | да | Offset inline-метки в теле сообщения |
122| `resolvedAtUtc` | да | Момент resolve @ send |
123| `resolveOutcome` | опционально | Кэш последнего reveal у получателя: `resolved` \| `file_missing` \| … — §9.1 |
124
125**Принцип:** то, что видит человек в ленте — **смысл** (`displayLabel`, excerpt); path/lines — вторично (hover, агент, fallback). При `file_missing` excerpt остаётся источником правды для всех.
126
127<a id="adr0128-p3b"></a>
128
129#### 3.1 Контекст отправителя @ send (`senderWorkspaceContext`)
130
131**Да, ветку (и короткий commit) стоит сохранять** — как **подсказку**, не как часть re-resolve.
132
133| Правило | Смысл |
134|---------|--------|
135| **Уровень** | На **сообщение** в payload [0045](0045-agent-chat-persistence-event-log-and-projections.md) (`message_added` / `message_completed`), **не** дублировать на каждый `AttachmentAnchor` |
136| **Обязательность** | Опционально; заполнять при send, если git repo доступен |
137| **Использование** | UI («отправитель был на `feature/x`»), агент в prompt, кнопка «переключиться?» — **не** автоматический checkout |
138| **Не использовать для** | Вычисления `lineStart`/`lineEnd` у получателя; единственного ключа навигации |
139
140Рекомендуемая форма *(имена при schema bump)*:
141
142```json
143"senderWorkspaceContext": {
144 "gitBranch": "feature/intercom-attach",
145 "gitCommitShort": "a1b2c3d",
146 "solutionPath": "relative/or/abs path to .sln at send",
147 "capturedAtUtc": "2026-05-19T12:00:00Z"
148}
149```
150
151| Поле | Заметки |
152|------|---------|
153| `gitBranch` | Имя ветки или `HEAD (detached)`; при detached — опираться на `gitCommitShort` |
154| `gitCommitShort` | 7–12 hex; для «та же ревизия?» у получателя |
155| `solutionPath` | Какой sln был активен @ send (multi-sln workspace) |
156| `capturedAtUtc` | Момент снимка (может совпадать с `resolvedAtUtc` у anchors) |
157
158**UX при `file_missing`:** toast + «файла нет в **твоём** workspace; отправитель: `feature/x` @ `a1b2c3d`» + excerpt; опционально действие **«Checkout ветки отправителя»** (явное, v2+, не silent).
159
160**Риск:** ветка устарела / force-push — поле **информационное**, может не совпадать с историей; не скрывать excerpt.
161
162<a id="adr0128-p4"></a>
163
164### 4. Ввод для человека (слои H / M)
165
166Приоритет **не** «сначала путь и строки»:
167
168| Слой | Ввод | `command_id` / механизм |
169|------|------|-------------------------|
170| **H0** | Выделение → attach | `attach_selection` → `/attach selection` |
171| **H0b** | Каретка в `for` → attach scope | `attach_scope` → `/attach scope` *(фаза 3)* |
172| **H1** | `[M:Method]`, `[Foo.cs M:Method]` | Parse bracket → resolve member |
173| **H2** | `[diagram.png]`, `[appsettings.json]` | `whole-file` |
174| **M0** | `[Foo.cs 50 100]`, `[F:…; L:…]` | Агент, paste; positional fallback |
175
176**Внутри длинного метода** без имени: H0, H0b, или H1 + prose («второй for»); v2 — `syntaxScope` / picker ([0053](0053-semantic-map-control-flow-pfd.md)).
177
178<a id="adr0128-p5"></a>
179
180### 5. Inline `[…]` — вторая поверхность, тот же anchor
181
182- `[M:GetUserAsync]` — **M**; **F** (файл) из active file или autocomplete, если путь не указан.
183- `[Foo.cs M:…]` — явный **F** + **M**.
184- Не путать: `@` — **люди**; `[` — **артефакты**; не markdown `[](url)`.
185- **L2** для агента: поля через **`;`** (единственный канон), тот же parse tree, что L1.
186
187#### 5.1 Оси полей bracket (`F` | `M` | `L` | `S`)
188
189Четыре **ортогональные** оси в одном `[…]` или в L2. В wire они попадают в те же поля `AttachmentAnchor` (`file`, `memberKey`, `lineStart`/`lineEnd`, `syntaxScope`). **`B:` (block)** не вводим — путается с **branch** (git); для `for`/`if`/`while` внутри члена — ось **`S:`** (syntax scope / statement).
190
191| Ось | Префикс | Смысл | Wire / resolve |
192|-----|---------|--------|----------------|
193| **File** | `F:` | Workspace-relative или абсолютный путь | `file` |
194| **Member** | `M:` | Метод, свойство, тип (stable / Roslyn key) | `memberKey` + re-resolve |
195| **Lines** | `L:` | Позиционный fallback (1-based inclusive) | `lineStart`, `lineEnd` @ send (hint) |
196| **Scope** | `S:` | Синтаксический фрагмент **внутри** члена | `syntaxScope`: `{ kind, indexInParent, parentMemberKey? }` |
197
198**`S:` — семантика индекса:** `S:for:2` = **второй** узел kind `for` (**1-based** `indexInParent`) от **начала тела** члена `M:…` (или текущего member при H0b). Не «второй for в файле». Kind — lowercase Roslyn-имя узла: `for`, `if`, `while`, `switch`, … (как в `AttachmentSyntaxScope.Kind`).
199
200**Примеры L1 (человек):**
201
202| Выражение | Разбор |
203|-----------|--------|
204| `[M:Run]` | только member |
205| `[M:Run S:for:2]` | member + второй `for` в `Run` |
206| `[M:Run S:for(2)]` | то же; скобки `()` — только читаемость; **канон для парсера:** `S:for:2` |
207| `[Foo.cs M:Bar S:if:1]` | явный файл + member + scope |
208| `[S:for:2]` | только scope (нужен active file + member @ caret или H0b) |
209
210**Примеры L2 (агент, `;`):**
211
212```text
213[F:src/Foo.cs; M:Run; S:for:2]
214[F:src/Foo.cs; M:Run; L:50-100]
215[F:src/Foo.cs; M:Run; S:for:2; L:50-100]
216```
217
218`L:` и `S:` могут сосуществовать: при re-resolve у получателя приоритет **member + syntaxScope**; `L:` — fallback @ send и при `member_not_found` (§8–9.1).
219
220**Связь с фазой 4:** UI picker и chip label (`displayLabel`: `Run › for (2)`) используют ту же ось **S:**, что prose `[M:Run S:for:2]`.
221
222**Реализация parse (0131):** `BracketCodeReferenceParser` — `F`/`M`/`L`/`S` → `AttachmentAnchor` (в т.ч. `syntaxScope` для `S:`). MCP по-прежнему предпочитает JSON `syntax_scope`, не prose.
223
224Парсер attach: только в **prose**-сегментах тела (после `SplitSegments` — [0129](0129-intercom-message-body-markdown-and-fenced-code.md) §5); **не** внутри fenced code.
225
226<a id="adr0128-p6"></a>
227
228### 6. Slash namespace `/attach`
229
230| Slash | `command_id` | Эффект |
231|-------|--------------|--------|
232| `/attach selection` | `attach_selection` | Chip из active editor selection |
233| `/attach scope` | `attach_scope` | Chip из innermost syntax @ caret |
234| `/attach file <path>` | `attach_file` | whole-file или prompt range для text |
235| `/attach file <path> <start> <end>` | `attach_file` | `text-range` (text-only) |
236
237- Autocomplete путей — **переиспользовать** [0125](0125-slash-workspace-file-commands-and-dynamic-completion.md); другой `command_id`, чем `file_open`.
238- ~~`/attach code`~~ — **не** использовать как корень.
239- ~~`@cc:`~~ — **не** использовать.
240
241Запись в `intent-catalog.toml` — по [0119](0119-chat-slash-commands-intercom-surface.md) / [0109](0109-declarative-parametric-melody-catalog-toml-and-code-binders.md) при внедрении.
242
243<a id="adr0128-p7"></a>
244
245### 7. Ортогональные команды (не смешивать)
246
247| Действие | Команда | Меняет буфер? | Attach? |
248|----------|---------|---------------|---------|
249| Открыть файл | `/file open` [0125](0125-slash-workspace-file-commands-and-dynamic-completion.md) | нет | нет |
250| Select/delete lines | `/editor line` [0124](0124-slash-parametric-editor-line-commands.md) | **да** | нет |
251| Select по смыслу в редакторе | `/editor select code [M:…]` [0131](0131-editor-slash-select-code-by-bracket-reference.md) | **да** | нет |
252| Прикрепить к реплике | `/attach …`, `[…]` | нет | **да** |
253| Агент показывает участок | MCP `reveal_editor_range` [0130](0130-editor-agent-range-reveal-without-selection.md) | нет | нет |
254| Агент идёт править | MCP `go_to_position` | **да** (select) | нет |
255| Цитата кода в тексте | markdown fence | нет | нет — [0129](0129-intercom-message-body-markdown-and-fenced-code.md) |
256
257<a id="adr0128-p8"></a>
258
259### 8. Reveal из ленты (клик по метке)
260
261`command_id`: **`intercom.reveal_attachment`** (или эквивалент в intent catalog).
262
263| Шаг | Поведение |
264|-----|-----------|
265| 1 | Open `file` (или preview для non-text) |
266| 2 | Если есть `memberKey` или `syntaxScope` → **re-resolve** в solution **получателя** → `LineRange` |
267| 3 | Иначе если есть lines @ send → best-effort + drift warning |
268| 4a | Если `file_missing` / `member_not_found` → шаги 4–5 **не** выполнять; UI по §9.1 |
269| 4 | Scroll into view + **transient range highlight** (рамка / gutter band), **не** `Selection` |
270| 5 | Shift+клик или настройка → `SelectInEditor` |
271
272Отличие от MCP `go_to_position`: reveal = **просмотр**; go_to_position = **правка** агентом. Прямой MCP reveal без сообщения в чат — [0130](0130-editor-agent-range-reveal-without-selection.md) (`editor.reveal_range`); **`intercom.reveal_attachment`** (и slash `/editor … code`) вызывают тот же `EditorAgentRangeReveal` / `IntercomAttachmentNavigator` — см. [0130](0130-editor-agent-range-reveal-without-selection.md), [0131](0131-editor-slash-select-code-by-bracket-reference.md).
273
274<a id="adr0128-p9"></a>
275
276### 9. Стабильность между отправителем и получателем
277
278- **Excerpt** и **displayLabel** (member, `Foo › for (2)`) — общий знаменатель в ленте.
279- **lineStart/lineEnd @ send** — снимок отправителя для агента и fallback; при клике у получателя — **re-resolve** при наличии `memberKey`/`syntaxScope`.
280- UI может показать: «при отправке L50–100» при hover, если текущий диапазон другой.
281
282<a id="adr0128-p9b"></a>
283
284### 9.1 Re-resolve у получателя: другая ветка, нет файла, нет символа
285
286**Вопрос:** отправитель сослался на `Foo.cs` / `[M:Bar]`, у получателя на другой ветке файла нет (или member переименован). Что видит человек и что получает агент?
287
288**Принцип:** attach — это **намерение + снимок @ send**, а не гарантия, что у всех открыт **тот же** снимок workspace. Re-resolve всегда идёт в **текущем** solution/ветке **получателя**; если там нет цели — **не ломаем** ленту и не открываем пустой редактор молча.
289
290| Исход `resolveOutcome` *(в UI / при reveal)* | Условие | Лента (chip / hover) | Клик **reveal** | Агент в prompt |
291|---------------------------------------------|---------|----------------------|-----------------|---------------|
292| `resolved` | Файл есть, member/scope найден | `displayLabel` | Open + рамка по **текущим** строкам; опционально «было L50–100 @ send» | `file` + lines + **excerpt** |
293| `file_missing` | Путь не в workspace (другая ветка, не checkout, другой sln) | Метка + иконка ⚠; **excerpt** в tooltip / expand | Toast: «`Foo.cs` нет в текущем workspace»; **не** создавать пустую вкладку; предложить **показать excerpt** (flyout) или копировать excerpt | **excerpt** + `displayLabel` + `file` (как hint); явно: *не удалось открыть в твоём дереве* |
294| `member_not_found` | Файл есть, символа нет (rename, ветка без метода) | `displayLabel` + ⚠ stale | Open файл; рамка по **lines @ send** если валидны, иначе только scroll к файлу + warning | excerpt обязателен; memberKey как hint |
295| `lines_drift` | Только M0 / fallback lines, диапазон пустой или сильно сдвинулся | как выше + «drift» | Open + best-effort lines + **предупреждение** | excerpt + lines @ send |
296| `excerpt_only` | Non-text / preview-only / resolve отключён | excerpt в expand | Preview или copy excerpt | excerpt |
297
298**Почему excerpt обязателен @ send:** именно он **одинаков** у отправителя и получателя, когда репозитории разъехались. Строки и path — подсказки для IDE **после** успешного resolve у **этого** человека.
299
300**Ветка отправителя:** см. §3.1 `senderWorkspaceContext` на уровне сообщения — **рекомендуется** сохранять; не путать с re-resolve у получателя.
301
302**Не делаем v1:**
303
304- Автоматический `git checkout` ветки отправителя по клику из чата.
305- Скрывать сообщение или chip, если файл отсутствует — смысл реплики и excerpt остаются видимыми.
306- Притворяться, что lines @ send — общий контракт между участниками.
307
308**Связь с [0129](0129-intercom-message-body-markdown-and-fenced-code.md):** fenced block в ответе агента **не** зависит от файла в репо; attach chip — да. Если файла нет, получатель всё равно читает **excerpt** или fenced текст, а не «битую» ссылку.
309
310<a id="adr0128-p10"></a>
311
312### 10. Composer и лента (UX attach)
313
314- Composer: **текст + chip-токены** в позиции курсора; несколько anchors в одной реплике.
315- Лента: **flat feed** ([0123](0123-intercom-full-skia-surface-evolution.md)); inline-метки attach; без messenger-пузырей.
316- Агенту: ordered `AttachmentAnchor[]` + prose (для LLM — excerpt обязателен для безымянных блоков).
317- Отрисовка fenced / inline MD в prose между метками — [0129](0129-intercom-message-body-markdown-and-fenced-code.md).
318
319<a id="adr0128-p10b"></a>
320
321### 10.1 Anchor id в CLI (CCL), без hit-test
322
323Wire-id: маркер `⟦a:{8 hex}⟧` (`IntercomAttachmentMarkers`) ↔ `AttachmentAnchor.Id`. Клик по chip в Skia остаётся; **второй путь** — клавиатура и CCL ([0138](0138-cockpit-command-line-and-parametric-ranges.md) [§ Инварианты внимания](#adr0138-attention-invariants)).
324
325| Команда *(предложение)* | Назначение |
326|-------------------------|------------|
327| `/intercom message anchors list` | Anchors выбранного / текущего сообщения (или черновика): `a:abcd1234`, `displayLabel`, `resolveOutcome` |
328| `/anchor peek <id>` | Reveal по id → тот же pipeline, что `intercom.reveal_attachment` / клик chip; **без** hit-test |
329
330**Валидация:** как §9.1 — preview-строка в CCL (`resolved` / `file_missing` / …), excerpt остаётся fallback; Enter допускается с предупреждением на degraded.
331
332**ID в ленте (UX, на обсуждение):**
333
334| Вариант | Когда |
335|---------|--------|
336| Short id **справа от chip** (приглушённый `a:abcd1234`) | Быстрый ввод `/anchor peek` без `list` |
337| Id только в **hover** / compact mode | Минимум шума в ленте |
338| Только **`anchors list`** | Старые сообщения, узкий layout |
339
340**Не цель:** отдельная Shell-страница в Forward или scrollback-терминал для slash — только CCL + при необходимости MFD Terminal для OS/process.
341
342---
343
344## Фазы внедрения
345
346| Фаза | Содержание | Зависимости | CIDE |
347|------|------------|-------------|------|
348| **0** | Schema `AttachmentAnchor` + опционально `senderWorkspaceContext` на сообщении в [0045](0045-agent-chat-persistence-event-log-and-projections.md); projection в ленту | 0045, git | **да** — payload `attachments[]`, `sender_workspace_context`, projector, лента |
349| **1** | `/attach selection`, `/attach file`; chips; resolve @ send; excerpt | 0125, 0111 | **да** — slash + marker в composer + `IntercomAttachmentResolveAtSend` |
350| **2** | Bracket parse `[M:…]`, `[path]`; `/attach` в TOML; reveal рамка; **composer autocomplete** в незакрытом `[` | 0058, Roslyn, 0125 | **да** — `BracketCodeReferenceParser`; reveal/select; клик в ленте → `intercom.reveal_attachment`; `ChatBracketAutocomplete` (оси `F`/`M`/`L`/`S`, файлы [0125]) |
351| **3** | `/attach scope`; `syntaxScope`; re-resolve @ recipient; Shift→select | 0053 опционально | **да** — `/attach scope`, `AttachmentAnchorCaretScopeResolver`, Shift+клик |
352| **4** | Structural picker (дерево узлов @ caret); chip `displayLabel`; stale hint | 0053 | **нет** — prose autocomplete в `[` — фаза **2**; полный picker — здесь |
353
354---
355
356## Не цели
357
358- Политика image/vision в prompt агента.
359- `@file` inline (если не понадобится после фазы 2).
360- Замена Solution Explorer или fuzzy-поиска по репо в composer.
361- Паритет Slack/MM server — слой B [0080 §5](0080-intercom-naming-and-multi-party-channel-model.md#adr0080-p5).
362- Fenced code, полный markdown в ленте — [0129](0129-intercom-message-body-markdown-and-fenced-code.md).
363
364---
365
366## Отклонённые альтернативы
367
368| Альтернатива | Почему нет |
369|--------------|------------|
370| Только `@cc:path:lines` | Путает с `@mention`; path-first |
371| Только positional `[file lines]` для людей | Строки не стабильны; тяжёлый ввод |
372| Клик → always selection | Риск случайного удаления |
373| Отдельный тип сообщения без canonical anchor | Дубли с prose; плохо для 0045 |
374| Две грамматики `T:,M:` и `type;member:` | Двойной парсер |
375| Fenced block = автоматический attach | Разные сущности — [0129](0129-intercom-message-body-markdown-and-fenced-code.md) |
376
377---
378
379## Согласованные решения (для реализации)
380
381| Тема | Решение |
382|------|---------|
383| Fenced vs anchor | Разные ADR: [0129](0129-intercom-message-body-markdown-and-fenced-code.md) vs этот |
384| `[` в fenced code | Attach-грамматика **не** применяется — [0129](0129-intercom-message-body-markdown-and-fenced-code.md) §5 |
385| `[` в prose | Только распознаваемые attach-токены; `\[` — фаза 2+ |
386| Reveal highlight v1 | Transient **overlay / gutter band** (~3 s), не `Selection`; editor adornment — v2 |
387| `memberKey` v1 | `file` + Roslyn **display/qualified name**; rename → stale + re-resolve |
388| `memberKey` v2 | DocumentId / symbol id при необходимости |
389| Excerpt лимит | **120 строк** или **16 KiB**, хвост `…` |
390| Redaction внешний контур | Отдельное событие / политика [0080](0080-intercom-naming-and-multi-party-channel-model.md) |
391| `@file` inline | **Отложено** до фазы 2 attach |
392| Нет файла у получателя | Excerpt + warning; reveal **не** открывает пустой файл — §9.1 |
393| Ветка @ send | `senderWorkspaceContext` на сообщении — подсказка UI/агенту; checkout только явно — §3.1 |
394| Anchor CLI | `anchors list` + `/anchor peek` в **CCL**; id в UI — hover vs inline — §10.1 |
395
396---
397
398## Открытые вопросы
399
4001. Точная JSON-schema `AttachmentAnchor` + `senderWorkspaceContext` в [0045](0045-agent-chat-persistence-event-log-and-projections.md) (`schema_version` bump).
4012. Нужен ли **отдельный** `command_id` «скопировать excerpt anchor» vs общий copy selection.
4023. Паритет: агент шлёт только fenced code без anchor — когда UI предлагает «attach как ссылку на файл» (heuristic, v2+; координация с [0129](0129-intercom-message-body-markdown-and-fenced-code.md)).
403
404---
405
406## История
407
408| Дата | Изменение |
409|------|-----------|
410| 2026-05-19 | Proposed: канон AttachmentAnchor, H/M слои, `/attach`, reveal, re-resolve. |
411| 2026-05-19 | §11–12 fenced/MD → [0129](0129-intercom-message-body-markdown-and-fenced-code.md); attach-only scope. |
412| 2026-05-19 | §9.1 исходы re-resolve: другая ветка / нет файла; excerpt как общий знаменатель. |
413| 2026-05-19 | §3.1 `senderWorkspaceContext` (ветка, commit) на уровне сообщения @ send. |
414| 2026-05-19 | §5.1 оси bracket `F` \| `M` \| `L` \| `S` (scope/statement); `S:for:n`, не `B:`; L2 с `S:`. |
415| 2026-05-20 | **Accepted · In progress**; колонка CIDE в фазах; связка с [0130](0130-editor-agent-range-reveal-without-selection.md) / [0131](0131-editor-slash-select-code-by-bracket-reference.md) в §8. |
416| 2026-05-21 | §10.1 anchor id в CCL (`anchors list`, `/anchor peek`); связь с [0138](0138-cockpit-command-line-and-parametric-ranges.md) |
417| 2026-05-20 | **Accepted · Implemented**; фаза 2: composer bracket autocomplete (`ChatBracketAutocomplete`, общий popup с slash); фаза 4 — только structural picker. |
418
View only · write via MCP/CIDE