@rt-tools/agent-kit 0.12.0 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/assets/checks/board-runs.github.mjs +87 -0
  2. package/assets/checks/board.github.mjs +0 -40
  3. package/assets/checks/check-board.github.mjs +39 -8
  4. package/assets/checks/check-schema-drift.mjs +28 -5
  5. package/assets/checks/rt-kit-checks.config.mjs +12 -0
  6. package/assets/commands/feedback.md +8 -0
  7. package/assets/defaults/project.sh +50 -5
  8. package/assets/hooks/browser-guard-no-other-drivers.sh +1 -1
  9. package/assets/hooks/dev-server-guard.sh +1 -1
  10. package/assets/hooks/git-guard-delivery-folder.sh +99 -0
  11. package/assets/hooks/git-guard-delivery-signature.sh +10 -4
  12. package/assets/hooks/git-guard-delivery.sh +21 -69
  13. package/assets/hooks/git-guard-push-tests.sh +2 -2
  14. package/assets/hooks/hook-input.sh +23 -0
  15. package/assets/hooks/override-write-guard.sh +107 -0
  16. package/assets/hooks/rerun-guard.sh +1 -1
  17. package/assets/hooks/rule-source-guard.sh +126 -0
  18. package/assets/hooks/task-flow-guard.sh +18 -0
  19. package/assets/hooks/turn-exit-guard.sh +17 -0
  20. package/assets/patterns/git-workflow-pr.azure.md +4 -4
  21. package/assets/patterns/git-workflow-pr.github.md +4 -4
  22. package/assets/patterns/git-workflow-pr.gitlab.md +4 -4
  23. package/assets/patterns/task-flow-archive.md +23 -21
  24. package/assets/patterns/task-flow-close.md +98 -96
  25. package/assets/patterns/task-flow-resume.md +5 -3
  26. package/assets/pitfalls/agent-kit.md +80 -0
  27. package/assets/rules/deploy-flow.azure.md +15 -7
  28. package/assets/rules/deploy-flow.github.md +15 -6
  29. package/assets/rules/deploy-flow.gitlab.md +15 -7
  30. package/assets/rules/git-workflow.github.md +6 -1
  31. package/assets/rules/task-flow.md +41 -25
  32. package/assets/rules/turn-conduct.md +4 -0
  33. package/assets/skills/agent-kit.md +32 -77
  34. package/assets/templates/proposal.md +16 -1
  35. package/lib/proposals.d.ts +5 -1
  36. package/lib/proposals.d.ts.map +1 -1
  37. package/lib/proposals.js +74 -5
  38. package/lib/proposals.js.map +1 -1
  39. package/lib/shipment.d.ts.map +1 -1
  40. package/lib/shipment.fixture.d.ts +39 -0
  41. package/lib/shipment.fixture.d.ts.map +1 -0
  42. package/lib/shipment.fixture.js +99 -0
  43. package/lib/shipment.fixture.js.map +1 -0
  44. package/lib/shipment.js +59 -7
  45. package/lib/shipment.js.map +1 -1
  46. package/package.json +1 -1
  47. package/rt-tools-agent-kit-0.13.0.tgz +0 -0
  48. package/rt-tools-agent-kit-0.12.0.tgz +0 -0
@@ -55,10 +55,10 @@ description: Правило под «Закон о ведении работы»
55
55
  | `задача-взята` | задача в колонке работы, ветка по номеру, папка | написать замысел | `task-flow-start` |
56
56
  | `замысел-записан` | замысел лежит и после записи не правится | делать первый этап | `task-flow-start` |
57
57
  | `этап-идёт` | этап начат | доделать этап и отметить в ходе работы | `task-flow-resume` |
58
- | `этапы-кончились` | все этапы отмечены | прогнать набор и открыть PR черновиком | `task-flow-close` |
58
+ | `этапы-кончились` | все этапы отмечены | прогнать набор, влить договорённость, привести тексты | `task-flow-close` |
59
+ | `разбор-кончился` | набор зелёный, тексты приведены | разобрать папку последним коммитом | `task-flow-archive` |
60
+ | `папка-разобрана` | папки в ветке нет, запись в архиве есть | открыть PR черновиком | `task-flow-close` |
59
61
  | `работа-отдана` | PR открыт черновиком | взять следующую задачу | `task-flow-resume` |
60
- | `разбор-кончился` | прогон зелёный, замечаний нет | влить договорённость, привести тексты, разобрать папку | `task-flow-close` |
61
- | `папка-разобрана` | папки в ветке нет, запись в архиве есть | снять черновик и попросить влить | `task-flow-archive` |
62
62
  | `влито` | PR слит человеком | разбор работы правилами и сверка очереди | `task-flow-archive` |
63
63
 
64
64
  Ни у одного состояния обязательное действие не звучит как «ждать»: ожидание чужого шага
@@ -72,6 +72,12 @@ description: Правило под «Закон о ведении работы»
72
72
  Перечень показывается владельцу в начале работы, и на нём же отмечается, где стоим: иначе после
73
73
  шести вопросов не видно ни того, что будет дальше, ни сколько всего впереди.
74
74
 
75
+ Последние два состояния объявить на диске уже нечем: ход работы уезжает вместе с папкой, а
76
+ папка разбирается раньше, чем открывается PR. Признак у них поэтому в истории ветки — коммит
77
+ разбора папки, — и читает его гард, а не строка в файле. Цена названа прямо: с этой минуты и до
78
+ слияния состояние работы виду не подлежит, и хвост из четырёх шагов — открыть PR, дождаться
79
+ прогона, снять черновик, попросить влить — держится паттерном, а не объявлением.
80
+
75
81
  ## Ход
76
82
 
77
83
  Ход работы от просьбы владельца до закрытия: где стоит разбор, что требует гард и куда девается
@@ -93,16 +99,16 @@ flowchart TD
93
99
  H --> I{Этап сделан}
94
100
  I -->|Да| J[Отметка в ходе работы — единственном месте, где отмечается сделанное]
95
101
  J --> I
96
- I -->|Этапы кончились| K[PR открывается; исполнитель называет номер, чего ждёт и что сделает следом]
102
+ I -->|Этапы кончились| T[Набор прогоняется, договорённость вливается в спек домена, тексты приводятся к сделанному]
103
+ T --> N[Папка задачи разбирается последним коммитом: разбор просьбы — в описание прошлого, находки — к замыслу эпика, замысел — прочь]
104
+ N --> S[Сверка очереди работ]
105
+ S --> K[PR открывается черновиком; исполнитель называет номер, чего ждёт и что сделает следом]
97
106
  K --> W[Разбор закрытой работы правилами уходит в фон, находки ложатся на диск]
98
107
  W --> L[Пока PR ждёт разбора, берётся следующая задача]
99
108
  L --> M{Разбор и прогон кончились}
100
- M -->|Красный прогон или замечания| V[Чинится в той же ветке: замысел на диске ещё нужен]
109
+ M -->|Красный прогон или замечания| V[Чинится в той же ветке: замысла на диске уже нет, и признак работы гард берёт из истории ветки]
101
110
  V --> M
