@rt-tools/agent-kit 0.8.3 → 0.9.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 (132) hide show
  1. package/README.md +12 -0
  2. package/assets/checks/board.github.mjs +48 -1
  3. package/assets/checks/check-board.github.mjs +84 -1
  4. package/assets/checks/check-lib-layers.mjs +13 -524
  5. package/assets/checks/check-specs.mjs +11 -782
  6. package/assets/checks/check-styles.mjs +185 -15
  7. package/assets/checks/lib-boundaries.mjs +143 -0
  8. package/assets/checks/lib-common.mjs +149 -0
  9. package/assets/checks/lib-domains.mjs +205 -0
  10. package/assets/checks/lib-manifests.mjs +60 -0
  11. package/assets/checks/lib-reexports.mjs +101 -0
  12. package/assets/checks/rt-kit-checks.config.mjs +17 -0
  13. package/assets/checks/spec-anchors.mjs +297 -0
  14. package/assets/checks/spec-common.mjs +222 -0
  15. package/assets/checks/spec-contract.mjs +152 -0
  16. package/assets/checks/spec-scenarios.mjs +201 -0
  17. package/assets/defaults/project.sh +8 -0
  18. package/assets/hooks/git-guard-push-tests.sh +8 -4
  19. package/assets/hooks/skill-gate.sh +1 -1
  20. package/assets/hooks/sql-guard-parse.sh +187 -0
  21. package/assets/hooks/sql-guard-request.sh +117 -0
  22. package/assets/hooks/sql-guard-target.sh +134 -0
  23. package/assets/hooks/sql-guard-write.sh +212 -0
  24. package/assets/hooks/sql-guard.sh +26 -596
  25. package/assets/hooks/waiting-turn-guard.sh +42 -13
  26. package/assets/laws/delivery.md +7 -0
  27. package/assets/laws/work-conduct.md +9 -0
  28. package/assets/patterns/admin-lists-screen.md +25 -14
  29. package/assets/patterns/admin-nav-item.md +1 -1
  30. package/assets/patterns/component-structure-new.md +1 -1
  31. package/assets/patterns/entity-aside.md +4 -2
  32. package/assets/patterns/observability-record.md +9 -0
  33. package/assets/patterns/shared-code-new.md +2 -2
  34. package/assets/patterns/task-flow-close.md +7 -1
  35. package/assets/rules/git-workflow.azure.md +7 -0
  36. package/assets/rules/git-workflow.github.md +41 -0
  37. package/assets/rules/git-workflow.gitlab.md +7 -0
  38. package/assets/rules/lib-layers.md +4 -0
  39. package/assets/rules/lists.md +10 -10
  40. package/assets/rules/shared-code.md +1 -1
  41. package/assets/rules/task-flow.md +47 -7
  42. package/assets/rules/testing.md +31 -0
  43. package/assets/rules/typescript-conventions.md +7 -0
  44. package/assets/skills/agent-kit.md +36 -0
  45. package/assets/templates/proposal.md +21 -0
  46. package/bin/agent-kit.d.ts.map +1 -1
  47. package/bin/agent-kit.js +115 -87
  48. package/bin/agent-kit.js.map +1 -1
  49. package/index.d.ts +1 -0
  50. package/index.d.ts.map +1 -1
  51. package/index.js +1 -0
  52. package/index.js.map +1 -1
  53. package/lib/argv.d.ts.map +1 -1
  54. package/lib/argv.js +6 -4
  55. package/lib/argv.js.map +1 -1
  56. package/lib/assets.d.ts.map +1 -1
  57. package/lib/assets.js +2 -1
  58. package/lib/assets.js.map +1 -1
  59. package/lib/cargo.d.ts +20 -0
  60. package/lib/cargo.d.ts.map +1 -1
  61. package/lib/cargo.js.map +1 -1
  62. package/lib/cascade.d.ts +55 -0
  63. package/lib/cascade.d.ts.map +1 -0
  64. package/lib/cascade.js +131 -0
  65. package/lib/cascade.js.map +1 -0
  66. package/lib/catalog.d.ts +0 -75
  67. package/lib/catalog.d.ts.map +1 -1
  68. package/lib/catalog.js +44 -127
  69. package/lib/catalog.js.map +1 -1
  70. package/lib/commands.d.ts.map +1 -1
  71. package/lib/commands.js +152 -85
  72. package/lib/commands.js.map +1 -1
  73. package/lib/companion.d.ts.map +1 -1
  74. package/lib/companion.js +5 -5
  75. package/lib/companion.js.map +1 -1
  76. package/lib/config.d.ts +2 -0
  77. package/lib/config.d.ts.map +1 -1
  78. package/lib/config.js +7 -5
  79. package/lib/config.js.map +1 -1
  80. package/lib/enroll.d.ts +56 -0
  81. package/lib/enroll.d.ts.map +1 -0
  82. package/lib/enroll.js +123 -0
  83. package/lib/enroll.js.map +1 -0
  84. package/lib/freshness.d.ts.map +1 -1
  85. package/lib/freshness.js +31 -17
  86. package/lib/freshness.js.map +1 -1
  87. package/lib/hooks-map.d.ts +30 -0
  88. package/lib/hooks-map.d.ts.map +1 -1
  89. package/lib/hooks-map.js +80 -18
  90. package/lib/hooks-map.js.map +1 -1
  91. package/lib/integrity.d.ts +1 -2
  92. package/lib/integrity.d.ts.map +1 -1
  93. package/lib/integrity.js +0 -1
  94. package/lib/integrity.js.map +1 -1
  95. package/lib/observations.d.ts.map +1 -1
  96. package/lib/observations.js +25 -12
  97. package/lib/observations.js.map +1 -1
  98. package/lib/order.d.ts +10 -0
  99. package/lib/order.d.ts.map +1 -0
  100. package/lib/order.js +14 -0
  101. package/lib/order.js.map +1 -0
  102. package/lib/picker.d.ts.map +1 -1
  103. package/lib/picker.js +8 -2
  104. package/lib/picker.js.map +1 -1
  105. package/lib/plan.js +1 -1
  106. package/lib/plan.js.map +1 -1
  107. package/lib/proposals.d.ts.map +1 -1
  108. package/lib/proposals.js +25 -8
  109. package/lib/proposals.js.map +1 -1
  110. package/lib/sections.js +1 -1
  111. package/lib/sections.js.map +1 -1
  112. package/lib/ship.d.ts.map +1 -1
  113. package/lib/ship.js +9 -1
  114. package/lib/ship.js.map +1 -1
  115. package/lib/shipment.d.ts.map +1 -1
  116. package/lib/shipment.js +14 -10
  117. package/lib/shipment.js.map +1 -1
  118. package/lib/snapshot.d.ts.map +1 -1
  119. package/lib/snapshot.js +2 -1
  120. package/lib/snapshot.js.map +1 -1
  121. package/lib/stamp.js +1 -1
  122. package/lib/stamp.js.map +1 -1
  123. package/lib/sync.d.ts +12 -2
  124. package/lib/sync.d.ts.map +1 -1
  125. package/lib/sync.js +11 -10
  126. package/lib/sync.js.map +1 -1
  127. package/lib/vars.d.ts.map +1 -1
  128. package/lib/vars.js +2 -3
  129. package/lib/vars.js.map +1 -1
  130. package/package.json +1 -1
  131. package/rt-tools-agent-kit-0.9.0.tgz +0 -0
  132. package/rt-tools-agent-kit-0.8.3.tgz +0 -0
