@rt-tools/agent-kit 0.3.0 → 0.5.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 (202) 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 +329 -0
  9. package/assets/checks/check-board.github.mjs +181 -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 +1086 -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 +106 -0
  21. package/assets/defaults/project.sh +204 -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 +171 -31
  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/grill-gate.sh +96 -0
  37. package/assets/hooks/lint-after-edit.sh +155 -30
  38. package/assets/hooks/qa-dataid-guard.sh +72 -32
  39. package/assets/hooks/reuse-first-guard.sh +105 -34
  40. package/assets/hooks/skill-gate-rearm.sh +1 -0
  41. package/assets/hooks/skill-gate.sh +75 -15
  42. package/assets/hooks/skill-loaded.sh +1 -0
  43. package/assets/hooks/sql-guard.sh +606 -56
  44. package/assets/hooks/task-context-load.sh +100 -0
  45. package/assets/hooks/task-flow-guard.sh +118 -0
  46. package/assets/laws/{access.md → application/access.md} +1 -4
  47. package/assets/laws/{locales.md → application/locales.md} +1 -3
  48. package/assets/laws/application/money.md +41 -0
  49. package/assets/laws/application/ownership.md +32 -0
  50. package/assets/laws/{search-visibility.md → application/search-visibility.md} +1 -1
  51. package/assets/laws/code-structure.md +7 -6
  52. package/assets/laws/delivery.md +53 -3
  53. package/assets/laws/entity-editing.md +49 -55
  54. package/assets/laws/entity-models.md +4 -14
  55. package/assets/laws/frontend-application.md +5 -5
  56. package/assets/laws/lib-imports.md +14 -1
  57. package/assets/laws/lists.md +33 -0
  58. package/assets/laws/navigation.md +40 -0
  59. package/assets/laws/project-documentation.md +27 -8
  60. package/assets/laws/reuse-first.md +26 -21
  61. package/assets/laws/shared-code.md +13 -1
  62. package/assets/laws/verifiability.md +30 -1
  63. package/assets/laws/work-conduct.md +59 -0
  64. package/assets/patterns/admin-lists-screen.md +131 -0
  65. package/assets/patterns/admin-nav-item.md +71 -0
  66. package/assets/patterns/angular-patterns-state.md +29 -22
  67. package/assets/patterns/api-layer-pair.md +40 -30
  68. package/assets/patterns/browser-verification-measure.md +41 -38
  69. package/assets/patterns/browser-verification-stand.md +106 -42
  70. package/assets/patterns/component-structure-new.md +33 -32
  71. package/assets/patterns/dependencies-upgrade.md +65 -0
  72. package/assets/patterns/doc-style-sweep.md +65 -28
  73. package/assets/patterns/doc-style-write.md +36 -33
  74. package/assets/patterns/entity-aside.md +136 -0
  75. package/assets/patterns/entity-models-new.md +124 -0
  76. package/assets/patterns/entity-store.md +91 -0
  77. package/assets/patterns/git-workflow-commit.azure.md +259 -0
  78. package/assets/patterns/git-workflow-commit.github.md +337 -0
  79. package/assets/patterns/git-workflow-commit.gitlab.md +283 -0
  80. package/assets/patterns/git-workflow-merge.md +42 -25
  81. package/assets/patterns/git-workflow-migration.md +61 -31
  82. package/assets/patterns/git-workflow-restart.md +20 -20
  83. package/assets/patterns/lib-layers-move.md +50 -32
  84. package/assets/patterns/lib-layers-new.md +41 -29
  85. package/assets/patterns/ownership-scope-resolve.md +69 -0
  86. package/assets/patterns/permissions-procedure.md +35 -33
  87. package/assets/patterns/platform-access-di.md +39 -25
  88. package/assets/patterns/pricing-quote.md +71 -0
  89. package/assets/patterns/reuse-first-extend.md +22 -22
  90. package/assets/patterns/seo-page.md +52 -40
  91. package/assets/patterns/seo-verify.md +48 -29
  92. package/assets/patterns/shared-code-new.md +37 -31
  93. package/assets/patterns/spec-driven-domain.md +60 -37
  94. package/assets/patterns/spec-driven-rule.md +55 -40
  95. package/assets/patterns/styling-bem-component.md +43 -32
  96. package/assets/patterns/styling-bem-layout.md +30 -24
  97. package/assets/patterns/task-flow-close.md +154 -0
  98. package/assets/patterns/task-flow-resume.md +94 -0
  99. package/assets/patterns/task-flow-start.md +129 -0
  100. package/assets/patterns/testing-e2e.md +53 -51
  101. package/assets/patterns/testing-unit.md +70 -46
  102. package/assets/patterns/translations-key.md +32 -19
  103. package/assets/patterns/ts-procedure.md +24 -25
  104. package/assets/rules/angular-patterns.md +50 -27
  105. package/assets/rules/api-layer.md +46 -28
  106. package/assets/rules/browser-verification.md +67 -48
  107. package/assets/rules/component-structure.md +43 -27
  108. package/assets/rules/dependencies.md +66 -0
  109. package/assets/rules/doc-style.md +95 -39
  110. package/assets/rules/entity-conventions.md +78 -0
  111. package/assets/rules/entity-models.md +70 -0
  112. package/assets/rules/git-workflow.azure.md +116 -0
  113. package/assets/rules/git-workflow.github.md +123 -0
  114. package/assets/rules/git-workflow.gitlab.md +113 -0
  115. package/assets/rules/lib-layers.md +56 -30
  116. package/assets/rules/lists.md +73 -0
  117. package/assets/rules/navigation.md +78 -0
  118. package/assets/rules/ownership-scope.md +63 -0
  119. package/assets/rules/permissions.md +43 -25
  120. package/assets/rules/platform-access.md +57 -29
  121. package/assets/rules/pricing.md +64 -0
  122. package/assets/rules/reuse-first.md +57 -43
  123. package/assets/rules/seo.md +51 -30
  124. package/assets/rules/shared-code.md +51 -26
  125. package/assets/rules/spec-driven.md +107 -51
  126. package/assets/rules/styling-bem.md +54 -39
  127. package/assets/rules/task-flow.md +150 -0
  128. package/assets/rules/testing.md +78 -47
  129. package/assets/rules/translations.md +48 -31
  130. package/assets/rules/typescript-conventions.md +57 -27
  131. package/assets/skills/agent-kit.md +85 -0
  132. package/assets/skills/write-a-skill.md +108 -0
  133. package/assets/templates/gate-map.sh +23 -15
  134. package/assets/templates/implementation.md +14 -8
  135. package/assets/templates/pattern.md +1 -1
  136. package/assets/templates/project.sh +32 -19
  137. package/assets/templates/rule.md +2 -2
  138. package/assets/variants.json +20 -0
  139. package/assets/workflows/feature.js +134 -0
  140. package/assets/workflows/plan.js +150 -0
  141. package/bin/agent-kit.d.ts.map +1 -1
  142. package/bin/agent-kit.js +78 -5
  143. package/bin/agent-kit.js.map +1 -1
  144. package/bin/prompt.d.ts +5 -0
  145. package/bin/prompt.d.ts.map +1 -1
  146. package/bin/prompt.js +19 -7
  147. package/bin/prompt.js.map +1 -1
  148. package/index.d.ts +1 -0
  149. package/index.d.ts.map +1 -1
  150. package/index.js +1 -0
  151. package/index.js.map +1 -1
  152. package/lib/assets.d.ts +8 -3
  153. package/lib/assets.d.ts.map +1 -1
  154. package/lib/assets.js +13 -3
  155. package/lib/assets.js.map +1 -1
  156. package/lib/catalog.d.ts +52 -5
  157. package/lib/catalog.d.ts.map +1 -1
  158. package/lib/catalog.js +104 -16
  159. package/lib/catalog.js.map +1 -1
  160. package/lib/commands.d.ts +22 -1
  161. package/lib/commands.d.ts.map +1 -1
  162. package/lib/commands.js +202 -14
  163. package/lib/commands.js.map +1 -1
  164. package/lib/companion.d.ts +5 -1
  165. package/lib/companion.d.ts.map +1 -1
  166. package/lib/companion.js +29 -2
  167. package/lib/companion.js.map +1 -1
  168. package/lib/config.d.ts +26 -9
  169. package/lib/config.d.ts.map +1 -1
  170. package/lib/config.js +41 -15
  171. package/lib/config.js.map +1 -1
  172. package/lib/freshness.d.ts +14 -0
  173. package/lib/freshness.d.ts.map +1 -0
  174. package/lib/freshness.js +116 -0
  175. package/lib/freshness.js.map +1 -0
  176. package/lib/hooks-map.d.ts +27 -0
  177. package/lib/hooks-map.d.ts.map +1 -0
  178. package/lib/hooks-map.js +77 -0
  179. package/lib/hooks-map.js.map +1 -0
  180. package/lib/integrity.d.ts +36 -0
  181. package/lib/integrity.d.ts.map +1 -0
  182. package/lib/integrity.js +44 -0
  183. package/lib/integrity.js.map +1 -0
  184. package/lib/picker.d.ts +11 -1
  185. package/lib/picker.d.ts.map +1 -1
  186. package/lib/picker.js +44 -6
  187. package/lib/picker.js.map +1 -1
  188. package/lib/sync.d.ts +26 -0
  189. package/lib/sync.d.ts.map +1 -1
  190. package/lib/sync.js +59 -4
  191. package/lib/sync.js.map +1 -1
  192. package/lib/variants.d.ts +44 -0
  193. package/lib/variants.d.ts.map +1 -0
  194. package/lib/variants.js +82 -0
  195. package/lib/variants.js.map +1 -0
  196. package/package.json +1 -1
  197. package/rt-tools-agent-kit-0.5.0.tgz +0 -0
  198. package/assets/laws/admin-lists.md +0 -35
  199. package/assets/laws/admin-navigation.md +0 -38
  200. package/assets/patterns/git-workflow-commit.md +0 -175
  201. package/assets/rules/git-workflow.md +0 -106
  202. package/rt-tools-agent-kit-0.3.0.tgz +0 -0
