@rt-tools/agent-kit 0.3.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 (201) hide show
  1. package/README.md +194 -30
  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 +0 -0
  23. package/assets/hooks/browser-guard-device-id.sh +2 -1
  24. package/assets/hooks/browser-guard-no-asking.sh +27 -0
  25. package/assets/hooks/browser-guard-no-listing.sh +2 -1
  26. package/assets/hooks/browser-guard-no-other-drivers.sh +2 -1
  27. package/assets/hooks/browser-guard-require-select.sh +2 -1
  28. package/assets/hooks/commit-msg.sh +1 -1
  29. package/assets/hooks/constitution-index.sh +5 -4
  30. package/assets/hooks/dev-server-guard.sh +8 -6
  31. package/assets/hooks/docs-guard.sh +223 -37
  32. package/assets/hooks/git-guard-delivery.sh +86 -29
  33. package/assets/hooks/git-guard-main.sh +1 -0
  34. package/assets/hooks/git-guard-push-tests.sh +34 -13
  35. package/assets/hooks/glossary-load.sh +23 -0
  36. package/assets/hooks/lint-after-edit.sh +155 -30
  37. package/assets/hooks/qa-dataid-guard.sh +72 -32
  38. package/assets/hooks/reuse-first-guard.sh +105 -34
  39. package/assets/hooks/skill-gate-rearm.sh +1 -0
  40. package/assets/hooks/skill-gate.sh +75 -15
  41. package/assets/hooks/skill-loaded.sh +1 -0
  42. package/assets/hooks/sql-guard.sh +606 -56
  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 +29 -22
  66. package/assets/patterns/api-layer-pair.md +40 -30
  67. package/assets/patterns/browser-verification-measure.md +41 -38
  68. package/assets/patterns/browser-verification-stand.md +106 -42
  69. package/assets/patterns/component-structure-new.md +33 -32
  70. package/assets/patterns/dependencies-upgrade.md +65 -0
  71. package/assets/patterns/doc-style-sweep.md +65 -28
  72. package/assets/patterns/doc-style-write.md +36 -33
  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 +42 -25
  80. package/assets/patterns/git-workflow-migration.md +61 -31
  81. package/assets/patterns/git-workflow-restart.md +20 -20
  82. package/assets/patterns/lib-layers-move.md +50 -32
  83. package/assets/patterns/lib-layers-new.md +41 -29
  84. package/assets/patterns/ownership-scope-resolve.md +69 -0
  85. package/assets/patterns/permissions-procedure.md +35 -33
  86. package/assets/patterns/platform-access-di.md +39 -25
  87. package/assets/patterns/pricing-quote.md +71 -0
  88. package/assets/patterns/reuse-first-extend.md +22 -22
  89. package/assets/patterns/seo-page.md +52 -40
  90. package/assets/patterns/seo-verify.md +48 -29
  91. package/assets/patterns/shared-code-new.md +37 -31
  92. package/assets/patterns/spec-driven-domain.md +44 -37
  93. package/assets/patterns/spec-driven-rule.md +55 -40
  94. package/assets/patterns/styling-bem-component.md +43 -32
  95. package/assets/patterns/styling-bem-layout.md +30 -24
  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 +53 -51
  100. package/assets/patterns/testing-unit.md +70 -46
  101. package/assets/patterns/translations-key.md +32 -19
  102. package/assets/patterns/ts-procedure.md +24 -25
  103. package/assets/rules/angular-patterns.md +46 -27
  104. package/assets/rules/api-layer.md +46 -28
  105. package/assets/rules/browser-verification.md +66 -48
  106. package/assets/rules/component-structure.md +43 -27
  107. package/assets/rules/dependencies.md +66 -0
  108. package/assets/rules/doc-style.md +81 -39
  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 +56 -30
  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 +43 -25
  119. package/assets/rules/platform-access.md +57 -29
  120. package/assets/rules/pricing.md +64 -0
  121. package/assets/rules/reuse-first.md +57 -43
  122. package/assets/rules/seo.md +51 -30
  123. package/assets/rules/shared-code.md +51 -26
  124. package/assets/rules/spec-driven.md +96 -50
  125. package/assets/rules/styling-bem.md +54 -39
  126. package/assets/rules/task-flow.md +110 -0
  127. package/assets/rules/testing.md +78 -47
  128. package/assets/rules/translations.md +48 -31
  129. package/assets/rules/typescript-conventions.md +57 -27
  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 +23 -15
  133. package/assets/templates/implementation.md +14 -8
  134. package/assets/templates/pattern.md +1 -1
  135. package/assets/templates/project.sh +32 -19
  136. package/assets/templates/rule.md +1 -1
  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 +8 -3
  152. package/lib/assets.d.ts.map +1 -1
  153. package/lib/assets.js +13 -3
  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 +202 -14
  162. package/lib/commands.js.map +1 -1
  163. package/lib/companion.d.ts +5 -1
  164. package/lib/companion.d.ts.map +1 -1
  165. package/lib/companion.js +29 -2
  166. package/lib/companion.js.map +1 -1
  167. package/lib/config.d.ts +26 -9
  168. package/lib/config.d.ts.map +1 -1
  169. package/lib/config.js +41 -15
  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/sync.d.ts +26 -0
  188. package/lib/sync.d.ts.map +1 -1
  189. package/lib/sync.js +59 -4
  190. package/lib/sync.js.map +1 -1
  191. package/lib/variants.d.ts +44 -0
  192. package/lib/variants.d.ts.map +1 -0
  193. package/lib/variants.js +82 -0
  194. package/lib/variants.js.map +1 -0
  195. package/package.json +1 -1
  196. package/rt-tools-agent-kit-0.4.0.tgz +0 -0
  197. package/assets/laws/admin-lists.md +0 -35
  198. package/assets/laws/admin-navigation.md +0 -38
  199. package/assets/patterns/git-workflow-commit.md +0 -175
  200. package/assets/rules/git-workflow.md +0 -106
  201. package/rt-tools-agent-kit-0.3.0.tgz +0 -0