102
- M -->|Зелено и замечаний нет| T[Договорённость вливается в спек домена, тексты приводятся к сделанному]
103
- T --> N[Папка задачи разбирается последним коммитом: разбор просьбы — в описание прошлого, находки — к замыслу эпика, замысел — прочь]
104
- N --> S[Сверка очереди работ]
105
- S --> O[Черновик снимается, слияние нажимает человек]
111
+ M -->|Зелено и замечаний нет| O[Черновик снимается, слияние нажимает человек]
106
112
  ```
107
113
 
108
114
  ## Как закон применяется здесь
@@ -112,9 +118,11 @@ flowchart TD
112
118
  в ходе работы состояние и названную в замысле договорённость о продукте.
113
119
  - **Гард судит объявленный переход, а не наличие файлов.** Артефакт на диске не говорит, дошла
114
120
  ли работа до правки кода: пустой замысел, положенный ради снятия отказа, лежит так же, как
115
- написанный. Отказ снимает объявленное состояние, и снимают его четыре — `этап-идёт`,
116
- `этапы-кончились`, `работа-отдана` и `разбор-кончился`: прогон бывает красным, а разбор — с
117
- замечаниями, и починка идёт в ту же ветку.
121
+ написанный. Отказ снимает объявленное состояние, и снимают его три — `этап-идёт`,
122
+ `этапы-кончились` и `разбор-кончился`. Четвёртый путь у отказа не состояние, а история ветки:
123
+ папка, разобранная её коммитом, означает отданную работу, и правка по замечаниям разбора идёт
124
+ без замысла на диске — объявить состояние после уборки уже нечем. Прогон бывает красным, а
125
+ разбор — с замечаниями, и починка идёт в ту же ветку.
118
126
  - **Отказ по состоянию называет обязательное действие того состояния, которое объявлено.**
119
127
  Исполнитель, которому сказано только «не в том состоянии», перепишет строку состояния вместо
120
128
  того, чтобы сделать шаг.
@@ -136,13 +144,20 @@ flowchart TD
136
144
  дважды стало поводом обойти отказ гарда, вместо того чтобы завести папку и пойти дальше.
137
145
  Заводится она всегда и до первой правки; сколько заходов уйдёт на работу, заранее не знает
138
146
  никто.
147
+ - **Папка задачи разбирается последним коммитом до открытия PR, а не после одобрения.** Прежде
148
+ она стояла после: пока идёт разбор, замысел нужен на диске, иначе правку по замечаниям
149
+ отбивает гард. Но кнопку слияния нажимает человек на хостинге, куда гард не достаёт, и
150
+ вливает он, как только видит зелёное, — закрывающему коммиту места не остаётся вовсе. Трижды
151
+ подряд папка уехала в главную ветку неразобранной, и разобрать её было уже некому: работа
152
+ перешла к следующей задаче, а PR закрылся. Цена перестановки названа прямо: замысла с этой
153
+ минуты на диске нет, и признак отданной работы гард берёт из истории ветки.
139
154
  - **Открыв PR, исполнитель называет владельцу три вещи: номер, чего ждёт и что сделает
140
155
  следом.** Ждёт он прогона — до его конца о работе ничего не известно, кроме того, что она
141
- запушена. Следом идёт уборка: разбор папки задачи последним коммитом. Сказанное так владелец
142
- читает однозначно, а зелёный прогон на странице — нет: он говорит, что не сломано, и молчит
143
- о том, что ветка ждёт ещё одного коммита. Трижды подряд PR был влит внутри этого молчания.
144
- - **Просьба о слиянии — отдельный ход, и раньше уборки её не бывает.** Порядок один: PR открыт
145
- черновиком прогон зелёныйпапка задачи разобрана и запушена → черновик снят →
156
+ запушена. Следом идёт снятие черновика. Сказанное так владелец читает однозначно, а зелёный
157
+ прогон на странице — нет: он говорит, что не сломано, и молчит о том, что кнопка слияния у
158
+ черновика заблокирована.
159
+ - **Просьба о слиянии — отдельный ход, и раньше зелёного прогона её не бывает.** Порядок один:
160
+ папка задачи разобрана и запушена PR открыт черновиком прогон зелёный → черновик снят →
146
161
  исполнитель просит влить, называя номер. До этой просьбы работа не готова, сколько бы зелёного
147
162
  на её странице ни было.
148
163
  - **PR открывается черновиком, а не в конце работы.** Пока правка кода не выложена в PR,
@@ -192,21 +207,22 @@ flowchart TD
192
207
  это заготовка правки чужого дерева, и часть заготовок отпадает при первом же чтении; уехавшая
193
208
  без разбора, она становится работой того, кто её не заказывал. Сводка наблюдений уезжает
194
209
  всегда: она говорит, чем пользовались и чем нет, и мнением не является.
195
- - **Договорённость вливается в спек домена последним коммитом PR.** К этому моменту код
196
- написан, привязки известны, и в главной ветке директория `proposed/` не появляется вовсе.
210
+ - **Договорённость вливается в спек домена одним из последних коммитов ветки, до открытия
211
+ PR.** К этому моменту код написан, привязки известны, и в главной ветке директория
212
+ `proposed/` не появляется вовсе.
197
213
  Готовые к вливанию перечисляет `npm run check:specs`.
198
214
  - **Папка закрытой задачи разбирается, а не переносится целиком.** В `docs/archive/` уезжает
199
215
  то, что объясняет состоявшееся решение; остальное удаляется. Неразобранную ловит сверка
200
216
  очереди работ.
201
- - **Слияние отбивается, пока ветка везёт папку своей задачи.** Требование стоит на слиянии, а
202
- не на открытии PR: до слияния папка ещё нужна правка по замечаниям разбора идёт в ту
203
- же ветку, а без замысла на диске её отбивает гард хода работы. На открытии PR о лежащей
204
- папке говорится вслух, и только. Судится содержимое ветки, а не рабочее дерево: снесённая,
205
- но не закоммиченная папка въехала бы вместе с веткой.
217
+ - **Открытие PR отбивается, пока ветка везёт папку своей задачи.** Требование стоит здесь, а
218
+ не на слиянии: слияние нажимает человек на хостинге, где гардов нет вовсе, и его отказ до
219
+ владельца не доходит. На слиянии та же проверка остаётся вторым рубежом она ловит слияние,
220
+ идущее командой. Судится содержимое ветки, а не рабочее дерево: снесённая, но не
221
+ закоммиченная папка въехала бы вместе с веткой.
206
222
  - **Ветка, снёсшая папку, обязана прибавить запись в архив.** Снести дешевле, чем разобрать, и
207
223
  первым уходит разбор просьбы — единственная запись слов владельца. Что именно увезено,
208
224
  требование не судит: это судит владелец.
209
- - **Обход — строка `Task-folder-skip: <причина>` в PR или в самой команде слияния.**
225
+ - **Обход — строка `Task-folder-skip: <причина>` в PR или в самой команде.**
210
226
  Работа, вливаемая частями, папку до конца не разбирает. Чтение из команды работает и без
211
227
  сети: единственный сетевой путь отбивал бы оффлайн то самое слияние, причина которого
212
228
  написана в PR. Пустая причина обходом не считается, а сам обход снимает отказ, но не
@@ -101,6 +101,10 @@ flowchart TD
101
101
  работы и то, что за ход по ней сделано: правку файла или команду, меняющую дерево. Ход, в
102
102
  котором не было ни того ни другого, возвращается исполнителю вместе со следующим шагом из
103
103
  хода работы. Отданную и влитую работу страж не судит: она уже дождалась чужого шага.
104
+ - **Отданную работу страж узнаёт по истории ветки, когда объявить её на диске уже нечем.** Ход
105
+ работы уезжает вместе с папкой задачи, а папка разбирается до открытия заявки: с этой минуты
106
+ и до слияния строки состояния нет вовсе. Судимый вторым признаком, такой ход отбивался бы за
107
+ ожидание чужого прогона — за то самое, что работой не является и правильно им не считается.
104
108
  - **Работа без ветки и без папки задачи судится тем же стражем по второму признаку.** Состояния
105
109
  у неё нет, и первый признак взять неоткуда, — но ход, в котором не было ни одной правки
106
110
  дерева, не кончается и здесь. Раньше страж отпускал такую работу молча, и защищена она была
@@ -9,6 +9,9 @@ description: Переносимый слой правил агента — за
9
9
  настраивает надстройками. Разложенный файл несёт шапку и правке не подлежит — правится либо
10
10
  пакет, либо надстройка.
11
11
 
12
+ **Холодная часть:** `pitfalls.md` рядом — ловушки, грабли, на которые уже наступали. Грузится по
13
+ требованию, а не вместе со скилом.
14
+
12
15
  ## Когда брать
13
16
 
14
17
  - В файле, который собираешься править, стоит шапка
@@ -44,6 +47,20 @@ description: Переносимый слой правил агента — за
44
47
  разложенного хуже целого. Правится либо надстройка здесь, либо сам ресурс — а это работа того
45
48
  дерева, где пакет живёт исходниками, и здесь о ней не сказано ничего.
46
49
 
50
+ **Держит это гард места правки, а не память.** Правку файла с шапкой раскладки он отбивает в
51
+ минуту правки и называет адрес: источник, если дерево его держит, иначе надстройку. Прежде об
52
+ этом знал один `sync --check`, и говорил он на следующей раскладке — то есть в чужой ветке и
53
+ чужим ходом: отказ приходил тому, кто в этот день правил соседний ресурс. Правит копию не
54
+ только рука: форматтер дерева, дошедший до разложенного файла, переписывает его по-своему, и
55
+ раскладка читает это ровно как правку руками. Снятие копии гард пропускает: снятый файл
56
+ раскладка кладёт заново, и так эту поломку и чинят.
57
+
58
+ **Надстройка правится по разделу, а не кладётся целиком.** Разделы в неё дописывают разные ветки
59
+ и разные заходы, и положенная целиком она уносит все, которых эта правка не касалась: на их место
60
+ молча возвращается пакетный текст, раскладка после этого сходится, а сказанного деревом о себе
61
+ больше нет. Держит это второй гард — он отбивает запись поверх непустой надстройки и называет
62
+ размер того, что затрут: строки и число разделов. Дописывание в конец и правка по месту проходят.
63
+
47
64
  ## Команды
48
65
 
49
66
  ```bash
@@ -103,13 +120,24 @@ npx agent-kit propose # отправить груз в приём: сво
103
120
  сам агент: до неё такое слово адреса не получало вовсе и умирало вместе с сессией. Оба пути
104
121
  пишут в один файл дня и в одной форме; в сеть не ходит ни один — увозит их отправка.
105
122
 
