@rt-tools/agent-kit 0.5.3 → 0.6.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 (51) hide show
  1. package/assets/checks/check-file-size.mjs +127 -0
  2. package/assets/checks/rt-kit-checks.config.mjs +10 -0
  3. package/assets/defaults/gate-map.sh +90 -34
  4. package/assets/defaults/project.sh +26 -3
  5. package/assets/hooks/skill-gate-layers.sh +156 -0
  6. package/assets/hooks/skill-gate.sh +11 -2
  7. package/assets/laws/code-structure.md +3 -0
  8. package/assets/laws/delivery.md +9 -0
  9. package/assets/laws/observability.md +46 -0
  10. package/assets/laws/project-documentation.md +4 -0
  11. package/assets/laws/reuse-first.md +2 -0
  12. package/assets/laws/verifiability.md +5 -0
  13. package/assets/patterns/browser-verification-stand.md +22 -2
  14. package/assets/patterns/doc-style-trace.md +111 -0
  15. package/assets/patterns/git-workflow-commit.github.md +1 -1
  16. package/assets/patterns/git-workflow-docker.md +203 -0
  17. package/assets/patterns/git-workflow-secrets.md +93 -0
  18. package/assets/patterns/observability-record.md +114 -0
  19. package/assets/patterns/ownership-session-procedure.md +102 -0
  20. package/assets/patterns/seo-verify.md +1 -1
  21. package/assets/patterns/spec-driven-rule.md +5 -0
  22. package/assets/patterns/styling-bem-sheet.md +178 -0
  23. package/assets/patterns/task-flow-close.md +20 -0
  24. package/assets/patterns/task-flow-resume.md +5 -0
  25. package/assets/patterns/translations-content.md +107 -0
  26. package/assets/patterns/translations-key.md +1 -1
  27. package/assets/rules/angular-patterns.md +5 -0
  28. package/assets/rules/browser-verification.md +17 -12
  29. package/assets/rules/component-structure.md +6 -2
  30. package/assets/rules/doc-style.md +16 -0
  31. package/assets/rules/git-workflow.azure.md +45 -1
  32. package/assets/rules/git-workflow.github.md +52 -1
  33. package/assets/rules/git-workflow.gitlab.md +46 -1
  34. package/assets/rules/lists.md +13 -0
  35. package/assets/rules/observability.md +147 -0
  36. package/assets/rules/ownership-scope.md +5 -2
  37. package/assets/rules/ownership-session.md +124 -0
  38. package/assets/rules/permissions.md +23 -0
  39. package/assets/rules/pricing.md +4 -0
  40. package/assets/rules/reuse-first.md +9 -0
  41. package/assets/rules/seo.md +57 -9
  42. package/assets/rules/shared-code.md +6 -0
  43. package/assets/rules/spec-driven.md +9 -0
  44. package/assets/rules/styling-bem.md +34 -1
  45. package/assets/rules/task-flow.md +5 -0
  46. package/assets/rules/testing.md +46 -8
  47. package/assets/rules/translations.md +11 -5
  48. package/assets/rules/typescript-conventions.md +5 -0
  49. package/package.json +1 -1
  50. package/rt-tools-agent-kit-0.6.0.tgz +0 -0
  51. package/rt-tools-agent-kit-0.5.3.tgz +0 -0