@@ -143,16 +143,37 @@ flowchart TD
143
143
  Открытый PR при этом читается как приглашение влить — поэтому незаконченная работа идёт
144
144
  черновиком, и владельцу не приходится спрашивать, кончилась ли она. Черновик снимается тем
145
145
  ходом, которым исполнитель говорит, что решение готово.
146
+ - **Открытый PR остановкой не является.** Отданное на разбор ждёт владельца, а не исполнителя:
147
+ следующая задача берётся тем же движением, которым предыдущая ушла на разбор. Заход,
148
+ закрытый на готовой задаче, стоил владельцу целого захода на то, чтобы вернуть работу в
149
+ движение, — при том что порядок был назначен и лежал записанным в замысле эпика.
150
+ - **Владельцу не предлагается выбор, чем заняться дальше, пока эпик не кончился.** Меню при
151
+ назначенном порядке — это просьба назначить его заново. Если работы в эпике не осталось, так
152
+ и говорится: эпик кончился, — а не «чем займёмся».
153
+ - **Гард замысла — нижняя граница требования, а не его предел.** Он требует папку только под
154
+ правку кода приложения, и работа, которая туда не доходит, проходит мимо него — но папку
155
+ заводит всё равно: статья правила говорит «под любую работу, без исключений». Прочитанный
156
+ как признак, гард становится разрешением работать без замысла везде, куда он не смотрит.
157
+ - **Ход, сообщающий владельцу о чужом шаге, называет своё следующее действие и начинает его.**
158
+ Прогон, разбор владельцем и слияние идут без исполнителя и быстрее от взгляда не становятся,
159
+ поэтому сообщение о них — не работа, а сводка. Ход, кончившийся такой сводкой, владелец
160
+ читает как работу: она полна, в ней названы номера и состояния, и пустоты за ней не видно.
161
+ Открытый PR, красный прогон и ожидание слова владельца — случаи одного и того же, и правило
162
+ у них одно: о чужом шаге говорят вместе с начатым своим, а не вместо него. За один заход это
163
+ было нарушено четырежды, и готовая работа простояла в невлитом PR почти три часа, пока
164
+ исполнитель отвечал владельцу сводками о её состоянии.
146
165
  - **Пока PR ждёт разбора, исполнитель берёт следующую задачу.** Ожидание чужого шага заходом
147
166
  не занимают: работа уходит на разбор, и тем же движением берётся следующая. Готовым к
148
167
  слиянию прежний PR становится не сам — его доводит до готовности исполнитель, вернувшись к
149
168
  нему тем же ходом, которым прочитал конец прогона.
