@rt-tools/agent-kit 0.6.0 → 0.8.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 (61) hide show
  1. package/README.md +15 -1
  2. package/assets/checks/check-push-gate.mjs +139 -0
  3. package/assets/checks/check-reuse.mjs +32 -98
  4. package/assets/checks/rt-kit-checks.config.mjs +19 -0
  5. package/assets/checks/signals/angular.json +19 -0
  6. package/assets/checks/signals/core.json +19 -0
  7. package/assets/checks/signals/store.json +14 -0
  8. package/assets/checks/signals/ui-kit-v2.json +48 -0
  9. package/assets/checks/signals/ui-kit.json +11 -0
  10. package/assets/checks/signals/utils.json +15 -0
  11. package/assets/checks/signals.mjs +71 -0
  12. package/assets/defaults/project.sh +9 -7
  13. package/assets/hooks/reuse-first-guard.sh +90 -1
  14. package/assets/patterns/git-workflow-commit.azure.md +18 -0
  15. package/assets/patterns/git-workflow-commit.github.md +18 -0
  16. package/assets/patterns/git-workflow-commit.gitlab.md +18 -0
  17. package/assets/patterns/reuse-first-extend.md +30 -0
  18. package/assets/patterns/spec-driven-domain.md +2 -2
  19. package/assets/rules/git-workflow.azure.md +15 -0
  20. package/assets/rules/git-workflow.github.md +15 -0
  21. package/assets/rules/git-workflow.gitlab.md +15 -0
  22. package/assets/rules/reuse-first.md +31 -13
  23. package/assets/skills/agent-kit.md +13 -8
  24. package/lib/assets.d.ts +1 -1
  25. package/lib/assets.d.ts.map +1 -1
  26. package/lib/assets.js +2 -4
  27. package/lib/assets.js.map +1 -1
  28. package/lib/catalog.d.ts +73 -0
  29. package/lib/catalog.d.ts.map +1 -1
  30. package/lib/catalog.js +121 -0
  31. package/lib/catalog.js.map +1 -1
  32. package/lib/commands.d.ts.map +1 -1
  33. package/lib/commands.js +84 -5
  34. package/lib/commands.js.map +1 -1
  35. package/lib/integrity.d.ts +25 -12
  36. package/lib/integrity.d.ts.map +1 -1
  37. package/lib/integrity.js +40 -18
  38. package/lib/integrity.js.map +1 -1
  39. package/lib/proposals.d.ts +9 -1
  40. package/lib/proposals.d.ts.map +1 -1
  41. package/lib/proposals.js +11 -2
  42. package/lib/proposals.js.map +1 -1
  43. package/lib/retired.d.ts +30 -0
  44. package/lib/retired.d.ts.map +1 -0
  45. package/lib/retired.js +19 -0
  46. package/lib/retired.js.map +1 -0
  47. package/lib/sync.d.ts +45 -1
  48. package/lib/sync.d.ts.map +1 -1
  49. package/lib/sync.js +44 -10
  50. package/lib/sync.js.map +1 -1
  51. package/package.json +1 -1
  52. package/rt-tools-agent-kit-0.8.0.tgz +0 -0
  53. package/assets/laws/application/money.md +0 -41
  54. package/assets/laws/application/ownership.md +0 -32
  55. package/assets/patterns/ownership-scope-resolve.md +0 -69
  56. package/assets/patterns/ownership-session-procedure.md +0 -102
  57. package/assets/patterns/pricing-quote.md +0 -71
  58. package/assets/rules/ownership-scope.md +0 -66
  59. package/assets/rules/ownership-session.md +0 -124
  60. package/assets/rules/pricing.md +0 -68
  61. package/rt-tools-agent-kit-0.6.0.tgz +0 -0
@@ -132,8 +132,90 @@ has_re() {
132
132
  printf '%s' "$1" | RT_RE="$2" perl -0777 -ne 'exit(/$ENV{RT_RE}/s ? 0 : 1)' 2>/dev/null
133
133
  }
134
134
 
135
+ # Что дерево объявило своим: наборы признаков и файл собственных. Настройка проверок — та же,
136
+ # что читает сплошная проверка; каталог наборов называет профиль дерева, потому что раскладка
137
+ # проверок у каждого дерева своя.
138
+ rt_checks_json="${CLAUDE_PROJECT_DIR:-.}/.claude/rt-kit/checks.json"
139
+ rt_bundles=''
140
+ rt_own_signals=''
141
+ if [ -f "$rt_checks_json" ]; then
142
+ rt_bundles="$(jq -r '.reuse.bundles[]? // empty' "$rt_checks_json" 2>/dev/null | tr '\n' ' ')"
143
+ own="$(jq -r '.reuse.signals // empty' "$rt_checks_json" 2>/dev/null)"
144
+ [ -n "$own" ] && rt_own_signals="${CLAUDE_PROJECT_DIR:-.}/$own"
145
+ fi
146
+ rt_signals_dir="${CLAUDE_PROJECT_DIR:-.}/${RT_REUSE_SIGNALS_DIR:-tools/signals}"
147
+
148
+ # Признаки объявленных наборов: те же файлы читает сплошная проверка. Ключ признака совпал с
149
+ # ключом дерева — побеждает дерево: оно видит своё готовое, а пакет его не видел.
150
+ signals_json() {
151
+ [ -n "$rt_signals_dir" ] || return 0
152
+ [ -d "$rt_signals_dir" ] || return 0
153
+ # Шапка раскладки снимается до разбора: в JSON комментария нет, и с ней разбор падает.
154
+ for name in $rt_bundles; do
155
+ file="$rt_signals_dir/$name.json"
156
+ [ -f "$file" ] && grep -v '^# rt-kit ' "$file" | jq -c '.signals[]?' 2>/dev/null
157
+ done
158
+ [ -n "$rt_own_signals" ] && [ -f "$rt_own_signals" ] && grep -v '^# rt-kit ' "$rt_own_signals" | jq -c '.signals[]?' 2>/dev/null
159
+ }
160
+
161
+ # Поля читаются по одному, а не разбором строки: таб в `IFS` — пробельный разделитель, и пустое
162
+ # поле в середине схлопывается, из-за чего совет уезжает в образец отмены и гасит признак молча.
163
+ field() { printf '%s' "$1" | jq -r "$2 // empty" 2>/dev/null; }
164
+
135
165
  found=''