@@ -0,0 +1,124 @@
1
+ ---
2
+ name: ownership-session
3
+ kind: rule
4
+ law: ownership
5
+ description: Правило под «Закон о владеющей сущности». Брать при правке процедуры, которой нужна сущность захода, перехватчика доступа и выборок принадлежности — чем названа сущность захода, откуда она берётся, каким условием отбираются записи и какими кодами отвечают отказы. Готовый код — в паттерне ownership-session-procedure. Не брать для разрешения идентификатора в запросе — это правило ownership-scope.
6
+ ---
7
+
8
+ # Владеющая сущность захода — как это устроено здесь
9
+
10
+ Правило под закон `docs/constitution/application/ownership.md`. Закон говорит, что должно быть
11
+ верно, когда владеющих сущностей больше одной; здесь — как приложение узнаёт, в какой из них
12
+ работает вошедший, и чем держится граница между ними. Разрешение идентификатора, названного в
13
+ запросе, — правило `ownership-scope` под тем же законом.
14
+
15
+ ## Как это называется здесь
16
+
17
+ | В законе | Здесь |
18
+ | ---------------------------------------------- | -------------------------------------------------------------------------------- |
19
+ | владеющая сущность | запись своего домена; имя — в `implementation.md` рядом |
20
+ | принадлежность человека сущности | своя запись на пару «человек и сущность» |
21
+ | сущность захода | колонка учётной записи; внутри запроса — ключ контекста, читаемый одной функцией |
22
+ | потребность процедуры в сущности захода | декоратор на её классе, стоящий рядом с объявлением доступа |
23
+ | отказ вошедшему, у которого принадлежности нет | тот же код, что на неверный пароль, — вошедшего надо увести на вход |
24
+ | отказ на чужую сущность, названную в черновике | неверный аргумент |
25
+ | забытый декоратор | внутренняя ошибка — отказ приложения, а не вызывающему |
26
+
27
+ ## Где это лежит
28
+
29
+ В этом дереве — таблица в `implementation.md` рядом. Пути живут там, а не здесь: правило
30
+ переносится между репозиториями, раскладка — нет, и путь, названный в правиле, врёт в первом
31
+ же дереве, которое держит код иначе.
32
+
33
+ ## Как закон применяется здесь
34
+
35
+ - **Сущность захода приезжает в процедуру контекстом запроса, а не полем запроса.** Ключ и его
36
+ читатель лежат рядом, как ключ вошедшего и его читатель: вторая копия проверки разошлась бы с
37
+ первой в коде отказа.
38
+ - **Процедура объявляет потребность декоратором, и чтение без объявления — отказ приложения.**
39
+ Править такое нечего ни вызывающему, ни владельцу: это забытое объявление, и оно видно
40
+ отказом, а не работой неизвестно в какой сущности.
41
+ - **Сущность захода приезжает в контекст только процедурам, объявившим потребность.** Читает её
42
+ перехватчик всё равно — тем же обращением, которым читает права вошедшего, — но без
43
+ объявления в контекст она не попадает. Потребность стоит рядом с объявлением доступа и счёт
44
+ объявлений доступа не меняет.
45
+ - **Сущность захода лежит у учётной записи, а не в выданном входе.** Выбор переживает
46
+ перезагрузку и не зависит от того, что шлёт клиент; выданный вход говорит только о том, кто
47
+ пришёл.
48
+ - **Принадлежность человека сущности — своя запись, и в одной сущности она одна.** Две
49
+ принадлежности пришлось бы складывать, а результат сложения запретов и разрешений по двум
50
+ строкам не прочитать.
51
+ - **Человек, у которого принадлежности нет, не входит и не работает: оба отказа те же, что на
52
+ неверный пароль.** Принадлежность снимают, пока выданный вход ещё жив, — и тогда отказ
53
+ приходит уже посреди работы. Транспорт админки на этот код чистит хранилище и уводит на
54
+ страницу входа.
55
+ - **Чужая сущность, названная в черновике настроек, отбивается, а не выправляется молча.** Иначе
56
+ одним ответом скрывались бы и подделанный запрос, и админка, которая загрузила одну сущность,
57
+ а сохраняет в другую.
58
+ - **Выборка админки отбирает записи по сущности захода, и снять это условие нечем.** Оно
59
+ складывается с условиями отбора клиента, а не заменяется ими: иначе список, поиск и число в
60
+ сводке показывают чужое.
61
+ - **Запись, названную в запросе своим номером, выборка берёт с условием по сущности, и правка
62
+ со снятием идут тем же условием.** Сущность уезжает параметром рядом с идентификатором в то
63
+ же условие; у записи без своей колонки граница выводится связью. Чужая запись отвечает так же,
64
+ как несуществующая, — и связанный идентификатор сверяется наравне с основным.
65
+ - **Выборку по номеру без сущности в условии ловит проверка охвата.** Список точек умер бы со
66
+ слиянием: следующая процедура завелась бы без условия, и не заметил бы этого никто. Путь, где
67
+ сущности законно нет, назван в списке известного строкой с причиной.
68
+ - **Адрес публичной страницы уникален внутри сущности, а не по всей системе.** Пара «сущность и
69
+ адрес» стоит уникальным индексом и у записей, и у их прежних адресов; сквозную уникальность
70
+ между двумя таблицами держит хранилище — оно читает ту же пару. У прежнего адреса сущность
71
+ продублирована колонкой: связью пара в уникальный индекс не собирается.
72
+ - **Занятость адреса спрашивается внутри сущности, и чужая занятость владельцу не видна.** Отказ
73
+ «адрес занят» на имя, которое держит сосед, выдал бы существование его записи.
74
+ - **Публичный вход, нашедший под адресом больше одной записи, отвечает как на
75
+ несуществующую.** Гость приходит по адресу и сущности не называет, поэтому признак совпадения
76
+ едет отдельным полем результата выборки: пустой ответ забыли бы разобрать, а показ первого
77
+ попавшегося отдал бы гостю чужую запись молча. Владелец узнаёт о таком случае строкой лога,
78
+ заведённой в списке отбираемых.
79
+ - **Принадлежность записи сущности записана колонкой везде, где её не из чего вывести.** Там,
80
+ где у записи обязательна связь с другой записью сущности, граница выводится связью; у визита,
81
+ события и разговора выводить её не из чего — заход бывает записан на несуществующий путь, а
82
+ разговор начат с корня сайта.
83
+
84
+ ## Чего из закона здесь нет
85
+
86
+ Внутри сущности границ нет: право открывает раздел целиком, и сотрудник, которому доверена одна
87
+ запись, видит все записи своей сущности.
88
+
89
+ Признака, по которому гость назвал бы сущность, может не быть вовсе — тогда там, где гость не
90
+ назвал ничего, читатель берёт единственную сущность хранилища и отвечает отказом по состоянию,
91
+ когда их больше одной. Тем же вызвана и временная граница у адреса страницы: совпавший адрес
92
+ недоступен обеим сущностям, потому что выбрать между ними нечем.
93
+
94
+ Роль живёт у учётной записи, а не у принадлежности, пока принадлежность у человека одна:
95
+ колонки прав у принадлежности стоят и не читаются, а права читаются у учётной записи при каждом
96
+ вызове.
97
+
98
+ Выбора сущности сотрудником может не быть: сущностью захода остаётся то, что записано у учётной
99
+ записи, и сменить это значение нечем, пока нет ни селектора, ни процедуры. Тогда выбором
100
+ остаётся единственная принадлежность.
101
+
102
+ ## Паттерны
103
+
104
+ - `ownership-session-procedure` — процедура, которой нужна сущность захода: объявление, чтение,
105
+ отказы, контекст в тесте.
106
+
107
+ ## Ловушки
108
+
109
+ - **Сущность захода и принадлежность — разные вещи.** Колонка учётной записи говорит, в какой
110
+ сущности человек работает сейчас; принадлежность — в каких он вправе работать вообще.
111
+ Значение колонки без принадлежности к той же сущности ничего не значит: выборка сверяет их
112
+ между собой и отдаёт пусто, когда они разошлись.
113
+ - **Забытый декоратор не ловится ни сборкой, ни линтером.** Процедура, читающая сущность захода
114
+ без объявления потребности, собирается и проходит проверки — отказ приходит на первом же
115
+ вызове. Пишутся эти две строки вместе, а покрывается это тестом процедуры.
116
+ - **Потребность в сущности доступом не является.** Объявлений доступа у процедуры по-прежнему
117
+ ровно одно, и декоратор потребности к этому счёту не относится: сущность нужна и процедуре с
118
+ правом, и процедуре, открытой любому вошедшему.
119
+ - **Отказ снятой принадлежности — тот же, что на неверный пароль, а не отказ в доступе.**
120
+ Второй оставил бы человека на закрытом разделе с непонятной причиной и без выхода, хотя
121
+ лечится это только новым входом.
122
+ - **Снятое поле контракта не заводится заново под тем же номером.** Номер и имя помечены как
123
+ занятые, и метка снимается только вместе с решением отдать номер новому полю, — а такое
124
+ решение ломает уже выкаченного клиента молча: он прочитает чужое значение как своё.
@@ -36,6 +36,29 @@ description: Правило под «Закон о доступе». Брать
36
36
  Последний отдаёт вошедшему больше, чем гостю, — так владелец видит скрытые объекты в общем