@@ -2,13 +2,13 @@
2
2
  name: component-structure-new
3
3
  kind: pattern
4
4
  rule: component-structure
5
- description: Паттерн правила component-structure. Брать при заведении или правке файла компонента порядок свойств декоратора, группировка импортов, раскладка полей класса, договорённости шаблона и якорь для спек. Не брать для состояния и потоков — это паттерн angular-patterns-state.
5
+ description: Паттерн правила component-structure. Брать при заведении или правке *.component.tsготовый декоратор с порядком свойств, группировка импортов, раскладка полей класса, договорённости шаблона и qa-dataid. Не брать для состояния и потоков — это паттерн angular-patterns-state.
6
6
  ---
7
7
 
8
8
  # Файл компонента
9
9
 
10
10
  Паттерн правила `component-structure`. Что при этом должно быть верно — закон
11
- `{{lawsDir}}/frontend-application.md`.
11
+ `docs/constitution/frontend-application.md`.
12
12
 
13
13
  ## Когда брать
14
14
 
@@ -25,14 +25,14 @@ description: Паттерн правила component-structure. Брать пр
25
25
  changeDetection: ChangeDetectionStrategy.OnPush, // 4. стратегия перерисовки
26
26
  imports: [
27
27
  // 5. импорты, группами
28
- // каркас
28
+ // angular
29
29
  FormsModule,
30
30
 
31
- // директивы разметки
31
+ // rt-tools
32
32
  BlockDirective,
33
33
  ElemDirective,
34
34
 
35
- // компоненты
35
+ // components
36
36
  SomeChildComponent,
37
37
  ],
38
38
  providers: [], // 6. провайдеры
@@ -44,55 +44,56 @@ export class ComponentNameComponent {
44
44
  public readonly data: InputSignal<Item[]> = input.required<Item[]>();
45
45
  public readonly save: OutputEmitterRef<void> = output<void>();
46
46
 
47
+ protected readonly myButton: Signal<ElementRef | undefined> = viewChild<ElementRef>('button');
47
48
  protected readonly items: WritableSignal<Item[]> = signal<Item[]>([]);
48
49
  protected readonly itemCount: Signal<number> = computed((): number => this.items().length);
49
50
  }
50
51
  ```
51
52
 
52
- Группирующие комментарии в импортах обязательны: без них список растёт вперемешку, и первое,
53
- что в нём теряется, свои компоненты среди чужих.
53
+ Группирующие комментарии в `imports` обязательны: `// angular`, `// rt-tools`,
54
+ `// components`, `// directives`, `// pipes`.
54
55
 
55
56
  ## Шаблон
56
57
 
57
- - Самозакрывающиеся теги у компонентов без содержимого.
58
- - Лишних обёрток нет — корнем работает хост, класс блока приходит его привязкой.
59
- - Сложный шаблон объявляет блок в корне отдельной директивой.
60
- - Один компонент в обеих ветках условия — это условная привязка:
58
+ - Самозакрывающиеся теги у компонентов без содержимого: `<<префикс>-gallery />`.
59
+ - Лишних обёрток нет — корнем работает `:host`, класс блока приходит с `host: { class: … }`.
60
+ - Сложный шаблон объявляет блок `<ng-container rtBlock="component-name">` в корне.
61
+ - Один компонент в обеих ветках `@if` — это условная привязка:
61
62
 
62
63
  ```html
63
64
  <!-- ✗ -->
64
- @if (isRangeMode()) {
65
- <calendar [rangeMode]="true" />
66
- } @else {
67
- <calendar [rangeMode]="false" />
68
- }
65
+ @if (isRangeMode()) { <<префикс>-calendar [rangeMode]="true" /> } @else { <<префикс>-calendar [rangeMode]="false" /> }
69
66
 
70
67
  <!-- ✓ -->
71
- <calendar [rangeMode]="isRangeMode()" />
68
+ <<префикс>-calendar [rangeMode]="isRangeMode()" />
72
69
  ```
73
70
 