106
- Каждая запись несёт три строки: **место** — куда правка встаёт в ресурсе, **повод** — что пошло
107
- не так без неё, и **чем закрывается** какие надстройки и добавки этого дерева снимаются, когда
108
- правка приедет редакцией пакета. Третья пишется затем, что предложение уезжает наружу, а
123
+ Каждая запись несёт четыре строки: **место** — куда правка встаёт в ресурсе, **повод** — что
124
+ пошло не так без неё, **ближайшее** точная цитата той строки ресурса, к которой это ближе
125
+ всего, и чего она не покрывает, и **чем закрывается** какие надстройки и добавки этого дерева
126
+ снимаются, когда правка приедет редакцией пакета.
127
+
128
+ Третья строка — единственная, которую проверяет машина: цитата ищется в ресурсе, и ненайденная
129
+ отбивает блок. Ближайшего нет вовсе — так и пишется: «нет». Стоит она затем, чтобы ресурс был
130
+ прочитан: разбор происшествия кончается предложением дописать статью в тот же ресурс, который
131
+ промах уже описывал, и вторая статья о том же дороже, чем её отсутствие. Похожесть текстов сюда
132
+ не годится — замер показал, что законное соседство двух статей одного правила и законный перенос
133
+ удачной статьи на соседнее место дают одно и то же число. Отбитый блок остаётся лежать с
134
+ отметкой «отбито» и причиной: видно, что разбор был, и видно, почему он не стал правкой.
135
+
136
+ Четвёртая пишется затем, что предложение уезжает наружу, а
109
137
  надстройка остаётся лежать здесь: без неё дерево не может сказать, какие из его надстроек
110
138
  исправленная редакция закрыла, — снять наугад страшно, оставить дёшево, и надстройка остаётся
111
139
  навсегда, молча замещая исправленный раздел пакета. Снимать нечего — так и пишется; пустой
112
- третья строка не бывает.
140
+ четвёртая строка не бывает.
113
141
 
114
142
  Каждому предложению ставится адрес: «пакет», «компаньон» или «дерево». Выгружаются они файлом в `.claude/rt-kit/proposals/`
115
143
  (форма — шаблон `proposal.md`), а `agent-kit propose` увозит в приём те, что адресованы
@@ -205,76 +233,3 @@ npx agent-kit propose # отправить груз в приём: сво
205
233
  - **Выключение живёт в настройке дерева, а не в настройке агента.** Строка, вырезанная из
206
234
  настроек агента руками, теряется на первой же правке того файла, и вернуть роль будет нечем.
207
235
  Здесь оно объявлено в одном месте и видно всякому, кто читает настройку дерева.
208
-
209
- ## Ловушки
210
-
211
- - **Набор, проверяющий гард, читает настройку того дерева, из которого его запустили.** Помощник
212
- берёт настройку по каталогу проекта, а его агент выставляет своим — и `cd` в прогонщике этого
213
- не перебивает. Набор, не заведший своего одноразового дерева, зеленеет от строки в чужой
214
- настройке: шесть сценариев гарда экзамена прошли ровно так, потому что роль была выключена в
215
- дереве, где их гоняли. Своё дерево объявляется на каждый сценарий, а не один раз на файл.
216
-
217
- - **Надстройка замещает раздел целиком, и пакетные пункты в нём приходится держать копией.**
218
- Дописать в раздел одну статью нечем: слияние идёт по заголовку `## `. Дерево, которому нужен
219
- один свой пункт, копирует к нему все пакетные — и с этого дня правка любого из них,
220
- приехавшая с новой версией, до этого дерева не доходит. Сверка разложенного молчит: она
221
- считает расхождением правку на месте, а не замещённый раздел. Признак виден по самим
222
- надстройкам — три из трёх прочитанных кончались абзацем о том, что перенос придётся делать
223
- руками. Поэтому надстройкой берут раздел, у которого пакетных пунктов немного, а разросшийся
224
- замещённый раздел — повод внести своё в пакет, а не держать его копией. Предложение, которым
225
- своё вносят, называет эту надстройку строкой «чем закрывается»: иначе приехавшая редакция и
226
- надстройка, которую она закрыла, не сопоставляются ничем, и надстройка остаётся замещать уже
227
- исправленный раздел.
228
-
229
- - **Готовый код пакета не называет имён одного дерева.** Префикс директив кита, ключи подписей
230
- и имена сущностей принадлежат тому дереву, где паттерн писали; разложенные в соседнем, они
231
- учат звать то, чего там нет вовсе. Имя директивы при этом отличается от имени в примере: по
232
- примеру видно, что он пример, а `<префикс>TableRow` из чужого дерева выглядит рабочим кодом
233
- и правится только после того, как продовая сборка упадёт. Тринадцать таких имён простояли в
234
- четырёх ресурсах пакета, пока их не нашёл потребитель — и не своей сборкой, а надстройкой,
235
- которой перекрыл раздел.
236
-
237
- - **Правила и паттерны при отвергнутом законе в отказе не перечисляются.** Их снимает каскад, а
238
- строка на них становится выводимой: раскладка называет её лишней вместе с законом, из-за
239
- которого она перестала снимать. Отказ мерит слой законов, а не число файлов в пакете.
240
-
241
- - **Линтер по следам правки судит файл целиком, а не внесённую правку.** Импорт, добавленный
242
- отдельным шагом, отбивается как неиспользуемый ещё до того, как появится строка, которая его
243
- зовёт, и работа встаёт на половине. Правка делается одним вызовом либо в порядке «сначала
244
- использование, потом импорт».
245
- - **Прогонять сценарии гардов можно, ничего не раскладывая.** Обвязка набора принимает каталог
246
- гардов переменной, и пакетную редакцию гоняют по сценариям дерева до установки. Заход,
247
- потраченный на диагноз по последствиям, стоил ровно этой строки.
248
- - **Отбитая правка не всегда про текст гарда.** Гард зовут по пути, и файл без права на
249
- запуск отвечает отказом доступа — ненулевым кодом, который читается как «правка отбита».
250
- Набор при этом отбивает подряд всё, включая сборку и тесты, и причины не называет.
251
- - **Правка shell-скрипта заменой по шаблону сверяется `bash -n` сразу.** Замена границ
252
- конструкции не видит: `case` теряет свою `esac`, файл остаётся синтаксически неверным, а
253
- гард с ошибкой синтаксиса отвечает ненулевым кодом — то есть «правка отбита». Два раза за
254
- заход, и оба раза это выглядело дефектом самого гарда.
255
- - **Гард, подписанный не на то, что объявляет, выглядит работающим.** Событие и образец вызова
256
- гард несёт сам, строкой `# rt-hook:`, а зовёт его образец в настройке агента — и эти двое
257
- расходятся молча: путь гарда в настройке назван, файл разложен, набор сценариев зелёный.
258
- Набор тут ничего не ловит намеренно — он зовёт гард напрямую с подставленным вводом и
259
- объявления не читает вовсе. Так гейт правил и разбирал вызовы браузера веткой, которая не
260
- исполнялась ни разу. Расхождение находит сверка раскладки: она сравнивает объявленный образец
261
- с тем, под которым гард стоит, называет обе стороны и идёт в счёт расхождений. Правится
262
- настройка, а верное значение лежит в гарде — тело его разбирает то, что объявлено.
263
- - **Разложенный файл узнаётся по шапке, а не по каталогу.** Раскладка ложится в те же
264
- `tools/`, `.claude/hooks/` и `.claude/skills/`, где лежит своё, поэтому карта гейта,
265
- написанная по путям, требует под него доменное правило — а оно уводит править файл на месте.
266
- Правка на месте теряется на следующей раскладке, и до тех пор выглядит применённой. Ветка по
267
- шапке ставится в карте первой и решает раньше путей.
268
- - **Настройки проверок сливаются по ключам, а списки — замещаются.** Объект `checks.json`
269
- ложится поверх умолчания ключ за ключом на любой глубине: назвав один ключ борды, дерево не
270
- теряет соседних. Список приходит целиком — назвав корни исходников, дерево получает ровно
271
- названное, а не умолчание вместе со своим: «дописать в список» и «убрать из списка» в этой
272
- записи неразличимы.
273
- - **Утверждение правила переезжает вместе с кодом.** Вынесенное в надстройку перестаёт
274
- находиться по прежнему символу, и привязка в спутнике правила врёт молча — сверка спеков
275
- ловит это, но только если её позвать.
276
- - **Признак по подстроке пути судит и то, что лежит вне дерева.** Гарды отдают в профиль
277
- абсолютный путь целиком, и образец вида `*/projects/*` совпадает с домашним каталогом агента
278
- ровно так же, как с кодом дерева: запись в файл вне репозитория была отбита гардом хода
279
- работы с требованием замысла, к ней не относящегося. Путь в профиле сначала приводится к
280
- корню дерева, и всё, что вне корня, признака не получает.
@@ -22,13 +22,26 @@
22
22
  имени: файл уезжает в чужой репозиторий целиком. Найденный адрес дерева отбивает отправку с
23
23
  номером строки — это проверка, а не напоминание.
24
24
 
25
- Строк при заголовке три, и все три обязательны:
25
+ Строк при заголовке четыре, и все четыре обязательны:
26
26
 