37
37
  списке.
38
38
  - **Права пользователя — это права пресета, поверх которых применены его оверрайды.**
39
+ - **У человека одна роль во владении, и держит это хранилище.** Две принадлежности в одном
40
+ владении пришлось бы складывать, а результат сложения запретов и разрешений по двум строкам
41
+ не прочитать. Права разных владений не складываются вовсе: они записаны у принадлежности, а
42
+ не у учётной записи.
43
+ - **Право, о котором роль ничего не говорит, считается неданным.** Значение, которое не булево,
44
+ отбрасывается при сложении: отсутствующее право и прямо отобранное означают одно и то же, и
45
+ «непусто» правом не считается.
46
+ - **Права вошедшего читаются при каждом вызове, а не берутся из выданного входа.** Выданный
47
+ вход говорит только о том, кто пришёл: подписанное однажды живёт часами и правку прав не
48
+ переживает, поэтому отобранное право открывало бы раздел до конца дня.
49
+ - **Учётная запись, которой больше нет, вызовов, требующих входа, не открывает.** Тем же
50
+ чтением отбивается и человек, потерявший принадлежность в своём владении: работать ему не в
51
+ чем.
52
+ - **Счётчик и рекламные сигналы включает ответ гостя, а не наличие ключа в настройках.**
53
+ Решений два, и хранятся они парой: «разрешил счёт посещений, но не рекламу» — законное
54
+ состояние, а третьим значением перечисления его пришлось бы заводить заново на каждое новое
55
+ разрешение. Всё, что не пара булевых значений, читается как неотвеченный вопрос, то есть как
56
+ отказ: хранилище принимает что угодно, а решать по испорченной записи нельзя. Своя
57
+ статистика к согласию не привязана — она не уходит наружу.
58
+ - **Публичная процедура, заводящая запись, закрыта ещё и ограничителем частоты.** Право её не
59
+ сторожит, и без предела скорость роста таблицы задаёт отправитель, а не владелец. Считается
60
+ по ключу клиента, и ключ у всех таких процедур общий: второй ответ на вопрос «кто это»
61
+ разошёлся бы с первым. Публичная процедура, которая только читает, ограничителя не требует.
39
62
  - **Запрос без входа отбивается как неаутентифицированный, а вход без права — как отказ в
40
63
  доступе.** Это разные ответы: первый лечится входом, второй — нет.
41
64
  - **Право проверяется перехватчиком до тела процедуры.** Обработчик не решает, пускать ли
@@ -62,3 +62,7 @@ description: Правило под «Закон о деньгах». Брать
62
62
  одно, и живёт оно в расчёте.
63
63
  - **Справочная сумма рядом с суммой к оплате читается как цена.** В письме и в документе
64
64
  справочное число подписывается как справка.
65
+ - **Набор валют переключателя бывает шире набора курсов.** Набор переключателя собирается из
66
+ начальных валют локалей, и валюты, курса для которых никто не тянет, попадают в него сами:
67
+ гость такой локали получает отказ курса, ничего не выбирая. Два набора сверяются между собой,
68
+ а расхождение чинится задачей — правкой текста оно не лечится.
@@ -27,6 +27,10 @@ description: Правило под «Закон о единообразии пр
27
27
 
28
28
  ## Как закон применяется здесь
29
29
 
30
+ - **Источник вида выбирается по приложению, а не по привычке.** У каждого приложения дерева
31
+ своя опора: у публичного сайта — его дизайн-система, у остальных — кит. Перепутанный источник
32
+ приносит на экран форму, которой в этом приложении больше нигде нет. Какое приложение на что
33
+ опирается, названо в именах дерева.
30
34
  - **Работа начинается с чтения готового, а не с чистого файла.** Сначала находится опора —
31
35
  компонент кита, базовый класс, образец в соседнем домене, — потом пишется своё поверх неё.
32
36
  - **Свой примитив и своя основа заводятся только с явного одобрения владельца.** Спрашивается
@@ -76,6 +80,11 @@ description: Правило под «Закон о единообразии пр
76
80
 
77
81
  ## Ловушки
78
82
 
83
+ - **Образец ищется по именам кита, а не по тому, на чём кит написан.** Поиск по именам
84
+ библиотеки, поверх которой кит собран, не находит ни одного файла: наложение, портал и окно
85
+ закрыты китом и зовутся его именами. Обратное тоже бывает: библиотека стоит прямой
86
+ зависимостью, и то, чего кит не закрывает, зовётся в дереве её собственным именем. Прежде чем
87
+ решать, что имени в дереве нет, его ищут — переделок из-за этого выходит по две на приём.
79
88
  - Перенос переизобретением не считается: строку, которая уже лежит в файле, гард из
80
89
  проверяемого текста вычёркивает, а сверка идёт без отступов — при переезде блок меняет
81
90
  отступ, оставаясь тем же кодом.
@@ -2,7 +2,7 @@
2
2
  name: seo
3
3
  kind: rule
4
4
  law: search-visibility