74
- - Прокрутка по документу — средствами маршрутизатора, а не ссылкой на фрагмент: при объявленном
75
- базовом адресе браузер разрешает фрагмент относительно него и уходит в полную навигацию.
71
+ - Прокрутка по документу — через роутер, а не `href="#id"`:
72
+
73
+ ```html
74
+ <a fragment="booking" [routerLink]="[]">…</a>
75
+ ```
76
76
 
77
- ## Якорь для спек — на каждый интерактивный элемент
77
+ ## `qa-dataid` — на каждый интерактивный элемент
78
78
 
79
79
  ```html
80
- <button qa-dataid="calendar-retry-prices" type="button" (click)="retryPrices.emit()">Повторить</button>
80
+ <button vmButton qa-dataid="calendar-retry-prices" type="button" (click)="retryPrices.emit()">Повторить</button>
81
81
  <div rtElem="grid" qa-dataid="admin-calendar-grid"></div>
82
82
  ```
83
83
 
84
- - Значение — через дефис, по смыслу элемента, без имени компонента-обёртки.
84
+ - Значение — kebab-case по смыслу элемента, без имени компонента-обёртки: `calendar-day`,
85
+ `booking-submit`.
85
86
  - Уникальность — в пределах экрана; повторяющиеся элементы списка носят один якорь и
86
- различаются атрибутами данных.
87
- - Декоративный элемент помечается признаком пропуска на самом теге.
87
+ различаются через `data-*` (`[attr.data-iso]`, `[attr.data-state]`).
88
+ - Декоративный элемент помечается `qa-skip` на самом теге.
88
89
 
89
90
  ## Частые промахи
90
91
 
91
- - **Чужой префикс селектора** — компонент перестаёт узнаваться как свой.
92
- - **Множественная форма свойства стилей вместо единственной**стиль молча не подключается.
93
- - **Вызов метода в привязке** отбивается линтером; заменавычисляемое значение, а там, где
94
- оно зависит от контекста шаблона, — чистый преобразователь.
95
- - **Обёртка, которая существует только чтобы быть контейнером раскладки вокруг всех детей:** её
96
- раскладка уезжает на хост.
97
- - **Глубокий относительный импорт между либами** вместо алиаса.
98
- - **Своя разметка вместо готового компонента** — правило `reuse-first`.
92
+ - `selector: 'app-component'` вместо `<префикс>-component`.
93
+ - `styleUrls: ['./component.scss']` вместо `styleUrl`множественное число здесь не то.
94
+ - Вызов метода в биндинге: `{{ getTotal() }}` отбивается линтером. Замена`computed()`, а
95
+ там, где значение приходит из контекста шаблона, — чистый пайп.
96
+ - Обёртка, которая существует только чтобы быть flex- или grid-контейнером вокруг всех детей:
97
+ её раскладка уезжает на `:host`.
98
+ - Глубокий относительный импорт между либами вместо `@<область>/<семья>/<домен>/<слой>`.
99
+ - Своя разметка вместо готового компонента кита — правило `reuse-first`.
@@ -0,0 +1,65 @@
1
+ ---
2
+ name: dependencies-upgrade
3
+ kind: pattern
4
+ rule: dependencies
5
+ description: Паттерн правила dependencies. Брать при подъёме версий пакетов — выбор верхней границы по peer-диапазонам, порядок проверок, граница переформатирования после обновления форматтера, разбор новых правил линтера. Не брать для ветки, коммита и PR — это паттерн git-workflow-commit.
6
+ ---
7
+
8
+ # Подъём версий
9
+
10
+ Паттерн правила `dependencies`. Что при этом должно быть верно — закон
11
+ `docs/constitution/delivery.md`.
12
+
13
+ ## Когда брать
14
+
15
+ - Поднимается версия любого пакета.
16
+ - Пришло предупреждение об уязвимости в транзитивной зависимости.
17
+ - После установки посыпались замечания линтера или переформатировалось дерево.
18
+
19
+ ## Верхнюю границу задают peer-диапазоны
20
+
21
+ ```bash
22
+ npm view @angular/compiler-cli@22.1.0 peerDependencies
23
+ npm view typescript versions --json | tail -5
24
+ ```
25
+
26
+ Пакет поднимают до последней версии, которая ещё попадает в peer-диапазон каждого, кто от него
27
+ зависит, а не до последней в реестре. Проверять приходится по одному пакету: `autoInstallPeers:
28
+ true` молча доставляет недостающее, и конфликт peer-диапазонов установку не останавливает.
29
+
30
+ ## Порядок
31
+
32
+ 1. `package.json` — точные номера, ни одного `^` и `~`.
33
+ 2. `pnpm install`; снимок едет тем же коммитом.
34
+ 3. `npx nx run-many -t build -p site admin api` — сборка **до** линтеров: о несовместимости
35
+ типов она говорит одной строкой, а линтер на том же дереве даёт сотню замечаний, и
36
+ настоящая причина в них теряется.
37
+ 4. `npm run lint` и `npm run stylelint`.
38
+ 5. Тесты — правило `testing`. После смены версии Playwright сначала
39
+ `npx playwright install chromium`.
40
+ 6. Обход экранов в браузере — правило `browser-verification`: если пакет рисует вёрстку, смену
41
+ его версии тесты не покрывают.
42
+
43
+ ## Переформатирование
44
+
45
+ Обновлённый форматтер меняет все файлы дерева. Что из этого оставлять, решает не размер дифа,
46
+ а то, чьё форматирование проверяет линтер: `.ts` и `.html` — `eslint.config.mjs`, `.scss` —
47
+ `stylelint.config.js`. Остальное переформатируется потом, вместе с правкой, которая эти файлы
48
+ и так трогает: полный прогон изменил 883 файла, из которых проверяются 85, и осмысленные
49
+ правки в такой куче не разглядеть.
50
+
51
+ ## Новые правила линтера
52
+
53
+ Правило, которое запрещает приём, принятый в этом дереве, выключают один раз в конфиге и
54
+ пишут рядом причину. Правило, которое право по существу, обходят точечным
55
+ `eslint-disable-next-line` — тоже с причиной. Смешивать нельзя: выключенное в конфиге без
56
+ причины через месяц выглядит забытым, а шестьдесят пять точечных обходов одного правила —
57
+ списком того, что надо было выключить один раз.
58
+
59
+ ## Частые промахи
60
+
61
+ - `pnpm install` не двигает пакет, если он назван в `overrides`: строка из списка держит
62
+ прежнюю версию, а установка проходит без ошибок.
63
+ - Линтеры до сборки — лишний круг: разбираешь замечания, которых после сборки не будет.
64
+ - Обновление, попавшее в ветку с другой задачей, откатится только вместе с ней: у обновления
65
+ зависимостей своя задача и своя ветка.
@@ -8,8 +8,8 @@ description: Паттерн правила doc-style. Брать, когда д
8
8
  # Разбор документа, накопившего список работ