150
- - **Ход, в котором открыт PR, стережёт гард ожидания, а не память исполнителя.** Он отбивает
151
- завершение хода, в котором не было ни одного действия по следующей задаче заведения задачи,
152
- ветки, папки или перевода колонки. Признак берётся из самого хода: спросить хостинг об
153
- открытых PR было бы точнее, но сетевой вызов на завершении хода падает вместе со связью и
154
- отбивал бы работу вместо промаха. Слова «беру следующую задачу» гард действием не считает —
155
- ровно потому, что их и произносят вместо неё.
169
+ - **Ход о чужом шаге стережёт гард ожидания, а не память исполнителя.** Он отбивает завершение
170
+ хода, в котором о чужом шаге сказано, а по следующей задаче не сделано ни одного действия —
171
+ ни заведения задачи, ни ветки, ни папки, ни перевода колонки. Чужой шаг он узнаёт по двум
172
+ признакам: в ходе открыт PR либо в ходе прочитан красный прогон. Оба берутся из самого хода:
173
+ спросить хостинг было бы точнее, но сетевой вызов на завершении хода падает вместе со связью
174
+ и отбивал бы работу вместо промаха, а вывод команды о прогоне в записи хода уже лежит. Слова
175
+ «беру следующую задачу» гард действием не считает — ровно потому, что их и произносят вместо
176
+ неё.
156
177
  - **Конец прогона узнаётся возвратом фоновой команды, а не взглядом на страницу.** Ожидание,
157
178
  запущенное в фоне отдельным ходом, возвращает исполнителя к PR само; до тех пор ход занят
158
179
  следующей задачей. Взгляд на страницу этого не даёт: он либо повторяется вхолостую, либо не
@@ -171,7 +192,16 @@ flowchart TD
171
192
  - **Действия, которые исполнитель не делает без слова владельца, перечислены в компаньоне
172
193
  правила.** Список у каждого дерева свой — пакет знает только требование, чтобы список был
173
194
  назван. Не названный, он выводится из общих слов, и «делай, что нужно по плану» становится
174
- разрешением на пуш и правку общих документов заодно с коммитом.
195
+ разрешением на пуш и правку общих документов заодно с коммитом. Оценка «это безопасно» списка
196
+ не заменяет: её назначает тот, кому она в эту минуту удобна, и она плывёт. За один заход одна
197
+ и та же команда была сначала слишком опасной, чтобы её позвать, а через два хода —
198
+ достаточно безопасной, чтобы позвать без спроса.
199
+ - **У отказа от необратимого действия есть безопасная часть, и она делается.** Требование
200
+ спросить владельца относится к действию, а не к ходу: работа, у которой отделима часть без
201
+ последствий, делится, а не откладывается целиком. Список вариантов, поданный вместо работы,
202
+ читается как работа — тем полнее, чем аккуратнее он составлен: он пронумерован, в нём названы
203
+ цифры, и именно поэтому пустота хода за ним не видна. Владельцу называется, что уже сделано и
204
+ что осталось за его словом, — а не выбор из вариантов вместо и того и другого.
175
205
  - **Ход, в котором исполнитель признал промах, не заканчивается, пока записи о происшествии
176
206
  нет.** Отбивает гард происшествия — на завершении хода: к моменту признания промах уже
177
207
  случился, и ловить раньше нечего. Признание ловится набором образцов, а не пониманием смысла;
@@ -202,6 +232,16 @@ flowchart TD
202
232
  говорит, что эпик есть, но порядка задач не держит; папка задачи держала бы его ровно до
203
233
  слияния первой из них. Каталог для замысла называет компаньон правила: у пакета своего пути
204
234
  нет, а замысел, положенный каждым заходом заново, теряет решения предыдущих.
235
+ - **Сборка по образцу начинается с чтения самого образца, а не пересказа о нём.** Пересказ
236
+ лежит в разборе просьбы и в замысле эпика; читаются они оба, но образцом не считаются. Часть
237
+ образца, которую работа повторяет, открывается целиком — обходом каталогов, а не одним файлом,
238
+ за которым пришли. Расхождение, не найденное так, находит владелец на приёмке целой работой.
239
+ - **Путь к образцу лежит вне дерева и приходит в заход хуком запуска сессии.** Имя чужого
240
+ дерева в файлы репозитория не пишется, поэтому ни замысел эпика, ни разбор просьбы его не
241
+ держат: там законна ссылка без имени — «путь к образцу лежит вне дерева». Каталог для записи
242
+ — тот же, где лежит передача захода; называет его компаньон правила. Не записанный так,
243
+ образец теряется на первой же чистке контекста, и следующий заход собирает по памяти
244
+ предыдущего.
205
245
  - **Закрытая работа разбирается правилами, и это шаг закрытия, а не отдельная просьба.** Что
206
246
  грузилось, что помогло и чего не хватило, видно только тому заходу, который работу вёл; через
207
247
  сутки этого нет ни у кого. Разбор кончается правкой слоя правил или предложением наружу —
@@ -126,6 +126,19 @@ flowchart TD
126
126
  сценария в заголовке стоит — сценарий числится покрытым, а что именно утверждается, не
127
127
  спрашивает никто. Тело читается вместе с заголовком: обещание в заголовке и утверждение в