5
- description: Правило под «Закон о видимости в поиске». Брать при любой правке, доходящей до разметки публичного сайта — шаблоны страниц, meta/title/description, JSON-LD, canonical, hreflang, маршруты сайта, sitemap, robots, nginx. Называет восемь локалей, где что лежит и чего у нас нет. Готовый код — в паттернах seo-page и seo-verify.
5
+ description: Правило под «Закон о видимости в поиске». Брать при любой правке, доходящей до разметки публичного сайта — шаблоны страниц, meta/title/description, JSON-LD, canonical, hreflang, маршруты сайта, sitemap, robots, nginx. Называет локали перевода, где что лежит и чего у нас нет. Готовый код — в паттернах seo-page и seo-verify.
6
6
  ---
7
7
 
8
8
  # Видимость в поиске — как это устроено здесь
@@ -12,14 +12,14 @@ description: Правило под «Закон о видимости в пои
12
12
 
13
13
  ## Как это называется здесь
14
14
 
15
- | В законе | Здесь |
16
- | ---------------------------- | ---------------------------------------------------------------------------- |
17
- | язык страницы | локаль; их восемь`en`, `ru`, `de`, `zh-Hans`, `zh-Hant`, `ko`, `th`, `hi` |
18
- | язык по умолчанию | `DEFAULT_LOCALE`, то есть `en`; отдаётся из корня, без префикса в адресе |
19
- | язык, перевод которого готов | `readyLocales` — поле объекта, а не список локалей сайта |
20
- | канонический адрес | `${SITE_ORIGIN}${propertyPath(slug, locale)}` |
21
- | структурированные данные | JSON-LD типа `LodgingBusiness` |
22
- | прежний адрес страницы | прежний slug объекта |
15
+ | В законе | Здесь |
16
+ | ---------------------------- | ------------------------------------------------------------------------ |
17
+ | язык страницы | локаль перевода; их наборв `implementation.md` рядом |
18
+ | язык по умолчанию | `DEFAULT_LOCALE`, то есть `en`; отдаётся из корня, без префикса в адресе |
19
+ | язык, перевод которого готов | `readyLocales` — поле объекта, а не список локалей сайта |
20
+ | канонический адрес | `${SITE_ORIGIN}${propertyPath(slug, locale)}` |
21
+ | структурированные данные | JSON-LD типа `LodgingBusiness` |
22
+ | прежний адрес страницы | прежний slug объекта |
23
23
 
24
24
  Локаль по умолчанию отдаётся из корня, остальные — с префиксом `/<код>/`. Префикс — часть
25
25
  маршрута, а не часть сборки: `<base href>` в разметке всегда `/`.
@@ -46,6 +46,54 @@ description: Правило под «Закон о видимости в пои
46
46
  - **Перенаправление с прежнего адреса отдаётся с `Cache-Control: max-age`** и покрыто
47
47
  `proxy_cache_valid 200 301`. Ключ кэша строится без `$args`, поэтому запросы со строкой
48
48
  запроса идут мимо кэша (`proxy_cache_bypass` / `proxy_no_cache $is_args`).
49
+ - **Страница отдаёт блоки JSON-LD каждый своим тегом**, а не одним графом: отказ одного не
50
+ уносит остальные, и в отданной разметке видно, какой из них собрался.
51
+ - **Цена уходит и предложением, и диапазоном** — суммой в валюте хранения и с единицей своего
52
+ периода. Цена по датам, скидки и пересчёт в валюту гостя в разметку не идут.
53
+ - **Сводная оценка появляется от первого отзыва** и считается из оценок самих отзывов. Ноль
54
+ отзывов — поля нет вовсе: ни нуля, ни пустой строки.
55
+ - **Блок вопросов собирается из вопросов записи**, а ноль вопросов блока не даёт.
56
+ - **Крошки ведут от корня локали к странице записи** адресами той же локали, что и открытая
57
+ страница. Без названия сайта крошки не выпускаются вовсе.
58
+ - **Сохранение записи уведомляет поисковика об изменении её адресов.** В уведомление уходят
59
+ страницы записи на готовых локалях по нынешнему и прежним адресам; корни локалей и карта
60
+ сайта — нет. Отказ приёмника сохранение не отменяет и возвращается владельцу исходом.
61
+ - **Приёмников несколько, и каждый получает свой запрос.** Опрашиваются они одновременно, а
62
+ потолок ожидания считается на приёмник: последовательный обход сложил бы все потолки в одно
63
+ ожидание владельца. Переменная окружения перекрывает адреса всех приёмников разом — этим
64
+ отправку и проверяют локальным приёмником.
65
+ - **Исход возвращается перечислением по приёмнику, а владельцу показывается списком.** Приёмник
66
+ в нём назван словом, а не адресом. Строка отвечает за приём запроса, а не за осведомлённость
67
+ поисковика: раздаёт полученное любой приёмник, поэтому отказ одного адреса не значит, что его
68
+ поисковик не узнал. Подписью объявляется только полный отказ.
69
+ - **Удаление записи уведомляет поисковика теми же адресами.** Отдельного «страница исчезла»
70
+ протокол не знает: площадка приходит по адресу и видит ответ сайта сама. Адреса собираются до
71
+ удаления строки — каскад унесёт прежние вместе с записью, — а исход наружу не выходит:
72
+ страницы уже нет, и читать подпись некому. Снятие с публикации идёт через сохранение и
73
+ уведомляет тем же путём.
74
+ - **Переименование адреса и снятие прежнего адреса тоже уведомляют поисковика.**
75
+ Переименование — всеми адресами записи, снятие — одним снятым; готовые локали у снятого
76
+ считаются по текстам записи, которой он принадлежал, и ради них выборка снятия отдаёт не
77
+ только адрес. Исход наружу не выходит ни там, ни там: владелец на странице адресов
78
+ спрашивает про адрес, а не про уведомление.
79
+ - **Правка настроек владельца уведомляет поисковика адресами всех действующих записей.**
80
+ Видимой гостю считается та же правка, которой сбрасывается кэш, — а правка невидимого поля
81
+ уведомления не шлёт. Уходят живые адреса, без прежних, и готовые локали считаются по каждой
82
+ записи отдельно; отправка идёт на владеющую сущность — записи разных сущностей в одну не
83
+ попадают. Работа идёт в фоне за сохранением настроек, поэтому исход остаётся в логах:
84
+ показать его владельцу негде.
85
+ - **Каждая отправка оставляет запись, и заводится она до обращения к приёмникам.** Повод,
86
+ записи, объявленные адреса и исход по каждому приёмнику ложатся рядом; пустой исход означает
87
+ «отправка идёт», а спустя несколько минут — что процесс упал посреди опроса. Пишут её все
88
+ места, откуда уведомление уходит. Отказ записи отправку не отменяет.
89
+ - **Непрошедшие запросы владелец повторяет нажатием, и повтор заводит новую запись.**
90
+ Повторяется последняя отправка записи — у ранней адреса могли устареть, — и запрос уходит
91
+ только тем приёмникам, чей исход не «принял»; у оборванной записи это все её приёмники. Повод
92
+ у новой записи свой, а право строже, чем у чтения: повтор объявляет адреса поисковику.
93
+ Автоповтора по расписанию нет.
94
+ - **Записи отправок держит ночная чистка: сперва предел, потом срок.** Тот же порядок, что у
95
+ ленты происшествий: срок, снятый первым, оставил бы предел меряться по уже почищенной
96
+ таблице.
49
97
 