136
- while IFS="$(printf '\t')" read -r scope pattern cancel replacement; do
166
+ signals_seen=0
167
+ while IFS= read -r signal; do
168
+ [ -z "$signal" ] && continue
169
+ signals_seen=1
170
+ ext="$(field "$signal" '.ext')"
171
+ case "$ext" in
172
+ '') ;;
173
+ *) case "$path" in *"$ext") ;; *) continue ;; esac ;;
174
+ esac
175
+ only_named="$(field "$signal" '.onlyNamed')"
176
+ if [ -n "$only_named" ]; then
177
+ printf '%s' "${path##*/}" | grep -qE "$only_named" || continue
178
+ fi
179
+
180
+ case "$(field "$signal" '.scope')" in
181
+ whole) text="$whole" ;;
182
+ *) text="$added" ;;
183
+ esac
184
+
185
+ pattern="$(field "$signal" '.find')"
186
+ [ -z "$pattern" ] && continue
187
+ strip="$(field "$signal" '.strip')"
188
+ [ -n "$strip" ] && text="$(printf '%s' "$text" | RT_RE="$strip" perl -0777 -pe 's/$ENV{RT_RE}//gs' 2>/dev/null)"
189
+
190
+ has_re "$text" "$pattern" || continue
191
+
192
+ cancel="$(field "$signal" '.cancel')"
193
+ [ -n "$cancel" ] && has_re "$text" "$cancel" && continue
194
+
195
+ skip_signal=''
196
+ while IFS= read -r one; do
197
+ [ -z "$one" ] && continue
198
+ has_re "$text" "$one" || skip_signal=1
199
+ done <<ALL
200
+ $(printf '%s' "$signal" | jq -r '.all[]? // empty' 2>/dev/null)
201
+ ALL
202
+ [ -n "$skip_signal" ] && continue
203
+
204
+ found="${found}
205
+ - $(field "$signal" '.instead')"
206
+ done <<EOF
207
+ $(signals_json | jq -s -c 'reduce .[] as $one ({}; .[$one.key] = $one) | .[]' 2>/dev/null)
208
+ EOF
209
+
210
+ # Функция профиля остаётся вторым источником: деревья её уже написали. Поля читаются построчно,
211
+ # по тем же четырём колонкам, что объявлены выше.
212
+ while IFS= read -r line; do
213
+ [ -z "$line" ] && continue
214
+ signals_seen=1
215
+ scope="$(printf '%s' "$line" | cut -f1)"
216
+ pattern="$(printf '%s' "$line" | cut -f2)"
217
+ cancel="$(printf '%s' "$line" | cut -f3)"
218
+ replacement="$(printf '%s' "$line" | cut -f4)"
137
219
  [ -z "$pattern" ] && continue
138
220
  case "$scope" in
139
221
  whole) text="$whole" ;;
@@ -147,6 +229,13 @@ done <<EOF
147
229
  $(rt_reinvented_in "$path" 2>/dev/null)
148
230
  EOF
149
231
 
232
+ # Гард без единого признака неотличим от гарда, которому нечего отбивать. Правку он пропускает —
233
+ # останавливать работу за ненастроенное дерево не за что, — но говорит, чем это настраивается.
234
+ if [ "$signals_seen" = 0 ]; then
235
+ printf '%s\n' 'reuse-first-guard: признаков нет — объявите наборы ключом `reuse.bundles` в настройке проверок' >&2
236
+ exit 0
237
+ fi
238
+
150
239
  [ -z "$found" ] && exit 0
151
240
 
152
241
  reason="BLOCKED: это уже написано. Файл: ${path##*/}
@@ -201,6 +201,11 @@ az repos pr show --id 205 \
201
201
 
202
202
  Владельцу называют то, что прочитали, а не то, что заказывали.
203
203
 
204
+ Перечитывают его и по времени, а не только после вызовов, которые молча ничего не сделали:
205
+ состояние PR читается перед тем, как что-либо о нём сказать. Между «прогон зелёный» и следующей
206
+ фразой владелец успевает влить PR, и всё сказанное о нём после этого — про вчерашний день. Так
207
+ владельцу и было предложено влить то, что он влил часом раньше.
208
+
204
209
  Открытый PR означает, что элемент ждёт разбора, — состояние переставляется тем же движением:
205
210
 
206
211
  ```bash
@@ -236,6 +241,14 @@ npm run task:move -- 86 in-review
236
241
  сделано>`, тем же номером, что стоит у элемента и в имени ветки.
237
242
  12. **Очередь работ сходится** — `npm run check:board`.
238
243
  13. **Состояние PR прочитано, а не выведено из кодов возврата.**
244
+ 14. **Набор взят из файла конвейера, а не собран по памяти.** Гейт пуша заведомо уже: он стоит
245
+ между командой и пушем, и всё, что дольше секунд, из него вынесено. Что гоняет конвейер,
246
+ написано в его файле — этот список и повторяется локально; зелёный гейт полнотой набора не
247
+ является.
248
+ 15. **Набор пересмотрен после вливания главной ветки.** Он выбирается по тому, что ветка везёт
249
+ теперь, а не по тому, что правил автор. Ветка, не тронувшая ни строки показа, прогоняет
250
+ снимки витрин: с момента вливания их гоняет конвейер на её коде, и красное придёт на её
251
+ отчёт.
239
252
 
240
253
  Сразу после публикации элемент переводится в разбор, и сверка очереди прогоняется ещё раз: до
241
254
  открытия PR состояние она не судит, а после открытия расхождение видит.
@@ -243,6 +256,11 @@ npm run task:move -- 86 in-review
243
256
  Сделанное рассуждением и сделанное замером в теле PR разводятся прямо: непроверенное,
244
257
  названное проверенным, ревьювер принимает за проверенное.
245
258
 
259
+ **Раздел «Чем подтверждено» называет и то, что не гонялось.** Список одного прогнанного
260
+ неотличим от полного набора, и ревьювер по нему решает, что можно не перепроверять. Цена ошибки
261
+ здесь не красный конвейер, а доверие к разделу: однажды прочитанный как полнота, дальше он
262
+ перепроверяется весь.
263
+
246
264
  ## Частые промахи
247
265
 
248
266
  - Область и итерация не заданы: элемент заведён, но на доску команды не попал.
@@ -249,6 +249,11 @@ $GH api "repos/$REPO/pulls/321" \
249
249
  Учётная запись, из-под которой пришлось пушить, в этот вызов не переносится: пуш и авторство
250
250
  PR выбираются отдельно, и `GH_TOKEN` для публикации — всегда токен бота.
251
251
 
252
+ Перечитывают его и по времени, а не только после вызовов, которые молча ничего не сделали:
253
+ состояние PR читается перед тем, как что-либо о нём сказать. Между «прогон зелёный» и следующей
254
+ фразой владелец успевает влить PR, и всё сказанное о нём после этого — про вчерашний день. Так
255
+ владельцу и было предложено влить то, что он влил часом раньше.
256
+
252
257
  Открытый PR означает, что задача ждёт разбора, — колонка переставляется тем же движением:
