Forge
markdowndeeb25a2
1# ADR 0111: `LineNumber` и `LineRange` как доменные типы редактора
2
3## Связанные ADR
4
5| ADR | Роль |
6|-----|------|
7| [0081](0081-parametric-intent-melodies-editor-line-ranges.md) | Семантика параметрики по строкам; 0111 — VO LR/LN в домене IDE |
8| [0109](0109-declarative-parametric-melody-catalog-toml-and-code-binders.md) | Каталог, `tail_signature`; `:ln` — метаданные каталога |
9| [0110](0110-roslyn-refactor-intent-melody-bridge.md) | Roslyn по диапазону; будущий мост к аргументам MCP |
10
11## Резюме
12
13- Value objects **`LineNumber`** / **`LineRange`** (1-based, Start ≤ End).
14- Граница к JSON/командам — `int` в args; `ParsedLineRange` для парсинга.
15
16
17## Отклонённые альтернативы
18
19- **Только алиасы `:ln` в TOML** — не заменяют проверки и самодокументацию API в C#.
20- **`record` с int без инвариантов** — дублирование проверок в каждом потребителе.
21- **Полный переход команд на typed DTO вместо JSON** — вне scope v1; граница остаётся на сериализации.
22- **Спец-токены «до конца файла»** (`7:end`, `7:*` и т.п.) — не входят в v1: отдельная мини-грамматика, взаимодействие с длиной файла на parse vs build, риск коллизий; оставлено на отдельное решение при явной потребности.
23
24## Последствия (текущие)
25
26- Новые сценарии **только по строкам** в ветке parametric melody → предпочтительно через LR/LN.
27- Проверка «строки есть в файле» остаётся в **`ParametricLineRangeArgsBuilder`**, не в порядке двух чисел во вводе и не в LN/LR как таковых.
28
29---
30## Решение
31
32- Ввести **value objects** в пространстве имён `CascadeIDE.Models.Editor`:
33 - **LN (`LineNumber`)** — 1-based номер строки; инвариант `Value >= LineNumber.MinimumOneBasedInclusive` (1); создание через `TryCreate(int, out LineNumber)`; операторы сравнения (в т.ч. для CA1036 / `IComparable`).
34 - **LR (`LineRange`)** — пара `Start` / `End` типа LN с инвариантом `Start <= End` (инклюзивный диапазон в терминах редактора и IDE-команд); создание через `TryCreate(LineNumber, LineNumber, out LineRange)`.
35- **`ParametricIntentMelody.ParsedLineRange`** хранит диапазон как **`LineRange Lines`**, а не два «голых» `int`.
36- Парсер хвоста melody (`TryExtractLineRangeFromRemainder` в `ParametricIntentMelody`):
37 - **Одно целое** без второго слота — **одна строка** (`<line>` по смыслу совпадает с `<line>:<line>` как LR).
38 - **Два целых** — границы одного инклюзивного диапазона; порядок ввода не важен: после разбора применяется **`min..max`** (например `7:3` и `3:7` → один LR).
39 - У **`LineRange.TryCreate`** для **явного кода** контракт по-прежнему «второй аргумент не раньше первого»; «перевёрнутый» ввод обрабатывается **до** вызова `TryCreate`, за счёт нормализации в парсере.
40- На границе к JSON (**`ParametricLineRangeArgsBuilder`**) в анонимные DTO по-прежнему уходят **`int`** через `.Value` у LN — **wire-формат** `IdeCommands.Select` / `IdeCommands.ApplyEdit` не меняется.
41- **`IntentMelodyTailSemantics.MinEditorLineNumber`** согласован с **`LineNumber.MinimumOneBasedInclusive`** (одна константа-источник для «минимальной строки 1-based»).
42
43## Реализация v1 (источники в коде)
44
45| Артефакт | Назначение |
46|----------|------------|
47| `Models/Editor/LineNumber.cs` | LN, константа минимума, `TryCreate`, равенство и сравнение |
48| `Models/Editor/LineRange.cs` | LR, `TryCreate` при `End >= Start` |
49| `Services/ParametricIntentMelody.cs` | `ParsedLineRange`, `TryParseLineRangeTail`, `TryExtractLineRangeFromRemainder`, делегирование в `ParametricLineRangeArgsBuilder` |
50| `Services/ParametricLineRangeArgsBuilder.cs` | Сборка JSON-args из `ParsedLineRange.Lines` + проверка вылета за длину файла |
51| `Services/IntentMelodyTailSemantics.cs` | `MinEditorLineNumber` → ссылка на минимум LN |
52| `CascadeIDE.Tests/EditorLineNumberRangeTests.cs` | Юнит-тесты LN/LR |
53| `CascadeIDE.Tests/ParametricIntentMelodyTests.cs` | Парсинг, одна строка, `min..max`, сборка args |
54| `IntentMelody/intent-melody-aliases.toml` и копия в `publish-gh-release/IntentMelody/` | Подсказки палитры для `els` / `eld` (диапазон и одна строка) |
55
56Каталог по-прежнему описывает **два** числовых слота в `tail_signature` (`<start:ln>:<end:ln>`); сокращение «одно число» — **соглашение парсера приложения**, не отдельная строка каталога.
57
58## Реализация v2 (дорожная карта после v1 — закрыта в коде)
59
60| Артефакт | Назначение |
61|----------|------------|
62| `Models/Editor/ColumnNumber.cs` | CN — 1-based колонка (`TryCreate`, сравнение) |
63| `Models/Editor/EditorDocumentPath.cs` | Обёртка пути документа: `CanonicalFilePath.TryNormalize`, сравнение без регистра |
64| `Models/Editor/EditorMcpSpans.cs` | `EditorTextSpan.TryParse`, `EditorContentLineRangeMcpArgs.TryParse`, `EditorGoToPositionMcpArgs.TryParse` |
65| `Services/RoslynLinePositionMapper.cs` | `Microsoft.CodeAnalysis.Text.LinePosition` (0-based) → `(LineNumber, ColumnNumber)` для UI/MCP |
66| `Services/ContextMinimizer.cs`, `Services/WorkspaceDiagnosticsCoordinator.cs` | Используют маппер вместо дублирования `+1` |
67| `ViewModels/IdeMcpCommandExecutor.Handlers.Editor/*.cs` | `Select` / `ApplyEdit` / `GoToPosition` / `GetEditorContentRange` через общий разбор VO |
68| `Services/ParametricLineRangeArgsBuilder.cs` | Границы колонок через `ColumnNumber` + канонический `EditorDocumentPath` |
69| `Features/WebAiPortal/Application/WebAiPortalChatMixInFormatter.cs` | Снимок диапазона редактора: `LineRange` вместо «голых» int строк |
70| `CascadeIDE.Tests/EditorMcpSpansTests.cs`, расширение `EditorLineNumberRangeTests` | Отказы и границы парсера MCP |
71
72## Связанные ADR
73
74| ADR | Роль |
75|-----|------|
76| [0081](0081-parametric-intent-melodies-editor-line-ranges.md) | Семантика параметрики по строкам; 0111 — VO LR/LN в домене IDE |
77| [0109](0109-declarative-parametric-melody-catalog-toml-and-code-binders.md) | Каталог, `tail_signature`; `:ln` — метаданные каталога |
78| [0110](0110-roslyn-refactor-intent-melody-bridge.md) | Roslyn по диапазону; будущий мост к аргументам MCP |
79
80## Отклонённые альтернативы
81
82- **Только алиасы `:ln` в TOML** — не заменяют проверки и самодокументацию API в C#.
83- **`record` с int без инвариантов** — дублирование проверок в каждом потребителе.
84- **Полный переход команд на typed DTO вместо JSON** — вне scope v1; граница остаётся на сериализации.
85- **Спец-токены «до конца файла»** (`7:end`, `7:*` и т.п.) — не входят в v1: отдельная мини-грамматика, взаимодействие с длиной файла на parse vs build, риск коллизий; оставлено на отдельное решение при явной потребности.
86
87## Последствия (текущие)
88
89- Новые сценарии **только по строкам** в ветке parametric melody → предпочтительно через LR/LN.
90- Проверка «строки есть в файле» остаётся в **`ParametricLineRangeArgsBuilder`**, не в порядке двух чисел во вводе и не в LN/LR как таковых.
91
92---
93
94## Дорожная карта (после v1) — выполнено (v2)
95
96Цель итераций v2 — **тот же паттерн**: инварианты в типах до границы JSON/MCP, без смены wire-контрактов `IdeCommands`.
97
98### Приоритет 1 — граница MCP → редактор — **сделано**
99
100- **`EditorTextSpan`** + **`ColumnNumber`**, **`TryParse`** из `IReadOnlyDictionary<string, JsonElement>` для `Select` / `ApplyEdit`; **`EditorGoToPositionMcpArgs`** для `go_to_position`; **`EditorContentLineRangeMcpArgs`** для `get_editor_content_range` (при отсутствии ключей — 1..1 как раньше; инвертированный явный диапазон — отказ с сообщением).
101
102### Приоритет 2 — веб-портал — **сделано**
103
104- **`WebAiPortalChatMixInFormatter`**: `EditorRangeSnap` хранит **`LineRange`**; разбор JSON нормализует границы через `min/max` при «перевёрнутых» int в wire-ответе.
105
106### Приоритет 3 — parametric args — **сделано**
107
108- **`ParametricLineRangeArgsBuilder`**: колонки через **`ColumnNumber.TryCreate`**, путь через **`EditorDocumentPath`**.
109
110### Приоритет 4 — Roslyn — **сделано**
111
112- **`RoslynLinePositionMapper`**: `LinePosition` (0-based) → `(LineNumber, ColumnNumber)`; **`ContextMinimizer`**, **`WorkspaceDiagnosticsCoordinator`**.
113
114### Путь к файлу — **сделано**
115
116- **`EditorDocumentPath`** поверх **`CanonicalFilePath.TryNormalize`**.
117
118### Критерий готовности
119
120- Два call site с одним набором полей JSON (`Select` + `ApplyEdit`) → **`EditorTextSpan`**; тесты в **`EditorMcpSpansTests`**.
121
View only · write via MCP/CIDE