27
27
  место — куда правка встаёт в ресурсе
28
28
  повод — что пошло не так без неё
29
+ ближайшее — точная цитата той строки ресурса, к которой это ближе всего, и чего она не
30
+ покрывает; ближайшего нет вовсе — так и пишется: «нет»
29
31
  чем закрывается — какие надстройки и добавки этого дерева снимаются, когда правка приедет
30
32
  редакцией пакета
31
33
 
34
+ Третья строка — единственная, которую машина проверяет: цитата ищется в ресурсе, и ненайденная
35
+ отбивает блок. Написана она затем, чтобы ресурс был прочитан. Разбор происшествия кончается
36
+ предложением дописать статью в тот же ресурс, который промах уже описывал, — и снаружи такой
37
+ разбор неотличим от разбора, кончившегося исправлением. Мерить похожесть текстов пробовали
38
+ замером: законное соседство двух статей одного правила даёт 0.345 общих значимых слов, а
39
+ законный перенос удачной статьи на соседнее место — 0.355, и порога между ними нет. Названная
40
+ цитата судится фактом: она в ресурсе либо есть, либо её там нет.
41
+
42
+ Отбитый блок не пропадает: он остаётся лежать с отметкой «отбито» и причиной. Видно, что разбор
43
+ был, и видно, почему он не стал правкой.
44
+
32
45
  Третья пишется затем, что предложение уезжает наружу, а надстройка, ради которой оно написано,
33
46
  остаётся лежать в дереве, и связи между ними нет никакой. Пакет выпускает исправленную редакцию
34
47
  — и сказать, какие надстройки она закрыла, дереву нечем: имя ресурса совпадает у десятка
@@ -45,6 +58,7 @@
45
58
 
46
59
  - **место:** раздел «<заголовок>», в конец
47
60
  - **повод:** что в этой задаче пошло не так без этого правила
61
+ - **ближайшее:** «<точная строка правила, к которой это ближе всего>» — <чего она не покрывает>
48
62
  - **чем закрывается:** `.claude/rt-kit/overrides/rules/<правило>.md`, раздел «<заголовок>» —
49
63
  снимается целиком, когда правка приедет редакцией пакета
50
64
 
@@ -55,6 +69,7 @@
55
69
 
56
70
  - **место:** ветка `edit`, рядом с соседним родом файлов
57
71
  - **повод:** свой род файлов, которого у других деревьев нет
72
+ - **ближайшее:** нет — про этот род файлов карта не говорит вовсе
58
73
  - **чем закрывается:** ничего, надстройки под это нет — правка и есть надстройка
59
74
 
60
75
  > Готовый текст правки.
