| 1 | # ADR 0159: Bracket `[FRG:…]` — ссылки на артефакты Forge (расширение 0128) |
| 2 | |
| 3 | **Статус:** Accepted · Design |
| 4 | **Дата:** 2026-06-11 |
| 5 | |
| 6 | ## Резюме |
| 7 | |
| 8 | - Расширить **bracket-нотацию** ([0128](0128-intercom-attachment-anchors-and-code-references.md) §5) **второй семьёй** токенов **`[FRG:…]`** — ссылки на forge-артефакты (repo, issue, MR), **ортогонально** code-bracket `[F:…; M:…; L:…; S:…]`. |
| 9 | - **Код** по-прежнему → `AttachmentAnchor` / `CodeAnchor`; **forge** → `ForgeArtifactRef` (wire). |
| 10 | - **Prose / markdown / Intercom / slash** — bracket; **браузер / OS** — `forge://` ([FORGE-ADR-0007](../../../agent-forge/design/FORGE-ADR-0007-forge-magic-link-protocol.md)); **MCP** — structured JSON (как у attach). |
| 11 | - Один parse tree, два discriminant: `FRG` vs code (`F`/`M`/`L`/`S`). |
| 12 | |
| 13 | ## Связанные ADR |
| 14 | |
| 15 | | ADR | Роль | |
| 16 | |-----|------| |
| 17 | | [0128](0128-intercom-attachment-anchors-and-code-references.md) | `AttachmentAnchor`, оси `F`/`M`/`L`/`S`, attach/reveal | |
| 18 | | [0131](0131-editor-slash-select-code-by-bracket-reference.md) | `/editor select code [M:…]` — shared code parse | |
| 19 | | [0156](0156-correspondence-mfd-surface-and-reverse-code-anchors.md) | Bracket / CodeAnchor в correspondence | |
| 20 | | [0157](0157-cide-magic-link-protocol.md) | `cide://reveal?…&b=…` — bracket в query | |
| 21 | | [0158](0158-forge-lens-crs-overlay.md) | Forge Lens в CRS | |
| 22 | | [FORGE-ADR-0003](../../../agent-forge/design/FORGE-ADR-0003-forge-lens-cide-code-anchors.md) | Lens, storage `anchors[]`, MCP-first write | |
| 23 | | [FORGE-ADR-0007](../../../agent-forge/design/FORGE-ADR-0007-forge-magic-link-protocol.md) | `forge://` deep links | |
| 24 | | [FORGE-ADR-0012](../../../agent-forge/design/FORGE-ADR-0012-bracket-notation-and-lens-anchors.md) | Forge storage, thin web, round-trip | |
| 25 | |
| 26 | --- |
| 27 | |
| 28 | ## Контекст |
| 29 | |
| 30 | [0128](0128-intercom-attachment-anchors-and-code-references.md) зафиксировал: slash, скобки, chips и MCP сходятся в **один wire-тип** для **кода** (`AttachmentAnchor`). Оси bracket — `F`, `M`, `L`, `S`. |
| 31 | |
| 32 | [FORGE-ADR-0003](../../../agent-forge/design/FORGE-ADR-0003-forge-lens-cide-code-anchors.md) добавляет **forge-артефакты** (issue, MR) с тем же `CodeAnchor` в metadata, плюс `forge://` для навигации. |
| 33 | |
| 34 | **Проблема v0.3:** forge pilot показывает anchors как `file:line`; агент и оператор мыслят **`[M:…]`** и **`[FRG:…]`**, не JSON и не сырой path. Три параллельных «языка» (Intercom bracket, forge JSON, web `file:line`) ломают принцип §5.1 0128. |
| 35 | |
| 36 | **Решение:** не добавлять ось `I:` внутрь code-bracket (смешивает artifact и code), а ввести **отдельный namespace** `FRG:` — симметрично тому, как `cide://` и `forge://` разделены по схеме, но связаны делегированием. |
| 37 | |
| 38 | --- |
| 39 | |
| 40 | ## Решение |
| 41 | |
| 42 | <a id="adr0159-p1"></a> |
| 43 | |
| 44 | ### 1. Две семьи bracket |
| 45 | |
| 46 | | Семья | Пример | Wire | Эффект клика | |
| 47 | |-------|--------|------|--------------| |
| 48 | | **Code** | `[M:Run]`, `[F:src/Foo.cs; M:Run; S:for:2]` | `AttachmentAnchor` | `intercom.reveal_attachment` / `cide://reveal` / editor select | |
| 49 | | **Forge** | `[FRG:pilot/issues/7]`, `[FRG:pilot/mr/3]` | `ForgeArtifactRef` | Forge Lens panel / `forge://` / HTTPS view | |
| 50 | |
| 51 | **Не смешивать** в одной оси: code-bracket **не** содержит номер issue; forge-bracket **не** заменяет `F`/`M`/`L`/`S`. |
| 52 | |
| 53 | <a id="adr0159-p2"></a> |
| 54 | |
| 55 | ### 2. Грамматика `[FRG:…]` (v1) |
| 56 | |
| 57 | Форма (BNF-подобно): |
| 58 | |
| 59 | ```text |
| 60 | forge-bracket ::= "[FRG:" forge-path "]" |
| 61 | forge-path ::= repo "/" kind "/" number [ ";" code-tail ] |
| 62 | repo ::= slug (URL-safe, как в forge API) |
| 63 | kind ::= "issues" | "mr" | "repos" |
| 64 | number ::= 1..9 digit+ |
| 65 | code-tail ::= тот же L2, что 0128 §5.1 (F/M/L/S), **без** повторного "[" |
| 66 | ``` |
| 67 | |
| 68 | Примеры: |
| 69 | |
| 70 | ```text |
| 71 | [FRG:pilot/issues/1] |
| 72 | [FRG:cad-tools/mr/3] |
| 73 | [FRG:pilot/repos/pilot] |
| 74 | [FRG:pilot/issues/7; F:src/Foo.cs; M:CreateIssue] |
| 75 | ``` |
| 76 | |
| 77 | | Форма | Смысл | |
| 78 | |-------|--------| |
| 79 | | `…/issues/N` | Issue #N в repo | |
| 80 | | `…/mr/N` | Merge request #N | |
| 81 | | `…/repos/{slug}` | Repo profile (редко в prose) | |
| 82 | | `; F:…; M:…` *(optional)* | **Primary code anchor** inline — удобно в одной скобке в body issue; в wire всё равно split: `ForgeArtifactRef` + `AttachmentAnchor` | |
| 83 | |
| 84 | **Короткая форма (v1.1, optional):** `[FRG:#7]` при известном default `repo` из `[workspace.forge]` — только CIDE/slash, не в persisted forge body без repo. |
| 85 | |
| 86 | <a id="adr0159-p3"></a> |
| 87 | |
| 88 | ### 3. Wire: `ForgeArtifactRef` |
| 89 | |
| 90 | Минимальный контракт (JSON; shared с forge API): |
| 91 | |
| 92 | | Поле | Обязательность | Смысл | |
| 93 | |------|----------------|--------| |
| 94 | | `repo` | да | Repo slug | |
| 95 | | `kind` | да | `issue` \| `mr` \| `repo` | |
| 96 | | `number` | для issue/mr | 1-based номер | |
| 97 | | `primaryAnchor` | нет | `AttachmentAnchor` при compound bracket | |
| 98 | |
| 99 | **Code anchors на issue** (массив) остаются **`AttachmentAnchor[]`** / `CodeAnchor[]` — не вложены в `ForgeArtifactRef`, кроме optional `primaryAnchor` для inline compound. |
| 100 | |
| 101 | <a id="adr0159-p4"></a> |
| 102 | |
| 103 | ### 4. Parse → resolve → act |
| 104 | |
| 105 | ``` |
| 106 | Prose "[FRG:…]" → BracketForgeReferenceParser |
| 107 | → ForgeArtifactRef |
| 108 | → forge_lens.open | forge://issue | HTTPS fallback |
| 109 | |
| 110 | Prose "[F:…; M:…]" → BracketCodeReferenceParser (0128/0131) |
| 111 | → AttachmentAnchor |
| 112 | → reveal / select / attach |
| 113 | ``` |
| 114 | |
| 115 | | Поверхность | Code bracket | Forge bracket | |
| 116 | |-------------|--------------|---------------| |
| 117 | | Intercom attach | да | **нет** v1 *(forge — не chat attachment)* | |
| 118 | | Issue/MR body (forge) | да | да | |
| 119 | | ADR / docs markdown | да | да | |
| 120 | | `/editor select code` | да | нет | |
| 121 | | `/forge goto [FRG:…]` | — | да | |
| 122 | | MCP | `anchor_json` / поля | `forge_ref_json` или bracket string | |
| 123 | |
| 124 | <a id="adr0159-p5"></a> |
| 125 | |
| 126 | ### 5. Связка с `forge://` и `cide://` |
| 127 | |
| 128 | | Bracket | Magic link (canonical build) | |
| 129 | |---------|------------------------------| |
| 130 | | `[FRG:pilot/issues/7]` | `forge://{host}/issue?repo=pilot&n=7` | |
| 131 | | `[FRG:pilot/mr/3]` | `forge://{host}/mr?repo=pilot&n=3` | |
| 132 | | `[FRG:pilot/issues/7; F:src/Foo.cs; M:Run]` | `forge://{host}/lens?repo=pilot&n=7&root=…&b=F%3A…%3B%20M%3A…` → делегирует [0157](0157-cide-magic-link-protocol.md) | |
| 133 | |
| 134 | **Thin web:** рендер bracket как `<a href="https://…/view/…" data-forge-uri="forge://…">` — HTTPS первым ([FORGE-0007](../../../agent-forge/design/FORGE-ADR-0007-forge-magic-link-protocol.md)). |
| 135 | |
| 136 | **Display label** (UI): `[FRG:pilot/issues/7]` или chip «issue #7 · pilot» — derived from wire, не hand-authored. |
| 137 | |
| 138 | <a id="adr0159-p6"></a> |
| 139 | |
| 140 | ### 6. Code anchor: полный `AttachmentAnchor` |
| 141 | |
| 142 | Forge Lens storage и отображение **не** используют урезанный `file:line`: |
| 143 | |
| 144 | - wire / DB: те же поля, что [0128 §3](0128-intercom-attachment-anchors-and-code-references.md) (`file`, `memberKey`, `lineStart`, `lineEnd`, `syntaxScope`, …); |
| 145 | - human/agent surface: **`BracketCodeReferenceSerializer`** ↔ `AttachmentAnchor` (symmetric parse/format); |
| 146 | - subset в correspondence index: `CodeAnchor` ([0156](0156-correspondence-mfd-surface-and-reverse-code-anchors.md)). |
| 147 | |
| 148 | Пример в issue body: |
| 149 | |
| 150 | ```markdown |
| 151 | Регрессия в [F:src/hello.py; L:4-5; M:main] — см. [FRG:pilot/issues/1]. |
| 152 | ``` |
| 153 | |
| 154 | Structured block в metadata (FORGE-0012) дублирует anchors[]; body может содержать те же bracket для читаемости. |
| 155 | |
| 156 | <a id="adr0159-p7"></a> |
| 157 | |
| 158 | ### 7. Парсер: discriminant |
| 159 | |
| 160 | 1. Если inner начинается с `FRG:` (case-sensitive) → **forge family**. |
| 161 | 2. Иначе → **code family** (0128 §5.1). |
| 162 | |
| 163 | Общий entry point: `BracketReferenceParser.TryParse(string)` → `BracketReferenceKind.Code | Forge`. |
| 164 | |
| 165 | **Fenced code** ([0129](0129-intercom-message-body-markdown-and-fenced-code.md)): обе семьи **не** парсятся внутри fenced — как attach в 0128. |
| 166 | |
| 167 | <a id="adr0159-p8"></a> |
| 168 | |
| 169 | ### 8. Фазы |
| 170 | |
| 171 | | Фаза | Содержание | Репо | |
| 172 | |------|------------|------| |
| 173 | | **0** | Этот ADR + [FORGE-0012](../../../agent-forge/design/FORGE-ADR-0012-bracket-notation-and-lens-anchors.md) | cascade-ide, agent-forge | |
| 174 | | **1** | `ForgeArtifactRef` + parse/format unit tests; `ForgeLinkBuilder` bracket ↔ `forge://` | agent-forge | |
| 175 | | **2** | Thin web: anchor list as code-bracket; issue link as `[FRG:…]` | agent-forge | |
| 176 | | **3** | CIDE: `/forge goto`, markdown expander для `[FRG:…]`; CRS chip | cascade-ide | |
| 177 | | **4** | Intercom: опционально «copy as [FRG:…]» из Lens (не attach) | cascade-ide | |
| 178 | |
| 179 | --- |
| 180 | |
| 181 | ## Последствия |
| 182 | |
| 183 | ### Positive |
| 184 | |
| 185 | - Один mental model: **код** = `[F/M/L/S]`, **forge** = `[FRG:…]` — как Intercom, без третьего dialect. |
| 186 | - Агент пишет bracket в issue body; MCP может слать JSON — round-trip. |
| 187 | - Symmetry с [0157](0157-cide-magic-link-protocol.md) / [FORGE-0007](../../../agent-forge/design/FORGE-ADR-0007-forge-magic-link-protocol.md). |
| 188 | |
| 189 | ### Negative / trade-offs |
| 190 | |
| 191 | - Два парсера / один facade — нужны тесты на `[FRG:…]` vs `[F:…]` (false positive маловероятен: `FRG:` vs `F:`). |
| 192 | - Compound bracket `[FRG:…; F:…]` — optional; можно отложить до v1.1. |
| 193 | - Intercom attach forge v1 **не** входит — issue refs живут в forge/docs, не в chat event log. |
| 194 | |
| 195 | ## Alternatives considered |
| 196 | |
| 197 | | Alternative | Why not | |
| 198 | |-------------|---------| |
| 199 | | Ось `I:` внутри `[F;M;L;S]` | Смешивает artifact и code; ломает 0128 §5.1 | |
| 200 | | Только `forge://` без bracket | Плохо для агента, Intercom, plain markdown | |
| 201 | | Только `file:line` в web | Нет re-resolve, не AttachmentAnchor | |
| 202 | | `[FRG:]` как attach в Intercom v1 | Scope creep; forge refs — persistence layer | |
| 203 | |
| 204 | --- |
| 205 | |
| 206 | ## История |
| 207 | |
| 208 | | Дата | Изменение | |
| 209 | |------|-----------| |
| 210 | | 2026-06-11 | Accepted (design): `[FRG:…]` + полный AttachmentAnchor display для Lens. | |
| 211 | |