@rt-tools/agent-kit 0.2.0 → 0.4.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 (203) hide show
  1. package/README.md +235 -18
  2. package/assets/agents/business-analyst.md +74 -0
  3. package/assets/agents/project-manager.md +70 -0
  4. package/assets/agents/qa-engineer.md +72 -0
  5. package/assets/agents/skill-curator.md +110 -0
  6. package/assets/agents/spec-critic.md +44 -0
  7. package/assets/agents/spec-writer.md +50 -0
  8. package/assets/checks/board.github.mjs +286 -0
  9. package/assets/checks/check-board.github.mjs +188 -0
  10. package/assets/checks/check-doc-paths.mjs +163 -0
  11. package/assets/checks/check-dupes.mjs +277 -0
  12. package/assets/checks/check-lib-layers.mjs +573 -0
  13. package/assets/checks/check-reuse.mjs +208 -0
  14. package/assets/checks/check-schema-drift.mjs +186 -0
  15. package/assets/checks/check-specs.mjs +1007 -0
  16. package/assets/checks/check-styles.mjs +109 -0
  17. package/assets/checks/rt-kit-checks.config.mjs +134 -0
  18. package/assets/checks/task-new.github.mjs +198 -0
  19. package/assets/commands/skill-curator.md +70 -0
  20. package/assets/defaults/gate-map.sh +100 -0
  21. package/assets/defaults/project.sh +179 -0
  22. package/assets/hooks/browser-device-id.sh +20 -0
  23. package/assets/hooks/browser-guard-device-id.sh +28 -0
  24. package/assets/hooks/browser-guard-no-asking.sh +27 -0
  25. package/assets/hooks/browser-guard-no-listing.sh +18 -0
  26. package/assets/hooks/browser-guard-no-other-drivers.sh +79 -0
  27. package/assets/hooks/browser-guard-require-select.sh +54 -0
  28. package/assets/hooks/commit-msg.sh +26 -0
  29. package/assets/hooks/constitution-index.sh +43 -0
  30. package/assets/hooks/dev-server-guard.sh +115 -0
  31. package/assets/hooks/docs-guard.sh +282 -0
  32. package/assets/hooks/git-guard-delivery.sh +167 -0
  33. package/assets/hooks/git-guard-main.sh +73 -0
  34. package/assets/hooks/git-guard-push-tests.sh +94 -0
  35. package/assets/hooks/glossary-load.sh +23 -0
  36. package/assets/hooks/lint-after-edit.sh +219 -0
  37. package/assets/hooks/qa-dataid-guard.sh +121 -0
  38. package/assets/hooks/reuse-first-guard.sh +154 -0
  39. package/assets/hooks/skill-gate-rearm.sh +23 -0
  40. package/assets/hooks/skill-gate.sh +128 -0
  41. package/assets/hooks/skill-loaded.sh +21 -0
  42. package/assets/hooks/sql-guard.sh +679 -0
  43. package/assets/hooks/task-context-load.sh +100 -0
  44. package/assets/hooks/task-flow-guard.sh +107 -0
  45. package/assets/laws/{access.md → application/access.md} +1 -4
  46. package/assets/laws/{locales.md → application/locales.md} +1 -3
  47. package/assets/laws/application/money.md +41 -0
  48. package/assets/laws/application/ownership.md +32 -0
  49. package/assets/laws/{search-visibility.md → application/search-visibility.md} +1 -1
  50. package/assets/laws/code-structure.md +7 -6
  51. package/assets/laws/delivery.md +53 -3
  52. package/assets/laws/entity-editing.md +49 -55
  53. package/assets/laws/entity-models.md +4 -14
  54. package/assets/laws/frontend-application.md +5 -5
  55. package/assets/laws/lib-imports.md +14 -1
  56. package/assets/laws/lists.md +33 -0
  57. package/assets/laws/navigation.md +40 -0
  58. package/assets/laws/project-documentation.md +17 -8
  59. package/assets/laws/reuse-first.md +26 -21
  60. package/assets/laws/shared-code.md +13 -1
  61. package/assets/laws/verifiability.md +17 -1
  62. package/assets/laws/work-conduct.md +48 -0
  63. package/assets/patterns/admin-lists-screen.md +131 -0
  64. package/assets/patterns/admin-nav-item.md +71 -0
  65. package/assets/patterns/angular-patterns-state.md +101 -0
  66. package/assets/patterns/api-layer-pair.md +88 -0
  67. package/assets/patterns/browser-verification-measure.md +86 -0
  68. package/assets/patterns/browser-verification-stand.md +143 -0
  69. package/assets/patterns/component-structure-new.md +99 -0
  70. package/assets/patterns/dependencies-upgrade.md +65 -0
  71. package/assets/patterns/doc-style-sweep.md +137 -0
  72. package/assets/patterns/doc-style-write.md +109 -0
  73. package/assets/patterns/entity-aside.md +136 -0
  74. package/assets/patterns/entity-models-new.md +124 -0
  75. package/assets/patterns/entity-store.md +91 -0
  76. package/assets/patterns/git-workflow-commit.azure.md +259 -0
  77. package/assets/patterns/git-workflow-commit.github.md +333 -0
  78. package/assets/patterns/git-workflow-commit.gitlab.md +283 -0
  79. package/assets/patterns/git-workflow-merge.md +99 -0
  80. package/assets/patterns/git-workflow-migration.md +88 -0
  81. package/assets/patterns/git-workflow-restart.md +49 -0
  82. package/assets/patterns/lib-layers-move.md +95 -0
  83. package/assets/patterns/lib-layers-new.md +82 -0
  84. package/assets/patterns/ownership-scope-resolve.md +69 -0
  85. package/assets/patterns/permissions-procedure.md +71 -0
  86. package/assets/patterns/platform-access-di.md +84 -0
  87. package/assets/patterns/pricing-quote.md +71 -0
  88. package/assets/patterns/reuse-first-extend.md +73 -0
  89. package/assets/patterns/seo-page.md +104 -0
  90. package/assets/patterns/seo-verify.md +83 -0
  91. package/assets/patterns/shared-code-new.md +86 -0
  92. package/assets/patterns/spec-driven-domain.md +107 -0
  93. package/assets/patterns/spec-driven-rule.md +127 -0
  94. package/assets/patterns/styling-bem-component.md +88 -0
  95. package/assets/patterns/styling-bem-layout.md +73 -0
  96. package/assets/patterns/task-flow-close.md +90 -0
  97. package/assets/patterns/task-flow-resume.md +94 -0
  98. package/assets/patterns/task-flow-start.md +117 -0
  99. package/assets/patterns/testing-e2e.md +92 -0
  100. package/assets/patterns/testing-unit.md +117 -0
  101. package/assets/patterns/translations-key.md +64 -0
  102. package/assets/patterns/ts-procedure.md +65 -0
  103. package/assets/rules/angular-patterns.md +71 -0
  104. package/assets/rules/api-layer.md +71 -0
  105. package/assets/rules/browser-verification.md +87 -0
  106. package/assets/rules/component-structure.md +64 -0
  107. package/assets/rules/dependencies.md +66 -0
  108. package/assets/rules/doc-style.md +103 -0
  109. package/assets/rules/entity-conventions.md +78 -0
  110. package/assets/rules/entity-models.md +70 -0
  111. package/assets/rules/git-workflow.azure.md +116 -0
  112. package/assets/rules/git-workflow.github.md +123 -0
  113. package/assets/rules/git-workflow.gitlab.md +113 -0
  114. package/assets/rules/lib-layers.md +80 -0
  115. package/assets/rules/lists.md +73 -0
  116. package/assets/rules/navigation.md +78 -0
  117. package/assets/rules/ownership-scope.md +63 -0
  118. package/assets/rules/permissions.md +70 -0
  119. package/assets/rules/platform-access.md +77 -0
  120. package/assets/rules/pricing.md +64 -0
  121. package/assets/rules/reuse-first.md +83 -0
  122. package/assets/rules/seo.md +71 -0
  123. package/assets/rules/shared-code.md +70 -0
  124. package/assets/rules/spec-driven.md +135 -0
  125. package/assets/rules/styling-bem.md +74 -0
  126. package/assets/rules/task-flow.md +110 -0
  127. package/assets/rules/testing.md +100 -0
  128. package/assets/rules/translations.md +69 -0
  129. package/assets/rules/typescript-conventions.md +76 -0
  130. package/assets/skills/agent-kit.md +81 -0
  131. package/assets/skills/write-a-skill.md +108 -0
  132. package/assets/templates/gate-map.sh +45 -0
  133. package/assets/templates/implementation.md +44 -0
  134. package/assets/templates/pattern.md +5 -1
  135. package/assets/templates/project.sh +54 -0
  136. package/assets/templates/rule.md +12 -23
  137. package/assets/variants.json +20 -0
  138. package/assets/workflows/feature.js +134 -0
  139. package/assets/workflows/plan.js +150 -0
  140. package/bin/agent-kit.d.ts.map +1 -1
  141. package/bin/agent-kit.js +78 -5
  142. package/bin/agent-kit.js.map +1 -1
  143. package/bin/prompt.d.ts +5 -0
  144. package/bin/prompt.d.ts.map +1 -1
  145. package/bin/prompt.js +19 -7
  146. package/bin/prompt.js.map +1 -1
  147. package/index.d.ts +1 -0
  148. package/index.d.ts.map +1 -1
  149. package/index.js +1 -0
  150. package/index.js.map +1 -1
  151. package/lib/assets.d.ts +14 -1
  152. package/lib/assets.d.ts.map +1 -1
  153. package/lib/assets.js +23 -2
  154. package/lib/assets.js.map +1 -1
  155. package/lib/catalog.d.ts +52 -5
  156. package/lib/catalog.d.ts.map +1 -1
  157. package/lib/catalog.js +104 -16
  158. package/lib/catalog.js.map +1 -1
  159. package/lib/commands.d.ts +22 -1
  160. package/lib/commands.d.ts.map +1 -1
  161. package/lib/commands.js +218 -11
  162. package/lib/commands.js.map +1 -1
  163. package/lib/companion.d.ts +57 -0
  164. package/lib/companion.d.ts.map +1 -0
  165. package/lib/companion.js +60 -0
  166. package/lib/companion.js.map +1 -0
  167. package/lib/config.d.ts +42 -2
  168. package/lib/config.d.ts.map +1 -1
  169. package/lib/config.js +60 -2
  170. package/lib/config.js.map +1 -1
  171. package/lib/freshness.d.ts +14 -0
  172. package/lib/freshness.d.ts.map +1 -0
  173. package/lib/freshness.js +116 -0
  174. package/lib/freshness.js.map +1 -0
  175. package/lib/hooks-map.d.ts +24 -0
  176. package/lib/hooks-map.d.ts.map +1 -0
  177. package/lib/hooks-map.js +72 -0
  178. package/lib/hooks-map.js.map +1 -0
  179. package/lib/integrity.d.ts +36 -0
  180. package/lib/integrity.d.ts.map +1 -0
  181. package/lib/integrity.js +44 -0
  182. package/lib/integrity.js.map +1 -0
  183. package/lib/picker.d.ts +11 -1
  184. package/lib/picker.d.ts.map +1 -1
  185. package/lib/picker.js +44 -6
  186. package/lib/picker.js.map +1 -1
  187. package/lib/stamp.d.ts +2 -5
  188. package/lib/stamp.d.ts.map +1 -1
  189. package/lib/stamp.js +25 -10
  190. package/lib/stamp.js.map +1 -1
  191. package/lib/sync.d.ts +29 -0
  192. package/lib/sync.d.ts.map +1 -1
  193. package/lib/sync.js +78 -4
  194. package/lib/sync.js.map +1 -1
  195. package/lib/variants.d.ts +44 -0
  196. package/lib/variants.d.ts.map +1 -0
  197. package/lib/variants.js +82 -0
  198. package/lib/variants.js.map +1 -0
  199. package/package.json +1 -1
  200. package/rt-tools-agent-kit-0.4.0.tgz +0 -0
  201. package/assets/laws/admin-lists.md +0 -35
  202. package/assets/laws/admin-navigation.md +0 -38
  203. package/rt-tools-agent-kit-0.2.0.tgz +0 -0