253
258
 
254
259
  ```bash
@@ -293,6 +298,14 @@ npm run task:move -- 86 in-review
293
298
  номером в заголовке; PR один на задачу, и закрывает он её целиком.
294
299
  13. **Состояние PR прочитано, а не выведено из кодов возврата** — автор `<бот>`,
295
300
  ревьювер — владелец, метки те же, что у задачи. Владельцу называют прочитанное.
301
+ 14. **Набор взят из файла конвейера, а не собран по памяти.** Гейт пуша заведомо уже: он стоит
302
+ между командой и пушем, и всё, что дольше секунд, из него вынесено. Что гоняет конвейер,
303
+ написано в его файле — этот список и повторяется локально; зелёный гейт полнотой набора не
304
+ является.
305
+ 15. **Набор пересмотрен после вливания главной ветки.** Он выбирается по тому, что ветка везёт
306
+ теперь, а не по тому, что правил автор. Ветка, не тронувшая ни строки показа, прогоняет
307
+ снимки витрин: с момента вливания их гоняет конвейер на её коде, и красное придёт на её
308
+ отчёт.
296
309
 
297
310
  Сразу после публикации задача переставляется в разбор — `npm run task:move -- <номер>
298
311
  in-review`, — и `npm run check:board` прогоняется ещё раз: до открытия PR колонку он не судит,
@@ -301,6 +314,11 @@ in-review`, — и `npm run check:board` прогоняется ещё раз:
301
314
  Сделанное рассуждением и сделанное замером в теле PR разводятся прямо: непроверенное,
302
315
  названное проверенным, ревьювер принимает за проверенное.
303
316
 
317
+ **Раздел «Чем подтверждено» называет и то, что не гонялось.** Список одного прогнанного
318
+ неотличим от полного набора, и ревьювер по нему решает, что можно не перепроверять. Цена ошибки
319
+ здесь не красный конвейер, а доверие к разделу: однажды прочитанный как полнота, дальше он
320
+ перепроверяется весь.
321
+
304
322
  ## Частые промахи
305
323
 
306
324
  - `gh` в оболочке пользователя подменён — звать `/opt/homebrew/bin/gh` напрямую.
@@ -221,6 +221,11 @@ glab mr view 205 --output json \
221
221
  Учётная запись, из-под которой пришлось пушить, в этот вызов не переносится: пуш и авторство
222
222
  MR выбираются отдельно, и токен для публикации — всегда токен машинной работы.
223
223
 
224
+ Перечитывают его и по времени, а не только после вызовов, которые молча ничего не сделали:
225
+ состояние MR читается перед тем, как что-либо о нём сказать. Между «прогон зелёный» и следующей
226
+ фразой владелец успевает влить MR, и всё сказанное о нём после этого — про вчерашний день. Так
227
+ владельцу и было предложено влить то, что он влил часом раньше.
228
+
224
229
  Открытый MR означает, что задача ждёт разбора, — список переставляется тем же движением:
225
230
 
226
231
  ```bash
@@ -256,6 +261,14 @@ npm run task:move -- 86 in-review
256
261
  сделано>`, тем же номером, что стоит у задачи и в имени ветки.
257
262
  12. **Очередь работ сходится** — `npm run check:board`.
258
263
  13. **Состояние MR прочитано, а не выведено из кодов возврата.**
264
+ 14. **Набор взят из файла конвейера, а не собран по памяти.** Гейт пуша заведомо уже: он стоит
265
+ между командой и пушем, и всё, что дольше секунд, из него вынесено. Что гоняет конвейер,
266
+ написано в его файле — этот список и повторяется локально; зелёный гейт полнотой набора не
267
+ является.
268
+ 15. **Набор пересмотрен после вливания главной ветки.** Он выбирается по тому, что ветка везёт
269
+ теперь, а не по тому, что правил автор. Ветка, не тронувшая ни строки показа, прогоняет
270
+ снимки витрин: с момента вливания их гоняет конвейер на её коде, и красное придёт на её
271
+ отчёт.
259
272
 
260
273
  Сразу после публикации задача переставляется в разбор, и сверка очереди прогоняется ещё раз: до
261
274
  открытия MR список она не судит, а после открытия расхождение видит.
@@ -263,6 +276,11 @@ npm run task:move -- 86 in-review
263
276
  Сделанное рассуждением и сделанное замером в теле MR разводятся прямо: непроверенное,
264
277
  названное проверенным, ревьювер принимает за проверенное.
265
278
 
279
+ **Раздел «Чем подтверждено» называет и то, что не гонялось.** Список одного прогнанного
280
+ неотличим от полного набора, и ревьювер по нему решает, что можно не перепроверять. Цена ошибки
281
+ здесь не красный конвейер, а доверие к разделу: однажды прочитанный как полнота, дальше он
282
+ перепроверяется весь.
283
+
266
284
  ## Частые промахи
267
285
 
268
286
  - Метка списка не поставлена при заведении: задача есть, а на доске её нет. Доска показывает
@@ -45,6 +45,36 @@ grep -rn "<похожий приём>" libs/admin libs/site --include='*.html' |
45
45
  Спрашивается до того, как написан первый файл. То же относится к своей базе и к своему
46
46
  инлайновому стилю.
47
47
 
48
+ ## Своё готовое объявляется признаком
49
+
50
+ Заведённое с одобрения владельца видит только тот, кто о нём знает. Чтобы следующий его не
51
+ обошёл, дерево дописывает признак в свой файл признаков — тот, что назван ключом
52
+ `reuse.signals` в настройке проверок:
53
+
54
+ ```json
55
+ {
56
+ "key": "<короткое имя признака>",
57
+ "ext": ".ts",
58
+ "mode": "presence",
59
+ "scope": "whole",
60
+ "find": "implements +<интерфейс, который закрывает основа>",
61
+ "cancel": "extends +<основа>",
62
+ "instead": "<основа> — она уже держит <что именно>"
63
+ }
64
+ ```
65
+
66
+ Область объявляется явно. `whole` читает правку вместе с содержимым файла и берётся там, где
67
+ признак судит устройство класса: наследование и метка объявлены один раз и в точечную правку не
68
+ попадают, поэтому признак без `whole` молчит всегда. `added` читает только добавленный текст и
69
+ берётся там, где признак судит саму строку — сырой тег, свой стиль.
70
+
71
+ Отмена `cancel` обязательна везде, где готовое можно позвать законно: без неё признак отбивает и
72
+ того, кто основу уже унаследовал. Пустой её не оставляют — признак без отмены объявляется без
73
+ этого поля вовсе.
74
+
75
+ Ключ, совпавший с пакетным, замещает его: дерево вправе сказать о своём готовом точнее, чем
76
+ пакет, который его не видел.
77
+
48
78
  ## Разовое отступление объявляется маркером