50
98
  ## Чего из закона здесь нет
51
99
 
@@ -39,6 +39,12 @@ description: Правило под «Закон об общем коде при
39
39
  - **Общим стал только маппер страницы.** У сторон разная политика на непонятное значение, и
40
40
  общим может быть лишь то, где она одна: номер меньше единицы обе стороны читают как первую
41
41
  страницу.
42
+ - **Общая выборка держит форму запроса, а не набор условий.** Имена полей отбора объявляет сам
43
+ домен списком разрешённых, а в запрос к хранилищу их переводит его же выборка. Поэтому
44
+ условие вправе лечь на связанные строки, а не только на колонки самой записи, и отбор по
45
+ набору идентификаторов ей не запрещён: признак связанной записи — такое же поле набора, как
46
+ повод и объект. Общими здесь остаются разбор страницы, порядка и поиска, а не сам список
47
+ полей.
42
48
  - **Перечисления полей порядка и отбора домена копией не считаются.** `EActivitySortProperty`
43
49
  и подобные повторяют имена, по которым сортирует сервер именно этого домена.
44
50
  - **Строковая настройка и таблица соответствий сверяются по значению, а не по имени.** Имя
@@ -109,6 +109,15 @@ description: Правило под «Закон о документации пр
109
109
  про дерево, где того гарда не разложили; поправить это дерево не может ничем, если у ресурса
110
110
  нет надстройки. Требование ресурса к ресурсу при этом объявляется строкой в шапке, а не
111
111
  выводится из такой фразы.
112
+ - **Паттерн находится по полю `rule:`, а не по приставке имени.** Приставку имени несут не все
113
+ паттерны, и поиск по имени правила таких не видит: сверка ищет их полем, человек — разделом
114
+ «Паттерны» самого правила. Счёт паттернов, собранный приставками, выходит меньше настоящего, а
115
+ число потом уезжает в деление работы.
116
+ - **Якорь сверяется по сырому тексту файла, и комментарий засчитывается наравне с кодом.**
117
+ Существование символа проверка ищет словом по всему файлу, не вычищая комментарии, а живость
118
+ считает только у объявленного в коде. Имя, стоящее в одном лишь пояснении, проходит мимо обеих
119
+ сторон: якорем утверждения оказывается слово из комментария, тогда как объявление рядом
120
+ называется иначе.
112
121
 
113
122
  ## Чего из закона здесь нет
114
123
 
@@ -16,7 +16,7 @@ description: Правило под «Закон о фронтовом прило
16
16
 
17
17
  | В законе | Здесь |
18
18
  | ------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
19
- | общий набор значений оформления | шкалы кита `--rt-*`; своё поверх них — `--vm-*` в `styles.scss` приложения |
19
+ | общий набор значений оформления | шкалы кита `--rt-*`; своё поверх них — `--<префикс>-*` в `styles.scss` приложения |
20
20
  | класс в разметке | директивы `rtBlock` и `rtElem` из `@rt-tools`, а не строка в атрибуте |
21
21
  | правило стилей | объявление `&__<элемент>` в `.scss` — своём или в общем слое приложения |
22
22
  | общий слой раскладки | `apps/<app>/src/styles/`: `<префикс>-page`, `<префикс>-form`, `<префикс>-panel`, `<префикс>-window` у админки, `<префикс>-site-page` у сайта |
@@ -38,6 +38,19 @@ description: Правило под «Закон о фронтовом прило
38
38
  инъекцией от ближайшего предка с `rtBlock`, и повторить этот разбор по тексту шаблона нечем.
39
39
  - **Раскладка объявлена в общем слое приложения, а не в стилях экрана.** У компонента экрана
40
40
  вне кита файл стилей по умолчанию пустой.
41
+ - **Шторку и окно открывает служба кита, а не номер слоя.** Числа шкалы сравниваются только
42
+ между соседями по разметке; служба выносит разметку наружу, к `<body>`, и сравнивать её
43
+ становится не с чем.
44
+ - **Номер слоя берётся из шкалы, а не пишется числом в файле компонента.** Шкала — единственное
45
+ место, где слои видно рядом: написанное на месте число в неё не попадает, и следующий узел
46
+ занимает тот же номер, ничего об этом не узнав.
47
+ - **Размер элемента управления выбирается по признаку указателя, а не по ширине экрана.**
48
+ Планшет в ландшафте шире порога узкого вьюпорта, а нажимают по нему пальцем: `pointer: coarse`
49
+ отвечает про способ нажатия, ширина — про место под раскладку. Ступени берутся у кита, а не
50
+ назначаются пикселями.
51
+ - **Файл стилей не длиннее 500 строк.** Предел общий с кодом и текстами, но stylelint длину не
52
+ судит вовсе — держит его проверка дерева. Выросший файл экрана делится по блокам, а общая
53
+ раскладка уходит в свой слой.
41
54
  - **Предупреждение stylelint роняет прогон наравне с ошибкой.** `!important` объявлен