@@ -0,0 +1,71 @@
1
+ ---
2
+ name: api-layer
3
+ kind: rule
4
+ law: frontend-application
5
+ description: Правило под «Закон о фронтовом приложении». Брать при правке слоя api фронтового домена — *-api.facade.ts, *-api.service.ts и мапперов в его mappers/ под libs/admin и libs/site. Называет пару «фасад и сервис», один вход выборки у списка и общий конвертер страницы. Готовый код — в паттерне api-layer-pair.
6
+ ---
7
+
8
+ # Обращение к серверу — как это устроено здесь
9
+
10
+ Правило под закон `docs/constitution/frontend-application.md`. Закон говорит, что должно быть
11
+ верно; здесь — из чего сложен слой обращения к серверу. Состояние — `angular-patterns`, файл
12
+ компонента — `component-structure`, стили — `styling-bem`, окружение браузера —
13
+ `platform-access`. Все пять под одним законом.
14
+
15
+ Правило про обе фронтовые семьи: `libs/admin/*/api/**` и `libs/site/*/api/**`. На бэкенде слово
16
+ `api` означает выход к чужому сервису и устроено иначе — там `typescript-conventions`.
17
+
18
+ ## Как это называется здесь
19
+
20
+ | В законе | Здесь |
21
+ | ---------------- | ------------------------------------------------------------------------------ |
22
+ | страница записей | `IPageModel` — `pageNumber`, `pageSize`, `totalCount` |
23
+ | выборка списка | `IList.Query.State` — страница, порядок, условия отбора, строка поиска |
24
+ | ответ списка | `data`, `pageModel`, `sortModel`, `filterModel`, `searchTerm` |
25
+ | путь запроса | экран → стор → `<Сущность>ApiService` → `<Сущность>ApiFacade` → клиент Connect |
26
+
27
+ ## Где это лежит
28
+
29
+ В этом дереве — таблица в `implementation.md` рядом. Пути живут там, а не здесь: правило
30
+ переносится между репозиториями, раскладка — нет, и путь, названный в правиле, врёт в первом
31
+ же дереве, которое держит код иначе.
32
+
33
+ ## Как закон применяется здесь
34
+
35
+ - **Домен ходит за данными парой классов: фасад зовёт процедуру, сервис переводит модели.**
36
+ Один класс на оба дела означал бы, что подмена источника тянет за собой перевод.
37
+ - **Фасад знает только контракт, сервис отдаёт только `State`.** Тип из контракта до стора и
38
+ шаблона не доходит.
39
+ - **У списка один вход — выборка.** Объект, к которому привязан список, тип фида, состояние
40
+ подписки — такие же условия отбора, и лежат они в `filterModel`.
41
+ - **Ответ списка ложится в общий конвертер целиком.** Контракт отдаёт страницу в той же форме,
42
+ что и модель, и промежуточного объекта в сервисе не остаётся.
43
+ - **Поля порядка и отбора — перечисления домена, а не голая строка.** Голая строка означает,
44
+ что имя, по которому сервер не сортирует, компилируется и падает запросом.
45
+ - **Пара отдаёт поток, а не ожидание.** Основа списочного стора работает потоками, и промисный
46
+ сервис в неё не ложится.
47
+
48
+ ## Чего из закона здесь нет
49
+
50
+ Общей выборкой ходят только те списки, которым сервер отдаёт страницу — признак `page_model` в
51
+ ответе процедуры. Заявки и объекты приходят целиком: процедуры со страницей у них пока нет, и
52
+ это долги `Q-L-5` и `Q-L-7`, а не другая форма слоя.
53
+
54
+ Сторы, которые ещё держат прежнюю сигнатуру, зовут поток через `firstValueFrom` и несут над
55
+ классом комментарий с тем, когда мост уйдёт. Новый стор моста не заводит.
56
+
57
+ ## Паттерны
58
+
59
+ - `api-layer-pair` — готовые фасад, сервис и перевод выборки.
60
+
61
+ ## Ловушки
62
+
63
+ - **Выборка в ответе — применённая, а не запрошенная.** Порядок по умолчанию и отброшенное
64
+ сервером условие экран иначе не увидит.
65
+ - **Одна пара — одна сущность.** У объекта, его прежних адресов и подписок на календари свои
66
+ пары, хотя процедуры лежат в одном proto-сервисе.
67
+ - **Метод, которого у домена нет, не объявляется.** Список читают все, правят не все.
68
+ - **Серверный стрим — исключение из правила про поток:** живой срез аналитики и лента тредов
69
+ приходят асинхронным итератором, и заворачивать его некуда.
70
+ - Своей копии общих мапперов страницы, порядка и отбора домен не заводит — второй экземпляр
71
+ ловит `npm run check:dupes`.
@@ -0,0 +1,87 @@
1
+ ---
2
+ name: browser-verification
3
+ kind: rule
4
+ law: verifiability
5
+ description: Правило под «Закон о проверяемости». Брать при любой проверке через браузер и при запросах curl или wget к дев-серверу. Называет порты сайта, админки и API, чему на дев-сервере верить нельзя и чем измерять вместо взгляда. Готовый код — в паттернах browser-verification-stand и browser-verification-measure.
6
+ ---
7
+
8
+ # Проверка работающего приложения — как это устроено здесь
9
+
10
+ Правило под закон `docs/constitution/verifiability.md`. Закон говорит, что считается
11
+ подтверждением; здесь — где живут приложения, чему на них можно верить и чем измерять. Тесты
12
+ под тем же законом — правило `testing`.
13
+
14
+ ## Как это называется здесь
15
+
16
+ | В законе | Здесь |
17
+ | --------------------------------- | ----------------------------------------------------------------------------------------------- |
18
+ | работающее приложение | сайт на 4900, админка на 4901, API на 3333 — всё поднято владельцем |
19
+ | место, где его видит пользователь | прод-сборка за настоящим `deploy/nginx.conf`, а не дев-сервер |
20
+ | замер | `getComputedStyle`, `getBoundingClientRect`, контраст, совпадение центров, попадание во вьюпорт |
21
+ | драйвер браузера | `claude-in-chrome` на закреплённом профиле этого дерева |
22
+
23
+ ## Где это лежит
24
+
25
+ В этом дереве — таблица в `implementation.md` рядом. Пути живут там, а не здесь: правило
26
+ переносится между репозиториями, раскладка — нет, и путь, названный в правиле, врёт в первом
27
+ же дереве, которое держит код иначе.
28
+
29
+ ## Как закон применяется здесь
30
+
31
+ - **Свой дев-сервер не поднимается.** Приложения уже подняты владельцем, и попытка поднять
32
+ второй экземпляр отбивается гардом.
33
+ - **Браузер водится одним драйвером на закреплённом профиле.** Остальные двери — второй
34
+ драйвер, `open`, `osascript`, запуск бинарника — закреплённый профиль не спрашивают вовсе.
35
+ - **Выбор браузера протухает и требует повторного вызова.** Выбор, сделанный в начале
36
+ сессии, не держится: после паузы следующий вызов открывает вкладку в другом профиле молча.
37
+ - **Профиль не выбирается из списка и не спрашивается у владельца.** Список отдаёт неустойчивые
38
+ имена, которые не опознают ничего, а выбор из него ведёт на профиль без входа.
39
+ - **Прод-конфигурация проверяется только за настоящим прокси.** Голый сервер отдачи страниц
40
+ про кэш, перенаправления и заголовки не знает ничего.
41
+
42
+ Вывод о вёрстке подкрепляется числом: «выглядит нормально» результатом проверки не является.
43
+ Этого не стережёт ничто — как измерять, разобрано в паттерне `browser-verification-measure`.
44
+
45
+ ## Чего из закона здесь нет
46
+
47
+ Проверки на прямое обращение к окружению браузера нет — это `Q-FA-1` в законе о фронтовом
48
+ приложении: такое обращение компилируется и падает только при отдаче страницы сервером.
49
+
50
+ ## Паттерны
51
+
52
+ - `browser-verification-stand` — честный стенд из прод-сборки, вход в админку, разбор порта.
53
+ - `browser-verification-measure` — замер вместо взгляда, ловушки инструмента `computer`.
54
+
55
+ ## Ловушки
56
+
57
+ - **Сначала выяснить, что отвечает на порту:** `lsof -nP -iTCP:<порт> -sTCP:LISTEN` до первого
58
+ запроса. На 3333 регулярно висит собранный артефакт из прошлой сессии — он отвечает 200
59
+ старым кодом, а заведённой в ветке процедуры у него нет вовсе, и 404 читается как дефект
60
+ регистрации. Таких процессов бывает несколько; `pkill` по `nx serve api` не попадает ни в
61
+ один — убивать по PID из `lsof`, каждый.
62
+ - Инкрементальная сборка протухает поштучно: разметка на 4900 бывает уже новая, а клиентский
63
+ чанк — от компиляции до правки. Признак дев-сборки — имена бандла без хеша (`main.js`).
64
+ Расхождение между `curl` и страницей после гидратации — повод пересобрать, а не искать
65
+ дефект в коде. Отсюда же нельзя делать вывод «такого маршрута нет»: сверяться с
66
+ `app.routes.ts`.
67
+ - **Кэш объясняет расхождение, но не подтверждает его.** В `.angular/cache/…/vite/deps` лежат
68
+ только пакеты из `node_modules`, кода репозитория там нет вовсе. Вывод «дефекта нет, это
69
+ кэш» закрывает разбор, поэтому принимается только после проверки на чистой сборке — три
70
+ круга ушло на застрявшую панель ленты событий, пока дефект лежал в снятии аутлета.
71
+ - Сообщение `Angular debugging APIs are not available` в консоли принадлежит расширению
72
+ Chrome, а не приложению: прод-сборка не публикует `window.ng`. Лечить правкой кода не надо —
73
+ `window.ng` в проде это карта внутренностей в руках любого, кто откроет консоль.
74
+ - Поведение роутера воспроизводится нажатиями: подстановка адреса и заход по прямой ссылке
75
+ поднимают приложение заново, и накопленного состояния у него нет.
76
+ - Замер отвечает только на заданный вопрос. Строки попапа профиля сошлись с образцом по
77
+ отступам, кеглю и скруглению, а фон на наведении образец в этом месте не красит вовсе —
78
+ полноширинная подсветка держалась два круга при верных числах.
79
+ - **Если сменилась версия пакета, который рисует вёрстку, экраны обходят руками.** Тесты
80
+ нажимают по `qa-dataid` и остаются зелёными, даже когда отступ съехал, размер пропал, а
81
+ строка стала другой высоты: они проверяют переходы, а не вид. Пары скриншотов тут тоже мало
82
+ — смотрят по очереди все экраны, которые этот пакет рисует.
83
+ - **Путей запуска здесь три, и проверять их надо порознь:** локальная команда, образ
84
+ `deploy/api.Dockerfile` и состав `docker-compose.prod.yml`. Переменная, заданная в команде
85
+ проверки, не говорит про образ ничего: в составе прода она есть, а ручной прогон того же
86
+ образа идёт без неё. Пути перечисляются до проверки, а не после того, как один из них
87
+ сошёлся.
@@ -0,0 +1,64 @@
1
+ ---
2
+ name: component-structure
3
+ kind: rule
4
+ law: frontend-application
5
+ description: Правило под «Закон о фронтовом приложении». Брать при правке любого *.component.ts и его шаблона. Называет порядок свойств декоратора, группировку импортов, договорённости шаблона и обязательный qa-dataid. Готовый код — в паттерне component-structure-new.
6
+ ---
7
+
8
+ # Файл компонента — как это устроено здесь
9
+
10
+ Правило под закон `docs/constitution/frontend-application.md`. Закон говорит, что должно быть
11
+ верно; здесь — как устроен сам файл компонента и его шаблон. Состояние и потоки —
12
+ `angular-patterns`, стили — `styling-bem`, окружение браузера — `platform-access`, слой
13
+ обращения к серверу — `api-layer`. Все пять под одним законом.
14
+
15
+ ## Как это называется здесь
16
+
17
+ | В законе | Здесь |
18
+ | ---------------------------------- | ---------------------------------------------------------------------------- |
19
+ | компонент | `vm-<имя>` — префикс один на сайт и админку |
20
+ | готовое, а не вычисление в шаблоне | `computed()`; там, где значение приходит из контекста шаблона, — чистый пайп |
21
+ | якорь для проверки | атрибут `qa-dataid` в kebab-case по смыслу элемента |
22
+ | корень разметки | `:host` с классом блока от `host: { class: 'vm-<имя>' }` |
23
+
24
+ ## Где это лежит
25
+
26
+ В этом дереве — таблица в `implementation.md` рядом. Пути живут там, а не здесь: правило
27
+ переносится между репозиториями, раскладка — нет, и путь, названный в правиле, врёт в первом
28
+ же дереве, которое держит код иначе.
29
+
30
+ ## Как закон применяется здесь
31
+
32
+ - **Шаблон не зовёт методов.** Правило линтера банит `{{ getTotal() }}` и `@if
33
+ (computeFlag())`, чтения сигналов не трогает.
34
+ - **Каждый интерактивный элемент несёт `qa-dataid`.** Это единственный якорь спек: классы BEM
35
+ меняются вместе с вёрсткой, а поиск по роли и тексту ломается на локалях перевода.
36
+ - **Класс блока висит на хосте, а не на обёртке внутри шаблона.** Лишняя обёртка вокруг всех
37
+ детей — это раскладка, и ей место на `:host`.
38
+
39
+ ## Чего из закона здесь нет
40
+
41
+ Порядок свойств декоратора, группировку импортов и самозакрывающиеся теги не проверяет ничто —
42
+ они держатся чтением соседнего файла. Проверки на прямое обращение к окружению браузера тоже
43
+ нет — это `Q-FA-1` в законе.
44
+
45
+ ## Паттерны
46
+
47
+ - `component-structure-new` — готовый файл компонента и договорённости шаблона.
48
+
49
+ ## Ловушки
50
+
51
+ - **`href="#id"` в разметке не работает.** Сборка одна на все локали, в разметке стоит
52
+ `<base href="/">`, и браузер разрешает фрагмент относительно базы: вместо прокрутки
53
+ получается полная навигация с перезагрузкой. Прокрутка — через роутер:
54
+ `<a [routerLink]="[]" fragment="booking">`.
55
+ - **Один и тот же компонент в обеих ветках `@if` — это условная привязка.** Две ветки с
56
+ разными входами пересоздают компонент и теряют его состояние.
57
+ - **Кит, который рисуется в оверлее, из хоста вызывающего не адресуется:** его разметка лежит
58
+ вне хоста, и `[qa-dataid="x"] button` до кнопок не дотянется. Такие кнопки носят собственные
59
+ якоря прямо в шаблоне кита, а гард каталог зависимостей не проверяет.
60
+ - **`qa-dataid` не заменяет `aria-label` и роли:** доступность отдельно, якорь отдельно. И не
61
+ снимается при правке вёрстки — на него завязаны спеки.
62
+ - Готовое из кита не пишется заново: своя разметка с `role="alert"`, `<table>`,
63
+ `role="dialog"`, `role="tablist"` или `role="tooltip"` означает, что мимо `<префикс>-message`,
64
+ `<префикс>-table`, `<префикс>-dialog`, `<префикс>-tabs` или `<префикс>-tooltip` прошли. Правило целиком — `reuse-first`.
@@ -0,0 +1,66 @@
1
+ ---
2
+ name: dependencies
3
+ kind: rule
4
+ law: delivery
5
+ description: Правило под «Закон о поставке». Брать при правке package.json, pnpm-lock.yaml и pnpm-workspace.yaml и при обновлении любого пакета. Называет точный номер версии вместо диапазона, снимок дерева, подмену чужих версий, выдержку новой версии и границу переформатирования после обновления форматтера. Готовый порядок — в паттерне dependencies-upgrade.
6
+ ---
7
+
8
+ # Зависимости — как это устроено здесь
9
+
10
+ Правило под закон `docs/constitution/delivery.md`. Закон говорит, что должно быть верно;
11
+ здесь — чем это названо в этом дереве, где лежит и что из закона у нас не применяется. Ветка,
12
+ коммит и выкатка под тем же законом — правило `git-workflow`.
13
+
14
+ ## Как это называется здесь
15
+
16
+ | В законе | Здесь |
17
+ | ---------------------------- | -------------------------------------------------------------------------------------------------- |
18
+ | объявление зависимости | точный номер в `package.json` — `"prettier": "3.9.6"`; `^` и `~` в файле не встречаются ни разу |
19
+ | снимок установленного дерева | `pnpm-lock.yaml`; едет тем же коммитом, что и объявление |
20
+ | подмена чужой версии | `overrides` в `pnpm-workspace.yaml` — там лежат подменённые транзитивные зависимости |
21
+ | выдержка новой версии | `minimumReleaseAge`; пакет, нужный раньше срока, выписывается номером в `minimumReleaseAgeExclude` |
22
+ | менеджер пакетов | pnpm: `npm run` зовёт скрипты, установку делает `pnpm install` |
23
+
24
+ ## Где это лежит
25
+
26
+ В этом дереве — таблица в `implementation.md` рядом. Пути живут там, а не здесь: правило
27
+ переносится между репозиториями, раскладка — нет, и путь, названный в правиле, врёт в первом
28
+ же дереве, которое держит код иначе.
29
+
30
+ ## Как закон применяется здесь
31
+
32
+ - **Версия пакета записана точным номером.** Из диапазона сегодня и через неделю поставится
33
+ разное, и откат правки это не исправит.
34
+ - **Подменённые версии чужих зависимостей собраны в один список, и его пересматривают при
35
+ каждом обновлении.** Подмена, оставшаяся в списке после того, как основной пакет подняли,
36
+ незаметно откатывает его зависимость назад.
37
+ - **Свежая версия сначала выдерживается, а нужная раньше срока выписывается отдельно.** Иначе
38
+ выпуск, который автор успел отозвать, попадёт в снимок.
39
+ - **Переформатируется только то, чьё форматирование проверяет линтер.** Обновлённый форматтер
40
+ меняет все файлы, до которых дотянется, а `.md` и `.json` здесь не проверяет никто: правка в
41
+ них — просто шум, который придётся читать глазами.
42
+
43
+ ## Чего из закона здесь нет
44
+
45
+ Никто не сверяет, что объявленные версии совпадают со снимком: `--frozen-lockfile` стоит
46
+ только в выкатке, а она идёт от пуша в главную ветку, то есть уже после мержа. Диапазоны тоже
47
+ не проверяются: один `^` в `package.json` пройдёт все проверки дерева.
48
+
49
+ ## Паттерны
50
+
51
+ - `dependencies-upgrade` — подъём версий, выбор верхней границы, разбор последствий обновления.
52
+
53
+ ## Ловушки
54
+
55
+ - **Диапазон пропускает версию, которой в реестре нет.** В объявление кита записали `^0.2.0`,
56
+ а снимок остался на прежней версии: объявление выглядело верным, но всё собиралось на 0.1.0,
57
+ где нужного размера у компонента нет вовсе, и главная ветка перестала собираться. Нашли это
58
+ через две недели — когда понадобилось дерево для сравнения, а сравнивать оказалось не с чем.
59
+ - **Верхнюю границу задают peer-диапазоны, а не последний номер в реестре.** TypeScript
60
+ остался на 6.0.3 при вышедшей седьмой версии, потому что Angular объявляет `>=6.0 <6.1`.
61
+ `pnpm install` такую ошибку не ловит: `autoInstallPeers` молча доставляет недостающее.
62
+ - **После обновления плагина линтера появляются правила, которых вчера не было.** eslint 10
63
+ добавил `no-useless-assignment`, `eslint-plugin-playwright` 2 — сразу три правила. Замечания
64
+ приходят на файлы, которых правка не касалась, и выглядят её последствиями.
65
+ - Прогон тестов после обновления — правило `testing`: после смены версии Playwright браузер
66
+ надо поставить заново, и это не регрессия.
@@ -0,0 +1,103 @@
1
+ ---
2
+ name: doc-style
3
+ kind: rule
4
+ law: project-documentation
5
+ description: Правило под «Закон о документации проекта». Брать при правке любого .md, включая спеки, а также комментариев в коде, тел коммитов и описаний PR. Называет проверку путей, пары «правка и её документ» и то, что в этом дереве не проверяет ничто. Готовые формулировки — в паттерне doc-style-write.
6
+ ---
7
+
8
+ # Тексты проекта — как это устроено здесь
9
+
10
+ Правило под закон `docs/constitution/project-documentation.md`. Закон говорит, что должно
11
+ быть верно про тексты; здесь — чем это проверяется в этом дереве и что остаётся за автором.
12
+ Устройство спеков и слоёв документации — правило `spec-driven` под тем же законом; здесь
13
+ только формулировки.
14
+
15
+ ## Как это называется здесь
16
+
17
+ | В законе | Здесь |
18
+ | ---------------------------------- | ------------------------------------------------------------------------------------------------------------------ |
19
+ | документ | любой `.md` вне `docs/archive/`, плюс комментарии в коде, тела коммитов и описания PR |
20
+ | путь, названный в документе | строка с расширением в обратных кавычках — её и ищет проверка |
21
+ | правка, которую документ описывает | пара из `docs-guard`: правило и его зеркало, `.proto` и спек, хук и его сценарии, переезд файла и README обеих либ |
22
+ | описание прошлого | `docs/archive/` — из проверки путей выведено целиком |
23
+
24
+ ## Где это лежит
25
+
26
+ В этом дереве — таблица в `implementation.md` рядом. Пути живут там, а не здесь: правило
27
+ переносится между репозиториями, раскладка — нет, и путь, названный в правиле, врёт в первом
28
+ же дереве, которое держит код иначе.
29
+
30
+ ## Как закон применяется здесь
31
+
32
+ - **Путь, названный в документе, существует.** Ссылка на переехавший файл читается как
33
+ действующее указание, и следующий читатель заводит снятое заново. Судятся документы,
34
+ которые едут в репозиторий: личный черновик, закрытый `.gitignore` или
35
+ `.git/info/exclude`, проверка не читает — мёртвая ссылка в нём держала гейт пуша, хотя ни
36
+ в одну ветку этот файл не попадёт.
37
+ - **Описание прошлого из проверки путей выведено целиком.** Архив по устройству называет
38
+ файлы, которых уже нет, и правкой это не лечится.
39
+ - **Документ едет в том же коммите, что и правка, которую он описывает.** Обход — строка
40
+ `Docs-skip: <причина>` в теле коммита; пустая причина не принимается.
41
+
42
+ ## Чего из закона здесь нет
43
+
44
+ Ни одна из формулировочных договорённостей не проверяется: одна фраза на правило, простые
45
+ слова, отсутствие утверждений о будущем, свежесть числа в тексте. Две последние закон прямо
46
+ оставляет автору: открытый вопрос пишется теми же словами, что и обещание, а дата и номер —
47
+ такие же числа, как то, что пересчитывают.
48
+
49
+ На комментарии в коде правило распространяется, но гейтом не требуется: он зовёт его только
50
+ на `.md`. Расширять требование на каждый `.ts` значило бы шуметь на каждой правке, поэтому
51
+ здесь оно держится памятью автора — и цена этого видна: слова из левой колонки словаря живут
52
+ в комментариях хуков и `tools/*.mjs` десятками строк, включая текст отказа, который гард
53
+ печатает агенту.
54
+
55
+ ## Паттерны
56
+
57
+ - `doc-style-write` — как формулировать: примеры «так» и «не так», правила для комментариев.
58
+ - `doc-style-sweep` — разбор документа, накопившего список работ, на действующее и закрытое.
59
+
60
+ ## Ловушки
61
+
62
+ - **Оставшаяся работа не записывается в документ, а заводится задачей.** `docs/BACKLOG.md`
63
+ держит только то, что задачей не бывает: договорённости и решения, которые решено не
64
+ править. Признак — утверждение остаётся, если править его никто не собирается. «Сделать
65
+ потом» в плане, README или спеке — второй список работ: он расходится с бордой молча, а
66
+ разбирать его потом дороже, чем завести задачу сразу. Из 1411 строк документа действующими
67
+ оказались 71, и на разбор остальных ушла отдельная задача. Как разбирать накопившееся —
68
+ паттерн `doc-style-sweep`.
69
+ - **Словарь действует и на разговор с владельцем, не только на файлы.** Он приходит в контекст
70
+ на запуске сессии, поэтому «не читал» основанием не бывает. Слово из левой колонки «Так не
71
+ пишем» всплывало именно в ответах: в дереве его уже вычистили, а в отчёте о сделанном оно
72
+ оставалось, и владелец читал ровно то слово, от которого отказались.
73
+ - **Термин берётся из `docs/GLOSSARY.md`, а не придумывается на месте.** Слова, которого там
74
+ нет, у читателя нет тоже: «журнал приложения» простоял в спеке почты, пока владелец не
75
+ спросил, что это, — оказалось, логи бэкенда, а слово «журнал» здесь уже занято журналом
76
+ событий. Новое слово либо заводится в словаре вместе с правкой, либо заменяется тем, что
77
+ уже есть.
78
+ - **Проход по словарю глазами слово не находит.** «Формулировки приведены к словарю» означает
79
+ ровно те строки, которые в тот момент читали: «спека» пережила такой проход и осталась в
80
+ соседней строке того же файла. Слово из левой колонки таблицы «Так не пишем» вычищается
81
+ грепом по всему дереву, а не вычиткой. Форма задаётся точно: «спек» — документ — склоняется
82
+ в «спека» и «спеки» тоже, и совпадений по корню законных больше, чем нарушений; ищутся
83
+ сочетания («спеки на … нет», «спека проверяет»), а не корень.
84
+ - **Снятое имя вычищается одним грепом по всему дереву:** правила, их зеркала в скилах,
85
+ документы и комментарии. Описание того, чего в коде уже нет, читается как действующее
86
+ указание.
87
+ - **Число в тексте пересчитывается командой в том же коммите, где пишется.** Оно стареет
88
+ внутри одной ветки: «шестнадцать пар» стало неправдой через два коммита после того, как
89
+ было написано, и нашёл это владелец, а не проверка. Число, которое придётся пересчитывать
90
+ при каждой правке, лучше не писать вовсе. Число, полученное разбором текста, сверяется на
91
+ выборке руками до того, как его называют: разбор, не знающий второй формы записи, ошибается
92
+ молча — «51 пункт без задачи» оказался шестью, потому что номер стоял и отдельной строкой, и
93
+ в заголовке подраздела.
94
+ - **Сделанность читается по дереву, а не по тексту, который о ней написан.** Это верно в обе
95
+ стороны: строка про README обеих либ была вычеркнута как сделанная, а README остался с
96
+ прежним числом импортёров; задача, названная владельцу несделанной, оказалась наполовину
97
+ закрытой и покрытой сценариями хука. Пункт плана и тело задачи описывают день, когда их
98
+ написали, и с тех пор не менялись.
99
+ - **Комментарий в файле — такое же утверждение, как строка в документе.** Обоснование «строки
100
+ идут во всю ширину панели, иначе подсветка обрывается» было выдумано, прожило три сессии и
101
+ каждый раз читалось как основание вёрстку не трогать.
102
+ - **Чужие проекты не упоминаются нигде** — ни имени репозитория, ни «портировано из», ни
103
+ ссылок на его файлы. Описывается то, что код делает здесь, в терминах этого проекта.
@@ -0,0 +1,78 @@
1
+ ---
2
+ name: entity-conventions
3
+ kind: rule
4
+ law: entity-editing
5
+ description: Правило под «Закон о правке сущности». Брать при правке любого стора админки (*.store.ts) и любой панели создания или правки записи. Называет общую основу асайда, общую основу списочного стора, устройство панели и то, что асайд открывается маршрутом в аутлете ro. Готовый код — в паттернах entity-aside и entity-store.
6
+ ---
7
+
8
+ # Правка сущности — как это устроено здесь
9
+
10
+ Правило под закон `docs/constitution/entity-editing.md`. Закон говорит, как ведёт себя
11
+ приложение при создании и правке записи; здесь — из чего это собрано в этом дереве и как
12
+ выглядит. Про вид говорит правило: закон о нём молчит намеренно.
13
+
14
+ ## Как это называется здесь
15
+
16
+ | В законе | Здесь |
17
+ | ----------------------------- | ------------------------------------------------------------------------ |
18
+ | панель правки записи | асайд; открывается маршрутом с `outlet: 'ro'` |
19
+ | общая основа асайда | `RtRouteAsideComponent<T>` — директива без селектора |
20
+ | общая основа списочного стора | `BaseListStoreService` |
21
+ | запись | `entity`, `entityId`, `isCreateMode` — имена от сущности, а не от домена |
22
+ | обвязка записи | `runMutation` в панели, `mutate` в сторе |
23
+ | нетронутость формы | `pristineSignal(control)` |
24
+
25
+ ## Где это лежит
26
+
27
+ В этом дереве — таблица в `implementation.md` рядом. Пути живут там, а не здесь: правило
28
+ переносится между репозиториями, раскладка — нет, и путь, названный в правиле, врёт в первом
29
+ же дереве, которое держит код иначе.
30
+
31
+ ## Как закон применяется здесь
32
+
33
+ - **Асайд открывается маршрутом в аутлете `ro`, а не вызовом сервиса.** Программного открытия
34
+ через `RtAsideService.open()` в админке нет: так панель переживает перезагрузку, передаётся
35
+ ссылкой и попадает в историю браузера.
36
+ - **Запись идёт через `runMutation`, и панель отдаёт основе поток мутации и ключи.** Занятость,
37
+ гашение прежней ошибки, тост об успехе и закрытие держит основа.
38
+ - **Поток мутации обязан отдать значение или ошибку.** Пустой поток гасит панель навсегда:
39
+ она ждёт того или другого, а закрыть её владелец не может, пока идёт запись.
40
+ - **Мутация завершается перечитанным списком, а не отправленным запросом.** Список,
41
+ перечитанный после закрытия, показал бы прежнее значение.
42
+ - **Стор отвечает потоком: успех — значение, отказ — ошибка потока.** Булева ответа у сторов
43
+ админки не осталось: он терял и записанную запись, и причину отказа.
44
+ - **Имена берутся от сущности, а не от домена.** `save`, `remove`, `load` — не
45
+ `createBooking`, `loadBookings`: имя домена уже в имени стора.
46
+ - **Гард несохранённых правок ставит сама панель и на все четыре пути закрытия.** Проверять
47
+ один путь бессмысленно — Esc обойдёт то, что ловит кнопка. Четыре пути — кнопка в шапке,
48
+ кнопка в футере, нажатие мимо панели и Esc.
49
+ - **Уход из панели идёт через `openRelated`, а не своим `router.navigate`.** Абсолютные
50
+ команды меняют только первичную ветку, аутлет `ro` остаётся в адресе, и роутер отклоняет
51
+ навигацию молча.
52
+ - **Запись читается по идентификатору из адреса полной моделью, а не берётся из списка.**
53
+ Список отдаёт короткую.
54
+
55
+ ## Чего из закона здесь нет
56
+
57
+ Чтение записи отдельной процедурой заведено не везде — долг `Q-M-3`. Файл стора называется
58
+ `<сущность>.store.ts`; имя `<сущность>-store.service.ts` выводит его и из правила линтера, и
59
+ из гейта скилов, и общие сторы заявок и объектов правятся без правил сущностей вовсе.
60
+
61
+ ## Паттерны
62
+
63
+ - `entity-aside` — собрать панель правки: маршрут, основа, `runMutation`, шапка и футер, гард.
64
+ - `entity-store` — собрать стор сущности: наследник общей основы, `mutate`, ключи отказа.
65
+
66
+ ## Ловушки
67
+
68
+ - Действие со своей занятостью через основу не идёт: опрос подписки держит свой `pollingId`,
69
+ потому что панель на минуту опроса не гасится.
70
+ - Хвост с `EMPTY`, приклеенный к мутации, гасится `defaultIfEmpty`: иначе отказ приклеенного
71
+ потока превращает удачную запись в вечный спиннер.
72
+ - `routerLink` в панели не годится: директива навигирует сама, `preventDefault` её не
73
+ останавливает, и вопрос о несохранённых правках она обходит.
74
+ - `viewChild` на поле с `#` Angular не принимает — поле объявляется `protected`.
75
+ - Скелетоны полей идут по `resolving()`, не по `busy()`: `busy` включает и запись, и чтение.
76
+ - Панель, которая после успеха остаётся открытой, сбрасывает нетронутость сама.
77
+ - Ветка, куда забыли подмешать константу ro-маршрута, отличается только тем, что кнопка в
78
+ шапке на ней ничего не открывает: сборка, линт и маршруты остальных веток при этом целы.
@@ -0,0 +1,70 @@
1
+ ---
2
+ name: entity-models
3
+ kind: rule
4
+ law: entity-models
5
+ description: Правило под «Закон о моделях сущностей». Брать при объявлении или правке модели записи и её маппера в админке, при правке моделей и мапперов в libs/common/util и при правке .proto. Называет неймспейс I<Сущность>, уровни модели и что берётся из @rt-tools/utils. Готовый код — в паттерне entity-models-new.
6
+ ---
7
+
8
+ # Модели сущностей — как это устроено здесь
9
+
10
+ Правило под закон `docs/constitution/entity-models.md`. Закон говорит, сколько данных
11
+ приложение запрашивает на каждом экране; здесь — как эта модель объявляется в этом дереве.
12
+
13
+ ## Как это называется здесь
14
+
15
+ | В законе | Здесь |
16
+ | ------------------------ | -------------------------------------------------------------------- |
17
+ | сторона контракта | `Api` — псевдоним сгенерированного типа из `@<область>/common/proto` |
18
+ | то, чем пользуется экран | `State`, все поля `readonly` |
19
+ | то, что уходит на запись | `Draft` |
20
+ | короткий уровень | вложенный неймспейс `Short` с собственными `Api` и `State` |
21
+ | перевод | маппер-наследник `BaseMapper`, свой на каждый уровень |
22
+
23
+ Все три стороны лежат в одном неймспейсе `I<Сущность>` и спутать их в импортах нечем.
24
+
25
+ ## Где это лежит
26
+
27
+ В этом дереве — таблица в `implementation.md` рядом. Пути живут там, а не здесь: правило
28
+ переносится между репозиториями, раскладка — нет, и путь, названный в правиле, врёт в первом
29
+ же дереве, которое держит код иначе.
30
+
31
+ ## Как закон применяется здесь
32
+
33
+ - **У сущности две стороны, и обе лежат в неймспейсе `I<Сущность>`.** `Api` повторяет
34
+ контракт, `State` нормализован и от смены контракта не зависит.
35
+ - **Сторона контракта руками не пишется — она объявляется псевдонимом.** Своя копия разойдётся
36
+ с контрактом молча, а компилируется из них только одна.
37
+ - **Между сторонами стоит маппер-наследник `BaseMapper`, и экраны читают только `State`.** Тип
38
+ из контракта в шаблон не попадает.
39
+ - **Пустое выражается пустой строкой или нулём, а не отсутствием поля.** Необязательных
40
+ скаляров в контракте нет, поэтому `null` и `undefined` в `State` не заводятся; смысл нуля
41
+ объясняется комментарием рядом с полем.
42
+ - **Приведение идёт через `this.typeCast`, а не через `??`.** Контракт отдаёт значения по
43
+ умолчанию, а не пустоту, и проверка на `undefined` здесь не ловит ничего.
44
+ - **Типы страницы, порядка и отбора берутся из `@rt-tools/utils`.** Второго набора этих типов
45
+ в дереве нет: `rt-pagination` принимает `IPageModel` оттуда же.
46
+
47
+ ## Чего из закона здесь нет
48
+
49
+ Уровней нет ни у одной сущности, и контракт короткого сообщения не отдаёт — долги `Q-M-1` и
50
+ `Q-M-2`. Новая сущность заводится сразу с уровнями.
51
+
52
+ Правка `.proto` не проверяется ничем: `buf lint` и `buf breaking` настроены, но не входят ни в
53
+ `check:all`, ни в CI.
54
+
55
+ ## Паттерны
56
+
57
+ - `entity-models-new` — объявить модель и маппер: неймспейс, уровни, `typeCast`, перегенерация
58
+ контракта.
59
+
60
+ ## Ловушки
61
+
62
+ - `getAsType` умолчания не принимает: значение вне набора он пишет в консоль и возвращает
63
+ строкой `'unknown'`. Строковое поле с конечным набором значений сверяется с набором явно.
64
+ - `as Type` в маппере запрещено — правило `typescript-conventions`.
65
+ - Поле-сообщение необязательно всегда; обязательное поле модели им не заполнить без запасного
66
+ значения. Обратно, в запрос, `readonly`-массив не проходит: init-тип требует изменяемый.
67
+ - Модель админки и модель сайта — разные. Общий тип на два приложения означал бы, что сайт
68
+ тянет поля админки.
69
+ - Снятое поле контракта помечается `reserved` с номером и именем: номер, отданный новому полю,
70
+ ломает уже выкаченного клиента молча.