49
79
 
50
80
  ```html
@@ -48,8 +48,8 @@ docs/specs/<домен>/
48
48
  процедуры домен обслуживает.
49
49
 
50
50
  ```markdown
51
- **Зависимости:** `pricing` (сумма заявки), `availability` (занятость дат)
52
- **Законы:** `access`, `locales`, `money`, `ownership`
51
+ **Зависимости:** `catalog` (состав заявки), `availability` (занятость дат)
52
+ **Законы:** `access`, `locales`, `lists`
53
53
  **Процедуры:** `libs/api/<домен>`
54
54
  ```
55
55
 
@@ -108,6 +108,21 @@ description: Правило под «Закон о поставке» для д
108
108
  только то, что импортировано тестом: ошибка типов в непокрытом коде доживает до сборки
109
109
  образа, то есть до слияния. Четыре слияния подряд так и уехали в главную ветку, ломая
110
110
  выкатку.
111
+ - **Набор гейта пуша не бывает уже набора конвейера.** Гейт — обещание, что пуш не приедет
112
+ красным; набор, из которого выкинуты сборка и снимки, обещает то, чего не проверяет. Шаг
113
+ конвейера, которому в наборе гейта нет ни строки, ни объявленного исключения с причиной,
114
+ отбивает пуш, а не печатается рядом с ним: напечатанное предупреждение исполнитель читает как
115
+ разрешение. Дважды подряд правка, прошедшая гейт целиком, была отбита конвейером — и оба раза
116
+ зелёный гейт был прочитан как «локально всё зелено».
117
+ - **После вливания главной ветки набор проверок пересматривается по тому, что ветка везёт
118
+ теперь.** Вливание меняет состав правки: проверять по тому, что правил автор, — значит
119
+ проверять половину, а отвечает ветка целиком. Ветка, не тронувшая ни строки показа, прогоняет
120
+ снимки витрин с того момента, как вливание принесло чужую правку оформления.
121
+ - **Утверждение о главной ветке делается по удалённой ссылке, а не по локальной.** Локальная
122
+ протухает в ту минуту, когда её подтянули в последний раз, и молчит об этом: она не пуста и не
123
+ сломана, она описывает вчерашний день. Сравнение веток пишется от `origin/main` целиком —
124
+ смешав в одной команде удалённую ссылку для одной стороны и локальную для другой, промах
125
+ изнутри выглядит правильным.
111
126
  - **Рабочий элемент привязывается к PR при создании, а не после.** `az repos pr create`
112
127
  принимает `--work-items`; привязка второй командой обходится молча, когда у токена нет права
113
128
  править чужой элемент, и PR остаётся ни с чем не связанным.
@@ -118,6 +118,21 @@ description: Правило под «Закон о поставке» для д
118
118
  - **Сборка входит в набор наравне с линтом и юнитами.** Линтер типов не читает, а юниты читают
119
119
  только то, что импортировано тестом: ошибка типов в непокрытом коде доживает до сборки
120
120
  образа, то есть до мержа. Четыре мержа подряд так и уехали в главную ветку, ломая выкатку.
121
+ - **Набор гейта пуша не бывает уже набора конвейера.** Гейт — обещание, что пуш не приедет
122
+ красным; набор, из которого выкинуты сборка и снимки, обещает то, чего не проверяет. Шаг
123
+ конвейера, которому в наборе гейта нет ни строки, ни объявленного исключения с причиной,
124
+ отбивает пуш, а не печатается рядом с ним: напечатанное предупреждение исполнитель читает как
125
+ разрешение. Дважды подряд правка, прошедшая гейт целиком, была отбита конвейером — и оба раза
126
+ зелёный гейт был прочитан как «локально всё зелено».
127
+ - **После вливания главной ветки набор проверок пересматривается по тому, что ветка везёт
128
+ теперь.** Вливание меняет состав правки: проверять по тому, что правил автор, — значит
129
+ проверять половину, а отвечает ветка целиком. Ветка, не тронувшая ни строки показа, прогоняет
130
+ снимки витрин с того момента, как вливание принесло чужую правку оформления.
131
+ - **Утверждение о главной ветке делается по удалённой ссылке, а не по локальной.** Локальная
132
+ протухает в ту минуту, когда её подтянули в последний раз, и молчит об этом: она не пуста и не
133
+ сломана, она описывает вчерашний день. Сравнение веток пишется от `origin/main` целиком —
134
+ смешав в одной команде удалённую ссылку для одной стороны и локальную для другой, промах
135
+ изнутри выглядит правильным.
121
136
  - **Автор PR не может быть его ревьювером.** Запрос разбора на самого себя GitHub принимает и
122
137
  молча не создаёт — разбор при этом выглядит запрошенным.
123
138
  - **Метки, исполнитель и ревьювер PR ставятся вызовами `gh api`, а не `gh pr edit`.** На
@@ -108,6 +108,21 @@ description: Правило под «Закон о поставке» для д
108
108
  только то, что импортировано тестом: ошибка типов в непокрытом коде доживает до сборки
109
109
  образа, то есть до слияния. Четыре слияния подряд так и уехали в главную ветку, ломая
110
110
  выкатку.
111
+ - **Набор гейта пуша не бывает уже набора конвейера.** Гейт — обещание, что пуш не приедет
112
+ красным; набор, из которого выкинуты сборка и снимки, обещает то, чего не проверяет. Шаг
113
+ конвейера, которому в наборе гейта нет ни строки, ни объявленного исключения с причиной,
114
+ отбивает пуш, а не печатается рядом с ним: напечатанное предупреждение исполнитель читает как
115
+ разрешение. Дважды подряд правка, прошедшая гейт целиком, была отбита конвейером — и оба раза
116
+ зелёный гейт был прочитан как «локально всё зелено».
117
+ - **После вливания главной ветки набор проверок пересматривается по тому, что ветка везёт
118
+ теперь.** Вливание меняет состав правки: проверять по тому, что правил автор, — значит
119
+ проверять половину, а отвечает ветка целиком. Ветка, не тронувшая ни строки показа, прогоняет
120
+ снимки витрин с того момента, как вливание принесло чужую правку оформления.
121
+ - **Утверждение о главной ветке делается по удалённой ссылке, а не по локальной.** Локальная
122
+ протухает в ту минуту, когда её подтянули в последний раз, и молчит об этом: она не пуста и не
123
+ сломана, она описывает вчерашний день. Сравнение веток пишется от `origin/main` целиком —
124
+ смешав в одной команде удалённую ссылку для одной стороны и локальную для другой, промах
125
+ изнутри выглядит правильным.
111
126
  - **Слияние по кнопке «Merge when pipeline succeeds» не заменяет проверок до пуша.** Конвейер
112
127
  видит только то, что уже отправлено, а отправленная красная ветка занимает очередь работ и
113
128
  выглядит готовой к разбору.
@@ -46,9 +46,12 @@ description: Правило под «Закон о единообразии пр
46
46
  раз в общем слое приложения, а экран её только применяет теми же директивами BEM.
47
47
  - **Готовое расширяется, а не клонируется рядом.** Недостающий вариант заводится в ките или в
48
48
  базовом классе, и его видят остальные экраны.
49
- - **Накопленное до гарда сосчитано сплошной проверкой и в список только не растёт.** Признаки
50
- у неё те же, что у гарда, а смотрит она файл целиком: гейт падает на новом месте, старое
51
- остаётся числом в сводке.
49
+ - **Накопленное до гарда сосчитано сплошной проверкой и в список только не растёт.** Признаки у
50
+ неё те же, что у гарда, потому что оба читают одни и те же объявленные наборы, а смотрит она
51
+ файл целиком: гейт падает на новом месте, старое остаётся числом в сводке.
52
+ - **Признаки объявляются деревом, а не зашиты в проверку.** Дерево берёт из rt-tools не всё, и
53
+ признак о готовом из пакета, которого здесь нет, отвечает ложно ровно так же, как имя чужого
54
+ приложения.
52
55
 
53
56
  ## Чего из закона здесь нет
54
57
 
@@ -67,16 +70,24 @@ description: Правило под «Закон о единообразии пр
67
70
 
68
71
  ## Признаки, по которым видно, что готовое обошли
69
72
 
70
- - в шаблоне фичи стоит нативный `<input>`, `<button>`, `<select>`, `<table>` или `<dialog>`;
71
- - отказ или предупреждение собраны руками свой `role="alert"` вместо `rt-message`;
72
- - в `.scss` фичи появились `position: fixed` на весь экран, свой backdrop, `z-index` от тысячи,
73
- `@keyframes` вращения или мерцания;
74
- - в файле стилей экрана объявлена раскладка`display: flex` с `gap` и `padding` на `:host`;
75
- - компонент реализует `ControlValueAccessor` сам, а не наследует `RtFormControlBase`;
76
- - имя файла кончается на `-button`, `-input`, `-dialog`, `-spinner` или `-table` вне
77
- кита;
78
- - маппер переводит поля вручную, без `BaseMapper`;
79
- - процедура бэкенда объявлена без `@ConnectProcedure()`.
73
+ Списком их здесь нет: признак это данные, а не текст правила и не код проверки. Наборы лежат
74
+ при пакете, по файлу на пакет rt-tools, и в каждом только то, что везёт он сам. Дерево называет
75
+ нужные ему наборы ключом `reuse.bundles` в настройке проверок и дописывает свои признаки файлом,
76
+ названным ключом `reuse.signals`; совпавший ключ замещает пакетный, новый дописывается. Гард на
77
+ правке и сплошная проверка читают отсюда обаразойтись им нечем.
78
+
79
+ Что бывает признаком:
80
+
81
+ - нативный контрол в шаблоне там, где кит везёт свой;
82
+ - отказ, предупреждение или ожидание, собранные руками вместо готового;
83
+ - наложение поверх страницы, объявленное своими стилями, — подложка, порядок слоёв, вращение;
84
+ - раскладка, объявленная в файле стилей экрана, а не применённая директивами из общего слоя;
85
+ - своя реализация того, что даёт основа кита, вместо наследования;
86
+ - имя файла, повторяющее имя китового примитива, вне кита.
87
+
88
+ Признак называет свою область: наследование основы и метка класса в точечную правку не попадают,
89
+ и признак, судящий их по добавленному тексту, молчит всегда. Признак называет и отмену — образец,
90
+ при котором он не срабатывает: основа уже унаследована, готовое уже позвано.
80
91
 
81
92
  ## Ловушки
82
93
 
@@ -90,3 +101,10 @@ description: Правило под «Закон о единообразии пр
90
101
  отступ, оставаясь тем же кодом.
91
102
  - Ответ, данный до чтения образца, образец отменяет: согласованная форма выборки списка
92
103
  переигрывалась вместе с контрактом через два вопроса после того, как была принята.
104
+ - **Набор объявляется по тому, что дерево потребляет, а не по тому, что пакет везёт.** Дерево, в
105
+ котором кит написан, а не позван, объявив его набор, получает советы звать кит на файлах самого
106
+ кита: признак верен, но обращён не туда. Такое дерево объявляет только те наборы, чьё готовое
107
+ оно берёт со стороны.
108
+ - **Гард, не получивший ни одного признака, неотличим от гарда, которому нечего отбивать.** Он
109
+ говорит об этом сам, и первая же правка в дереве без объявленных наборов это показывает.
110
+ Молчание проверки признаком порядка не считается — сначала смотрят, есть ли ей чем судить.
@@ -19,14 +19,15 @@ description: Переносимый слой правил агента — за
19
19
 
20
20
  ## Где что настраивается
21
21
 
22
- | Что меняешь | Куда правка |
23
- | ---------------------------------------------- | -------------------------------------------------- |
24
- | какое правило гейт требует под какой файл | `.claude/rt-kit/gate-map.sh` — своя `skill_for` |
25
- | порты, адреса, линтеры, форма ветки, инвентарь | `.claude/rt-kit/project.sh` — свои `rt_*` |
26
- | пути и идентификаторы, которыми живут проверки | `.claude/rt-kit/checks.json` |
27
- | раздел разложенного текста | `.claude/rt-kit/overrides/<идентификатор ресурса>` |
28
- | что брать, а от чего отказаться | `.claude/rt-kit.json`, ключи `only` и `skip` |
29
- | сам механизм гард, проверка, текст правила | ресурс в пакете |
22
+ | Что меняешь | Куда правка |
23
+ | ---------------------------------------------- | ------------------------------------------------------ |
24
+ | какое правило гейт требует под какой файл | `.claude/rt-kit/gate-map.sh` — своя `skill_for` |
25
+ | порты, адреса, линтеры, форма ветки, инвентарь | `.claude/rt-kit/project.sh` — свои `rt_*` |
26
+ | пути и идентификаторы, которыми живут проверки | `.claude/rt-kit/checks.json` |
27
+ | какие признаки единообразия дерево берёт | `checks.json`, ключи `reuse.bundles` и `reuse.signals` |
28
+ | раздел разложенного текста | `.claude/rt-kit/overrides/<идентификатор ресурса>` |
29
+ | что брать, а от чего отказаться | `.claude/rt-kit.json`, ключи `only` и `skip` |
30
+ | сам механизм — гард, проверка, текст правила | ресурс в пакете |
30
31
 
31
32
  Надстройка объявляет функцию заново и вправе позвать умолчание тем же именем с суффиксом
32
33
  `_default`. Слияние текста идёт по разделам `## `: совпавший заголовок замещает, новый