9
9
 
10
10
  Паттерн правила `doc-style`. Что при этом должно быть верно — закон
11
- `{{lawsDir}}/project-documentation.md`, статья о том, что предстоящая работа перечислена в одном
12
- месте.
11
+ `docs/constitution/project-documentation.md`, статья о том, что предстоящая работа
12
+ перечислена в одном месте.
13
13
 
14
14
  ## Когда брать
15
15
 
@@ -31,63 +31,99 @@ description: Паттерн правила doc-style. Брать, когда д
31
31
  | описание сделанного | удаляется без переноса: о нём говорят закрытые задачи и история |
32
32
  | утверждение, разошедшееся с деревом | удаляется как протухшее, а не переносится в задачу |
33
33
 
34
- Спрашивать владельца об этом наборе не нужно — он записан здесь. Спрашивать стоит одно: судьбу
35
- самого файла, когда в нём не осталось ничего.
34
+ Спрашивать владельца об этом наборе не нужно — он записан здесь. Спрашивать стоит одно:
35
+ судьбу самого файла, когда в нём не осталось ничего.
36
36
 
37
37
  ## Обход идёт по утверждениям, а не по пунктам
38
38
 
39
- Работа лежит и в прозе: раздел без единого маркера обход по списку не увидит вовсе. Абзац
40
- судится тем же признаком, что и пункт.
39
+ Работа лежит и в прозе. Раздел «Экран заявок» перечислял недостающие якоря сплошным текстом,
40
+ без единого маркера, и обход по `- ` его не увидел бы.
41
41
 
42
- ## Номер задачи стоит в нескольких местах
42
+ ```bash
43
+ # сколько чего в файле: пункты, абзацы, разделы
44
+ grep -c '^- ' docs/BACKLOG.md
45
+ grep -c '^## ' docs/BACKLOG.md
46
+ ```
43
47
 
44
- Прежде чем считать «пункты без задачи», надо знать все формы записи — в заголовке, отдельной
45
- строкой у раздела и в конце пункта. Разбор, знающий одну форму, ошибается молча. **Число,
48
+ Абзац судится тем же признаком, что и пункт.
49
+
50
+ ## Номер задачи стоит в трёх местах
51
+
52
+ Прежде чем считать «пункты без задачи», надо знать все формы записи. В этом дереве их три:
53
+
54
+ ```markdown
55
+ ## Раздел — #149 ← в заголовке
56
+
57
+ **Тикеты:** #163, #164 ← отдельной строкой у раздела или подраздела
58
+
59
+ - Пункт про дефект. #101 ← в конце пункта
60
+ ```
61
+
62
+ Разбор, знающий одну форму, ошибается молча: «51 пункт без задачи» оказался шестью. **Число,
46
63
  полученное разбором текста, сверяется на выборке руками до того, как его называют.**
47
64
 
48
65
  ## Сверка с очередью работ
49
66
 
50
67
  У пункта есть задача — это ещё не значит, что задача несёт его содержание. Перед удалением
51
- меряется покрытие: сколько значимых слов пункта встречается в теле его задачи. Пункт, покрытый
52
- наполовину и меньше, читается глазами и разводится на три исхода:
68
+ меряется покрытие: сколько значимых слов пункта встречается в теле его задачи.
69
+
70
+ ```bash
71
+ /opt/homebrew/bin/gh issue list --state all --limit 400 --json number,title,body,state > /tmp/issues.json
72
+ ```
73
+
74
+ Пункт, покрытый телом наполовину и меньше, читается глазами и разводится на три исхода:
53
75
 