@@ -11,6 +11,8 @@ export interface IProposal {
11
11
  readonly body: string;
12
12
  /** Ссылка на заведённую запись; пусто — не отправлялось. */
13
13
  readonly sent: string;
14
+ /** Поле ближайшего утверждения, как его написали; пусто — поля нет вовсе. */
15
+ readonly nearest: string;
14
16
  /** Файл, в котором блок лежит, путём от корня дерева. */
15
17
  readonly file: string;
16
18
  /** Строка заголовка в файле, считая с единицы. */
@@ -43,6 +45,8 @@ export declare function leaksIn(text: string, marks: readonly string[]): readonl
43
45
  * встречается чаще, чем путь целиком.
44
46
  */
45
47
  export declare function marksOf(root: string, remote: string): readonly string[];
48
+ /** Почему блок не уезжает; пустая строка — уезжает. */
49
+ export declare function nearestMissing(proposal: IProposal, assetsDir: string): string;
46
50
  /**
47
51
  * Пометка об отправке дописывается в блок, а не в конец файла: блоков в файле несколько.
48
52
  *
@@ -52,5 +56,5 @@ export declare function marksOf(root: string, remote: string): readonly string[]
52
56
  * пятая — на четыре, то есть посреди готового текста правки, а последнему блоку её не достаётся
53
57
  * вовсе, и при следующей отправке он уезжает вторым разом.
54
58
  */
55
- export declare function markSent(text: string, proposal: IProposal, url: string): string;
59
+ export declare function markSent(text: string, proposal: IProposal, url: string, field?: string): string;
56
60
  //# sourceMappingURL=proposals.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"proposals.d.ts","sourceRoot":"","sources":["../../../projects/agent-kit/src/lib/proposals.ts"],"names":[],"mappings":"AAeA,mEAAmE;AACnE,eAAO,MAAM,aAAa,EAAE,MAAmC,CAAC;AAEhE,yEAAyE;AACzE,eAAO,MAAM,UAAU,EAAE,MAAgB,CAAC;AAC1C,eAAO,MAAM,YAAY,EAAE,MAAoB,CAAC;AAChD,eAAO,MAAM,OAAO,EAAE,MAAiB,CAAC;AAqCxC,MAAM,WAAW,SAAS;IACtB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,8DAA8D;IAC9D,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,4DAA4D;IAC5D,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,yDAAyD;IACzD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,kDAAkD;IAClD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACzB;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,SAAS,SAAS,EAAE,CAgC/E;AAED,oDAAoD;AACpD,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,SAAS,EAAE,CAUhE;AAED,MAAM,WAAW,KAAK;IAClB,mDAAmD;IACnD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;CACxB;AAKD;;;;;GAKG;AACH,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,KAAK,EAAE,CAgBhF;AAED;;;GAGG;AACH,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,CAIvE;AAED;;;;;;;;GAQG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,SAAS,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAQ/E"}
1
+ {"version":3,"file":"proposals.d.ts","sourceRoot":"","sources":["../../../projects/agent-kit/src/lib/proposals.ts"],"names":[],"mappings":"AAeA,mEAAmE;AACnE,eAAO,MAAM,aAAa,EAAE,MAAmC,CAAC;AAEhE,yEAAyE;AACzE,eAAO,MAAM,UAAU,EAAE,MAAgB,CAAC;AAC1C,eAAO,MAAM,YAAY,EAAE,MAAoB,CAAC;AAChD,eAAO,MAAM,OAAO,EAAE,MAAiB,CAAC;AA2ExC,MAAM,WAAW,SAAS;IACtB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,8DAA8D;IAC9D,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,4DAA4D;IAC5D,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,6EAA6E;IAC7E,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,yDAAyD;IACzD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,kDAAkD;IAClD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACzB;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,SAAS,SAAS,EAAE,CAwC/E;AAED,oDAAoD;AACpD,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,SAAS,EAAE,CAUhE;AAED,MAAM,WAAW,KAAK;IAClB,mDAAmD;IACnD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;CACxB;AAKD;;;;;GAKG;AACH,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,KAAK,EAAE,CAgBhF;AAED;;;GAGG;AACH,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,CAIvE;AAOD,uDAAuD;AACvD,wBAAgB,cAAc,CAAC,QAAQ,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAuB7E;AAED;;;;;;;;GAQG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,SAAS,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,GAAE,MAAqB,GAAG,MAAM,CAU7G"}
package/lib/proposals.js CHANGED
@@ -39,7 +39,40 @@ function headingOf(line) {
39
39
  return address && !address.includes(' ') && resource ? { address, resource } : null;
40
40
  }
41
41
  /** Пометка об отправке. По ней же предложение узнаётся отправленным. */
42
- const SENT = /^-\s+\*\*отправлено:\*\*\s*(\S+)/m;
42
+ const SENT = /^- +\*\*отправлено:\*\* *(\S+)/m;
43
+ /**
44
+ * Поле ближайшего утверждения: точная цитата строки ресурса, к которой блок относится, — либо
45
+ * слово о том, что ближайшего нет вовсе.
46
+ *
47
+ * Поле обязательно, и обязательно оно затем, чтобы ресурс был прочитан. Мерить похожесть текстов
48
+ * пробовали замером: законное соседство двух статей одного правила дало 0.345 общих значимых
49
+ * слов, а законный перенос удачной статьи на соседнее место — 0.355. Порога между ними нет, и
50
+ * назначенный наугад он отбивал бы правки вместо повторов. Названная цитата судится фактом: она
51
+ * в ресурсе либо есть, либо её там нет.
52
+ */
53
+ const NEAREST = /^- +\*\*ближайшее:\*\* *([^\n]+)/m;
54
+ /**
55
+ * Слово о том, что ближайшего утверждения в ресурсе нет: цитировать нечего.
56
+ *
57
+ * Конец слова здесь проверяется отрицательным просмотром, а не границей слова: границу движок
58
+ * считает по латинице и цифрам, и между «т» и пробелом её нет вовсе — образец с `\b` не совпал
59
+ * бы ни с одним русским словом.
60
+ */
61
+ const NOTHING_NEAR = /^нет(?![а-яё])/i;
62
+ /**
63
+ * Цитата ближайшего утверждения: кавычки-ёлочки, как в остальных текстах дерева.
64
+ *
65
+ * Ищется позициями, а не образцом: образец на «открыть, набрать не-закрывающих, закрыть» линтер
66
+ * отбивает как ветвящийся, и поводы у него есть — строка без закрывающей кавычки перебирается им
67
+ * до конца.
68
+ */
69
+ function quotedIn(text) {
70
+ const from = text.indexOf('«');
71
+ const to = from < 0 ? -1 : text.indexOf('»', from + 1);
72
+ return from >= 0 && to > from ? text.slice(from + 1, to) : '';
73
+ }
74
+ /** Пометка любого рода: по ней считается сдвиг, когда в файл вставляют ещё одну. */
75
+ const MARKED = /^- +\*\*(отправлено|отбито):\*\*/m;
43
76
  /**
44
77
  * Блоки файла предложений.
45
78
  *
@@ -61,7 +94,15 @@ export function parseProposals(text, file) {
61
94
  .trim();
62
95
  const { address, resource } = heading;
63
96
  if (ADDRESSES.includes(address) && !resource.includes('<')) {
64
- found.push({ address, resource, body, file, sent: SENT.exec(body)?.[1] ?? '', line: at + 1 });
97
+ found.push({
98
+ address,
99
+ resource,
100
+ body,
101
+ file,
102
+ sent: SENT.exec(body)?.[1] ?? '',
103
+ nearest: (NEAREST.exec(body)?.[1] ?? '').trim(),
104
+ line: at + 1,
105
+ });
65
106
  }
66
107
  at = -1;
67
108
  };
@@ -117,6 +158,32 @@ export function marksOf(root, remote) {
117
158
  const name = basename(root);
118
159
  return [root, name, remote].filter(Boolean);
119
160
  }
161
+ /** Один пробел вместо любой пробельной вереницы: цитата в блоке перенесена по своей ширине. */
162
+ function flat(text) {
163
+ return text.replace(/\s+/g, ' ').trim();
164
+ }
165
+ /** Почему блок не уезжает; пустая строка — уезжает. */
166
+ export function nearestMissing(proposal, assetsDir) {
167
+ if (!proposal.nearest) {
168
+ return 'поля «ближайшее» нет: назови точную строку ресурса, к которой это относится, и то, чего она не покрывает, — либо напиши «нет»';
169
+ }
170
+ if (NOTHING_NEAR.test(proposal.nearest)) {
171
+ return '';
172
+ }
173
+ const quoted = quotedIn(proposal.nearest);
174
+ if (!quoted) {
175
+ return 'ближайшее названо без цитаты: строка ресурса приводится в кавычках-ёлочках дословно — иначе проверить нечего';
176
+ }
177
+ const path = join(assetsDir, proposal.resource);
178
+ if (!existsSync(path)) {
179
+ // Ресурса у этого дерева нет — сверять не с чем, и отбивать нечего: блок про ресурс,
180
+ // которого пакет здесь не держит, судится на приёмной стороне.
181
+ return '';
182
+ }
183
+ return flat(readFileSync(path, 'utf8')).includes(flat(quoted))
184
+ ? ''
185
+ : `цитаты нет в «${proposal.resource}»: ${flat(quoted).slice(0, 60)}… — либо ресурс не читали, либо утверждение переписано с тех пор`;
186
+ }
120
187
  /**
121
188
  * Пометка об отправке дописывается в блок, а не в конец файла: блоков в файле несколько.
122
189
  *
@@ -126,12 +193,14 @@ export function marksOf(root, remote) {
126
193
  * пятая — на четыре, то есть посреди готового текста правки, а последнему блоку её не достаётся
127
194
  * вовсе, и при следующей отправке он уезжает вторым разом.
128
195
  */
129
- export function markSent(text, proposal, url) {
196
+ export function markSent(text, proposal, url, field = 'отправлено') {
130
197
  const lines = text.split('\n');
131
- const shift = lines.slice(0, proposal.line).filter((line) => SENT.test(line)).length;
198
+ // Сдвиг считает пометки обоих родов: отбитый блок получает свою, и следующая за ним встала бы
199
+ // строкой выше своего места, если бы её не посчитали.
200
+ const shift = lines.slice(0, proposal.line).filter((line) => MARKED.test(line)).length;
132
201
  // Пометка встаёт сразу под заголовок: конец блока определяется следующим заголовком, а его
133
202
  // может и не быть — тогда «конец» пришлось бы искать по пустым строкам в хвосте файла.
134
- lines.splice(proposal.line + shift, 0, `- **отправлено:** ${url}`);
203
+ lines.splice(proposal.line + shift, 0, `- **${field}:** ${url}`);
135
204
  return lines.join('\n');
136
205
  }
137
206
  //# sourceMappingURL=proposals.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"proposals.js","sourceRoot":"","sources":["../../../projects/agent-kit/src/lib/proposals.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Предложения по слою правил: разбор файла, отбор того, что уезжает наружу, и проверка на адрес\n * дерева.\n *\n * Файл пишет главный агент после разбора закрытой задачи — по блоку на предложение, с адресом в\n * заголовке. Адрес роль проставляет и так; здесь он впервые становится тем, что читает машина.\n *\n * Проверка на утечку живёт в этом же модуле не для удобства: предложение уезжает в чужой\n * репозиторий целиком, и запрет называть чужое дерево обязан держаться проверкой, а не памятью\n * того, кто пишет.\n */\nimport { existsSync, readdirSync, readFileSync } from 'node:fs';\nimport { basename, join } from 'node:path';\nimport { byText } from './order.js';\n\n/** Куда главный агент кладёт предложения. Путь от корня дерева. */\nexport const PROPOSALS_DIR: string = '.claude/rt-kit/proposals';\n\n/** Адрес предложения: куда правка идёт. Наружу уезжает только первый. */\nexport const TO_PACKAGE: string = 'пакет';\nexport const TO_COMPANION: string = 'компаньон';\nexport const TO_TREE: string = 'дерево';\n\nconst ADDRESSES: readonly string[] = [TO_PACKAGE, TO_COMPANION, TO_TREE];\n\n/** Разобранный заголовок блока. */\ninterface IHeading {\n readonly address: string;\n readonly resource: string;\n}\n\n/**\n * Заголовок блока: `## <адрес> · <ресурс>`. Разделитель — тот же, что в шаблоне.\n *\n * Разбирается строкой, а не образцом: образец на «слово, разделитель, остаток» перебирает\n * границу слова столько раз, сколько в строке знаков.\n */\nfunction headingOf(line: string): IHeading | null {\n if (!line.startsWith('## ')) {\n return null;\n }\n\n const rest: string = line.slice(3);\n const at: number = rest.indexOf('·');\n\n if (at < 0) {\n return null;\n }\n\n const address: string = rest.slice(0, at).trim();\n const resource: string = rest.slice(at + 1).trim();\n\n return address && !address.includes(' ') && resource ? { address, resource } : null;\n}\n\n/** Пометка об отправке. По ней же предложение узнаётся отправленным. */\nconst SENT: RegExp = /^-\\s+\\*\\*отправлено:\\*\\*\\s*(\\S+)/m;\n\nexport interface IProposal {\n readonly address: string;\n readonly resource: string;\n /** Тело блока без заголовка: место, повод и готовый текст. */\n readonly body: string;\n /** Ссылка на заведённую запись; пусто — не отправлялось. */\n readonly sent: string;\n /** Файл, в котором блок лежит, путём от корня дерева. */\n readonly file: string;\n /** Строка заголовка в файле, считая с единицы. */\n readonly line: number;\n}\n\n/**\n * Блоки файла предложений.\n *\n * Незаполненный образец пропускается: в шаблоне ресурс стоит скобками — `rules/<правило>.md`, —\n * и отправить его значило бы завести запись о правке несуществующего файла.\n */\nexport function parseProposals(text: string, file: string): readonly IProposal[] {\n const lines: readonly string[] = text.split('\\n');\n const found: IProposal[] = [];\n let at: number = -1;\n\n const close: (end: number) => void = (end: number): void => {\n if (at < 0) {\n return;\n }\n const heading: IHeading = headingOf(lines[at]) as IHeading;\n const body: string = lines\n .slice(at + 1, end)\n .join('\\n')\n .trim();\n const { address, resource }: IHeading = heading;\n if (ADDRESSES.includes(address) && !resource.includes('<')) {\n found.push({ address, resource, body, file, sent: SENT.exec(body)?.[1] ?? '', line: at + 1 });\n }\n at = -1;\n };\n\n lines.forEach((line: string, index: number): void => {\n if (line.startsWith('## ')) {\n close(index);\n if (headingOf(line) !== null) {\n at = index;\n }\n }\n });\n close(lines.length);\n\n return found;\n}\n\n/** Все файлы предложений дерева, в порядке имён. */\nexport function readProposals(root: string): readonly IProposal[] {\n const dir: string = join(root, PROPOSALS_DIR);\n if (!existsSync(dir)) {\n return [];\n }\n\n return readdirSync(dir)\n .filter((name: string): boolean => name.endsWith('.md'))\n .sort(byText)\n .flatMap((name: string): readonly IProposal[] => parseProposals(readFileSync(join(dir, name), 'utf8'), join(PROPOSALS_DIR, name)));\n}\n\nexport interface ILeak {\n /** Строка блока, считая с единицы от заголовка. */\n readonly line: number;\n readonly text: string;\n readonly why: string;\n}\n\n/** Абсолютный путь машины: он не бывает общим ни для одного дерева, кроме этого. */\nconst ABSOLUTE: RegExp = /(^|[\\s(`'\"])(~\\/|\\/(Users|home|var|opt|srv|mnt)\\/)\\S+/;\n\n/**\n * Что в тексте называет дерево, из которого предложение уезжает.\n *\n * `marks` — имена, которые знает только это дерево: корень, его последнее звено, адрес его\n * удалённого репозитория. Пакет их не угадывает — они приходят доводом с края.\n */\nexport function leaksIn(text: string, marks: readonly string[]): readonly ILeak[] {\n const found: ILeak[] = [];\n\n text.split('\\n').forEach((line: string, index: number): void => {\n if (ABSOLUTE.test(line)) {\n found.push({ line: index + 1, text: line.trim(), why: 'абсолютный путь этой машины' });\n\n return;\n }\n const mark: string | undefined = marks.find((value: string): boolean => Boolean(value) && line.includes(value));\n if (mark) {\n found.push({ line: index + 1, text: line.trim(), why: `имя этого дерева: ${mark}` });\n }\n });\n\n return found;\n}\n\n/**\n * Чем дерево себя выдаёт. Последнее звено пути берётся отдельно от полного: в тексте оно\n * встречается чаще, чем путь целиком.\n */\nexport function marksOf(root: string, remote: string): readonly string[] {\n const name: string = basename(root);\n\n return [root, name, remote].filter(Boolean);\n}\n\n/**\n * Пометка об отправке дописывается в блок, а не в конец файла: блоков в файле несколько.\n *\n * Место считается от номера строки заголовка — но номера у всех блоков посчитаны при разборе, до\n * единой вставки, а каждая вставка сдвигает файл на строку вниз. Поэтому к номеру прибавляется\n * число пометок, уже стоящих выше: без этого вторая пометка встаёт на строку выше своего места,\n * пятая — на четыре, то есть посреди готового текста правки, а последнему блоку её не достаётся\n * вовсе, и при следующей отправке он уезжает вторым разом.\n */\nexport function markSent(text: string, proposal: IProposal, url: string): string {\n const lines: string[] = text.split('\\n');\n const shift: number = lines.slice(0, proposal.line).filter((line: string): boolean => SENT.test(line)).length;\n // Пометка встаёт сразу под заголовок: конец блока определяется следующим заголовком, а его\n // может и не быть — тогда «конец» пришлось бы искать по пустым строкам в хвосте файла.\n lines.splice(proposal.line + shift, 0, `- **отправлено:** ${url}`);\n\n return lines.join('\\n');\n}\n"]}
1
+ {"version":3,"file":"proposals.js","sourceRoot":"","sources":["../../../projects/agent-kit/src/lib/proposals.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAChE,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAC3C,OAAO,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AAEpC,mEAAmE;AACnE,MAAM,CAAC,MAAM,aAAa,GAAW,0BAA0B,CAAC;AAEhE,yEAAyE;AACzE,MAAM,CAAC,MAAM,UAAU,GAAW,OAAO,CAAC;AAC1C,MAAM,CAAC,MAAM,YAAY,GAAW,WAAW,CAAC;AAChD,MAAM,CAAC,MAAM,OAAO,GAAW,QAAQ,CAAC;AAExC,MAAM,SAAS,GAAsB,CAAC,UAAU,EAAE,YAAY,EAAE,OAAO,CAAC,CAAC;AAQzE;;;;;GAKG;AACH,SAAS,SAAS,CAAC,IAAY;IAC3B,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,EAAE,CAAC;QAC1B,OAAO,IAAI,CAAC;IAChB,CAAC;IAED,MAAM,IAAI,GAAW,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IACnC,MAAM,EAAE,GAAW,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IAErC,IAAI,EAAE,GAAG,CAAC,EAAE,CAAC;QACT,OAAO,IAAI,CAAC;IAChB,CAAC;IAED,MAAM,OAAO,GAAW,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;IACjD,MAAM,QAAQ,GAAW,IAAI,CAAC,KAAK,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IAEnD,OAAO,OAAO,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,QAAQ,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;AACxF,CAAC;AAED,wEAAwE;AACxE,MAAM,IAAI,GAAW,iCAAiC,CAAC;AAEvD;;;;;;;;;GASG;AACH,MAAM,OAAO,GAAW,mCAAmC,CAAC;AAE5D;;;;;;GAMG;AACH,MAAM,YAAY,GAAW,iBAAiB,CAAC;AAE/C;;;;;;GAMG;AACH,SAAS,QAAQ,CAAC,IAAY;IAC1B,MAAM,IAAI,GAAW,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IACvC,MAAM,EAAE,GAAW,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,IAAI,GAAG,CAAC,CAAC,CAAC;IAE/D,OAAO,IAAI,IAAI,CAAC,IAAI,EAAE,GAAG,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,GAAG,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;AAClE,CAAC;AAED,oFAAoF;AACpF,MAAM,MAAM,GAAW,mCAAmC,CAAC;AAiB3D;;;;;GAKG;AACH,MAAM,UAAU,cAAc,CAAC,IAAY,EAAE,IAAY;IACrD,MAAM,KAAK,GAAsB,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAClD,MAAM,KAAK,GAAgB,EAAE,CAAC;IAC9B,IAAI,EAAE,GAAW,CAAC,CAAC,CAAC;IAEpB,MAAM,KAAK,GAA0B,CAAC,GAAW,EAAQ,EAAE;QACvD,IAAI,EAAE,GAAG,CAAC,EAAE,CAAC;YACT,OAAO;QACX,CAAC;QACD,MAAM,OAAO,GAAa,SAAS,CAAC,KAAK,CAAC,EAAE,CAAC,CAAa,CAAC;QAC3D,MAAM,IAAI,GAAW,KAAK;aACrB,KAAK,CAAC,EAAE,GAAG,CAAC,EAAE,GAAG,CAAC;aAClB,IAAI,CAAC,IAAI,CAAC;aACV,IAAI,EAAE,CAAC;QACZ,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,GAAa,OAAO,CAAC;QAChD,IAAI,SAAS,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;YACzD,KAAK,CAAC,IAAI,CAAC;gBACP,OAAO;gBACP,QAAQ;gBACR,IAAI;gBACJ,IAAI;gBACJ,IAAI,EAAE,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE;gBAChC,OAAO,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE;gBAC/C,IAAI,EAAE,EAAE,GAAG,CAAC;aACf,CAAC,CAAC;QACP,CAAC;QACD,EAAE,GAAG,CAAC,CAAC,CAAC;IACZ,CAAC,CAAC;IAEF,KAAK,CAAC,OAAO,CAAC,CAAC,IAAY,EAAE,KAAa,EAAQ,EAAE;QAChD,IAAI,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,EAAE,CAAC;YACzB,KAAK,CAAC,KAAK,CAAC,CAAC;YACb,IAAI,SAAS,CAAC,IAAI,CAAC,KAAK,IAAI,EAAE,CAAC;gBAC3B,EAAE,GAAG,KAAK,CAAC;YACf,CAAC;QACL,CAAC;IACL,CAAC,CAAC,CAAC;IACH,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;IAEpB,OAAO,KAAK,CAAC;AACjB,CAAC;AAED,oDAAoD;AACpD,MAAM,UAAU,aAAa,CAAC,IAAY;IACtC,MAAM,GAAG,GAAW,IAAI,CAAC,IAAI,EAAE,aAAa,CAAC,CAAC;IAC9C,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;QACnB,OAAO,EAAE,CAAC;IACd,CAAC;IAED,OAAO,WAAW,CAAC,GAAG,CAAC;SAClB,MAAM,CAAC,CAAC,IAAY,EAAW,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;SACvD,IAAI,CAAC,MAAM,CAAC;SACZ,OAAO,CAAC,CAAC,IAAY,EAAwB,EAAE,CAAC,cAAc,CAAC,YAAY,CAAC,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,EAAE,IAAI,CAAC,aAAa,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC;AAC3I,CAAC;AASD,oFAAoF;AACpF,MAAM,QAAQ,GAAW,uDAAuD,CAAC;AAEjF;;;;;GAKG;AACH,MAAM,UAAU,OAAO,CAAC,IAAY,EAAE,KAAwB;IAC1D,MAAM,KAAK,GAAY,EAAE,CAAC;IAE1B,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,IAAY,EAAE,KAAa,EAAQ,EAAE;QAC3D,IAAI,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;YACtB,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,KAAK,GAAG,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,EAAE,GAAG,EAAE,6BAA6B,EAAE,CAAC,CAAC;YAEvF,OAAO;QACX,CAAC;QACD,MAAM,IAAI,GAAuB,KAAK,CAAC,IAAI,CAAC,CAAC,KAAa,EAAW,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC;QAChH,IAAI,IAAI,EAAE,CAAC;YACP,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,KAAK,GAAG,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,EAAE,GAAG,EAAE,qBAAqB,IAAI,EAAE,EAAE,CAAC,CAAC;QACzF,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,OAAO,KAAK,CAAC;AACjB,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,OAAO,CAAC,IAAY,EAAE,MAAc;IAChD,MAAM,IAAI,GAAW,QAAQ,CAAC,IAAI,CAAC,CAAC;IAEpC,OAAO,CAAC,IAAI,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;AAChD,CAAC;AAED,+FAA+F;AAC/F,SAAS,IAAI,CAAC,IAAY;IACtB,OAAO,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC;AAC5C,CAAC;AAED,uDAAuD;AACvD,MAAM,UAAU,cAAc,CAAC,QAAmB,EAAE,SAAiB;IACjE,IAAI,CAAC,QAAQ,CAAC,OAAO,EAAE,CAAC;QACpB,OAAO,+HAA+H,CAAC;IAC3I,CAAC;IACD,IAAI,YAAY,CAAC,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC;QACtC,OAAO,EAAE,CAAC;IACd,CAAC;IAED,MAAM,MAAM,GAAW,QAAQ,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;IAClD,IAAI,CAAC,MAAM,EAAE,CAAC;QACV,OAAO,8GAA8G,CAAC;IAC1H,CAAC;IAED,MAAM,IAAI,GAAW,IAAI,CAAC,SAAS,EAAE,QAAQ,CAAC,QAAQ,CAAC,CAAC;IACxD,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;QACpB,qFAAqF;QACrF,+DAA+D;QAC/D,OAAO,EAAE,CAAC;IACd,CAAC;IAED,OAAO,IAAI,CAAC,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QAC1D,CAAC,CAAC,EAAE;QACJ,CAAC,CAAC,iBAAiB,QAAQ,CAAC,QAAQ,MAAM,IAAI,CAAC,MAAM,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,kEAAkE,CAAC;AAC9I,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,QAAQ,CAAC,IAAY,EAAE,QAAmB,EAAE,GAAW,EAAE,QAAgB,YAAY;IACjG,MAAM,KAAK,GAAa,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IACzC,8FAA8F;IAC9F,sDAAsD;IACtD,MAAM,KAAK,GAAW,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,QAAQ,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,CAAC,IAAY,EAAW,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC;IAChH,2FAA2F;IAC3F,uFAAuF;IACvF,KAAK,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,GAAG,KAAK,EAAE,CAAC,EAAE,OAAO,KAAK,OAAO,GAAG,EAAE,CAAC,CAAC;IAEjE,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC5B,CAAC","sourcesContent":["/**\n * Предложения по слою правил: разбор файла, отбор того, что уезжает наружу, и проверка на адрес\n * дерева.\n *\n * Файл пишет главный агент после разбора закрытой задачи — по блоку на предложение, с адресом в\n * заголовке. Адрес роль проставляет и так; здесь он впервые становится тем, что читает машина.\n *\n * Проверка на утечку живёт в этом же модуле не для удобства: предложение уезжает в чужой\n * репозиторий целиком, и запрет называть чужое дерево обязан держаться проверкой, а не памятью\n * того, кто пишет.\n */\nimport { existsSync, readdirSync, readFileSync } from 'node:fs';\nimport { basename, join } from 'node:path';\nimport { byText } from './order.js';\n\n/** Куда главный агент кладёт предложения. Путь от корня дерева. */\nexport const PROPOSALS_DIR: string = '.claude/rt-kit/proposals';\n\n/** Адрес предложения: куда правка идёт. Наружу уезжает только первый. */\nexport const TO_PACKAGE: string = 'пакет';\nexport const TO_COMPANION: string = 'компаньон';\nexport const TO_TREE: string = 'дерево';\n\nconst ADDRESSES: readonly string[] = [TO_PACKAGE, TO_COMPANION, TO_TREE];\n\n/** Разобранный заголовок блока. */\ninterface IHeading {\n readonly address: string;\n readonly resource: string;\n}\n\n/**\n * Заголовок блока: `## <адрес> · <ресурс>`. Разделитель — тот же, что в шаблоне.\n *\n * Разбирается строкой, а не образцом: образец на «слово, разделитель, остаток» перебирает\n * границу слова столько раз, сколько в строке знаков.\n */\nfunction headingOf(line: string): IHeading | null {\n if (!line.startsWith('## ')) {\n return null;\n }\n\n const rest: string = line.slice(3);\n const at: number = rest.indexOf('·');\n\n if (at < 0) {\n return null;\n }\n\n const address: string = rest.slice(0, at).trim();\n const resource: string = rest.slice(at + 1).trim();\n\n return address && !address.includes(' ') && resource ? { address, resource } : null;\n}\n\n/** Пометка об отправке. По ней же предложение узнаётся отправленным. */\nconst SENT: RegExp = /^- +\\*\\*отправлено:\\*\\* *(\\S+)/m;\n\n/**\n * Поле ближайшего утверждения: точная цитата строки ресурса, к которой блок относится, — либо\n * слово о том, что ближайшего нет вовсе.\n *\n * Поле обязательно, и обязательно оно затем, чтобы ресурс был прочитан. Мерить похожесть текстов\n * пробовали замером: законное соседство двух статей одного правила дало 0.345 общих значимых\n * слов, а законный перенос удачной статьи на соседнее место — 0.355. Порога между ними нет, и\n * назначенный наугад он отбивал бы правки вместо повторов. Названная цитата судится фактом: она\n * в ресурсе либо есть, либо её там нет.\n */\nconst NEAREST: RegExp = /^- +\\*\\*ближайшее:\\*\\* *([^\\n]+)/m;\n\n/**\n * Слово о том, что ближайшего утверждения в ресурсе нет: цитировать нечего.\n *\n * Конец слова здесь проверяется отрицательным просмотром, а не границей слова: границу движок\n * считает по латинице и цифрам, и между «т» и пробелом её нет вовсе — образец с `\\b` не совпал\n * бы ни с одним русским словом.\n */\nconst NOTHING_NEAR: RegExp = /^нет(?![а-яё])/i;\n\n/**\n * Цитата ближайшего утверждения: кавычки-ёлочки, как в остальных текстах дерева.\n *\n * Ищется позициями, а не образцом: образец на «открыть, набрать не-закрывающих, закрыть» линтер\n * отбивает как ветвящийся, и поводы у него есть — строка без закрывающей кавычки перебирается им\n * до конца.\n */\nfunction quotedIn(text: string): string {\n const from: number = text.indexOf('«');\n const to: number = from < 0 ? -1 : text.indexOf('»', from + 1);\n\n return from >= 0 && to > from ? text.slice(from + 1, to) : '';\n}\n\n/** Пометка любого рода: по ней считается сдвиг, когда в файл вставляют ещё одну. */\nconst MARKED: RegExp = /^- +\\*\\*(отправлено|отбито):\\*\\*/m;\n\nexport interface IProposal {\n readonly address: string;\n readonly resource: string;\n /** Тело блока без заголовка: место, повод и готовый текст. */\n readonly body: string;\n /** Ссылка на заведённую запись; пусто — не отправлялось. */\n readonly sent: string;\n /** Поле ближайшего утверждения, как его написали; пусто — поля нет вовсе. */\n readonly nearest: string;\n /** Файл, в котором блок лежит, путём от корня дерева. */\n readonly file: string;\n /** Строка заголовка в файле, считая с единицы. */\n readonly line: number;\n}\n\n/**\n * Блоки файла предложений.\n *\n * Незаполненный образец пропускается: в шаблоне ресурс стоит скобками — `rules/<правило>.md`, —\n * и отправить его значило бы завести запись о правке несуществующего файла.\n */\nexport function parseProposals(text: string, file: string): readonly IProposal[] {\n const lines: readonly string[] = text.split('\\n');\n const found: IProposal[] = [];\n let at: number = -1;\n\n const close: (end: number) => void = (end: number): void => {\n if (at < 0) {\n return;\n }\n const heading: IHeading = headingOf(lines[at]) as IHeading;\n const body: string = lines\n .slice(at + 1, end)\n .join('\\n')\n .trim();\n const { address, resource }: IHeading = heading;\n if (ADDRESSES.includes(address) && !resource.includes('<')) {\n found.push({\n address,\n resource,\n body,\n file,\n sent: SENT.exec(body)?.[1] ?? '',\n nearest: (NEAREST.exec(body)?.[1] ?? '').trim(),\n line: at + 1,\n });\n }\n at = -1;\n };\n\n lines.forEach((line: string, index: number): void => {\n if (line.startsWith('## ')) {\n close(index);\n if (headingOf(line) !== null) {\n at = index;\n }\n }\n });\n close(lines.length);\n\n return found;\n}\n\n/** Все файлы предложений дерева, в порядке имён. */\nexport function readProposals(root: string): readonly IProposal[] {\n const dir: string = join(root, PROPOSALS_DIR);\n if (!existsSync(dir)) {\n return [];\n }\n\n return readdirSync(dir)\n .filter((name: string): boolean => name.endsWith('.md'))\n .sort(byText)\n .flatMap((name: string): readonly IProposal[] => parseProposals(readFileSync(join(dir, name), 'utf8'), join(PROPOSALS_DIR, name)));\n}\n\nexport interface ILeak {\n /** Строка блока, считая с единицы от заголовка. */\n readonly line: number;\n readonly text: string;\n readonly why: string;\n}\n\n/** Абсолютный путь машины: он не бывает общим ни для одного дерева, кроме этого. */\nconst ABSOLUTE: RegExp = /(^|[\\s(`'\"])(~\\/|\\/(Users|home|var|opt|srv|mnt)\\/)\\S+/;\n\n/**\n * Что в тексте называет дерево, из которого предложение уезжает.\n *\n * `marks` — имена, которые знает только это дерево: корень, его последнее звено, адрес его\n * удалённого репозитория. Пакет их не угадывает — они приходят доводом с края.\n */\nexport function leaksIn(text: string, marks: readonly string[]): readonly ILeak[] {\n const found: ILeak[] = [];\n\n text.split('\\n').forEach((line: string, index: number): void => {\n if (ABSOLUTE.test(line)) {\n found.push({ line: index + 1, text: line.trim(), why: 'абсолютный путь этой машины' });\n\n return;\n }\n const mark: string | undefined = marks.find((value: string): boolean => Boolean(value) && line.includes(value));\n if (mark) {\n found.push({ line: index + 1, text: line.trim(), why: `имя этого дерева: ${mark}` });\n }\n });\n\n return found;\n}\n\n/**\n * Чем дерево себя выдаёт. Последнее звено пути берётся отдельно от полного: в тексте оно\n * встречается чаще, чем путь целиком.\n */\nexport function marksOf(root: string, remote: string): readonly string[] {\n const name: string = basename(root);\n\n return [root, name, remote].filter(Boolean);\n}\n\n/** Один пробел вместо любой пробельной вереницы: цитата в блоке перенесена по своей ширине. */\nfunction flat(text: string): string {\n return text.replace(/\\s+/g, ' ').trim();\n}\n\n/** Почему блок не уезжает; пустая строка — уезжает. */\nexport function nearestMissing(proposal: IProposal, assetsDir: string): string {\n if (!proposal.nearest) {\n return 'поля «ближайшее» нет: назови точную строку ресурса, к которой это относится, и то, чего она не покрывает, — либо напиши «нет»';\n }\n if (NOTHING_NEAR.test(proposal.nearest)) {\n return '';\n }\n\n const quoted: string = quotedIn(proposal.nearest);\n if (!quoted) {\n return 'ближайшее названо без цитаты: строка ресурса приводится в кавычках-ёлочках дословно — иначе проверить нечего';\n }\n\n const path: string = join(assetsDir, proposal.resource);\n if (!existsSync(path)) {\n // Ресурса у этого дерева нет — сверять не с чем, и отбивать нечего: блок про ресурс,\n // которого пакет здесь не держит, судится на приёмной стороне.\n return '';\n }\n\n return flat(readFileSync(path, 'utf8')).includes(flat(quoted))\n ? ''\n : `цитаты нет в «${proposal.resource}»: ${flat(quoted).slice(0, 60)}… — либо ресурс не читали, либо утверждение переписано с тех пор`;\n}\n\n/**\n * Пометка об отправке дописывается в блок, а не в конец файла: блоков в файле несколько.\n *\n * Место считается от номера строки заголовка — но номера у всех блоков посчитаны при разборе, до\n * единой вставки, а каждая вставка сдвигает файл на строку вниз. Поэтому к номеру прибавляется\n * число пометок, уже стоящих выше: без этого вторая пометка встаёт на строку выше своего места,\n * пятая — на четыре, то есть посреди готового текста правки, а последнему блоку её не достаётся\n * вовсе, и при следующей отправке он уезжает вторым разом.\n */\nexport function markSent(text: string, proposal: IProposal, url: string, field: string = 'отправлено'): string {\n const lines: string[] = text.split('\\n');\n // Сдвиг считает пометки обоих родов: отбитый блок получает свою, и следующая за ним встала бы\n // строкой выше своего места, если бы её не посчитали.\n const shift: number = lines.slice(0, proposal.line).filter((line: string): boolean => MARKED.test(line)).length;\n // Пометка встаёт сразу под заголовок: конец блока определяется следующим заголовком, а его\n // может и не быть — тогда «конец» пришлось бы искать по пустым строкам в хвосте файла.\n lines.splice(proposal.line + shift, 0, `- **${field}:** ${url}`);\n\n return lines.join('\\n');\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"shipment.d.ts","sourceRoot":"","sources":["../../../projects/agent-kit/src/lib/shipment.ts"],"names":[],"mappings":"AAcA,OAAO,EAGH,cAAc,EACd,eAAe,EACf,iBAAiB,EAEjB,eAAe,EACf,aAAa,EAChB,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAEhE,OAAO,EAAe,QAAQ,EAA+B,MAAM,mBAAmB,CAAC;AACvF,OAAO,EAAS,SAAS,EAAyD,MAAM,gBAAgB,CAAC;AACzG,OAAO,EAAE,SAAS,EAAuB,KAAK,EAAE,MAAM,WAAW,CAAC;AAalE,4FAA4F;AAC5F,MAAM,WAAW,YAAY;IACzB,uDAAuD;IACvD,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IACzB,QAAQ,CAAC,IAAI,EAAE,KAAK,CAAC;IACrB,4EAA4E;IAC5E,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,iGAAiG;IACjG,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACzB;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAcnD;AAED;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAIjE;AAaD,uFAAuF;AACvF,wBAAgB,YAAY,CACxB,OAAO,EAAE,QAAQ,EACjB,IAAI,EAAE,MAAM,EACZ,SAAS,EAAE,SAAS,cAAc,EAAE,EACpC,QAAQ,EAAE,SAAS,MAAM,EAAE,EAC3B,KAAK,EAAE,WAAW,CAAC,MAAM,CAAC,GAC3B,aAAa,CAkBf;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,eAAe,EAAE,CAe5E;AAED,iFAAiF;AACjF,wBAAgB,WAAW,CAAC,OAAO,EAAE,aAAa,EAAE,SAAS,EAAE,eAAe,EAAE,WAAW,EAAE,iBAAiB,GAAG,SAAS,SAAS,EAAE,CAMpI;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,aAAa,EAAE,SAAS,EAAE,SAAS,SAAS,EAAE,EAAE,KAAK,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,EAAE,CAWxH;AAyFD;;;;;;GAMG;AACH,wBAAsB,OAAO,CAAC,GAAG,EAAE,YAAY,EAAE,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAkFlG"}
1
+ {"version":3,"file":"shipment.d.ts","sourceRoot":"","sources":["../../../projects/agent-kit/src/lib/shipment.ts"],"names":[],"mappings":"AAcA,OAAO,EAGH,cAAc,EACd,eAAe,EACf,iBAAiB,EAEjB,eAAe,EACf,aAAa,EAChB,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAEhE,OAAO,EAAe,QAAQ,EAA+B,MAAM,mBAAmB,CAAC;AACvF,OAAO,EAAS,SAAS,EAAyE,MAAM,gBAAgB,CAAC;AACzH,OAAO,EAAE,SAAS,EAAuB,KAAK,EAAE,MAAM,WAAW,CAAC;AAalE,4FAA4F;AAC5F,MAAM,WAAW,YAAY;IACzB,uDAAuD;IACvD,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IACzB,QAAQ,CAAC,IAAI,EAAE,KAAK,CAAC;IACrB,4EAA4E;IAC5E,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,iGAAiG;IACjG,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACzB;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAcnD;AAED;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAIjE;AAaD,uFAAuF;AACvF,wBAAgB,YAAY,CACxB,OAAO,EAAE,QAAQ,EACjB,IAAI,EAAE,MAAM,EACZ,SAAS,EAAE,SAAS,cAAc,EAAE,EACpC,QAAQ,EAAE,SAAS,MAAM,EAAE,EAC3B,KAAK,EAAE,WAAW,CAAC,MAAM,CAAC,GAC3B,aAAa,CAkBf;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,eAAe,EAAE,CAe5E;AAED,iFAAiF;AACjF,wBAAgB,WAAW,CAAC,OAAO,EAAE,aAAa,EAAE,SAAS,EAAE,eAAe,EAAE,WAAW,EAAE,iBAAiB,GAAG,SAAS,SAAS,EAAE,CAMpI;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,aAAa,EAAE,SAAS,EAAE,SAAS,SAAS,EAAE,EAAE,KAAK,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,EAAE,CAWxH;AAkID;;;;;;GAMG;AACH,wBAAsB,OAAO,CAAC,GAAG,EAAE,YAAY,EAAE,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAgHlG"}