@@ -84,6 +85,10 @@ npx agent-kit propose # отправить предложения, адр
84
85
 
85
86
  ## Ловушки
86
87
 
88
+ - **Правила и паттерны при отвергнутом законе в отказе не перечисляются.** Их снимает каскад, а
89
+ строка на них становится выводимой: раскладка называет её лишней вместе с законом, из-за
90
+ которого она перестала снимать. Отказ мерит слой законов, а не число файлов в пакете.
91
+
87
92
  - **Линтер по следам правки судит файл целиком, а не внесённую правку.** Импорт, добавленный
88
93
  отдельным шагом, отбивается как неиспользуемый ещё до того, как появится строка, которая его
89
94
  зовёт, и работа встаёт на половине. Правка делается одним вызовом либо в порядке «сначала
package/lib/assets.d.ts CHANGED
@@ -19,6 +19,6 @@ export interface IAsset {
19
19
  * ресурса нет. У разложенного `git-workflow` хостинга в имени не остаётся: второго вида в этом
20
20
  * дереве нет, а агенту, который правило читает, различать их незачем.
21
21
  */
22
- export declare function targetOf(entry: IEntryOfCatalog, layout: Readonly<Record<TKind, string>>): string;
22
+ export declare function targetOf(entry: Pick<IEntryOfCatalog, 'id' | 'kind' | 'name'>, layout: Readonly<Record<TKind, string>>): string;
23
23
  export declare function collectAssets(config: IConfig, assetsDir: string): readonly IAsset[];
24
24
  //# sourceMappingURL=assets.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"assets.d.ts","sourceRoot":"","sources":["../../../projects/agent-kit/src/lib/assets.ts"],"names":[],"mappings":"AASA,OAAO,EAAE,eAAe,EAAyB,MAAM,cAAc,CAAC;AACtE,OAAO,EAAE,OAAO,EAA2B,KAAK,EAAE,MAAM,aAAa,CAAC;AAEtE,MAAM,WAAW,MAAM;IACnB,kGAAkG;IAClG,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,IAAI,EAAE,KAAK,CAAC;IACrB,8FAA8F;IAC9F,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,kDAAkD;IAClD,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,gFAAgF;IAChF,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC;CAChC;AAUD;;;;;;GAMG;AACH,wBAAgB,QAAQ,CAAC,KAAK,EAAE,eAAe,EAAE,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,GAAG,MAAM,CAIhG;AAED,wBAAgB,aAAa,CAAC,MAAM,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,CAWnF"}
1
+ {"version":3,"file":"assets.d.ts","sourceRoot":"","sources":["../../../projects/agent-kit/src/lib/assets.ts"],"names":[],"mappings":"AASA,OAAO,EAAiB,eAAe,EAAe,MAAM,cAAc,CAAC;AAC3E,OAAO,EAAE,OAAO,EAA2B,KAAK,EAAE,MAAM,aAAa,CAAC;AAEtE,MAAM,WAAW,MAAM;IACnB,kGAAkG;IAClG,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,IAAI,EAAE,KAAK,CAAC;IACrB,8FAA8F;IAC9F,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,kDAAkD;IAClD,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,gFAAgF;IAChF,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC;CAChC;AAUD;;;;;;GAMG;AACH,wBAAgB,QAAQ,CAAC,KAAK,EAAE,IAAI,CAAC,eAAe,EAAE,IAAI,GAAG,MAAM,GAAG,MAAM,CAAC,EAAE,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,GAAG,MAAM,CAI9H;AAED,wBAAgB,aAAa,CAAC,MAAM,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,CASnF"}
package/lib/assets.js CHANGED
@@ -6,7 +6,7 @@
6
6
  * показывает и то, от чего проект отказался, — обоим нужен набор до отбора.
7
7
  */
8
8
  import { join } from 'node:path';
9
- import { isChosen, readCatalog } from './catalog.js';
9
+ import { chosenEntries, readCatalog } from './catalog.js';
10
10
  import { SKILL_FILE, SKILL_KINDS } from './config.js';
11
11
  /** Расширение ресурса вместе с точкой; у файла без расширения — пустая строка. */
12
12
  const extensionOf = (id) => {
@@ -27,9 +27,7 @@ export function targetOf(entry, layout) {
27
27
  : join(layout[entry.kind], `${entry.name}${extensionOf(entry.id)}`);
28
28
  }
29
29
  export function collectAssets(config, assetsDir) {
30
- return readCatalog(assetsDir)
31
- .filter((entry) => isChosen(entry, config))
32
- .map((entry) => ({
30
+ return chosenEntries(readCatalog(assetsDir), config).map((entry) => ({
33
31
  id: entry.id,
34
32
  kind: entry.kind,
35
33
  name: entry.name,
package/lib/assets.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"assets.js","sourceRoot":"","sources":["../../../projects/agent-kit/src/lib/assets.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,OAAO,EAAmB,QAAQ,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AACtE,OAAO,EAAW,UAAU,EAAE,WAAW,EAAS,MAAM,aAAa,CAAC;AAetE,kFAAkF;AAClF,MAAM,WAAW,GAA2B,CAAC,EAAU,EAAU,EAAE;IAC/D,MAAM,GAAG,GAAW,EAAE,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;IACxC,MAAM,KAAK,GAAW,EAAE,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;IAE1C,OAAO,GAAG,GAAG,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;AAC5C,CAAC,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,UAAU,QAAQ,CAAC,KAAsB,EAAE,MAAuC;IACpF,OAAO,WAAW,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC;QACnC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,KAAK,CAAC,IAAI,EAAE,UAAU,CAAC;QAClD,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,GAAG,KAAK,CAAC,IAAI,GAAG,WAAW,CAAC,KAAK,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;AAC5E,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,MAAe,EAAE,SAAiB;IAC5D,OAAO,WAAW,CAAC,SAAS,CAAC;SACxB,MAAM,CAAC,CAAC,KAAsB,EAAW,EAAE,CAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;SACpE,GAAG,CAAC,CAAC,KAAsB,EAAU,EAAE,CAAC,CAAC;QACtC,EAAE,EAAE,KAAK,CAAC,EAAE;QACZ,IAAI,EAAE,KAAK,CAAC,IAAI;QAChB,IAAI,EAAE,KAAK,CAAC,IAAI;QAChB,IAAI,EAAE,KAAK,CAAC,IAAI;QAChB,MAAM,EAAE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,CAAC;QACtC,UAAU,EAAE,KAAK,CAAC,UAAU;KAC/B,CAAC,CAAC,CAAC;AACZ,CAAC","sourcesContent":["/**\n * Что из везомого пакетом взял этот проект и куда оно ложится.\n *\n * Полный набор читает `catalog.ts`, здесь из него остаётся выбранное. Разделено потому, что\n * выбирать приходится раньше, чем раскладывать: `init` спрашивает, какие законы взять, а `list`\n * показывает и то, от чего проект отказался, — обоим нужен набор до отбора.\n */\nimport { join } from 'node:path';\n\nimport { IEntryOfCatalog, isChosen, readCatalog } from './catalog.js';\nimport { IConfig, SKILL_FILE, SKILL_KINDS, TKind } from './config.js';\n\nexport interface IAsset {\n /** Идентификатор: `laws/application/money.md`. Он же путь надстройки и ключ в `only` и `skip`. */\n readonly id: string;\n readonly kind: TKind;\n /** Имя без рода, вида и расширения: `delivery`. У скила — имя каталога, которым его зовут. */\n readonly name: string;\n readonly text: string;\n /** Путь в дереве проекта, куда ресурс ложится. */\n readonly target: string;\n /** Запускается ли файл сам по себе: гарда без права на запуск не существует. */\n readonly executable: boolean;\n}\n\n/** Расширение ресурса вместе с точкой; у файла без расширения — пустая строка. */\nconst extensionOf: (id: string) => string = (id: string): string => {\n const dot: number = id.lastIndexOf('.');\n const slash: number = id.lastIndexOf('/');\n\n return dot > slash ? id.slice(dot) : '';\n};\n\n/**\n * Куда ложится ресурс. Скил читается агентом как `<имя>/SKILL.md`, и другого имени у него быть\n * не может: файл, названный по ресурсу, агент не найдёт вовсе. Остальные роды ложатся файлом —\n * по имени, а не по идентификатору: слой законов из идентификатора в путь переезжает, а вид\n * ресурса нет. У разложенного `git-workflow` хостинга в имени не остаётся: второго вида в этом\n * дереве нет, а агенту, который правило читает, различать их незачем.\n */\nexport function targetOf(entry: IEntryOfCatalog, layout: Readonly<Record<TKind, string>>): string {\n return SKILL_KINDS.includes(entry.kind)\n ? join(layout[entry.kind], entry.name, SKILL_FILE)\n : join(layout[entry.kind], `${entry.name}${extensionOf(entry.id)}`);\n}\n\nexport function collectAssets(config: IConfig, assetsDir: string): readonly IAsset[] {\n return readCatalog(assetsDir)\n .filter((entry: IEntryOfCatalog): boolean => isChosen(entry, config))\n .map((entry: IEntryOfCatalog): IAsset => ({\n id: entry.id,\n kind: entry.kind,\n name: entry.name,\n text: entry.text,\n target: targetOf(entry, config.layout),\n executable: entry.executable,\n }));\n}\n"]}
1
+ {"version":3,"file":"assets.js","sourceRoot":"","sources":["../../../projects/agent-kit/src/lib/assets.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,OAAO,EAAE,aAAa,EAAmB,WAAW,EAAE,MAAM,cAAc,CAAC;AAC3E,OAAO,EAAW,UAAU,EAAE,WAAW,EAAS,MAAM,aAAa,CAAC;AAetE,kFAAkF;AAClF,MAAM,WAAW,GAA2B,CAAC,EAAU,EAAU,EAAE;IAC/D,MAAM,GAAG,GAAW,EAAE,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;IACxC,MAAM,KAAK,GAAW,EAAE,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;IAE1C,OAAO,GAAG,GAAG,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;AAC5C,CAAC,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,UAAU,QAAQ,CAAC,KAAoD,EAAE,MAAuC;IAClH,OAAO,WAAW,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC;QACnC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,KAAK,CAAC,IAAI,EAAE,UAAU,CAAC;QAClD,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,GAAG,KAAK,CAAC,IAAI,GAAG,WAAW,CAAC,KAAK,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;AAC5E,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,MAAe,EAAE,SAAiB;IAC5D,OAAO,aAAa,CAAC,WAAW,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC,KAAsB,EAAU,EAAE,CAAC,CAAC;QAC1F,EAAE,EAAE,KAAK,CAAC,EAAE;QACZ,IAAI,EAAE,KAAK,CAAC,IAAI;QAChB,IAAI,EAAE,KAAK,CAAC,IAAI;QAChB,IAAI,EAAE,KAAK,CAAC,IAAI;QAChB,MAAM,EAAE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,CAAC;QACtC,UAAU,EAAE,KAAK,CAAC,UAAU;KAC/B,CAAC,CAAC,CAAC;AACR,CAAC","sourcesContent":["/**\n * Что из везомого пакетом взял этот проект и куда оно ложится.\n *\n * Полный набор читает `catalog.ts`, здесь из него остаётся выбранное. Разделено потому, что\n * выбирать приходится раньше, чем раскладывать: `init` спрашивает, какие законы взять, а `list`\n * показывает и то, от чего проект отказался, — обоим нужен набор до отбора.\n */\nimport { join } from 'node:path';\n\nimport { chosenEntries, IEntryOfCatalog, readCatalog } from './catalog.js';\nimport { IConfig, SKILL_FILE, SKILL_KINDS, TKind } from './config.js';\n\nexport interface IAsset {\n /** Идентификатор: `laws/application/money.md`. Он же путь надстройки и ключ в `only` и `skip`. */\n readonly id: string;\n readonly kind: TKind;\n /** Имя без рода, вида и расширения: `delivery`. У скила — имя каталога, которым его зовут. */\n readonly name: string;\n readonly text: string;\n /** Путь в дереве проекта, куда ресурс ложится. */\n readonly target: string;\n /** Запускается ли файл сам по себе: гарда без права на запуск не существует. */\n readonly executable: boolean;\n}\n\n/** Расширение ресурса вместе с точкой; у файла без расширения — пустая строка. */\nconst extensionOf: (id: string) => string = (id: string): string => {\n const dot: number = id.lastIndexOf('.');\n const slash: number = id.lastIndexOf('/');\n\n return dot > slash ? id.slice(dot) : '';\n};\n\n/**\n * Куда ложится ресурс. Скил читается агентом как `<имя>/SKILL.md`, и другого имени у него быть\n * не может: файл, названный по ресурсу, агент не найдёт вовсе. Остальные роды ложатся файлом —\n * по имени, а не по идентификатору: слой законов из идентификатора в путь переезжает, а вид\n * ресурса нет. У разложенного `git-workflow` хостинга в имени не остаётся: второго вида в этом\n * дереве нет, а агенту, который правило читает, различать их незачем.\n */\nexport function targetOf(entry: Pick<IEntryOfCatalog, 'id' | 'kind' | 'name'>, layout: Readonly<Record<TKind, string>>): string {\n return SKILL_KINDS.includes(entry.kind)\n ? join(layout[entry.kind], entry.name, SKILL_FILE)\n : join(layout[entry.kind], `${entry.name}${extensionOf(entry.id)}`);\n}\n\nexport function collectAssets(config: IConfig, assetsDir: string): readonly IAsset[] {\n return chosenEntries(readCatalog(assetsDir), config).map((entry: IEntryOfCatalog): IAsset => ({\n id: entry.id,\n kind: entry.kind,\n name: entry.name,\n text: entry.text,\n target: targetOf(entry, config.layout),\n executable: entry.executable,\n }));\n}\n"]}
package/lib/catalog.d.ts CHANGED
@@ -30,6 +30,22 @@ export interface IEntryOfCatalog {
30
30
  */
31
31
  readonly requires: readonly string[];
32
32
  }
33
+ /** Что объявила шапка ресурса. Пустое поле и отсутствующее здесь одно и то же. */
34
+ export interface IFrontMatter {
35
+ readonly name: string;
36
+ readonly kind: string;
37
+ /** Закон, под которым стоит правило. */
38
+ readonly law: string;
39
+ /** Правило, при котором стоит паттерн. */
40
+ readonly rule: string;
41
+ }
42
+ /**
43
+ * Вступление между `---` в начале файла; его нет — вернётся пустая шапка.
44
+ *
45
+ * Живёт при каталоге, потому что связь родителя с потомком читают двое: сверка связности пакета
46
+ * и каскад отказа. Объявление у одного из них заводило бы круговой импорт между ними.
47
+ */
48
+ export declare function frontMatterOf(text: string): IFrontMatter;
33
49
  /**
34
50
  * Что ресурс о себе объявил. Имена читаются идентификаторами — `hooks/observe.sh`: короткая
35
51
  * форма совпала бы у закона и правила с одним именем, а требование, указавшее не туда, хуже
@@ -91,6 +107,63 @@ export interface IGapOfVariant {
91
107
  * существующие в `skip` — дерево тем самым говорит, что обходится без этого ресурса.
92
108
  */
93
109
  export declare function variantGaps(catalog: readonly IEntryOfCatalog[], selection: ISelection): readonly IGapOfVariant[];
110
+ /** Ресурс, снятый вслед за отвергнутым родителем. */
111
+ export interface ICascadeCut {
112
+ readonly id: string;
113
+ /** Ближайший родитель: у правила — его закон, у паттерна — его правило. */
114
+ readonly parent: string;
115
+ /**
116
+ * Отвергнутый корень цепочки — то, что дерево действительно отвергло или не выбрало.
117
+ * Ближайшего родителя мало: паттерн уходит вслед за правилом, которого в отказе нет, и
118
+ * искать его там читатель пойдёт зря.
119
+ */
120
+ readonly root: string;
121
+ }
122
+ /**
123
+ * Ресурсы, снятые каскадом: правила при невзятом законе и паттерны при невзятых правилах.
124
+ *
125
+ * Связь читается из вступления самого ресурса — отдельный список при пакете разошёлся бы с
126
+ * ресурсами молча, тем же молчанием, ради которого каскад и заводится. Родитель ищется по
127
+ * последнему звену имени: полного пути в шапке нет, потому что переезд закона между слоями
128
+ * переписывал бы шапки всех правил при нём.
129
+ *
130
+ * Родителя в каталоге нет вовсе — каскад молчит: снимать не по чему, а промах в шапке судит
131
+ * сверка связности пакета, до всякой раскладки в дереве.
132
+ */
133
+ export declare function cascadeCuts(catalog: readonly IEntryOfCatalog[], selection: ISelection): readonly ICascadeCut[];
134
+ /**
135
+ * Ресурсы, названные выбором поимённо и всё равно снятые каскадом.
136
+ *
137
+ * Дерево, назвавшее правило в выборе при невыбранном законе, не получает его молча — то самое
138
+ * молчание, ради которого каскад и заводится, только с другой стороны: там о снятии не знал тот,
139
+ * кто ничего не просил, здесь — тот, кто попросил прямо.
140
+ */
141
+ export declare function namedButCut(catalog: readonly IEntryOfCatalog[], selection: ISelection): readonly ICascadeCut[];
142
+ /**
143
+ * Что ложится в дерево: прямой отбор за вычетом снятого каскадом.
144
+ *
145
+ * Отдельной функцией, а не внутри `isChosen`, потому что каскад знает весь каталог, а отбор —
146
+ * один ресурс: связь родителя с потомком по одной записи не видна.
147
+ */
148
+ export declare function chosenEntries(catalog: readonly IEntryOfCatalog[], selection: ISelection): readonly IEntryOfCatalog[];
149
+ /** Строка отказа, которая ничего не снимает. */
150
+ export interface IIdleSkip {
151
+ readonly id: string;
152
+ /** Чем названное ею уже снято: имя отвергнутого родителя, либо пусто — такого ресурса нет. */
153
+ readonly by: string;
154
+ }
155
+ /**
156
+ * Строки отказа, которые ничего не снимают: названное ими снято каскадом или в каталоге не
157
+ * значится вовсе.
158
+ *
159
+ * Не отказ, а предупреждение: настройка, верная до обновления пакета, становится лишней сама,
160
+ * без единой правки в дереве, и отбивать за это раскладку значило бы наказывать за вчерашнюю
161
+ * правоту.
162
+ *
163
+ * Строка на ресурс чужого вида лишней не считается никогда: ею дерево гасит отказ о ресурсе,
164
+ * у которого нет подходящего вида, — то есть работу она делает.
165
+ */
166
+ export declare function idleSkips(catalog: readonly IEntryOfCatalog[], selection: ISelection): readonly IIdleSkip[];
94
167
  /** Выбранный ресурс, чьё требование в дерево не поехало. */
95
168
  export interface IBrokenLink {
96
169
  /** Кто требует — идентификатор выбранного ресурса. */