54
76
  - живое уточнение, которого в задаче нет, — дописать в тело;
55
77
  - другой дефект — завести своей задачей;
56
- - протухшее — удалить, **не** перенося. Иначе разбор занесёт в задачу ложь.
78
+ - протухшее — удалить, **не** перенося. Иначе разбор занесёт в задачу ложь: «гард не проверяет
79
+ каталог кита» переносить было некуда, каталога уже не существовало.
57
80
 
58
81
  **Наследование номера от заголовка — предположение, а не факт.** Пункт под заголовком с
59
- несколькими номерами не принадлежит ни одному из них: привязка проверяется чтением.
82
+ четырьмя номерами не принадлежит ни одному из них: привязка проверяется чтением.
60
83
 
61
84
  ## Сделанность читается по дереву
62
85
 
63
- Утверждение о том, что задача не сделана, стареет так же, как любое другое. Проверяется то, о
64
- чём собираешься сказать «не сделано», поиском по дереву, а не памятью.
86
+ Утверждение о том, что задача не сделана, стареет так же, как любое другое. Дважды за один
87
+ разбор устаревшее было названо действующим: маркер непросмотренного уже вёз кит, а половина
88
+ задачи про гейт скилов была сделана и покрыта сценариями.
89
+
90
+ ```bash
91
+ # проверять то, о чём собираешься сказать «не сделано»
92
+ grep -rn '<символ>' libs apps .claude/hooks
93
+ grep -n '<имя>' node_modules/<пакет>/types/*.d.ts
94
+ ```
65
95
 
66
96
  ## Доводы за отсрочку — это «чего это стоит»
67
97
 
68
- Раздел «что стоит отложить» переезжает не в архив, а **в тела тех задач, которых он касается**:
69
- там он и есть оценка цены. В файле он остаётся, только если отсрочка — решение владельца, а не
70
- предложение разбора. Признак — записанный ответ владельца с датой.
98
+ Раздел «что стоит отложить» переезжает не в архив, а **в тела тех задач, которых он
99
+ касается**: там он и есть оценка цены. В файле он остаётся, только если отсрочка — решение
100
+ владельца, а не предложение разбора. Признак — записанный ответ владельца с датой; нет
101
+ его — значит, предложение.
71
102
 
72
103
  ## Ссылки на снятые разделы
73
104
 
74
- Задача, чьё тело ссылается на раздел документа, после разбора ведёт в пустоту, и проверка путей
75
- этого не видит: она читает файлы репозитория, а не тела задач. Строка снимается тем же заходом.
105
+ Задача, чьё тело говорит `**Источник:** <документ>, раздел «…»`, после разбора ведёт в
106
+ пустоту, и проверка путей этого не видит: она читает файлы репозитория, а не тела задач.
107
+ Строка снимается тем же заходом.
76
108
 
77
109
  ## Судьба файла
78
110
 
79
- - Осталось незадачное — файл живёт, и его вступление объявляет новый признак отбора.
80
- - Не осталось ничего, а документ описывал состоявшуюся работу — уезжает в архив.
111
+ - Осталось незадачное — файл живёт, и его преамбула объявляет новый признак отбора.
112
+ - Не осталось ничего, а документ описывал состоявшуюся работу — уезжает в `docs/archive/`.
81
113
  - Не осталось ничего, и это был список работ — удаляется.
82
114
 
83
- Разбор целиком — одна задача и одна ветка: делится то, что придётся откатывать порознь, а здесь
84
- откат общий. Правка кода, найденная по дороге, в эту ветку не идёт — иначе откат разбора унесёт
85
- починку.
115
+ Разбор целиком — одна задача и одна ветка: делится то, что придётся откатывать порознь, а
116
+ здесь откат общий. Правка кода, найденная по дороге, в эту ветку не идёт — иначе откат разбора
117
+ унесёт починку.
86
118
 
87
119
  ## Что чинится в дереве следом
88
120
 
89
- Документ — не единственное место, обещающее, что работа живёт в нём. Снятое имя вычищается одним
90
- проходом по всему дереву, включая описания ролей, скилы, README и комментарии в коде.
121
+ Документ — не единственное место, обещающее, что работа живёт в нём. Снятое имя вычищается
122
+ одним грепом, включая описания агентов, скилы, README и комментарии в коде:
123
+
124
+ ```bash
125
+ grep -rn 'BACKLOG' --exclude-dir=node_modules --exclude-dir=.git --exclude-dir=archive .
126
+ ```
91
127
 
92
128
  ## Частые промахи
93
129
 
@@ -96,5 +132,6 @@ description: Паттерн правила doc-style. Брать, когда д
96
132
  - Число пунктов названо владельцу до сверки на выборке.
97
133
  - Протухшее утверждение перенесено в задачу и стало действующим указанием.
98
134
  - Доводы за отсрочку оставлены в документе: он снова становится вторым списком работ.
99
- - Разбор поделён на несколько задач «по объёму» — признак деления не объём, а раздельный откат.
135
+ - Разбор поделён на несколько задач «по объёму» — признак деления не объём, а раздельный
136
+ откат.
100
137
  - Архив тронут: он описывает состояние на момент написания и под новые термины не правится.