128
128
  теле — два разных текста, и расходятся они молча.
129
+ - **Растр браузера называется явно, иначе кадр не сходится сам с собой.** Профиль цвета, взятый
130
+ у дисплея машины, ускоритель, считающий растр, и дорисовка кусками — каждый из трёх двигает
131
+ цвет на единицу-другую по каналу, и видно это только там, где смешение стоит на границе
132
+ округления: на сглаженных уголках тёмной темы. Ожидание вставшего кадра тут не помогает —
133
+ страница нарисована, а нарисована она каждый раз чуть иначе; расхождение гуляет по кадру,
134
+ приходит примерно раз в четыре прогона и на светлых экранах не показывается вовсе, поэтому
135
+ читается случайным. Все три называются доводами браузеру при запуске и становятся частью
136
+ эталона.
137
+ - **Маска закрывает содержимое, но не ширину.** Узел под маской занимает своё место в раскладке
138
+ по-прежнему, и плывущее значение внутри него двигает соседей мимо маски: кадр списка уезжал на
139
+ пиксель целиком, включая столбцы, где не менялось ничего. Там, где размер узла считается по
140
+ содержимому — ячейка таблицы, надпись, растягивающая кнопку, — плывущее лечится в данных
141
+ стенда постоянным значением, и тогда маска не нужна вовсе.
129
142
 
130
143
  ## Чего из закона здесь нет
131
144
 
@@ -186,3 +199,21 @@ flowchart TD
186
199
  соревнуется за машину с тем, что снимают, и делает исход прогона зависящим от того, чем занят
187
200
  сосед. Нагрузка, которую задание создаёт себе само — соседняя витрина, только что законченная
188
201
  сборка, — ничем не отличается от чужой.
202
+ - **Свой стенд снимается перед тем, как звать набор.** Прогон переиспользует поднятое на его
203
+ портах, и стенд, оставленный для замера, отдаёт ему чужую сборку с чужими данными. Красное при
204
+ этом приходит не строкой про занятый порт, а десятком спек про экраны — то есть выглядит
205
+ дефектом правки: за один заход так покраснели сначала шесть новых тестов, потом гейт пуша, и
206
+ оба раза причиной был свой же стенд. Разобранный занятый порт эту сторону не закрывает: он про
207
+ чужой стенд, а этот — про свой.
208
+ - **Разбор упавшего кадра начинается с чисел, а не с картинки расхождения.** Доля площади
209
+ говорит, сколько разошлось, и молчит о том, что именно: сдвиг всего кадра на пиксель,
210
+ переставленные строки и рябь на сглаженных уголках выглядят на картинке одинаково — «стало
211
+ другим». Читаются координаты разошедшихся точек и величина расхождения по каналу: сдвинутые
212
+ границы блоков — это раскладка, разошедшийся текст при неподвижных границах — это данные,
213
+ единица-две по каналу на кривых краях — это цвет. Три расхождения одного набора разобрались
214
+ ровно так, и ни одно из трёх не оказалось дефектом экрана.
215
+ - **Ожидаемое значение теста не берётся из кода, который тест проверяет.** Вывезенное из
216
+ проверяемой либы, оно делает тест зелёным при любом значении: «колесо показывает пять строк»
217
+ сходится и тогда, когда строк стало три. Ожидаемое пишется числом в самой спеке рядом с
218
+ проверкой, а общий модуль сквозных спек держит приёмы — открыть, дождаться, снять со
219
+ страницы, — но не то, что от страницы ожидается.
@@ -59,6 +59,13 @@ flowchart TD
59
59
  остального. Для `.ts` это держит правило линтера; файлы обвязки — сценарии и скрипты — до
60
60
  него не доходят и судятся отдельной проверкой дерева. Накопленное к дню включения
61
61
  перечислено поимённо, и строка оттуда снимается вместе с делением своего файла.
62
+ - **Значение из закрытого набора приходит перечислением `E<Имя>`, а не строкой или числом в
63
+ месте использования.** Ключ переводимого поля, имя вкладки, слот обложки, вид записи — всё
64
+ это наборы: их называют форма, стор, разметка и сравнение черновиков, и написанное на месте
65
+ значение ни находится по дереву, ни правится разом. Перечисление живёт в слое `util` того
66
+ домена, чей это набор, а общее нескольким приложениям — в общей либе. Одиночный адрес,
67
+ разделитель и знак-подпись перечислением не становятся: закрытого набора у них нет, и они
68
+ объявляются константой файла с говорящим именем.
62
69
  - **Тип берётся из того пакета, где объявлен.** Своя копия чужого типа расходится с оригиналом
63
70
  молча, а компилируется из них только одна.
64
71
  - **Двухступенчатое приведение `as unknown as` запрещено правилом линтера.** Вместо него —
@@ -79,6 +79,14 @@ npx agent-kit propose # отправить груз в приём: сво
79
79
  сам агент: до неё такое слово адреса не получало вовсе и умирало вместе с сессией. Оба пути
80
80
  пишут в один файл дня и в одной форме; в сеть не ходит ни один — увозит их отправка.
81
81
 
