@rt-tools/agent-kit 0.2.0 → 0.3.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 (95) hide show
  1. package/README.md +59 -6
  2. package/assets/hooks/browser-device-id.sh +20 -0
  3. package/assets/hooks/browser-guard-device-id.sh +27 -0
  4. package/assets/hooks/browser-guard-no-listing.sh +17 -0
  5. package/assets/hooks/browser-guard-no-other-drivers.sh +78 -0
  6. package/assets/hooks/browser-guard-require-select.sh +53 -0
  7. package/assets/hooks/commit-msg.sh +26 -0
  8. package/assets/hooks/constitution-index.sh +42 -0
  9. package/assets/hooks/dev-server-guard.sh +113 -0
  10. package/assets/hooks/docs-guard.sh +96 -0
  11. package/assets/hooks/git-guard-delivery.sh +110 -0
  12. package/assets/hooks/git-guard-main.sh +72 -0
  13. package/assets/hooks/git-guard-push-tests.sh +73 -0
  14. package/assets/hooks/lint-after-edit.sh +94 -0
  15. package/assets/hooks/qa-dataid-guard.sh +81 -0
  16. package/assets/hooks/reuse-first-guard.sh +83 -0
  17. package/assets/hooks/skill-gate-rearm.sh +22 -0
  18. package/assets/hooks/skill-gate.sh +68 -0
  19. package/assets/hooks/skill-loaded.sh +20 -0
  20. package/assets/hooks/sql-guard.sh +129 -0
  21. package/assets/patterns/angular-patterns-state.md +94 -0
  22. package/assets/patterns/api-layer-pair.md +78 -0
  23. package/assets/patterns/browser-verification-measure.md +83 -0
  24. package/assets/patterns/browser-verification-stand.md +79 -0
  25. package/assets/patterns/component-structure-new.md +98 -0
  26. package/assets/patterns/doc-style-sweep.md +100 -0
  27. package/assets/patterns/doc-style-write.md +106 -0
  28. package/assets/patterns/git-workflow-commit.md +175 -0
  29. package/assets/patterns/git-workflow-merge.md +82 -0
  30. package/assets/patterns/git-workflow-migration.md +58 -0
  31. package/assets/patterns/git-workflow-restart.md +49 -0
  32. package/assets/patterns/lib-layers-move.md +77 -0
  33. package/assets/patterns/lib-layers-new.md +70 -0
  34. package/assets/patterns/permissions-procedure.md +69 -0
  35. package/assets/patterns/platform-access-di.md +70 -0
  36. package/assets/patterns/reuse-first-extend.md +73 -0
  37. package/assets/patterns/seo-page.md +92 -0
  38. package/assets/patterns/seo-verify.md +64 -0
  39. package/assets/patterns/shared-code-new.md +80 -0
  40. package/assets/patterns/spec-driven-domain.md +100 -0
  41. package/assets/patterns/spec-driven-rule.md +112 -0
  42. package/assets/patterns/styling-bem-component.md +77 -0
  43. package/assets/patterns/styling-bem-layout.md +67 -0
  44. package/assets/patterns/testing-e2e.md +90 -0
  45. package/assets/patterns/testing-unit.md +93 -0
  46. package/assets/patterns/translations-key.md +51 -0
  47. package/assets/patterns/ts-procedure.md +66 -0
  48. package/assets/rules/angular-patterns.md +52 -0
  49. package/assets/rules/api-layer.md +53 -0
  50. package/assets/rules/browser-verification.md +69 -0
  51. package/assets/rules/component-structure.md +48 -0
  52. package/assets/rules/doc-style.md +61 -0
  53. package/assets/rules/git-workflow.md +106 -0
  54. package/assets/rules/lib-layers.md +54 -0
  55. package/assets/rules/permissions.md +52 -0
  56. package/assets/rules/platform-access.md +49 -0
  57. package/assets/rules/reuse-first.md +69 -0
  58. package/assets/rules/seo.md +50 -0
  59. package/assets/rules/shared-code.md +45 -0
  60. package/assets/rules/spec-driven.md +89 -0
  61. package/assets/rules/styling-bem.md +59 -0
  62. package/assets/rules/testing.md +69 -0
  63. package/assets/rules/translations.md +52 -0
  64. package/assets/rules/typescript-conventions.md +46 -0
  65. package/assets/templates/gate-map.sh +37 -0
  66. package/assets/templates/implementation.md +38 -0
  67. package/assets/templates/pattern.md +4 -0
  68. package/assets/templates/project.sh +41 -0
  69. package/assets/templates/rule.md +12 -23
  70. package/lib/assets.d.ts +8 -0
  71. package/lib/assets.d.ts.map +1 -1
  72. package/lib/assets.js +12 -1
  73. package/lib/assets.js.map +1 -1
  74. package/lib/commands.d.ts.map +1 -1
  75. package/lib/commands.js +21 -2
  76. package/lib/commands.js.map +1 -1
  77. package/lib/companion.d.ts +53 -0
  78. package/lib/companion.d.ts.map +1 -0
  79. package/lib/companion.js +33 -0
  80. package/lib/companion.js.map +1 -0
  81. package/lib/config.d.ts +24 -1
  82. package/lib/config.d.ts.map +1 -1
  83. package/lib/config.js +33 -1
  84. package/lib/config.js.map +1 -1
  85. package/lib/stamp.d.ts +2 -5
  86. package/lib/stamp.d.ts.map +1 -1
  87. package/lib/stamp.js +25 -10
  88. package/lib/stamp.js.map +1 -1
  89. package/lib/sync.d.ts +3 -0
  90. package/lib/sync.d.ts.map +1 -1
  91. package/lib/sync.js +20 -1
  92. package/lib/sync.js.map +1 -1
  93. package/package.json +1 -1
  94. package/rt-tools-agent-kit-0.3.0.tgz +0 -0
  95. package/rt-tools-agent-kit-0.2.0.tgz +0 -0