@@ -8,52 +8,53 @@ description: Паттерн правила doc-style. Брать при напи
8
8
  # Как формулировать
9
9
 
10
10
  Паттерн правила `doc-style`. Что при этом должно быть верно — закон
11
- `{{lawsDir}}/project-documentation.md`.
11
+ `docs/constitution/project-documentation.md`.
12
12
 
13
13
  ## Когда брать
14
14
 
15
15
  - Пишется правило, статья закона, пункт спека или README.
16
16
  - Пишется комментарий в коде, тело коммита, описание PR.
17
17
 
18
- ## Статья — одна фраза
18
+ ## Правило — одна фраза
19
19
 
20
- Сама статья укладывается в одно предложение. Следом — не больше двух предложений о том, что
20
+ Само правило укладывается в одно предложение. Следом — не больше двух предложений о том, что
21
21
  сломается иначе, и только если из самой фразы это не видно. Обоснование, у которого была
22
- альтернатива, идёт в «Ловушки» правила, а не в закон: разросшийся пункт читают по диагонали, а
23
- закон говорит, что верно, — не почему когда-то выбрали так.
22
+ альтернатива, идёт в «Ловушки» правила, а не в сам закон: разросшийся буллет читают по
23
+ диагонали, а закон говорит, что верно, — не почему когда-то выбрали так.
24
24
 
25
25
  ```
26
- ✗ **Взрослых в записи хотя бы один — на любом пути записи, без исключений.** Нижнюю
27
- границу держит хранилище: путей записи три, и проверка в одном из них закрывает не все.
28
- Верхнюю ограничением хранилища не выразить — она лежит в самой записи и меняется
29
- владельцем.
26
+ ✗ **Взрослых в брони хотя бы один — на любом пути записи, без исключений.** Нижнюю
27
+ границу держит база: путей записи три, и проверка в одном из них закрывает не все.
28
+ Верхнюю (вместимость объекта) ограничением базы не выразить — она лежит в строке
29
+ объекта и меняется владельцем.
30
30
 
31
- ✓ **Взрослых в записи хотя бы один.** Исключений нет.
32
- ✓ **Взрослые и дети не превышают вместимость.**
31
+ ✓ **Взрослых в брони хотя бы один.** Исключений нет: заезда без взрослых не бывает.
32
+ ✓ **Взрослые и дети не превышают вместимость объекта.**
33
33
  ```
34
34
 
35
35
  ## Что верно, а не где это держится
36
36
 
37
- «Держит хранилище, а не служба», «проверяется в транзакции», «умолчание колонки», «на трёх
38
- путях записи из четырёх», имена функций и колонок внутри фразы — это устройство кода. Читателю
39
- нужно знать, **что** верно.
37
+ «Держит база, а не сервис», «проверяется в транзакции», «умолчание колонки», «на трёх путях
38
+ записи из четырёх», имена функций и колонок внутри фразы — это устройство кода. Читателю нужно
39
+ знать, **что** верно.
40
40
 
41
- Место исполнения меняется при первом же переносе, и документ, который его называет, устаревает
42
- молча. Для него заведён отдельный файл — `implementation.md` рядом с правилом.
41
+ Место исполнения меняется при первом же рефакторинге, и документ, который его называет,
42
+ устаревает молча. Для него заведён отдельный файл — `implementation.md` рядом.
43
43
 
44
- Исключение — «Ловушки» правила: там устройство кода называется прямо, потому что ловушка и есть
45
- место, где на него наступают.
44
+ Исключение — «Ловушки» правила: там устройство кода называется прямо, потому что ловушка и
45
+ есть место, где на него наступают.
46
46
 
47
47
  ## Без утверждений о будущем
48
48
 
49
- «Не планируется», «не будет», «отдельная задача по запросу» — это намерение владельца, а не
49
+ «Не планируется», «не будет», «отдельная фича по запросу» — это намерение владельца, а не
50
50
  свойство системы. Что не сделано — да, почему не сделано — да, что не будет сделано никогда —
51
- нет.
51
+ нет. Вместо приговора — открытый вопрос `Q-N` с тем, что решение изменит.
52
52
 
53
- Ошибка беззвучная: сверять утверждение о будущем не с чем, оно проходит любую проверку.
53
+ Ошибка беззвучная: сверять утверждение о будущем не с чем, оно проходит любую проверку. Так в
54
+ первый живой спек попало «онлайн-оплаты нет и не планируется», хотя оплата в планах.
54
55
 
55
- Отсюда же: того, чего нет в документе о продукте, пересказ не утверждает. Домысел при пересказе
56
- звучит убедительнее исходника — он короче и категоричнее.
56
+ Отсюда же: того, чего нет в `docs/PRD.md`, документ о продукте не утверждает. Домысел при
57
+ пересказе звучит убедительнее исходника — он короче и категоричнее.
57
58
 
58
59
  ## Простыми словами
59
60
 
@@ -61,9 +62,10 @@ description: Паттерн правила doc-style. Брать при напи
61
62
 