82
+ Каждая запись несёт три строки: **место** — куда правка встаёт в ресурсе, **повод** — что пошло
83
+ не так без неё, и **чем закрывается** — какие надстройки и добавки этого дерева снимаются, когда
84
+ правка приедет редакцией пакета. Третья пишется затем, что предложение уезжает наружу, а
85
+ надстройка остаётся лежать здесь: без неё дерево не может сказать, какие из его надстроек
86
+ исправленная редакция закрыла, — снять наугад страшно, оставить дёшево, и надстройка остаётся
87
+ навсегда, молча замещая исправленный раздел пакета. Снимать нечего — так и пишется; пустой
88
+ третья строка не бывает.
89
+
82
90
  Каждому предложению ставится адрес: «пакет», «компаньон» или «дерево». Выгружаются они файлом в `.claude/rt-kit/proposals/`
83
91
  (форма — шаблон `proposal.md`), а `agent-kit propose` увозит в приём те, что адресованы
84
92
  пакету, вместе со сводкой и разборами происшествий. Адрес дерева в сводке или в тексте
@@ -150,6 +158,26 @@ npx agent-kit propose # отправить груз в приём: сво
150
158
 
151
159
  ## Ловушки
152
160
 
161
+ - **Надстройка замещает раздел целиком, и пакетные пункты в нём приходится держать копией.**
162
+ Дописать в раздел одну статью нечем: слияние идёт по заголовку `## `. Дерево, которому нужен
163
+ один свой пункт, копирует к нему все пакетные — и с этого дня правка любого из них,
164
+ приехавшая с новой версией, до этого дерева не доходит. Сверка разложенного молчит: она
165
+ считает расхождением правку на месте, а не замещённый раздел. Признак виден по самим
166
+ надстройкам — три из трёх прочитанных кончались абзацем о том, что перенос придётся делать
167
+ руками. Поэтому надстройкой берут раздел, у которого пакетных пунктов немного, а разросшийся
168
+ замещённый раздел — повод внести своё в пакет, а не держать его копией. Предложение, которым
169
+ своё вносят, называет эту надстройку строкой «чем закрывается»: иначе приехавшая редакция и
170
+ надстройка, которую она закрыла, не сопоставляются ничем, и надстройка остаётся замещать уже
171
+ исправленный раздел.
172
+
173
+ - **Готовый код пакета не называет имён одного дерева.** Префикс директив кита, ключи подписей
174
+ и имена сущностей принадлежат тому дереву, где паттерн писали; разложенные в соседнем, они
175
+ учат звать то, чего там нет вовсе. Имя директивы при этом отличается от имени в примере: по
176
+ примеру видно, что он пример, а `<префикс>TableRow` из чужого дерева выглядит рабочим кодом
177
+ и правится только после того, как продовая сборка упадёт. Тринадцать таких имён простояли в
178
+ четырёх ресурсах пакета, пока их не нашёл потребитель — и не своей сборкой, а надстройкой,
179
+ которой перекрыл раздел.
180
+
153
181
  - **Правила и паттерны при отвергнутом законе в отказе не перечисляются.** Их снимает каскад, а
154
182
  строка на них становится выводимой: раскладка называет её лишней вместе с законом, из-за
155
183
  которого она перестала снимать. Отказ мерит слой законов, а не число файлов в пакете.
@@ -168,6 +196,14 @@ npx agent-kit propose # отправить груз в приём: сво
168
196
  конструкции не видит: `case` теряет свою `esac`, файл остаётся синтаксически неверным, а
169
197
  гард с ошибкой синтаксиса отвечает ненулевым кодом — то есть «правка отбита». Два раза за
170
198
  заход, и оба раза это выглядело дефектом самого гарда.
199
+ - **Гард, подписанный не на то, что объявляет, выглядит работающим.** Событие и образец вызова
200
+ гард несёт сам, строкой `# rt-hook:`, а зовёт его образец в настройке агента — и эти двое
201
+ расходятся молча: путь гарда в настройке назван, файл разложен, набор сценариев зелёный.
202
+ Набор тут ничего не ловит намеренно — он зовёт гард напрямую с подставленным вводом и
203
+ объявления не читает вовсе. Так гейт правил и разбирал вызовы браузера веткой, которая не
204
+ исполнялась ни разу. Расхождение находит сверка раскладки: она сравнивает объявленный образец
205
+ с тем, под которым гард стоит, называет обе стороны и идёт в счёт расхождений. Правится
206
+ настройка, а верное значение лежит в гарде — тело его разбирает то, что объявлено.
171
207
  - **Разложенный файл узнаётся по шапке, а не по каталогу.** Раскладка ложится в те же
172
208
  `tools/`, `.claude/hooks/` и `.claude/skills/`, где лежит своё, поэтому карта гейта,
173
209
  написанная по путям, требует под него доменное правило — а оно уводит править файл на месте.
@@ -21,12 +21,32 @@
21
21
  В тексте предложения не бывает ни путей этого дерева, ни имён его доменов, ни его собственного
22
22
  имени: файл уезжает в чужой репозиторий целиком. Найденный адрес дерева отбивает отправку с
23
23
  номером строки — это проверка, а не напоминание.