42
55
  предупреждением, а прогон идёт с `--max-warnings 0`: иначе запрет читается как пожелание —
43
56
  два таких предупреждения лежали в дереве, а `npm run stylelint` возвращал ноль и гейтом не был.
@@ -52,10 +65,30 @@ description: Правило под «Закон о фронтовом прило
52
65
 
53
66
  - `styling-bem-layout` — экран на общем слое раскладки, блоки приложения.
54
67
  - `styling-bem-component` — стили компонента кита, `:host`, модификаторы, язык оформления сайта.
68
+ - `styling-bem-sheet` — шторка и окно поверх страницы: чем открываются, подложка, замер.
55
69
 
56
70
  ## Ловушки
57
71
 
58
72
  - **`rtElem` без предка с `rtBlock` роняет отрисовку в рантайме** — сборка и линт молчат.
73
+ - **Спроецированный узел блока-предка не имеет.** `rtElem` берёт имя блока инъекцией от
74
+ ближайшего предка **по месту объявления шаблона**, а не по месту вставки: элемент, который
75
+ экран объявляет у себя и отдаёт в проекцию чужого компонента, ищет `rtBlock` в своём шаблоне
76
+ и не находит. Отрисовка падает в рантайме, сборка и линт зелёные. Класс на такой узел
77
+ вешается правилом по селектору кита в общем слое раскладки, а не директивой.
78
+ - **Элемент с `backdrop-filter` или своим `z-index` замыкает потомков в свой слой.** Липкая
79
+ шапка с размытием — самый частый случай: номер слоя у того, что лежит внутри неё,
80
+ сравнивается не с соседями по странице, а только с соседями внутри шапки, и нижняя панель
81
+ накрывает открытую шторку вместе с её кнопкой. Проверяется это `elementFromPoint` в центре
82
+ кнопки: сборка, линт и скриншот показывают тут целую страницу.
83
+ - **До узла, вынесенного к `<body>`, стили компонента не достают.** Превью и заглушку переноса
84
+ кладёт туда библиотека, а правила компонента заскоуплены атрибутом: файл выглядит рабочим и
85
+ не красит ничего. Такие правила объявляются в общем слое приложения. Ни сборка, ни линт, ни
86
+ проверка «класс без правила» этого не видят: класса такого в шаблоне нет вовсе, и пролежать
87
+ это может несколько задач подряд.
88
+ - **Имя токена не сверяется ничем.** Ссылка на несуществующий токен собирается, проходит
89
+ stylelint и проверку класса без правила, а свойство молча берёт наследованное значение:
90
+ правило выглядит написанным и не красит ничего. Ловится это только замером в браузере, а
91
+ имена берутся из объявлений кита, а не по догадке о том, как токен должен был бы называться.
59
92
  - **`rtBlock` на `<ng-container>` класса не ставит вовсе:** узел это комментарий, и имя блока
60
93
  он только объявляет потомкам. Класс блока экрана вешает хост через `host: { class: … }`.
61
94
  - **`justify-content: center` во flex-контейнере с `overflow-x` уводит первые элементы за
@@ -73,6 +73,11 @@ description: Правило под «Закон о ведении работы»
73
73
  — признак; правила, тексты, обвязка и зависимости под него не подпадают. Обход — строка
74
74
  `**Поведение:** не меняется — <причина владельца>` в замысле; пустая причина не
75
75
  принимается.
76
+ - **Гард замысла — нижняя граница, а не признак папки задачи.** Он требует её только под правку
77
+ кода приложения; нужна ли папка работе, которая туда не доходит, решает число заходов, а не
78
+ путь. Работа в один заход и один коммит целиком помещается в тело отчёта — так закрывается
79
+ разбор чужой папки. Работа с этапами и передачей папку заводит: между заходами её состояние
80
+ не держит ничто, кроме хода работы.
76
81
  - **Ход, в котором владельцу задан вопрос, не заканчивается, пока за этот же ход не читались
77
82
  законы и правила.** Чтением считается любой из трёх путей: загрузка правила, чтение файла
78
83
  законов или правил, поиск по ним. Отбивает гард разговора — на завершении хода, а не на
@@ -2,7 +2,7 @@
2
2
  name: testing
3
3
  kind: rule
4
4
  law: verifiability
5
- description: Правило под «Закон о проверяемости». Брать при правке любого *.spec.ts и всего, что лежит в apps/site-e2e и apps/admin-e2e. Называет Vitest и Playwright, идентификатор сценария в заголовке, вынос решения в чистую функцию и то, что закрывается сквозной спекой. Готовый код — в паттернах testing-unit и testing-e2e.
5
+ description: Правило под «Закон о проверяемости». Брать при правке любого *.spec.ts и всего, что лежит в сквозных наборах дерева. Называет Vitest и Playwright, идентификатор сценария в заголовке, вынос решения в чистую функцию и то, что закрывается сквозной спекой. Готовый код — в паттернах testing-unit и testing-e2e.
6
6
  ---
7
7
 
8
8
  # Проверяемость — как это устроено здесь
@@ -45,8 +45,21 @@ description: Правило под «Закон о проверяемости».
45
45
  переименованный или выкинутый сценарий: тесты при этом остаются зелёными.
46
46
  - **Решение выносится в чистую функцию и проверяется вызовом.** Компонент и сервис остаются
47
47
  тонкой обёрткой и отдельно не проверяются, пока своего ветвления у них нет.
48
+ - **Решение, зависящее от текущего момента, принимает момент параметром.** Часы машины оно не
49
+ читает: правило проверяется вызовом, а не подкруткой времени вокруг теста. Умолчание
50
+ `= new Date()` ставится на границе — там, где решение зовёт процедура или служба.
48
51
  - **Процедура Connect проверяется вызовом своего метода с рукописным двойником базы.**
49
52
  Контейнер и роутер поднимать не надо: спека проверяет решение, а не раскладку полей.