62
63
  ```
63
64
  ✗ запись о прошлом не должна упираться в правило, появившееся позже
65
+ ✗ свою запись владельца стирать по молчанию площадки нельзя
64
66
  ✗ пустой список без текста и отказ без кнопки выглядят одинаково — как сломанная страница
65
67
 
66
- запись, внесённую владельцем, пропажа события из ленты не снимает
68
+ бронь, которую владелец внёс сам, пропажа события из фида не снимает
67
69
  ✓ у пустого списка должен быть текст, у отказа — кнопка повтора
68
70
  ```
69
71
 
@@ -71,23 +73,24 @@ description: Паттерн правила doc-style. Брать при напи
71
73
 
72
74
  ## Факт проверяется, а не вспоминается
73
75
 
74
- Перед тем как написать, что код делает то-то, — открыть код и посмотреть. Пересказ по памяти
76
+ Перед тем как написать, что код делает X, — открыть код и посмотреть. Пересказ по памяти
75
77
  выглядит так же уверенно, как проверенное утверждение, и отличить их потом нечем.
76
78
 
77
- Особенно это касается отказов: недостижимый отказ, названный в документе, живёт там годами
78
- проверить его читателю нечем.
79
+ Особенно это касается отказов: «вернётся `value out of range`» продержалось в двух документах,
80
+ хотя такой отказ недостижим — в контракте и в колонке одна ширина.
79
81
 
80
82
  ## Не пересказывать то, у чего есть источник
81
83
 
82
- - типы и поля контракта — ссылкой на сам контракт;
83
- - колонки и индексы — ссылкой на схему хранилища;
84
- - числа, которые правит владелец, — только как они применяются;
84
+ - типы и поля контракта — `libs/common/proto/proto/<область>/v1/`, ссылкой;
85
+ - колонки и индексы — `prisma/schema.prisma`;
86
+ - числа, которые правит владелец (базовая цена, min-nights, вместимость) — только как они
87
+ применяются;
85
88
  - слои и имена классов — правило `lib-layers` и сам код.
86
89
 
87
90
  ## Комментарии в коде
88
91
 
89
- Те же правила. Комментарий отвечает на вопрос «почему так, а не иначе» — если ответ неочевиден.
90
- Что делает строка, видно из строки.
92
+ Те же правила. Комментарий отвечает на вопрос «почему так, а не иначе» — если ответ
93
+ неочевиден. Что делает строка, видно из строки.
91
94
 
92
95
  Многострочный комментарий над каждой функцией — признак того, что объяснение подменило имя.
93
96
  Сначала переименовать, потом писать комментарий.
@@ -102,5 +105,5 @@ description: Паттерн правила doc-style. Брать при напи
102
105
  «портировано из», ни ссылок на его файлы — нигде.
103
106
  - Число написано по памяти, а не пересчитано командой в том же коммите.
104
107
  - Пункт плана вычеркнут по памяти о работе, а не по дереву.
105
- - Формулировка звучит как заголовок, а не как статья: «работа со скидками» нарушить нельзя,
108
+ - Формулировка звучит как заголовок, а не как правило: «работа со скидками» нарушить нельзя,
106
109
  «применяется одна максимальная скидка» — можно.