24
+
25
+ Строк при заголовке три, и все три обязательны:
26
+
27
+ место — куда правка встаёт в ресурсе
28
+ повод — что пошло не так без неё
29
+ чем закрывается — какие надстройки и добавки этого дерева снимаются, когда правка приедет
30
+ редакцией пакета
31
+
32
+ Третья пишется затем, что предложение уезжает наружу, а надстройка, ради которой оно написано,
33
+ остаётся лежать в дереве, и связи между ними нет никакой. Пакет выпускает исправленную редакцию
34
+ — и сказать, какие надстройки она закрыла, дереву нечем: имя ресурса совпадает у десятка
35
+ предложений, а раздел надстройки называется тем же заголовком, что и пакетный. Снять наугад
36
+ страшно, оставить дёшево — и надстройка остаётся навсегда, молча замещая исправленный раздел
37
+ пакета.
38
+
39
+ Снимать нечего — так и пишется: «ничего, надстройки под это нет». Пустая строка третьей не
40
+ считается; машине она не видна вовсе — ни одна проверка тела предложения не читает, и держится
41
+ эта форма тем, кто пишет запись.
24
42
  -->
25
43
 
26
44
  ## пакет · rules/<правило>.md
27
45
 
28
46
  - **место:** раздел «<заголовок>», в конец
29
47
  - **повод:** что в этой задаче пошло не так без этого правила
48
+ - **чем закрывается:** `.claude/rt-kit/overrides/rules/<правило>.md`, раздел «<заголовок>» —
49
+ снимается целиком, когда правка приедет редакцией пакета
30
50
 
31
51
  > Готовый текст правки — ровно то, что вставить, в стиле соседних правил: по-русски,
32
52
  > утверждением, без воды.
@@ -35,5 +55,6 @@
35
55
 
36
56
  - **место:** ветка `edit`, рядом с соседним родом файлов
37
57
  - **повод:** свой род файлов, которого у других деревьев нет
58
+ - **чем закрывается:** ничего, надстройки под это нет — правка и есть надстройка
38
59
 
39
60
  > Готовый текст правки.
@@ -1 +1 @@
1
- {"version":3,"file":"agent-kit.d.ts","sourceRoot":"","sources":["../../../projects/agent-kit/src/bin/agent-kit.ts"],"names":[],"mappings":";AAeA,OAAO,EAAqC,iBAAiB,EAAqB,MAAM,oBAAoB,CAAC;AAqM7G,wBAAsB,IAAI,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAsE9E"}
1
+ {"version":3,"file":"agent-kit.d.ts","sourceRoot":"","sources":["../../../projects/agent-kit/src/bin/agent-kit.ts"],"names":[],"mappings":";AAeA,OAAO,EAAqC,iBAAiB,EAAqB,MAAM,oBAAoB,CAAC;AA4S7G,wBAAsB,IAAI,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAU9E"}
package/bin/agent-kit.js CHANGED
@@ -13,8 +13,10 @@ import process from 'node:process';
13
13
  import { unknownFlagsIn } from '../lib/argv.js';
14
14
  import { readCatalog, resolveSelection } from '../lib/catalog.js';
15
15
  import { adopt, doctor, init, list, stats, sync } from '../lib/commands.js';
16
+ import { CONFIG_PATH, readConfig } from '../lib/config.js';
17
+ import { enroll, httpEnroll } from '../lib/enroll.js';
16
18
  import { httpShip } from '../lib/ship.js';
17
- import { propose } from '../lib/shipment.js';
19
+ import { propose, treeSlugOf } from '../lib/shipment.js';
18
20
  import { DEFAULT_DAYS } from '../lib/observations.js';
19
21
  import { staleBuild } from '../lib/freshness.js';
20
22
  import { packageRootFrom } from '../lib/package-root.js';