53
+ - **Сид заводит то, без чего экран не открыть, и ничего, что гость примет за настоящее.**
54
+ Содержимое, у которого есть автор, — отзывы, вопросы, обсуждения — гость читает как
55
+ написанное людьми, а стенд собирается из той же базы, что и проверка глазами: три выдуманные
56
+ цитаты дожили до отдельной задачи и всё это время выглядели отзывами. Настройки владельца —
57
+ контакты, адрес отправителя, ключи внешних служб — сид не заводит тоже: их вписывают в
58
+ приложении. То, что нужно редко, включается признаком в окружении, а не сеется всем.
59
+ - **Заведённая проверка встаёт в гейт пуша или в конвейер, а не только в сводную цель.**
60
+ Сводная цель запускается руками, и проверка, которая живёт только в ней, отвечает тому, кто
61
+ её вспомнил: молчание такой проверки читается как её зелёный ответ. Три проверки простояли
62
+ вне гейта, объявляя в собственных списках известного, что падают на новом.
50
63
  - **Спека, необратимо меняющая данные стенда, выключена по умолчанию.** `BASE_URL` уводит
51
64
  прогон одной переменной, и без выключателя такая спека правила бы данные чужого стенда.
52
65
  - **Спеки, которым нужен nginx перед приложением, просыпаются вместе с `BASE_URL`.** Голый
@@ -56,6 +69,24 @@ description: Правило под «Закон о проверяемости».
56
69
  `playwright/no-skipped-test` запретил бы его сразу в шестидесяти пяти местах. Рядом со
57
70
  строкой отключения пишут причину, а точечный `eslint-disable` остаётся для того, что
58
71
  запрещено по делу.
72
+ - **Гард отпускает действие, когда сам сломался.** Нет разборщика входа, пустой ввод, не тот
73
+ каталог, любая своя ошибка — гард выходит нулём и пропускает: сломанная проверка не имеет
74
+ права заклинить работу. Объявляется это строкой `FAIL-OPEN` в шапке самого гарда, рядом с
75
+ перечислением случаев, и туда же дописывается новый случай, когда он находится.
76
+ - **Список известного у проверки именной и объясняет себя сам.** Первым полем перечня стоит имя
77
+ проверки и слово о том, что перечисленное отказом не считается. Дальше либо два ключа —
78
+ принятое остаётся навсегда, долг накоплен к заведению проверки и только сокращается, — либо
79
+ столько ключей, сколько у записей родов. Причина обязательна: снятая проверка без причины
80
+ через месяц неотличима от недосмотра.
81
+ - **Тест, утверждающий отсутствие, зелен и тогда, когда ищет не то.** Совпадения нет ни у
82
+ верного текста, ни у опечатки в образце, ни у переименованного ключа — отличить их по цвету
83
+ прогона нечем. Отрицательное утверждение поэтому идёт в паре с положительным: сначала
84
+ проверяется, что искомое место вообще найдено, и только потом — что в нём нет того, чего быть
85
+ не должно.
86
+ - **Заголовок теста обещает больше, чем тело проверяет, и сверка этого не видит.** Номер
87
+ сценария в заголовке стоит — сценарий числится покрытым, а что именно утверждается, не
88
+ спрашивает никто. Тело читается вместе с заголовком: обещание в заголовке и утверждение в
89
+ теле — два разных текста, и расходятся они молча.
59
90
 
60
91
  ## Чего из закона здесь нет
61
92
 
@@ -76,23 +107,30 @@ description: Правило под «Закон о проверяемости».
76
107
  - **Зелёный `nx test <проект>` не значит, что хоть один файл исполнялся.** Либа без своего
77
108
  `vitest.config.mts` не запускает ничего — так тесты домена броней не запускались ни разу.
78
109
  Либа с конфигом, но без единого `*.spec.ts`, проходит зелёной из-за `passWithNoTests: true`,
79
- который стоит во всех 203 конфигах дерева, и на глаз эти два случая неотличимы: в обоих
80
- прогон успешен. Без единого теста живут 131 либа из 203 почти две трети. Перед правкой в
110
+ который обычно стоит в каждом конфиге дерева, и на глаз эти два случая неотличимы: в обоих
111
+ прогон успешен. Доля либ без единого теста меряется пересчётом нижев дереве, где его
112
+ завели впервые, она вышла почти в две трети. Перед правкой в
81
113
  незнакомой либе проверяется, есть ли в ней хоть один `*.spec.ts`; если нет — первый
82
114
  заводится этой же правкой, а не откладывается: откладывать здесь не с чего, долг уже
83
115
  накоплен. Пересчёт: `for d in $(find libs -name vitest.config.mts -exec dirname {} \;); do
84
116
  [ -z "$(find "$d" -name '*.spec.ts')" ] && echo "$d"; done | wc -l`.
117
+ - **Зелёная сводка покрытия не значит, что тесты проходят.** Сверка читает заголовки тестов и
118
+ сопоставляет их со сценариями спека; исполняется ли тест и чем он кончается — она не знает
119
+ вовсе, и падающий тест значится в ней покрытием. Три сценария одной панели падали и до правки
120
+ экрана, а нашлось это только прогоном. Перед правкой экрана его сквозные тесты гоняются один
121
+ раз до первой строки кода: иначе чужое падение читается как своя регрессия, а своё — как
122
+ чужое.
85
123
  - **«Executable doesn't exist» — состояние машины, а не дефект правки.** Установлен только
86
124
  chromium, `firefox` и `webkit` падают всегда: гонять `--project=chromium`, узкий экран —
87
125
  `--project=mobile-chrome`. Та же ошибка приходит после смены версии Playwright: браузер
88
126
  ставится под конкретную версию, и после подъёма нужен повторный
89
127
  `npx playwright install chromium`. Девять тестов так и упали, и это выглядело регрессией
90
128
  обновления.