@@ -0,0 +1,100 @@
1
+ ---
2
+ name: spec-driven-domain
3
+ kind: pattern
4
+ rule: spec-driven
5
+ description: Паттерн правила spec-driven. Брать при заведении или правке спека домена — раскладка файлов, обязательные разделы, форма правила и его привязки, форма сценария, порядок работы от спека к коду. Не брать для заведения закона, правила или паттерна — это паттерн spec-driven-rule.
6
+ ---
7
+
8
+ # Спек домена
9
+
10
+ Паттерн правила `spec-driven`. Что при этом должно быть верно — закон
11
+ `{{lawsDir}}/project-documentation.md`.
12
+
13
+ ## Когда брать
14
+
15
+ - Заводится новый домен или фича, которой ещё нет.
16
+ - Правится контракт — спеки задетых доменов едут той же веткой.
17
+ - Замечено расхождение спека с кодом.
18
+
19
+ ## Раскладка
20
+
21
+ ```
22
+ <спеки>/<домен>/
23
+ spec.md — как домен работает
24
+ implementation.md — таблица «правило → файл:символ»
25
+ scenarios.md — сценарии SC-<ПРЕФИКС>-<НОМЕР>
26
+ proposed/<фича>/ — только то, чего ещё нет
27
+ ```
28
+
29
+ ## Обязательные разделы
30
+
31
+ Набор разделов задан заранее и сверяется дословно: зачем, терминология, правила, что не входит,
32
+ контракт с кодами отказов, данные, экраны и состояния, сквозные требования, решения. «Не
33
+ применимо» — законный ответ, отсутствие раздела — нет: сквозные требования вспоминаются
34
+ постфактум именно тогда, когда для них не заведено места.
35
+
36
+ Шапка несёт статус, префикс сценариев, зависимости от других доменов, строку с законами,
37
+ которые домен применяет, и строку с корнями либ, чьи обработчики он обслуживает.
38
+
39
+ ```markdown
40
+ **Зависимости:** `<домен>` (что берётся), `<домен>` (что берётся)
41
+ **Законы:** `access`, `locales`, `shared-code`
42
+ **Обработчики:** `<корень либы>`
43
+ ```
44
+
45
+ Закон, названный где-нибудь в тексте спека, обязан стоять в этой строке: связь сверяется в обе
46
+ стороны.
47
+
48
+ ## Правило и его привязка
49
+
50
+ Правило формулируется так, чтобы его можно было нарушить, и начинается с жирной фразы:
51
+
52
+ ```markdown
53
+ - **Применяется одна максимальная скидка.** Сложение скидок даёт цену ниже себестоимости.
54
+ ```
55
+
56
+ Привязка живёт в `implementation.md` рядом, ключ связи — сам текст правила:
57
+
58
+ ```markdown
59
+ | Правило | Где исполняется |
60
+ | ------------------------------------- | ----------------- |
61
+ | Применяется одна максимальная скидка. | `<путь>:<символ>` |
62
+ ```
63
+
64
+ Правило, которому места в коде не нашлось, — намерение: ему место в открытых вопросах, а не
65
+ формальный якорь.
66
+
67
+ ## Сценарий
68
+
69
+ ```markdown
70
+ ### SC-<ПРЕФИКС>-19 — подтверждение на занятые даты отбивается
71
+
72
+ Дано у записи есть подтверждённая соседняя на пересекающиеся даты
73
+ Когда владелец подтверждает заявку
74
+ Тогда отказ подаётся владельцу как занятые даты, а не как ошибка хранилища
75
+ ```
76
+
77
+ Идентификатор ставится в начало заголовка теста, через тире. Сценарий без теста помечается
78
+ отметкой с причиной, сценарий с неполным тестом — отметкой о частичном покрытии.
79
+
80
+ ## Порядок работы
81
+
82
+ 1. Задача заводится сценариями: что станет верно, когда работа закончится.
83
+ 2. Спек домена правится **до** кода.
84
+ 3. Код пишется под сценарии, тесты называются их идентификаторами.
85
+ 4. Проверка спеков — до пуша.
86
+ 5. Приёмка идёт по сценариям, а не по пересказу правки.
87
+
88
+ ## Частые промахи
89
+
90
+ - **Список шагов в спеке:** шаги — артефакт сессии, им место в ветке или в описании PR.
91
+ - **Скопированная из контракта таблица полей:** источник один, а компилируется из двух только
92
+ одна.
93
+ - **Колонки и индексы в спеке:** они в схеме хранилища, а в спеке остаётся правило, которое
94
+ ограничение выражает.
95
+ - **Место, где правило исполняется, внутри текста правила:** оно меняется при первом же
96
+ переносе, и для него заведён отдельный файл.
97
+ - **Отметка о непокрытом при существующем тесте** — отказ: долг закрыли, а отметку не сняли.
98
+ - **Закон, названный в тексте, но забытый в шапке:** по закону тогда не узнать, какие домены на
99
+ нём стоят.
100
+ - **Правка контракта без спеков задетых доменов** — гард документов отбивает такой коммит.
@@ -0,0 +1,112 @@
1
+ ---
2
+ name: spec-driven-rule
3
+ kind: pattern
4
+ rule: spec-driven
5
+ description: Паттерн правила spec-driven. Брать при заведении или правке закона, правила или паттерна — готовые шапки, набор разделов каждого слоя, таблица привязки, признак того, что правило пора делить. Не брать для спека домена — это паттерн spec-driven-domain.
6
+ ---
7
+
8
+ # Закон, правило и паттерн
9
+
10
+ Паттерн правила `spec-driven`. Что при этом должно быть верно — закон
11
+ `{{lawsDir}}/project-documentation.md`.
12
+
13
+ ## Когда брать
14
+
15
+ - Правило приходится повторять ещё в двух местах — пора заводить закон.
16
+ - Заводится правило под уже существующий закон.
17
+ - Готовый код в правиле разросся — пора выносить паттерн.
18
+
19
+ ## Закон
20
+
21
+ `{{lawsDir}}/<закон>.md`. О проекте не знает ничего: ни путей, ни имён файлов, ни привязок.
22
+ Признак закона: попытка положить статью в один спек заставляет повторить то же самое ещё в
23
+ двух.
24
+
25
+ Разделы: вводный абзац без заголовка, затем `## Статьи`. Обязательны статьи — их и сверяет
26
+ проверка.
27
+
28
+ Ни истории правок, ни доводов о том, почему когда-то выбрали так, в законе нет. Историю держит
29
+ система контроля версий, а довод с отвергнутой альтернативой — свойство работы, а не продукта:
30
+ ему место в «Ловушках» правила под этим законом, где и путям к файлам можно. Утверждение,
31
+ которое нельзя написать как «верно всегда», статьёй не становится вовсе.
32
+
33
+ ```markdown
34
+ # Поставка
35
+
36
+ Как правка доезжает до работающего приложения. …
37
+
38
+ ## Статьи
39
+
40
+ - **Выкатывается образ того коммита, который выкатывают.** Умолчание «последний» отстаёт от
41
+ главной ветки, и приложение молча возвращается к прежней версии, продолжая отвечать.
42
+ ```
43
+
44
+ Поведение кода законом не является: «стор отвечает булевым», «метод называется так-то» — этого
45
+ не видит ни гость, ни владелец. Граница простая: закон описывает то, что видно снаружи
46
+ приложения.
47
+
48
+ ## Правило
49
+
50
+ `{{rulesDir}}/<правило>/SKILL.md`. Говорит, каким приёмом закон исполняется; на один закон их
51
+ бывает несколько.
52
+
53
+ ```markdown
54
+ ---
55
+ name: git-workflow
56
+ kind: rule
57
+ law: delivery
58
+ description: Правило под закон «Поставка». Брать на … Готовый код — в паттернах … Чем это названо здесь — в implementation.md рядом.
59
+ ---
60
+ ```
61
+
62
+ Разделы: `## Когда берётся` · `## Что здесь действует` · `## Паттерны` · `## Ловушки`.
63
+
64
+ Имён этого дерева в правиле нет — оно переносимо ровно поэтому. Как что называется здесь и где
65
+ лежит, пишется рядом, в `implementation.md`, и оттуда же идёт привязка статей к коду:
66
+
67
+ ```markdown
68
+ | Статья | Где исполняется |
69
+ | --------------------------------------------------------------- | ----------------------------- |
70
+ | Образы выкатываются по хешу коммита, а не по метке «последний». | `<состав прода>:<переменная>` |
71
+ ```
72
+
73
+ Каждый пункт раздела `## Что здесь действует` начинается жирной статьёй, и у каждой статьи есть
74
+ строка в компаньоне. Утверждение, которому места в коде не нашлось, в этот раздел не ставится:
75
+ оно уходит прозой в «Ловушки» или статьёй в закон.
76
+
77
+ ## Паттерн
78
+
79
+ `{{rulesDir}}/<правило>-<что>/SKILL.md`. Минимум один на правило.
80
+
81
+ ```markdown
82
+ ---
83
+ name: git-workflow-commit
84
+ kind: pattern
85
+ rule: git-workflow
86
+ description: Паттерн правила git-workflow. Брать … Не брать для … — это паттерн …
87
+ ---
88
+ ```
89
+
90
+ Разделы: `## Когда брать` · готовый код · `## Частые промахи`. Компаньона у паттерна нет:
91
+ сверять готовый код с ним самим нечем.
92
+
93
+ ## Порядок
94
+
95
+ 1. Статья пишется в закон — без путей и имён файлов.
96
+ 2. Правило объявляет закон в шапке и называет приём, которым статья исполняется.
97
+ 3. Имена этого дерева и привязка каждой статьи уходят в `implementation.md` рядом; якорь
98
+ проверяется открытием файла, а не памятью.
99
+ 4. Готовый код уезжает в паттерн, а правило на него ссылается.
100
+ 5. Проверка спеков — до пуша.
101
+
102
+ ## Частые промахи
103
+
104
+ - Закон назвал файл проекта. Путям место в правиле, а точнее — в его компаньоне.
105
+ - Компаньон лежит не рядом с правилом, а рядом с законом: он привязывает закон к этому проекту,
106
+ и зелёная проверка это утвердит, потому что структура совпадёт с тем, чего проверка сама и
107
+ ждёт.
108
+ - Якорь ведёт в мёртвый символ: объявлен и больше нигде не встречается.
109
+ - Статью переформулировали, а строку в привязке не тронули: связь идёт по тексту, и проверка
110
+ перестанет её находить.
111
+ - В описании не сказано, когда паттерн **не** брать, — соседний паттерн того же правила
112
+ становится неотличимым.
@@ -0,0 +1,77 @@
1
+ ---
2
+ name: styling-bem-component
3
+ kind: pattern
4
+ rule: styling-bem
5
+ description: Паттерн правила styling-bem. Брать при правке стилей компонента источника вида — готовый хост, модификаторы, значения только токенами, перебивание умолчаний источника вида. Не брать для раскладки экрана — это паттерн styling-bem-layout.
6
+ ---
7
+
8
+ # Стили компонента
9
+
10
+ Паттерн правила `styling-bem`. Что при этом должно быть верно — закон
11
+ `{{lawsDir}}/frontend-application.md`.
12
+
13
+ ## Когда брать
14
+
15
+ - Правится компонент источника вида.
16
+ - Правится оформление отдельного компонента.
17
+ - Нужно перебить умолчание источника вида в своём компоненте.
18
+
19
+ ## Хост — сам блок
20
+
21
+ Хост несёт класс блока, раскладка на хосте, элементы вложены внутрь:
22
+
23
+ ```scss
24
+ :host {
25
+ display: inline-flex;
26
+
27
+ .<блок > {
28
+ display: inline-flex;
29
+ gap: var(--<токен отступа>);
30
+ align-items: center;
31
+
32
+ &--<модификатор > {
33
+ border-radius: var(--<токен скругления>);
34
+ }
35
+ }
36
+ }
37
+ ```
38
+
39
+ Модификатор — вложенным селектором или привязкой класса на хосте. У экрана вне источника вида
40
+ такого раздела нет: раскладка на хосте — это общий слой приложения, а не файл экрана.
41
+
42
+ ## Значения — только токенами
43
+
44
+ ```scss
45
+ ✗ color: #fff;
46
+ ✗ box-shadow: 0 1px 2px rgb(0 0 0 / 12%);
47
+ ✓ color: var(--<токен цвета поверхности>);
48
+ ✓ box-shadow: var(--<токен тени>);
49
+ ```
50
+
51
+ Составное значение берётся готовым токеном целиком, а не собирается из частей. Переменные
52
+ препроцессора для значений оформления не используются вовсе: они разрешаются на сборке и темой
53
+ не переключаются.
54
+
55
+ ## Перебить умолчание источника вида
56
+
57
+ Стили компонента без инкапсуляции весят столько же, сколько умолчания источника вида: у обоих
58
+ селекторов по одному классу, и исход решает порядок подключения. Переопределение пишется
59
+ потомком блока — так вес растёт на единицу, и порядок перестаёт что-либо решать:
60
+
61
+ ```scss
62
+ & &__item {
63
+ color: var(--<токен приглушённого текста>);
64
+ }
65
+ ```
66
+
67
+ ## Частые промахи
68
+
69
+ - **Комментарий-выключатель линтера стилей:** селекторы объединяются вложенностью, а не
70
+ отключением правила.
71
+ - **Приоритетное объявление:** запрещено, и оформление, заданное на месте, перебить им всё
72
+ равно не выйдет.
73
+ - **Новые объявления при переносе стилей:** переносится существующее, новое появляется, только
74
+ когда задача — фича.
75
+ - **Замечание линтера, лежавшее в файле раньше, оставлено:** правятся все, и новые, и старые.
76
+ - **Своё наследование гарнитуры в компоненте:** оно включено глобально, и местное объявление
77
+ только расходится с ним.
@@ -0,0 +1,67 @@
1
+ ---
2
+ name: styling-bem-layout
3
+ kind: pattern
4
+ rule: styling-bem
5
+ description: Паттерн правила styling-bem. Брать при сборке экрана раздела, формы, панели или окна — блоки общего слоя, разметка директивами блока и элемента, свой элемент в чужом поддереве, признак того, что правка идёт не туда. Не брать для стилей компонента источника вида — это паттерн styling-bem-component.
6
+ ---
7
+
8
+ # Экран на общем слое раскладки
9
+
10
+ Паттерн правила `styling-bem`. Что при этом должно быть верно — закон
11
+ `{{lawsDir}}/frontend-application.md`.
12
+
13
+ ## Когда брать
14
+
15
+ - Собирается экран раздела, форма, содержимое панели или модального окна.
16
+ - В файле стилей экрана появляется раскладка.
17
+
18
+ ## Файл стилей экрана по умолчанию пустой
19
+
20
+ Раскладка объявлена один раз в общем слое приложения, а экран её только применяет. Блоков этого
21
+ слоя немного, и каждый отвечает за свой род экрана: экран раздела с заголовком и прокруткой,
22
+ форма с разделами и строками полей, содержимое панели правки, содержимое модального окна.
23
+
24
+ Блок вешается на хост, элементы получают классы от директивы блока в корне шаблона:
25
+
26
+ ```
27
+ host: { class: '<блок экрана>' },
28
+ ```
29
+
30
+ ```html
31
+ <ng-container rtBlock="<блок экрана>">
32
+ <header rtElem="header">
33
+ <div rtElem="header-main">
34
+ <h1 rtElem="title">{{ '<ключ заголовка>' | <перевод> }}</h1>
35
+ </div>
36
+ </header>
37
+ </ng-container>
38
+ ```
39
+
40
+ Директива блока на контейнере без своего узла класса не ставит — узел это комментарий. Второго
41
+ носителя класса блока не нужно, и своей обёртки тоже.
42
+
43
+ ## Свой элемент в чужом поддереве
44
+
45
+ Пара директив на одном элементе; класс блока при этом не ставится, только класс элемента:
46
+
47
+ ```html
48
+ <div rtBlock="<свой блок>" rtElem="confirm"></div>
49
+ ```
50
+
51
+ Класс получается составной, а потомки внутри считаются от того же блока.
52
+
53
+ ## Своё в файле экрана
54
+
55
+ Остаётся только то, что принадлежит одному этому экрану и в общий слой не просится, — сетка
56
+ календаря, карта на странице записи, лента переписки. Рядом пишется, почему это не общее.
57
+
58
+ ## Частые промахи
59
+
60
+ - **Раскладка на хосте в файле экрана.** Одинаковые с виду экраны от неё расходятся: заголовок
61
+ страницы, объявленный в каждом экране заново, живёт тремя разными кеглями.
62
+ - **Директива элемента без предка, объявившего блок:** отрисовка падает во время работы, сборка
63
+ и линтер молчат.
64
+ - **Класс, у которого правило сняли, а директива в шаблоне осталась:** ловит проверка «класс
65
+ без правила».
66
+ - **Элемент чужого блока в чужом поддереве:** имя блока приходит от предка, и подмешать его
67
+ нечем.
@@ -0,0 +1,90 @@
1
+ ---
2
+ name: testing-e2e
3
+ kind: pattern
4
+ rule: testing
5
+ description: Паттерн правила testing. Брать при правке и прогоне сквозных спек — что вообще закрывается сквозной спекой, прогон одним рабочим, стенд из прод-сборки под настоящим прокси, выключатели разрушающих спек. Не брать для юнитов — это паттерн testing-unit.
6
+ ---
7
+
8
+ # Сквозные спеки
9
+
10
+ Паттерн правила `testing`. Что при этом должно быть верно — закон
11
+ `{{lawsDir}}/verifiability.md`.
12
+
13
+ ## Когда брать
14
+
15
+ - Пишется или правится сквозная спека.
16
+ - Надо воспроизвести дефект, который виден только на прод-конфигурации.
17
+ - Готовится стенд под прогон против прокси.
18
+
19
+ ## Сквозной спекой закрывается накопленное состояние
20
+
21
+ Случай для неё — тот, где состояние копится нажатиями: открытая панель, активный маршрут, страж
22
+ прошлого экрана. Всё, что проверяется одним заходом по адресу, дешевле закрыть юнитом на чистой
23
+ функции или замером экрана.
24
+
25
+ Отсюда и способ: нажатия по тем же элементам, что нажимает владелец, несколько подряд, без
26
+ перезагрузки между ними. Заход по прямому адресу поднимает приложение заново, накопленного
27
+ состояния у него нет, и проход читается как «дефект не подтверждается».
28
+
29
+ Элементы находятся по якорям спек: классы меняются вместе с вёрсткой, а поиск по роли и тексту
30
+ ломается на переводах.
31
+
32
+ ## Прогон
33
+
34
+ По умолчанию конфиг идёт на порт разработки и подхватывает уже поднятый сервер. С заданным
35
+ адресом стенда свой сервер не стартует вовсе.
36
+
37
+ Полный набор спек с настоящей сессией гоняется **одним рабочим**: такие тесты правят одни и те
38
+ же записи живого хранилища и в параллельном прогоне мешают друг другу. Одни и те же файлы дают
39
+ падения на многих рабочих и ноль на одном.
40
+
41
+ ## Стенд из прод-сборки под настоящим прокси
42
+
43
+ Дешевле полного состава прода: один контейнер прокси с боевым конфигом, а приложения за ним —
44
+ процессы на хосте.
45
+
46
+ ```bash
47
+ <сборка всех приложений>
48
+ <адрес хранилища> <порт> node <собранный сервер> &
49
+ docker run -d --name <стенд> \
50
+ --add-host <имя апстрима>:host-gateway \
51
+ -p <внешний порт>:80 \
52
+ -v "$PWD/<конфиг прокси>:/etc/nginx/conf.d/default.conf:ro" \
53
+ -v "$PWD/<каталог сборки>:/usr/share/nginx/html:ro" \
54
+ <образ прокси>
55
+ ```
56
+
57
+ Порты апстримов зашиты в конфиг именами — менять их нельзя, подстановка хоста заменяет только
58
+ адрес.
59
+
60
+ ## Выключатели
61
+
62
+ ```
63
+ const РАЗРЕШЕНО = окружение['<имя переменной>'] === '1';
64
+ тест.пропустить(!РАЗРЕШЕНО, 'меняет живой адрес записи: включается переменной');
65
+ ```
66
+
67
+ - Спека, необратимо меняющая данные стенда, по умолчанию пропускается и включается своей
68
+ переменной.
69
+ - Проверки, которым нужен прокси, просыпаются вместе с адресом стенда. Голый сервер отдачи
70
+ страниц их не проходит, и падения выглядят регрессией.
71
+ - Правило линтера, запрещающее выключенный тест, снимается в конфиге: выключатель здесь — приём,
72
+ а не забытый пропуск.
73
+
74
+ ## Частые промахи
75
+
76
+ - **Порт приложения занимать осторожно:** стенд разработчика ходит по тому же имени через
77
+ подстановку хоста, и пока на нём висит чужой процесс, стенд отдаёт чужую сборку.
78
+ - **Браузер обычно установлен один.** Ошибка «Executable doesn't exist» разобрана в правиле
79
+ `testing`: она же приходит после смены версии прогонщика.
80
+ - **Спеки без учётных данных пропускаются молча** — прогон выглядит успешным, а проверено
81
+ меньше половины.
82
+ - **Конфиг прокси монтируется каталогом, а не одиночным файлом:** редактор пересоздаёт файл, и
83
+ контейнеру остаётся обрезанная копия.
84
+ - **Подменяется не только то, что запрашивает экран, но и то, что запрашивает шапка.** Общий
85
+ запрос идёт на каждом экране; без подмены на него отвечает настоящий сервер, поддельный вход
86
+ он отбивает, перехватчик сбрасывает сессию — и десятки тестов падают на пропавшей шапке, что
87
+ выглядит дефектом экрана.
88
+ - **Каталог сборки, удалённый под смонтированным томом, оставляет контейнер с пустым корнем:**
89
+ стенд отвечает отказом на всё, и падают сразу все тесты. Контейнер после такого удаления
90
+ пересоздаётся.
@@ -0,0 +1,93 @@
1
+ ---
2
+ name: testing-unit
3
+ kind: pattern
4
+ rule: testing
5
+ description: Паттерн правила testing. Брать при заведении или правке файла спеки рядом с исходником — раскладка блоков, сборщик фикстур, идентификатор сценария в заголовке, спека обработчика серверной стороны с рукописным двойником хранилища, разовый тест-доказательство. Не брать для сквозных спек — это паттерн testing-e2e.
6
+ ---
7
+
8
+ # Спека на чистую функцию и на обработчик
9
+
10
+ Паттерн правила `testing`. Что при этом должно быть верно — закон
11
+ `{{lawsDir}}/verifiability.md`.
12
+
13
+ ## Когда брать
14
+
15
+ - Заводится или правится файл спеки рядом с исходником.
16
+ - Логику надо вынести из компонента или службы, чтобы её стало чем проверить.
17
+ - Пишется спека на обработчик серверной стороны.
18
+
19
+ ## Импорты явные
20
+
21
+ Даже когда прогонщик кладёт свои имена в глобальную область, список пишется: файл, читаемый
22
+ без конфига, не должен зависеть от настройки, о которой в нём ни слова.
23
+
24
+ ## Один блок на функцию
25
+
26
+ Имя блока совпадает с именем функции дословно; заголовки тестов — предложения настоящим
27
+ временем, о поведении, а не об устройстве:
28
+
29
+ ```
30
+ описание('applyDayClick', () => {
31
+ тест('SC-<ПРЕФИКС>-19 — нажатие по занятому дню ничего не меняет', () => {
32
+ ожидать(applyDayClick(day('2026-08-12'), selection)).равно(selection);
33
+ });
34
+ });
35
+ ```
36
+
37
+ Идентификатор сценария из спека домена стоит в начале заголовка, через тире. Краевые случаи —
38
+ отдельные тесты в том же блоке, а не один тест с десятком проверок.
39
+
40
+ ## Фикстура собирается функцией с частичной подменой
41
+
42
+ Не повторяющимся литералом: литерал, размноженный по файлу, при первой же новой обязательной
43
+ колонке правится в каждом месте — и в одном обязательно забывается.
44
+
45
+ ```
46
+ функция day(iso, подмена = {}) {
47
+ вернуть { iso, dayOfMonth: число(iso.срез(8)), busyNight: занято(iso), past: ложь, ...подмена };
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
+ при этом остаются зелёными.
@@ -0,0 +1,51 @@
1
+ ---
2
+ name: translations-key
3
+ kind: pattern
4
+ rule: translations
5
+ description: Паттерн правила translations. Брать, когда в интерфейсе появляется видимый текст — куда положить ключ, как подставить его в разметку и в класс, чем дозаполнить остальные локали и чем проверить полноту. Не брать для перевода содержимого записи — его заполняет серверная сторона.
6
+ ---
7
+
8
+ # Ключ перевода
9
+
10
+ Паттерн правила `translations`. Что при этом должно быть верно — закон `{{lawsDir}}/locales.md`.
11
+
12
+ ## Когда брать
13
+
14
+ - В интерфейсе появляется любой текст, который видит человек.
15
+ - Правится подпись, пустое состояние, текст отказа, подпись кнопки подтверждения.
16
+
17
+ ## Куда кладётся ключ
18
+
19
+ Словари разложены по разделам, и наборы ключей сверяются **внутри раздела**: общее для всех
20
+ приложений, публичная часть, внутренняя часть, письма. Ключ заводится во всех локалях сразу —
21
+ пустое значение считается пропуском, а не переводом.
22
+
23
+ ## Подстановка
24
+
25
+ В разметке — преобразователем, в классе — реактивным значением:
26
+
27
+ ```html
28
+ <h1 rtElem="title">{{ '<ключ заголовка>' | <перевод> }}</h1>
29
+ <table [emptyMessage]="'<ключ пустого списка>' | <перевод>"></table>
30
+ ```
31
+
32
+ ```typescript
33
+ protected readonly title: Signal<string> = translateSignal('<ключ заголовка>');
34
+ ```
35
+
36
+ ## Дозаполнить и проверить
37
+
38
+ Дозаполнение недостающего делается командой, а полноту держит тест: он роняет прогон на
39
+ недостающем или пустом ключе. Глазами это не проверяется — ключей тысячи.
40
+
41
+ ## Частые промахи
42
+
43
+ - **Текст строкой прямо в шаблоне:** он уедет в интерфейс на одном языке во всех локалях.
44
+ - **Ключ заведён только в паре локалей:** тест полноты падает, но замечают это уже в гейте
45
+ пуша.
46
+ - **Пустая строка вместо перевода:** на экране она выглядит как задуманная — кнопка без
47
+ подписи, заголовок без текста.
48
+ - **Ключ положен не в свой раздел:** наборы сверяются внутри раздела, и расхождение вылезет как
49
+ недостача в другом.
50
+ - **Свой ключ успеха у панели правки записи:** текст успеха принадлежит самой операции и
51
+ заводится во всех локалях сразу.