@@ -34,6 +36,8 @@ const USAGE = [
34
36
  ' propose отправить груз в приём: сводку со снимком надстроек, предложения и разборы',
35
37
  ' propose --dry-run показать, что уехало бы, и ничего не отправлять',
36
38
  ' adopt [файлы] отдать пакету файлы, лежащие на его путях не от него',
39
+ ' enroll --code <код> завести дерево по приглашению владельца и положить его токен',
40
+ ' enroll --force перезаписать уже лежащий токен намеренно',
37
41
  '',
38
42
  ' --root <путь> корень проекта; по умолчанию текущий каталог',
39
43
  '',
@@ -79,6 +83,7 @@ function environmentOf(root) {
79
83
  /** Чем это дерево себя выдаёт снаружи. Нет удалённого репозитория — нечем, и это не отказ. */
80
84
  function remoteOf(root) {
81
85
  try {
86
+ // eslint-disable-next-line sonarjs/no-os-command-from-path -- путь к git у каждой машины свой, и прибитый здесь сделал бы пакет непереносимым
82
87
  return execFileSync('git', ['-C', root, 'remote', 'get-url', 'origin'], {
83
88
  encoding: 'utf8',
84
89
  stdio: ['ignore', 'pipe', 'ignore'],
@@ -137,39 +142,36 @@ async function selectionFor(argv, assetsDir) {
137
142
  * отказ там означал бы, что пакет нельзя разложить без человека. Первый вид — умолчание, а не
138
143
  * догадка: порядок в объявлении оси и есть порядок предпочтения.
139
144
  */
145
+ async function variantOfAxis(axis, argv) {
146
+ const spoken = optionOf(argv, `--${axis.name}`, '');
147
+ const values = axis.options.map((option) => option.value);
148
+ if (spoken) {
149
+ return values.includes(spoken)
150
+ ? spoken
151
+ : {
152
+ code: 1,
153
+ lines: [`такого вида по оси «${axis.name}» пакет не везёт: ${spoken}`, `есть: ${values.join(', ')}`],
154
+ };
155
+ }
156
+ if (argv.includes('--all')) {
157
+ return axis.options[0]?.value ?? '';
158
+ }
159
+ if (!canAsk()) {
160
+ return {
161
+ code: 1,
162
+ lines: ['спросить некого: запуск без терминала', `назови вид флагом \`--${axis.name} <вид>\`: ${values.join(', ')}`],
163
+ };
164
+ }
165
+ return askOne(axis.options.map((option) => ({ id: option.value, name: option.value, title: option.title })), axis.question);
166
+ }
140
167
  async function variantsFor(argv, assetsDir) {
141
168
  const axes = readAxes(assetsDir);
142
169
  const chosen = {};
143
170
  for (const axis of axes) {
144
- const spoken = optionOf(argv, `--${axis.name}`, '');
145
- const known = (value) => axis.options.some((option) => option.value === value);
146
- if (spoken) {
147
- if (!known(spoken)) {
148
- return {
149
- code: 1,
150
- lines: [
151
- `такого вида по оси «${axis.name}» пакет не везёт: ${spoken}`,
152
- `есть: ${axis.options.map((option) => option.value).join(', ')}`,
153
- ],
154
- };
155
- }
156
- chosen[axis.name] = spoken;
157
- continue;
158
- }
159
- if (argv.includes('--all')) {
160
- chosen[axis.name] = axis.options[0]?.value ?? '';
161
- continue;
171
+ const answer = await variantOfAxis(axis, argv);
172
+ if (isOutcome(answer)) {
173
+ return answer;
162
174
  }
163
- if (!canAsk()) {
164
- return {
165
- code: 1,
166
- lines: [
167
- 'спросить некого: запуск без терминала',
168
- `назови вид флагом \`--${axis.name} <вид>\`: ${axis.options.map((option) => option.value).join(', ')}`,
169
- ],
170
- };
171
- }
172
- const answer = await askOne(axis.options.map((option) => ({ id: option.value, name: option.value, title: option.title })), axis.question);
173
175
  if (answer === null) {
174
176
  return null;
175
177
  }
@@ -177,68 +179,94 @@ async function variantsFor(argv, assetsDir) {
177
179
  }
178
180
  return chosen;
179
181
  }
182
+ /** Заведение настройки: выбор ресурсов и видов, брошенный выбор ничего не пишет. */
183
+ async function runInit(env, argv) {
184
+ const selection = await selectionFor(argv, env.assetsDir);
185
+ if (isOutcome(selection)) {
186
+ return selection;
187
+ }
188
+ if (selection === null) {
189
+ return { code: 1, lines: ['выбор брошен — ничего не заведено'] };
190
+ }
191
+ const variants = await variantsFor(argv, env.assetsDir);
192
+ if (isOutcome(variants)) {
193
+ return variants;
194
+ }
195
+ return variants === null
196
+ ? { code: 1, lines: ['выбор брошен — ничего не заведено'] }
197
+ : init(env.root, selection, variants, env.assetsDir);
198
+ }
199
+ /** Сводка наблюдений: отрезок из флага, сегодняшний день — с края. */
200
+ function runStats(env, argv) {
201
+ const spoken = Number(optionOf(argv, '--days', ''));
202
+ return stats(env, {
203
+ days: Number.isFinite(spoken) && spoken > 0 ? Math.floor(spoken) : DEFAULT_DAYS,
204
+ // Сегодняшний день берётся здесь: у команды своих часов нет, иначе сводку за
205
+ // отрезок не проверить спекой — вчерашняя фикстура завтра станет позавчерашней.
206
+ today: new Date().toISOString().slice(0, 10),
207
+ json: argv.includes('--json'),
208
+ });
209
+ }
210
+ /**
211
+ * Отправка накопленного.
212
+ *
213
+ * Единственное действие этой команды необратимо и уходит наружу, поэтому незнакомый довод её
214
+ * кончает, а не пропускается молча: вызов ради списка режимов отправил в приём всё накопленное, и
215
+ * по выводу это не отличалось от «команда ничего не сделала». Прочие команды такого разбора не
216
+ * знают — их действие обратимо.
217
+ */
218
+ async function runPropose(env, argv) {
219
+ const unknown = unknownFlagsIn(argv.slice(1), PROPOSE_FLAGS);
220
+ if (unknown.length) {
221
+ return {
222
+ code: 1,
223
+ lines: [`таких доводов у \`propose\` нет: ${unknown.join(', ')}`, '', ...PROPOSE_USAGE],
224
+ };
225
+ }
226
+ return propose(env, {
227
+ dryRun: argv.includes('--dry-run'),
228
+ ship: httpShip,
229
+ remote: remoteOf(env.root),
230
+ // Отрезок тот же, что у сводки по умолчанию: отправку зовут по свежей задаче.
231
+ days: DEFAULT_DAYS,
232
+ today: new Date().toISOString().slice(0, 10),
233
+ });
234
+ }
235
+ /** Приписка дерева к приёмнику по коду приглашения. */
236
+ async function runEnroll(env, argv) {
237
+ const config = readConfig(env.root);
238
+ if (!config) {
239
+ return { code: 1, lines: [`настройки дерева нет: ${CONFIG_PATH}. Заведите её командой \`init\``] };
240
+ }
241
+ return enroll({
242
+ root: env.root,
243
+ intake: config.intake,
244
+ code: optionOf(argv, '--code', ''),
245
+ tree: treeSlugOf(remoteOf(env.root), config.tree),
246
+ token: config.token,
247
+ force: argv.includes('--force'),
248
+ call: httpEnroll,
249
+ });
250
+ }
251
+ /** Что делает каждая команда строки запуска. Ключ — слово, которым её зовут. */
252
+ const COMMANDS = {
253
+ init: runInit,
254
+ list: (env) => list(env),
255
+ sync: (env, argv) => sync(env, argv.includes('--check')),
256
+ stats: runStats,
257
+ propose: runPropose,
258
+ enroll: runEnroll,
259
+ doctor: (env) => doctor(env),
260
+ adopt: (env, argv) => adopt(env, argv.slice(1).filter((value) => !value.startsWith('--') && value !== optionOf(argv, '--root', ''))),
261
+ };
180
262
  export async function main(argv) {
181
263
  const command = argv[0] ?? '';
182
264
  const env = environmentOf(resolve(optionOf(argv, '--root', process.cwd())));
183
- switch (command) {
184
- case 'init': {
185
- const selection = await selectionFor(argv, env.assetsDir);
186
- if (isOutcome(selection)) {
187
- return selection;
188
- }
189
- if (selection === null) {
190
- return { code: 1, lines: ['выбор брошен — ничего не заведено'] };
191
- }
192
- const variants = await variantsFor(argv, env.assetsDir);
193
- if (isOutcome(variants)) {
194
- return variants;
195
- }
196
- return variants === null
197
- ? { code: 1, lines: ['выбор брошен — ничего не заведено'] }
198
- : init(env.root, selection, variants, env.assetsDir);
199
- }
200
- case 'list':
201
- return list(env);
202
- case 'sync':
203
- return sync(env, argv.includes('--check'));
204
- case 'stats': {
205
- const spoken = Number(optionOf(argv, '--days', ''));
206
- return stats(env, {
207
- days: Number.isFinite(spoken) && spoken > 0 ? Math.floor(spoken) : DEFAULT_DAYS,
208
- // Сегодняшний день берётся здесь: у команды своих часов нет, иначе сводку за
209
- // отрезок не проверить спекой — вчерашняя фикстура завтра станет позавчерашней.
210
- today: new Date().toISOString().slice(0, 10),
211
- json: argv.includes('--json'),
212
- });
213
- }
214
- case 'propose': {
215
- // Единственное действие этой команды необратимо и уходит наружу, поэтому незнакомый
216
- // довод её кончает, а не пропускается молча: вызов ради списка режимов отправил в
217
- // приём всё накопленное, и по выводу это не отличалось от «команда ничего не
218
- // сделала». Прочие команды такого разбора не знают — их действие обратимо.
219
- const unknown = unknownFlagsIn(argv.slice(1), PROPOSE_FLAGS);
220
- if (unknown.length) {
221
- return {
222
- code: 1,
223
- lines: [`таких доводов у \`propose\` нет: ${unknown.join(', ')}`, '', ...PROPOSE_USAGE],
224
- };
225
- }
226
- return propose(env, {
227
- dryRun: argv.includes('--dry-run'),
228
- ship: httpShip,
229
- remote: remoteOf(env.root),
230
- // Отрезок тот же, что у сводки по умолчанию: отправку зовут по свежей задаче.
231
- days: DEFAULT_DAYS,
232
- today: new Date().toISOString().slice(0, 10),
233
- });
234
- }
235
- case 'doctor':
236
- return doctor(env);
237
- case 'adopt':
238
- return adopt(env, argv.slice(1).filter((value) => !value.startsWith('--') && value !== optionOf(argv, '--root', '')));
239
- default:
240
- return { code: command ? 1 : 0, lines: command ? [`неизвестная команда «${command}»`, '', ...USAGE] : USAGE };
265
+ const run = Object.hasOwn(COMMANDS, command) ? COMMANDS[command] : undefined;
266
+ if (!run) {
267
+ return { code: command ? 1 : 0, lines: command ? [`неизвестная команда «${command}»`, '', ...USAGE] : USAGE };
241
268
  }
269
+ return run(env, argv);
242
270
  }
243
271
  const outcome = await main(process.argv.slice(2));
244
272
  for (const line of outcome.lines) {