91
- - **Первому прогону сразу после установки браузера верить нельзя.** Два падения `admin-e2e`
92
- не повторились ни при отдельном прогоне тех же тестов, ни при втором полном. Такой прогон
93
- повторяют, а выводы делают по второму.
94
- - Сквозные тесты админки без `E2E_ADMIN_EMAIL` и `E2E_ADMIN_PASSWORD` пропускаются молча — в
95
- отчёте они значатся `skipped`, и прогон выглядит успешным.
129
+ - **Первому прогону сразу после установки браузера верить нельзя.** Два падения сквозного
130
+ набора не повторились ни при отдельном прогоне тех же тестов, ни при втором полном. Такой
131
+ прогон повторяют, а выводы делают по второму.
132
+ - Сквозная спека, которой нужен вход, без учётных данных в окружении пропускается молча — в
133
+ отчёте она значится `skipped`, и прогон выглядит успешным. Имена переменных — при дереве.
96
134
  - **Справочник флоу вторых сценариев не заводит.** В `docs/E2E_<ДОМЕН>_FLOWS.md` кладут то,
97
135
  чего в спеке домена нет и быть не должно: `qa-dataid` элементов, состояния разметки, ловушки
98
136
  стенда. Обещанное поведение остаётся сценарием в `scenarios.md`: если списать его во второе
@@ -2,7 +2,7 @@
2
2
  name: translations
3
3
  kind: rule
4
4
  law: locales
5
- description: Правило под «Закон о локалях и переводах». Брать при заведении любого видимого текста, правке словарей libs/common/i18n, префиксов локалей сайта и перевода контента объекта. Называет восемь локалей, Transloco, производные переводы и начальную валюту локали. Готовый код — в паттерне translations-key.
5
+ description: Правило под «Закон о локалях и переводах». Брать при заведении любого видимого текста, правке словарей libs/common/i18n, префиксов локалей сайта и перевода контента объекта. Называет локали перевода, словари, производные переводы и начальную валюту локали. Готовый код — в паттерне translations-key.
6
6
  ---
7
7
 
8
8
  # Локали и переводы — как это устроено здесь
@@ -15,7 +15,7 @@ description: Правило под «Закон о локалях и перев
15
15
 
16
16
  | В законе | Здесь |
17
17
  | -------------------- | --------------------------------------------------------------------------------------------------- |
18
- | локаль сайта | одна из восьми: `en`, `ru`, `de`, `zh-Hans`, `zh-Hant`, `ko`, `th`, `hi` |
18
+ | локаль сайта | одна из локалей перевода; их набор в `implementation.md` рядом |
19
19
  | локаль по умолчанию | `en` — отдаётся из корня, без префикса в адресе |
20
20
  | локаль ввода | `source_locale` в запросе сохранения объекта; берётся из языка админки |
21
21
  | словарь | JSON-словари Transloco в `libs/common/i18n/src/lib/dictionaries/<локаль>/`, разложенные по разделам |
@@ -54,6 +54,8 @@ description: Правило под «Закон о локалях и перев
54
54
  ## Паттерны
55
55
 
56
56
  - `translations-key` — заведение ключа во всех локалях перевода и подстановка в разметку.
57
+ - `translations-content` — перевод содержимого записи: производный перевод, запись локали
58
+ руками, отказ провайдера, сброс кэша отданных страниц.
57
59
 
58
60
  ## Ловушки
59
61
 
@@ -63,7 +65,11 @@ description: Правило под «Закон о локалях и перев
63
65
  на общий, сайт, письма и админку.
64
66
  - **Перевод контента идёт до транзакции сохранения:** страница объекта не должна оказаться
65
67
  наполовину переведённой. Кэш сбрасывается после записи и один раз.
66
- - **Без ключа `ANTHROPIC_API_KEY` сохранение проходит,** но переводы остаются прежними, и
67
- владелец видит предупреждение `propertySaveTranslationFailed`.
68
+ - **Без ключа переводов сохранение проходит,** но переводы остаются прежними, и владелец видит
69
+ предупреждение об этом на своём экране. Ключ лежит секретом владения в хранилище, а не
70
+ переменной окружения, и заводит его владелец экраном интеграций: одноимённая переменная в
71
+ составе прода приложением не читается. Состояния ключа — паттерн `git-workflow-secrets`.
72
+ - **Ветки локалей надеты не на все маршруты сайта, и новый раздел в них не попадает** —
73
+ раскладка, её ловушка и готовый код лежат в правиле `seo` и паттерне `seo-page`.
68
74
  - Новый маршрут сайта без ветки под каждую локаль существует только в локали по умолчанию:
69
- `/de/<путь>` отдаст 404 и поисковику, и гостю.
75
+ путь под префиксом другой локали отдаст 404 и поисковику, и гостю.
@@ -33,6 +33,11 @@ description: Правило под «Закон об устройстве код
33
33
  различаются в месте использования, а не переходом к объявлению.
34
34
  - **Суффикс имени файла находит в нём обещанное объявление.** Список суффиксов закрыт: слово,
35
35
  которого в нём нет, суффиксом не считается, и такой файл правило не судит.
36
+ - **Файл не длиннее 500 строк, и считаются все строки — пустые и комментарии тоже.** Файл,
37
+ который не влезает на экран целиком, читают по частям, и правку в нём делают, не увидев
38
+ остального. Для `.ts` это держит правило линтера; файлы обвязки — сценарии и скрипты — до
39
+ него не доходят и судятся отдельной проверкой дерева. Накопленное к дню включения
40
+ перечислено поимённо, и строка оттуда снимается вместе с делением своего файла.
36
41
  - **Тип берётся из того пакета, где объявлен.** Своя копия чужого типа расходится с оригиналом
37
42
  молча, а компилируется из них только одна.
38
43
  - **Двухступенчатое приведение `as unknown as` запрещено правилом линтера.** Вместо него —
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rt-tools/agent-kit",
3
- "version": "0.5.3",
3
+ "version": "0.6.0",
4
4
  "description": "Переносимый слой правил для агента: законы, хуки, проверки и агенты, раскладываемые в репозиторий одной командой",
5
5
  "author": "RT Team",
6
6
  "license": "Apache-2.0",
Binary file
Binary file