@@ -0,0 +1,136 @@
1
+ ---
2
+ name: entity-aside
3
+ kind: pattern
4
+ rule: entity-conventions
5
+ description: Паттерн правила entity-conventions. Брать при сборке или правке панели создания и правки записи — готовый маршрут в аутлете ro, наследование общей основы, runMutation, гард несохранённых правок, шапка и футер, уход на связанную запись. Не брать для стора — это паттерн entity-store.
6
+ ---
7
+
8
+ # Панель правки записи
9
+
10
+ Паттерн правила `entity-conventions`. Что при этом должно быть верно — закон
11
+ `docs/constitution/entity-editing.md`.
12
+
13
+ ## Когда брать
14
+
15
+ - Заводится или правится панель создания и правки записи.
16
+ - Появляется панель без записи — настройки таблицы, лента событий.
17
+
18
+ ## Панель объявляется маршрутом в аутлете `ro`
19
+
20
+ ```typescript
21
+ {
22
+ path: 'booking/:id',
23
+ outlet: 'ro',
24
+ component: BookingDetailAsideComponent,
25
+ }
26
+ ```
27
+
28
+ Так панель переживает перезагрузку, передаётся ссылкой и попадает в историю браузера.
29
+ Программного открытия через сервис в админке нет.
30
+
31
+ Панель, которую открывают из шапки, объявляется константой и подмешивается в `children` каждой
32
+ доменной ветки — аутлет стоит в шаблоне хрома, а хром надевает `shell` каждого домена:
33
+
34
+ ```typescript
35
+ export const ACTIVITY_ASIDE_ROUTES: Route[] = [{ path: 'activity', outlet: 'ro', component: ActivityFeedAsideComponent }];
36
+ ```
37
+
38
+ ## Экран наследует общую основу
39
+
40
+ `RtRouteAsideComponent<T>` держит `entity`, `entityId`, `isCreateMode`, `submitting`,
41
+ `resolving`, `submitError`, открытие панели по маршруту, уход с адреса и всю механику записи.
42
+ Имена из основы не переименовываются: `booking`, `feed`, `slug` вместо `entity` ломают то самое
43
+ единообразие, ради которого основа заведена.
44
+
45
+ ## Запись идёт через `runMutation`
46
+
47
+ ```typescript
48
+ protected save(): void {
49
+ if (!this.canSave()) {
50
+ return;
51
+ }
52
+
53
+ this.runMutation(this.#store.save(this.#draft()), {
54
+ successKey: 'promoCodeSaved',
55
+ errorText: (): string => this.#store.errorKey() ?? 'promoCodeSaveFailed',
56
+ closeOnSuccess: true,
57
+ });
58
+ }
59
+ ```
60
+
61
+ Занятость, гашение прежней ошибки, тост об успехе и закрытие панели держит основа. `errorText`
62
+ — функция: причина отказа известна только после него. Поток мутации обязан отдать значение или
63
+ ошибку — пустой поток гасит панель навсегда.
64
+
65
+ ## Гард несохранённых правок
66
+
67
+ Ставит сама панель, и он встаёт на все четыре пути закрытия: кнопку в шапке, кнопку в футере,
68
+ нажатие мимо панели и Esc.
69
+
70
+ ```typescript
71
+ protected readonly panelForm: Signal<NgForm | undefined> = viewChild(NgForm);
72
+ protected readonly formPristine: Signal<boolean> = this.pristineSignal(
73
+ computed((): AbstractControl | undefined => this.panelForm()?.control)
74
+ );
75
+
76
+ constructor() {
77
+ super();
78
+ this.guardUnsavedChanges({ pristine: this.formPristine, save: (): void => this.save() });
79
+ }
80
+ ```
81
+
82
+ `viewChild` на поле с `#` Angular не принимает — поле объявляется `protected`.
83
+
84
+ ## Шапка и футер
85
+
86
+ ```html
87
+ <<префикс>-aside-header [title]="title()" [overline]="overline()" [loading]="resolving()" (dismiss)="onClose()">
88
+ <ng-container asideActions>
89
+ <!-- доменные действия иконками; больше двух — под одну кнопку меню -->
90
+ </ng-container>
91
+ </<префикс>-aside-header>
92
+ ```
93
+
94
+ Заголовок называет действие, имя записи идёт надстрочником. В футере две зоны и не больше двух
95
+ кнопок: `asideDismiss` — закрытие, `asidePrimary` — запись. Доменные глаголы в футер не
96
+ ставятся: подтверждение, отказ, снятие с публикации — иконки в шапке.
97
+
98
+ Подписи кнопок закреплены, панель их не выбирает:
99
+
100
+ | Кнопка | Подпись |
101
+ | ------------------------------------ | ------------------------------------------------- |
102
+ | запись при создании | «Создать» |
103
+ | запись при правке | «Сохранить» |
104
+ | закрытие панели, которая записывает | «Закрыть и не сохранять» (`uiCloseWithoutSaving`) |
105
+ | закрытие панели только для просмотра | «Закрыть» |
106
+
107
+ Кнопка записи стоит у противоположного края от кнопки закрытия, а пока идёт запрос —
108
+ показывает спиннер и не нажимается: занятость берётся из `submitting()` основы, своего флага
109
+ панель не заводит. Шапка и футер при прокрутке остаются на месте — прокручивается только зона
110
+ содержимого.
111
+
112
+ ## Уход на связанную запись
113
+
114
+ ```html
115
+ <a rtElem="related" qa-dataid="promo-code-property-link" [href]="propertyHref()" (click)="openProperty($event)">
116
+ {{ 'propertyOpenLink' | transloco }} <<префикс>-icon name="arrow-right" size="sm" />
117
+ </a>
118
+ ```
119
+
120
+ Адрес даёт `relatedUrl(commands)`, уход — `openRelated(commands)`. `routerLink` здесь не
121
+ годится: директива навигирует сама, `preventDefault` её не останавливает, и вопрос о
122
+ несохранённых правках она обходит.
123
+
124
+ ## Частые промахи
125
+
126
+ - Свой `router.navigate` в панели: абсолютные команды меняют только первичную ветку, аутлет
127
+ остаётся в адресе, и роутер отклоняет навигацию молча.
128
+ - Скелетоны по `busy()`, а не по `resolving()`: `busy` включает и запись, и на сохранении поля
129
+ превратились бы в скелетоны.
130
+ - Своё «не найдено» на панели: ненайденная запись уводит с адреса силами основы.
131
+ - Свои отступы поверх общей основы: они дают разную ширину полей на разных панелях и срезают
132
+ обводку фокуса у края прокрутки.
133
+ - Панель, остающаяся открытой после успеха, не сбросила нетронутость (`markAsPristine`) —
134
+ вопрос о правках задаётся сразу после записи.
135
+ - Отключённые поля ввода вместо данных: запись, которую только смотрят, показывается через
136
+ `<dl>`, `<префикс>-detail-list`, `<префикс>-info-item`.