@@ -0,0 +1,124 @@
1
+ ---
2
+ name: entity-models-new
3
+ kind: pattern
4
+ rule: entity-models
5
+ description: Паттерн правила entity-models. Брать при объявлении новой модели сущности и её маппера — готовый неймспейс I<Сущность> с Api, State и Draft, короткий и полный уровни, наследник BaseMapper с typeCast, что делать после правки .proto.
6
+ ---
7
+
8
+ # Объявить модель сущности и её перевод
9
+
10
+ Паттерн правила `entity-models`. Что при этом должно быть верно — закон
11
+ `docs/constitution/entity-models.md`.
12
+
13
+ ## Когда брать
14
+
15
+ - Заводится новая сущность админки.
16
+ - У существующей появляется короткий уровень.
17
+ - Правится маппер или контракт этой сущности.
18
+
19
+ ## Модель — неймспейс в `util` домена
20
+
21
+ Файл `libs/<семья>/<домен>/util/src/lib/models/<сущность>.model.ts`:
22
+
23
+ ```typescript
24
+ export namespace IPromoCode {
25
+ export namespace Short {
26
+ export type Api = PromoCodeListItem;
27
+
28
+ export interface State {
29
+ readonly id: string;
30
+ readonly code: string;
31
+ }
32
+ }
33
+
34
+ export type Api = PromoCodeInfo;
35
+
36
+ export interface State extends Short.State {
37
+ readonly usageCount: number;
38
+ /** Пусто = код действует на любой объект владельца */
39
+ readonly propertyId: string;
40
+ /** 0 = без предела */
41
+ readonly usageLimit: number;
42
+ }
43
+
44
+ /** Что уходит на сервер при сохранении: id пуст — код новый */
45
+ export interface Draft {
46
+ readonly id: string;
47
+ readonly code: string;
48
+ }
49
+ }
50
+
51
+ export enum EPromoDiscountKind {
52
+ Percent = 'percent',
53
+ Amount = 'amount',
54
+ }
55
+ ```
56
+
57
+ Перечисления домена лежат в том же файле, но **вне** неймспейса. Глубже двух уровней
58
+ вложенности не заводить: `IPromoCode.Short.State` читается, третий уровень уже нет.
59
+
60
+ Псевдонимы выборки объявляются там же:
61
+
62
+ ```typescript
63
+ export type Query = IList.Query.State<EPromoCodeSortProperty, EPromoCodeFilterProperty>;
64
+ export type ListResult = IList.Result.State<IPromoCode.State, EPromoCodeSortProperty, EPromoCodeFilterProperty>;
65
+ ```
66
+
67
+ ## Маппер — в `api` домена, свой на каждый уровень
68
+
69
+ Файл `libs/<семья>/<домен>/api/src/lib/mappers/<сущность>-model.mapper.ts`:
70
+
71
+ ```typescript
72
+ export class PromoCodeModelMapper extends BaseMapper<IPromoCode.State> {
73
+ public override mapFrom(raw: IPromoCode.Api): IPromoCode.State {
74
+ return {
75
+ id: this.typeCast.getAsString(raw?.id),
76
+ code: this.typeCast.getAsString(raw?.code),
77
+ usageCount: this.typeCast.getAsNumber(raw?.usageCount),
78
+ usageLimit: this.typeCast.getAsNumber(raw?.usageLimit),
79
+ };
80
+ }
81
+ }
82
+ ```
83
+
84
+ Маппер без состояния и без DI:
85
+
86
+ ```typescript
87
+ readonly #mapper: PromoCodeModelMapper = new PromoCodeModelMapper();
88
+ ```
89
+
90
+ На каждый уровень — свой класс: `PromoCodeShortModelMapper` и `PromoCodeModelMapper`.
91
+
92
+ ## Строковое поле с конечным набором сверяется явно
93
+
94
+ `getAsType` умолчания не принимает: значение вне набора он пишет в консоль и возвращает
95
+ строкой `'unknown'`.
96
+
97
+ ```typescript
98
+ kind: promoDiscountKindOf(raw?.kind) ?? EPromoDiscountKind.Percent,
99
+ ```
100
+
101
+ ## После правки контракта
102
+
103
+ ```bash
104
+ cd libs/common/proto && npx buf lint
105
+ npm run proto:generate
106
+ ```
107
+
108
+ Ни `buf lint`, ни `buf breaking` не входят в `check:all` и в CI — гоняются руками.
109
+ Сгенерированные типы лежат в репозитории, и без перегенерации расхождение вылезет сборкой
110
+ чужого приложения.
111
+
112
+ Снятое поле помечается `reserved` с его номером и именем.
113
+
114
+ ## Частые промахи
115
+
116
+ - `Api` переписан руками вместо псевдонима — разойдётся с контрактом молча.
117
+ - `??` вместо `typeCast` — контракт отдаёт значения по умолчанию, и проверка на `undefined`
118
+ не ловит ничего.
119
+ - `as Type` в маппере — запрещено правилом `typescript-conventions`.
120
+ - `null` или `undefined` в `State` — пустое выражается пустой строкой или нулём, а смысл нуля
121
+ объясняется комментарием рядом с полем.
122
+ - `readonly`-массив отдан в запрос: init-тип сообщения требует изменяемый, модель отдаётся
123
+ копией.
124
+ - Общий тип на админку и сайт — у гостя своя короткая форма записи.
@@ -0,0 +1,91 @@
1
+ ---
2
+ name: entity-store
3
+ kind: pattern
4
+ rule: entity-conventions
5
+ description: Паттерн правила entity-conventions. Брать при заведении или правке стора админки — готовый наследник общей основы списочного стора, обвязка mutate, имена методов от действия, действие со своей занятостью. Не брать для панели — это паттерн entity-aside.
6
+ ---
7
+
8
+ # Стор сущности
9
+
10
+ Паттерн правила `entity-conventions`. Что при этом должно быть верно — закон
11
+ `docs/constitution/entity-editing.md`.
12
+
13
+ ## Когда брать
14
+
15
+ - Заводится `<сущность>.store.ts`.
16
+ - Правится метод загрузки или записи существующего стора.
17
+
18
+ ## Наследник объявляет своё в четырёх строках
19
+
20
+ ```typescript
21
+ @Injectable()
22
+ export class PromoCodesStore extends BaseListStoreService<
23
+ IPromoCodesState,
24
+ string,
25
+ IPromoCode.State,
26
+ EPromoCodeSortProperty,
27
+ EPromoCodeFilterProperty,
28
+ IPromoCode.Draft
29
+ > {
30
+ protected override readonly apiService: PromoCodeApiService = inject(PromoCodeApiService);
31
+ protected override readonly listErrorKey: string = 'promoCodesLoadFailed';
32
+
33
+ constructor() {
34
+ super({ ...INITIAL_STATE.LIST }, { name: 'PromoCodesStore' });
35
+
36
+ this.setConfig({ usePagination: true, useSorting: true, useFiltering: true, useSearch: true });
37
+ }
38
+
39
+ protected override mutationErrorKeyOf(error: unknown): string {
40
+ return promoRejectionKey(error);
41
+ }
42
+ }
43
+ ```
44
+
45
+ Конфиг решает, что уходит в выборку. Выключено всё — выборки нет вовсе, и сервер отдаёт список
46
+ целиком. Из основы работают загрузка и перезапрос, смена страницы, порядка, условий отбора и
47
+ строки поиска, догрузка следующей страницы, правка одной записи в списке и сброс выборки.
48
+
49
+ Файл называется `<сущность>.store.ts`: имя `<сущность>-store.service.ts` выводит его и из
50
+ правила линтера, и из гейта скилов.
51
+
52
+ ## Метод правки отдаёт поток
53
+
54
+ ```typescript
55
+ public save(draft: IPromoCode.Draft): Observable<IPromoCode.State | null> {
56
+ return this.mutate(this.apiService.save(draft));
57
+ }
58
+ ```
59
+
60
+ `mutate` держит занятость, гашение прежней ошибки, перечитывание списка после успеха и ключ
61
+ отказа. Мутация завершается **перечитанным списком**, а не отправленным запросом: панель
62
+ закрывается по значению потока, и список, перечитанный после закрытия, показал бы прежнее
63
+ значение.
64
+
65
+ ## Имена — от действия, а не от домена
66
+
67
+ | ✗ | ✓ |
68
+ | ------------------------------------ | ---------- |
69
+ | `createBooking()`, `updateBooking()` | `save()` |
70
+ | `deleteBooking()`, `removeFeed()` | `remove()` |
71
+ | `loadBookings()`, `fetchFeeds()` | `load()` |
72
+
73
+ Имя домена уже в имени стора и в его алиасе.
74
+
75
+ ## Действие со своей занятостью
76
+
77
+ Идёт мимо `mutate`: опрос подписки на календарь держит `pollingId`, потому что панель на минуту
78
+ опроса не гасится, а ключ отказа кладёт `setErrorKey`.
79
+
80
+ ## Частые промахи
81
+
82
+ - Булев ответ у метода правки: он теряет и записанную запись, и причину отказа — панель узнаёт
83
+ только «не вышло».
84
+ - Своя обвязка занятости и ошибки вокруг вызова сервиса: всё это в `mutate`.
85
+ - Свои сигналы записей, занятости и отказа: они в общей основе.
86
+ - Подписка на сигнал отказа загрузки: сигнал делят список и панель, и один отказ показался бы
87
+ дважды. Отказ загрузки идёт отдельным потоком.
88
+ - Второе поле сортировки: порядок в выборке один — его не принимают ни таблица, ни контракт,
89
+ ни разбор на сервере.
90
+ - Хвост с `EMPTY`, приклеенный к мутации: он гасится `defaultIfEmpty`, иначе отказ приклеенного
91
+ потока превращает удачную запись в вечный спиннер.
@@ -0,0 +1,259 @@
1
+ ---
2
+ name: git-workflow-commit
3
+ kind: pattern
4
+ rule: git-workflow
5
+ description: Паттерн правила git-workflow для дерева в Azure DevOps. Брать на заведение рабочего элемента, ветки, коммит, пуш и создание PR — заведение элемента со всеми шагами, перевод по состояниям, слияние двух задач в одну, сверка очереди работ, работа от учётной записи машинной работы, формат заголовка, привязка PR к элементу, ревьювер, исполнитель и метки, чеклист проверок до публикации, обход требования документа. Не брать для миграций и перезапуска прода — это паттерны git-workflow-migration и git-workflow-restart.
6
+ ---
7
+
8
+ # Ветка, коммит и PR
9
+
10
+ Паттерн правила `git-workflow`. Что при этом должно быть верно — закон
11
+ `docs/constitution/delivery.md`.
12
+
13
+ ## Когда брать
14
+
15
+ - Заводится рабочий элемент, с которого начинается правка.
16
+ - Заводится ветка под него.
17
+ - Готовится коммит или пуш.
18
+ - Открывается PR.
19
+ - Работа перешла на следующий шаг, и элемент переводится в другое состояние.
20
+
21
+ ## Сначала рабочий элемент, потом ветка
22
+
23
+ Заведение состоит из четырёх шагов: элемент, номер в его заголовке, исполнитель, состояние
24
+ `New`. Область и итерация ставятся тут же: элемент без них лежит в корне проекта и на доску
25
+ команды не попадает — заведён, а в очереди работ его нет.
26
+
27
+ Все четыре делает одна команда дерева, а не рука: делить их значит забывать последний.
28
+
29
+ ```bash
30
+ npm run task:new -- --title 'Письма владельцу не уходят молча' \
31
+ --type Bug --area '<проект>\<команда>' --slug mail-owner-silence < описание.md
32
+ ```
33
+
34
+ Скрипт под этой командой заводит проект — пакет её не везёт. Что он делает вызовами `az`:
35
+
36
+ ```bash
37
+ az boards work-item create --type Bug --title '[<номер>] …' \
38
+ --org https://dev.azure.com/<организация> --project <проект> \
39
+ --assigned-to <бот> --area '<проект>\<команда>' --iteration '<проект>\<итерация>'
40
+ az boards work-item update --id <номер> --title '[<номер>] …' # номер известен после создания
41
+ ```
42
+
43
+ Номер в заголовок руками не пишется — он известен только после создания, и команда дописывает
44
+ его сама.
45
+
46
+ Чем сверить, что очередь работ в порядке:
47
+
48
+ ```bash
49
+ npm run check:board
50
+ ```
51
+
52
+ Она смотрит только открытое: состояние, область и исполнителя у каждого открытого элемента, а
53
+ у каждого открытого PR — номер в заголовке, привязанный элемент и то, что второго PR с тем же
54
+ номером нет. Имя ветки не судит: у открытого PR его не переименовать.
55
+
56
+ ## Две задачи, которые чинятся одной правкой
57
+
58
+ Если по ходу выяснилось, что правка закрывает и соседний элемент, — это одна задача, а не две.
59
+ Слить их можно, пока правка не въехала в главную ветку:
60
+
61
+ ```bash
62
+ # то, чего в поглотившем элементе не было, дописывается в его описание
63
+ az boards work-item update --id <поглотивший> --description "$(cat тело.md)"
64
+ # поглощённый связывается с ним как дубликат и закрывается
65
+ az boards work-item relation add --id <поглощённый> --relation-type duplicate-of \
66
+ --target-id <поглотивший>
67
+ az boards work-item update --id <поглощённый> --state 'Removed'
68
+ ```
69
+
70
+ Связь ставится до закрытия: закрытый без неё элемент читается как сделанный, а сделан он не
71
+ был. Состояние снятого зависит от процесса проекта — `Removed` есть в Agile и Scrum, в Basic
72
+ его нет; какое здесь, сказано в `implementation.md`.
73
+
74
+ После слияния ветки поглощения нет: она въехала, и откатывается целиком.
75
+
76
+ ## Ветка заводится отдельным вызовом
77
+
78
+ Гард главной ветки разбирает текст команды и смотрит ветку на момент запуска, поэтому
79
+ составная команда отклоняется целиком — ветки в ней ещё нет:
80
+
81
+ ```bash
82
+ ✗ git checkout -b 85-guest-token && git commit -m 'feat(admin): …'
83
+ ✓ git checkout -b 85-guest-token
84
+ ✓ git commit -F -
85
+ ```
86
+
87
+ Имя несёт номер элемента, slug строчными латинскими через дефис; точная форма — в
88
+ `implementation.md`. Гард поставки разбирает её на месте и отбивает промах до первого коммита,
89
+ а по номеру спрашивает доску: элемент должен существовать, быть открытым и иметь исполнителя.
90
+
91
+ Имя без номера (`feat/…`, `fix/…`) законно, пока ветка живёт локально — под пробу и разбор.
92
+ PR с неё не откроется: правка, доезжающая до главной ветки, начинается с задачи.
93
+
94
+ ## Состояние элемента двигается вместе с работой
95
+
96
+ Ветка заведена — элемент уже не `New`, а `Active`. PR открыт — он ждёт разбора. Оба перевода
97
+ делает одна команда, вторым вызовом сразу за тем, который его вызвал:
98
+
99
+ ```bash
100
+ npm run task:move -- 86 in-progress # сразу после git checkout -b 86-…
101
+ npm run task:move -- 86 in-review # сразу после az repos pr create
102
+ ```
103
+
104
+ Под ней — правка поля состояния:
105
+
106
+ ```bash
107
+ az boards work-item update --id 86 --state 'Active'
108
+ ```
109
+
110
+ Имена состояний берутся у процесса проекта, а не назначаются правилом: Agile, Scrum и Basic
111
+ называют одни и те же три шага по-разному, и перевод в состояние, которого в процессе нет,
112
+ отвечает отказом на каждой задаче подряд.
113
+
114
+ Перевод не откладывается на потом: очередь работ читают между шагами, а не после них.
115
+
116
+ ## Коммит подписывается учётной записью машинной работы
117
+
118
+ Токен читается в переменную и не печатается; автор и коммиттер задаются переменными той же
119
+ команды. `git config` не годится — конфиг общий с основным деревом и переписал бы подпись
120
+ владельцу:
121
+
122
+ ```bash
123
+ TOKEN=$(tr -d '\n' < ~/.config/<дерево>-bot-token)
124
+
125
+ GIT_AUTHOR_NAME="<бот>" GIT_AUTHOR_EMAIL="<почта бота>" \
126
+ GIT_COMMITTER_NAME="<бот>" GIT_COMMITTER_EMAIL="<почта бота>" \
127
+ git commit -F -
128
+ ```
129
+
130
+ Заголовок — `type(scope): description`. Типы: `feat`, `fix`, `refactor`, `docs`, `style`,
131
+ `test`, `chore`, `perf`. Области — свои у дерева, они перечислены в `implementation.md`. Точка
132
+ в конце заголовка не принимается.
133
+
134
+ Строка `AB#<номер>` в теле коммита привязывает его к рабочему элементу. Она не заменяет
135
+ привязки самого PR: коммит связывается с элементом, а очередь работ читает связь PR.
136
+
137
+ ## Документ едет тем же коммитом
138
+
139
+ `docs-guard` требует пару и называет её сам. Обход — строка в теле, причина обязательна:
140
+
141
+ ```
142
+ Docs-skip: правка только в тестах хука, зеркала у него нет
143
+ ```
144
+
145
+ ## Номер элемента стоит в его заголовке и в заголовке PR
146
+
147
+ Форма одна на оба — `[<номер>] <текст>`. Номер стоит в самом заголовке, а не только в теле: в
148
+ списке PR тела не видно. Тот же номер несёт и имя ветки, поэтому элемент, ветка и PR читаются
149
+ как одно.
150
+
151
+ Элемент говорит, что не так; PR тем же номером отчитывается, что сделано:
152
+
153
+ ```
154
+ элемент [86] Пустой адрес владельца — письма не уходят молча
155
+ PR [86] Письмо владельцу с незаполненным адресом попадает в логи
156
+ ```
157
+
158
+ Инфинитив в заголовок PR не переносится: «исправить» становится «исправлено», «вернуть» —
159
+ «возвращено», «добавить» — «добавлено».
160
+
161
+ Тип и область — `fix(site):`, `docs(common):` — в заголовок PR не идут: это формат заголовка
162
+ коммита, и там его сверяет `commitlint`.
163
+
164
+ ## PR привязывается к элементу при создании
165
+
166
+ Привязка задаётся флагом, а не правкой после: у токена может не быть права править чужой
167
+ элемент, и вторая команда обойдётся молча, оставив PR ни с чем не связанным.
168
+
169
+ ```bash
170
+ AZURE_DEVOPS_EXT_PAT="$TOKEN" az repos pr create \
171
+ --title '[86] Письмо владельцу с незаполненным адресом попадает в логи' \
172
+ --source-branch 86-mail-owner-silence --target-branch main \
173
+ --work-items 86 --reviewers <владелец> --delete-source-branch true \
174
+ --description 'Закрывает рабочий элемент 86.'
175
+ ```
176
+
177
+ Ревьювер — всегда владелец: без запроса разбора PR не показывается ему в очереди. Один PR
178
+ закрывает элемент целиком — половину задачи одним PR не выкатывают: у задачи одна ветка, и
179
+ работа, которая в неё не влезает, делится на задачи до того, как ветка заводится.
180
+
181
+ У уже открытого PR то же ставится правкой:
182
+
183
+ ```bash
184
+ az repos pr work-item add --id 205 --work-items 86
185
+ az repos pr reviewer add --id 205 --reviewers <владелец>
186
+ az repos pr update --id 205 --description "$(cat тело.md)"
187
+ ```
188
+
189
+ Правка описания переписывает его целиком. Тело перечитывается всякий раз, когда в ветку что-то
190
+ влилось после публикации: отчёт утверждает про дерево, а дерево с тех пор изменилось.
191
+
192
+ ## Состояние PR читается, а не додумывается
193
+
194
+ Команды правки отвечают нулевым кодом и тогда, когда ничего не сделали. Поэтому после них PR
195
+ перечитывают:
196
+
197
+ ```bash
198
+ az repos pr show --id 205 \
199
+ --query '{author: createdBy.uniqueName, reviewers: reviewers[].uniqueName, work: workItemRefs[].id}'
200
+ ```
201
+
202
+ Владельцу называют то, что прочитали, а не то, что заказывали.
203
+
204
+ Открытый PR означает, что элемент ждёт разбора, — состояние переставляется тем же движением:
205
+
206
+ ```bash
207
+ npm run task:move -- 86 in-review
208
+ ```
209
+
210
+ ## Что проверяется до публикации PR
211
+
212
+ Конвейер видит только отправленное, а отправляется оно пушем. Линтеры, юниты и сценарии хуков
213
+ снимает гейт пуша — ниже то, чего он не знает.
214
+
215
+ 1. **В ветке только та правка, за которой её заводили** — `git diff main...HEAD --stat`. Чужой
216
+ домен в списке файлов означает, что правка расползлась, и её надо вернуть в свои границы.
217
+ 2. **Ни мока, ни подменённого ответа, ни отладочной строки** — `git diff main...HEAD` читается
218
+ целиком, а не по именам файлов. На прод они уезжают молча и портят настоящие данные.
219
+ 3. **Документ едет тем же коммитом.** Пару называет `docs-guard`, но спек домена и правку его
220
+ поведения он не знает — это остаётся за автором.
221
+ 4. **Проверки текстов и раскладки зелёные** — те, что дерево завело в `tools/`. Какие именно
222
+ есть здесь — `implementation.md` правила.
223
+ 5. **Все приложения дерева собираются** — `nx build` по каждому. Гейт пуша сборку не гоняет.
224
+ 6. **Видимый текст заведён во всех локалях перевода** — тестом полноты словарей, если дерево
225
+ переводится.
226
+ 7. **Правка вёрстки подтверждена замером**, а не взглядом, и снята при узком экране — паттерн
227
+ `browser-verification-measure`.
228
+ 8. **Правка разметки публичного сайта проверена на прод-сборке по всем локалям перевода** —
229
+ паттерн `seo-verify`.
230
+ 9. **PR привязан к рабочему элементу**, ревьювер и исполнитель стоят.
231
+ 10. **Заголовок PR несёт номер элемента и называет работу сделанной:** `[<номер>] <Что
232
+ сделано>`, тем же номером, что стоит у элемента и в имени ветки.
233
+ 11. **Очередь работ сходится** — `npm run check:board`.
234
+ 12. **Состояние PR прочитано, а не выведено из кодов возврата.**
235
+
236
+ Сразу после публикации элемент переводится в разбор, и сверка очереди прогоняется ещё раз: до
237
+ открытия PR состояние она не судит, а после открытия расхождение видит.
238
+
239
+ Сделанное рассуждением и сделанное замером в теле PR разводятся прямо: непроверенное,
240
+ названное проверенным, ревьювер принимает за проверенное.
241
+
242
+ ## Частые промахи
243
+
244
+ - Область и итерация не заданы: элемент заведён, но на доску команды не попал.
245
+ - Состояние взято не из процесса проекта: перевод отвечает отказом на каждой задаче, и это
246
+ читается как сломанная команда, а не как неверное имя состояния.
247
+ - PR открыт без `--work-items`: связи нет, и по очереди работ не видно, за чем эта правка.
248
+ - `AB#<номер>` в коммите принят за привязку PR: он связывает коммит, а очередь читает связь PR.
249
+ - `git add` с несколькими путями не добавляет ничего, если хоть один путь не существует:
250
+ команда обрывается на первом промахе целиком. Следующий `git commit --amend` при этом уносит
251
+ в коммит всё, что осталось в индексе. Состав коммита читается `git show --stat` сразу после
252
+ него, а не на разборе PR.
253
+ - PR открыт без ревьювера: он не попадает во входящие владельца, и очередь стоит, выглядя
254
+ работающей.
255
+ - `--delete-source-branch` забыт: ветки задач копятся в репозитории, и по списку веток больше
256
+ не видно, какая работа идёт сейчас.
257
+ - Автозавершение включено до того, как прогнаны проверки до пуша: конвейер зелёный на том, что
258
+ он умеет, и слияние происходит без всего остального.
259
+ - Правка владельца ни токена, ни переменных не берёт — они только для машинной работы.