@rt-tools/agent-kit 0.2.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (203) hide show
  1. package/README.md +235 -18
  2. package/assets/agents/business-analyst.md +74 -0
  3. package/assets/agents/project-manager.md +70 -0
  4. package/assets/agents/qa-engineer.md +72 -0
  5. package/assets/agents/skill-curator.md +110 -0
  6. package/assets/agents/spec-critic.md +44 -0
  7. package/assets/agents/spec-writer.md +50 -0
  8. package/assets/checks/board.github.mjs +286 -0
  9. package/assets/checks/check-board.github.mjs +188 -0
  10. package/assets/checks/check-doc-paths.mjs +163 -0
  11. package/assets/checks/check-dupes.mjs +277 -0
  12. package/assets/checks/check-lib-layers.mjs +573 -0
  13. package/assets/checks/check-reuse.mjs +208 -0
  14. package/assets/checks/check-schema-drift.mjs +186 -0
  15. package/assets/checks/check-specs.mjs +1007 -0
  16. package/assets/checks/check-styles.mjs +109 -0
  17. package/assets/checks/rt-kit-checks.config.mjs +134 -0
  18. package/assets/checks/task-new.github.mjs +198 -0
  19. package/assets/commands/skill-curator.md +70 -0
  20. package/assets/defaults/gate-map.sh +100 -0
  21. package/assets/defaults/project.sh +179 -0
  22. package/assets/hooks/browser-device-id.sh +20 -0
  23. package/assets/hooks/browser-guard-device-id.sh +28 -0
  24. package/assets/hooks/browser-guard-no-asking.sh +27 -0
  25. package/assets/hooks/browser-guard-no-listing.sh +18 -0
  26. package/assets/hooks/browser-guard-no-other-drivers.sh +79 -0
  27. package/assets/hooks/browser-guard-require-select.sh +54 -0
  28. package/assets/hooks/commit-msg.sh +26 -0
  29. package/assets/hooks/constitution-index.sh +43 -0
  30. package/assets/hooks/dev-server-guard.sh +115 -0
  31. package/assets/hooks/docs-guard.sh +282 -0
  32. package/assets/hooks/git-guard-delivery.sh +167 -0
  33. package/assets/hooks/git-guard-main.sh +73 -0
  34. package/assets/hooks/git-guard-push-tests.sh +94 -0
  35. package/assets/hooks/glossary-load.sh +23 -0
  36. package/assets/hooks/lint-after-edit.sh +219 -0
  37. package/assets/hooks/qa-dataid-guard.sh +121 -0
  38. package/assets/hooks/reuse-first-guard.sh +154 -0
  39. package/assets/hooks/skill-gate-rearm.sh +23 -0
  40. package/assets/hooks/skill-gate.sh +128 -0
  41. package/assets/hooks/skill-loaded.sh +21 -0
  42. package/assets/hooks/sql-guard.sh +679 -0
  43. package/assets/hooks/task-context-load.sh +100 -0
  44. package/assets/hooks/task-flow-guard.sh +107 -0
  45. package/assets/laws/{access.md → application/access.md} +1 -4
  46. package/assets/laws/{locales.md → application/locales.md} +1 -3
  47. package/assets/laws/application/money.md +41 -0
  48. package/assets/laws/application/ownership.md +32 -0
  49. package/assets/laws/{search-visibility.md → application/search-visibility.md} +1 -1
  50. package/assets/laws/code-structure.md +7 -6
  51. package/assets/laws/delivery.md +53 -3
  52. package/assets/laws/entity-editing.md +49 -55
  53. package/assets/laws/entity-models.md +4 -14
  54. package/assets/laws/frontend-application.md +5 -5
  55. package/assets/laws/lib-imports.md +14 -1
  56. package/assets/laws/lists.md +33 -0
  57. package/assets/laws/navigation.md +40 -0
  58. package/assets/laws/project-documentation.md +17 -8
  59. package/assets/laws/reuse-first.md +26 -21
  60. package/assets/laws/shared-code.md +13 -1
  61. package/assets/laws/verifiability.md +17 -1
  62. package/assets/laws/work-conduct.md +48 -0
  63. package/assets/patterns/admin-lists-screen.md +131 -0
  64. package/assets/patterns/admin-nav-item.md +71 -0
  65. package/assets/patterns/angular-patterns-state.md +101 -0
  66. package/assets/patterns/api-layer-pair.md +88 -0
  67. package/assets/patterns/browser-verification-measure.md +86 -0
  68. package/assets/patterns/browser-verification-stand.md +143 -0
  69. package/assets/patterns/component-structure-new.md +99 -0
  70. package/assets/patterns/dependencies-upgrade.md +65 -0
  71. package/assets/patterns/doc-style-sweep.md +137 -0
  72. package/assets/patterns/doc-style-write.md +109 -0
  73. package/assets/patterns/entity-aside.md +136 -0
  74. package/assets/patterns/entity-models-new.md +124 -0
  75. package/assets/patterns/entity-store.md +91 -0
  76. package/assets/patterns/git-workflow-commit.azure.md +259 -0
  77. package/assets/patterns/git-workflow-commit.github.md +333 -0
  78. package/assets/patterns/git-workflow-commit.gitlab.md +283 -0
  79. package/assets/patterns/git-workflow-merge.md +99 -0
  80. package/assets/patterns/git-workflow-migration.md +88 -0
  81. package/assets/patterns/git-workflow-restart.md +49 -0
  82. package/assets/patterns/lib-layers-move.md +95 -0
  83. package/assets/patterns/lib-layers-new.md +82 -0
  84. package/assets/patterns/ownership-scope-resolve.md +69 -0
  85. package/assets/patterns/permissions-procedure.md +71 -0
  86. package/assets/patterns/platform-access-di.md +84 -0
  87. package/assets/patterns/pricing-quote.md +71 -0
  88. package/assets/patterns/reuse-first-extend.md +73 -0
  89. package/assets/patterns/seo-page.md +104 -0
  90. package/assets/patterns/seo-verify.md +83 -0
  91. package/assets/patterns/shared-code-new.md +86 -0
  92. package/assets/patterns/spec-driven-domain.md +107 -0
  93. package/assets/patterns/spec-driven-rule.md +127 -0
  94. package/assets/patterns/styling-bem-component.md +88 -0
  95. package/assets/patterns/styling-bem-layout.md +73 -0
  96. package/assets/patterns/task-flow-close.md +90 -0
  97. package/assets/patterns/task-flow-resume.md +94 -0
  98. package/assets/patterns/task-flow-start.md +117 -0
  99. package/assets/patterns/testing-e2e.md +92 -0
  100. package/assets/patterns/testing-unit.md +117 -0
  101. package/assets/patterns/translations-key.md +64 -0
  102. package/assets/patterns/ts-procedure.md +65 -0
  103. package/assets/rules/angular-patterns.md +71 -0
  104. package/assets/rules/api-layer.md +71 -0
  105. package/assets/rules/browser-verification.md +87 -0
  106. package/assets/rules/component-structure.md +64 -0
  107. package/assets/rules/dependencies.md +66 -0
  108. package/assets/rules/doc-style.md +103 -0
  109. package/assets/rules/entity-conventions.md +78 -0
  110. package/assets/rules/entity-models.md +70 -0
  111. package/assets/rules/git-workflow.azure.md +116 -0
  112. package/assets/rules/git-workflow.github.md +123 -0
  113. package/assets/rules/git-workflow.gitlab.md +113 -0
  114. package/assets/rules/lib-layers.md +80 -0
  115. package/assets/rules/lists.md +73 -0
  116. package/assets/rules/navigation.md +78 -0
  117. package/assets/rules/ownership-scope.md +63 -0
  118. package/assets/rules/permissions.md +70 -0
  119. package/assets/rules/platform-access.md +77 -0
  120. package/assets/rules/pricing.md +64 -0
  121. package/assets/rules/reuse-first.md +83 -0
  122. package/assets/rules/seo.md +71 -0
  123. package/assets/rules/shared-code.md +70 -0
  124. package/assets/rules/spec-driven.md +135 -0
  125. package/assets/rules/styling-bem.md +74 -0
  126. package/assets/rules/task-flow.md +110 -0
  127. package/assets/rules/testing.md +100 -0
  128. package/assets/rules/translations.md +69 -0
  129. package/assets/rules/typescript-conventions.md +76 -0
  130. package/assets/skills/agent-kit.md +81 -0
  131. package/assets/skills/write-a-skill.md +108 -0
  132. package/assets/templates/gate-map.sh +45 -0
  133. package/assets/templates/implementation.md +44 -0
  134. package/assets/templates/pattern.md +5 -1
  135. package/assets/templates/project.sh +54 -0
  136. package/assets/templates/rule.md +12 -23
  137. package/assets/variants.json +20 -0
  138. package/assets/workflows/feature.js +134 -0
  139. package/assets/workflows/plan.js +150 -0
  140. package/bin/agent-kit.d.ts.map +1 -1
  141. package/bin/agent-kit.js +78 -5
  142. package/bin/agent-kit.js.map +1 -1
  143. package/bin/prompt.d.ts +5 -0
  144. package/bin/prompt.d.ts.map +1 -1
  145. package/bin/prompt.js +19 -7
  146. package/bin/prompt.js.map +1 -1
  147. package/index.d.ts +1 -0
  148. package/index.d.ts.map +1 -1
  149. package/index.js +1 -0
  150. package/index.js.map +1 -1
  151. package/lib/assets.d.ts +14 -1
  152. package/lib/assets.d.ts.map +1 -1
  153. package/lib/assets.js +23 -2
  154. package/lib/assets.js.map +1 -1
  155. package/lib/catalog.d.ts +52 -5
  156. package/lib/catalog.d.ts.map +1 -1
  157. package/lib/catalog.js +104 -16
  158. package/lib/catalog.js.map +1 -1
  159. package/lib/commands.d.ts +22 -1
  160. package/lib/commands.d.ts.map +1 -1
  161. package/lib/commands.js +218 -11
  162. package/lib/commands.js.map +1 -1
  163. package/lib/companion.d.ts +57 -0
  164. package/lib/companion.d.ts.map +1 -0
  165. package/lib/companion.js +60 -0
  166. package/lib/companion.js.map +1 -0
  167. package/lib/config.d.ts +42 -2
  168. package/lib/config.d.ts.map +1 -1
  169. package/lib/config.js +60 -2
  170. package/lib/config.js.map +1 -1
  171. package/lib/freshness.d.ts +14 -0
  172. package/lib/freshness.d.ts.map +1 -0
  173. package/lib/freshness.js +116 -0
  174. package/lib/freshness.js.map +1 -0
  175. package/lib/hooks-map.d.ts +24 -0
  176. package/lib/hooks-map.d.ts.map +1 -0
  177. package/lib/hooks-map.js +72 -0
  178. package/lib/hooks-map.js.map +1 -0
  179. package/lib/integrity.d.ts +36 -0
  180. package/lib/integrity.d.ts.map +1 -0
  181. package/lib/integrity.js +44 -0
  182. package/lib/integrity.js.map +1 -0
  183. package/lib/picker.d.ts +11 -1
  184. package/lib/picker.d.ts.map +1 -1
  185. package/lib/picker.js +44 -6
  186. package/lib/picker.js.map +1 -1
  187. package/lib/stamp.d.ts +2 -5
  188. package/lib/stamp.d.ts.map +1 -1
  189. package/lib/stamp.js +25 -10
  190. package/lib/stamp.js.map +1 -1
  191. package/lib/sync.d.ts +29 -0
  192. package/lib/sync.d.ts.map +1 -1
  193. package/lib/sync.js +78 -4
  194. package/lib/sync.js.map +1 -1
  195. package/lib/variants.d.ts +44 -0
  196. package/lib/variants.d.ts.map +1 -0
  197. package/lib/variants.js +82 -0
  198. package/lib/variants.js.map +1 -0
  199. package/package.json +1 -1
  200. package/rt-tools-agent-kit-0.4.0.tgz +0 -0
  201. package/assets/laws/admin-lists.md +0 -35
  202. package/assets/laws/admin-navigation.md +0 -38
  203. package/rt-tools-agent-kit-0.2.0.tgz +0 -0
@@ -0,0 +1,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
+ - Правка владельца ни токена, ни переменных не берёт — они только для машинной работы.
@@ -0,0 +1,333 @@
1
+ ---
2
+ name: git-workflow-commit
3
+ kind: pattern
4
+ rule: git-workflow
5
+ description: Паттерн правила git-workflow. Брать на заведение задачи, ветки, коммит, пуш и создание 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
+ Заведение состоит из четырёх шагов: issue, номер в его заголовке, добавление на борду,
24
+ первая колонка. Борда к репозиторию не привязана — `projectsV2` у него пуст, — поэтому третий
25
+ шаг сам не случается, и задача без него не видна ни в очереди работ, ни владельцу: так две
26
+ задачи и простояли месяц.
27
+
28
+ Все четыре шага делает одна команда дерева, а не рука: делить их значит забывать третий.
29
+
30
+ ```bash
31
+ npm run task:new -- --title 'Письма владельцу не уходят молча' \
32
+ --label bug --label area:api --slug mail-owner-silence < описание.md
33
+ ```
34
+
35
+ Тело читается со стандартного ввода, `--slug` необязателен и идёт только в подсказку с именем
36
+ ветки. Автор и исполнитель — учётная запись машинной работы; токен команда читает сама, из
37
+ файла вне репозитория.
38
+
39
+ Скрипт под этой командой заводит проект — пакет её не везёт. Что он делает вызовами `gh`:
40
+
41
+ ```bash
42
+ gh issue create --title '[<КЛЮЧ>-<номер>] …' --label bug --assignee <бот> --body-file -
43
+ gh issue edit <номер> --title '[<КЛЮЧ>-<номер>] …' # номер известен только после создания
44
+ gh project item-add <номер борды> --owner <владелец> --url <адрес issue>
45
+ ```
46
+
47
+ Последний шаг и есть тот, который забывается: без него задача заведена, но её нет в очереди.
48
+
49
+ Название тикета говорит, что не так, а не что сделать: PR потом переводит его в сделанное.
50
+ Номер в заголовок руками не пишется — он известен только после создания, и команда дописывает
51
+ его сама.
52
+
53
+ Чем сверить, что очередь работ в порядке:
54
+
55
+ ```bash
56
+ npm run check:board
57
+ ```
58
+
59
+ Она смотрит только открытое: тикеты на борде, номер и исполнителя у каждой открытой задачи,
60
+ а у каждого открытого PR — номер в заголовке, строку `Closes`, открытую задачу за ним и то,
61
+ что второго PR с тем же номером нет. Имя ветки не судит: у открытого PR его не переименовать.
62
+
63
+ Закрытые задачи сверка на борде не ищет, и добавлять их туда задним числом не надо: закрытая
64
+ задача уходит из очереди мержем, а колонки под неё у борды нет. О закрытой задаче сверка
65
+ помнит только одно — её папку в `docs/tasks/`.
66
+
67
+ ## Две задачи, которые чинятся одной правкой
68
+
69
+ Если по ходу выяснилось, что правка закрывает и соседнюю задачу, — это одна задача, а не две.
70
+ Слить их можно, пока правка не въехала в главную ветку:
71
+
72
+ ```bash
73
+ GH=/opt/homebrew/bin/gh
74
+ # то, чего в поглотившей задаче не было, дописывается в её тело
75
+ $GH api -X PATCH repos/<владелец>/<репозиторий>/issues/<поглотившая> -f body="$(cat тело.md)"
76
+ # поглощённая стирается вместе с номером — две строки об одной работе хуже дыры в нумерации
77
+ $GH api graphql -f query='mutation { deleteIssue(input: {issueId: "<node-id>"})
78
+ { repository { name } } }'
79
+ ```
80
+
81
+ Удаление необратимо и уносит с собой ссылки вида `Closes #<номер>` из чужих тел — поэтому
82
+ сначала правится поглотившая задача, и только потом стирается поглощённая. После мержа
83
+ поглощения нет: ветка въехала, и откатывается она целиком.
84
+
85
+ ## Ветка заводится отдельным вызовом
86
+
87
+ Гард главной ветки разбирает текст команды и смотрит ветку на момент запуска, поэтому
88
+ составная команда отклоняется целиком — ветки в ней ещё нет:
89
+
90
+ ```bash
91
+ ✗ git checkout -b <КЛЮЧ>-85-guest-token && git commit -m 'feat(admin): …'
92
+ ✓ git checkout -b <КЛЮЧ>-85-guest-token
93
+ ✓ git commit -F -
94
+ ```
95
+
96
+ Имя — `<КЛЮЧ>-<номер задачи>-<короткий-slug>`, slug строчными латинскими через дефис. Гард
97
+ поставки разбирает его на месте и отбивает промах в форме до первого коммита, а по номеру
98
+ спрашивает борду: задача должна существовать, быть открытой, стоять в очереди и иметь
99
+ исполнителя.
100
+
101
+ Имя без номера (`feat/…`, `fix/…`) законно, пока ветка живёт локально — под пробу и разбор.
102
+ PR с неё не откроется: правка, доезжающая до главной ветки, начинается с задачи.
103
+
104
+ ## Колонка задачи двигается вместе с работой
105
+
106
+ Ветка заведена — задача уже не в `📋 Backlog`, а в работе. PR открыт — она ждёт разбора.
107
+ Оба перевода делает одна команда, вторым вызовом сразу за тем, который его вызвал:
108
+
109
+ ```bash
110
+ npm run task:move -- 86 in-progress # сразу после git checkout -b <КЛЮЧ>-86-…
111
+ npm run task:move -- 86 in-review # сразу после gh pr create
112
+ ```
113
+
114
+ Колонки под своими именами: `backlog`, `in-progress`, `in-review`, а ещё `new`, `ready`,
115
+ `done` и `deployed`, через которые работа не проходит — закрытая задача уходит из очереди
116
+ мержем. Команда правит борду под ботом, читает токен сама и печатает, откуда куда переставила;
117
+ задачи не на борде и незнакомой колонки не принимает.
118
+
119
+ Перевод не откладывается на потом: очередь работ читают между шагами, а не после них. Задача
120
+ с открытым PR простояла в `📋 Backlog` до самой сверки — всё это время она выглядела
121
+ нетронутой, а разбора за неё никто не ждал.
122
+
123
+ ## Коммит подписывается ботом
124
+
125
+ Токен читается в переменную и не печатается; автор и коммиттер задаются переменными той же
126
+ команды. `git config` не годится — конфиг общий с основным деревом и переписал бы подпись
127
+ владельцу:
128
+
129
+ ```bash
130
+ TOKEN=$(tr -d '\n' < ~/.config/<дерево>-bot-token)
131
+
132
+ GIT_AUTHOR_NAME="<бот>" GIT_AUTHOR_EMAIL="<номер>+<бот>@users.noreply.github.com" \
133
+ GIT_COMMITTER_NAME="<бот>" GIT_COMMITTER_EMAIL="<номер>+<бот>@users.noreply.github.com" \
134
+ git commit -F -
135
+ ```
136
+
137
+ Заголовок — `type(scope): description`. Типы: `feat`, `fix`, `refactor`, `docs`, `style`,
138
+ `test`, `chore`, `perf`. Области: `site`, `admin`, `api`, `common`, `proto`, `deploy`. Точка в
139
+ конце заголовка не принимается, длина — до 150 знаков.
140
+
141
+ ```
142
+ feat(site): availability calendar with season prices
143
+ fix(api): reject overlapping booking dates
144
+ chore(deploy): docker-compose for vps
145
+ ```
146
+
147
+ ## Документ едет тем же коммитом
148
+
149
+ `docs-guard` требует пару и называет её сам. Обход — строка в теле, причина обязательна:
150
+
151
+ ```
152
+ Docs-skip: правка только в тестах хука, зеркала у него нет
153
+ ```
154
+
155
+ ## Номер задачи стоит в её заголовке и в заголовке PR
156
+
157
+ Форма одна на оба — `[<КЛЮЧ>-<номер>] <текст>`. Номер стоит в самом заголовке, а не только в
158
+ теле: в списке PR тела не видно, а в списке задач номер иначе приходится искать глазами по
159
+ колонке слева. Тот же номер несёт и имя ветки — `<КЛЮЧ>-<номер>-<короткий-slug>`, — поэтому
160
+ задача, ветка и PR читаются как одно.
161
+
162
+ Задача говорит, что не так; PR тем же номером отчитывается, что сделано:
163
+
164
+ ```
165
+ задача [<КЛЮЧ>-86] Пустой MAIL_OWNER — письма владельцу не уходят молча
166
+ PR [<КЛЮЧ>-86] Письмо владельцу с незаполненным адресом попадает в логи
167
+
168
+ задача [<КЛЮЧ>-101] Вернуть оверлей загрузки таблицы и включить stylelint гейтом
169
+ PR [<КЛЮЧ>-101] Stylelint включён гейтом
170
+
171
+ задача [<КЛЮЧ>-212] Сайт не собирается: компонентам кита проставлен префикс vm- вместо rt-
172
+ PR [<КЛЮЧ>-212] Виджет переписки зовёт кит его собственными именами
173
+ ```
174
+
175
+ Инфинитив из задачи в заголовок PR не переносится: «исправить» становится «исправлено»,
176
+ «вернуть» — «возвращено», «добавить» — «добавлено».
177
+
178
+ Номер в заголовке обязан совпасть с номером ветки: гард поставки сверяет их до отправки
179
+ команды, а сверка очереди — у каждого открытого PR.
180
+
181
+ Тип и область — `fix(site):`, `docs(common):` — в заголовок PR не идут: это формат заголовка
182
+ коммита, и там его сверяет `commitlint`. В списке PR он занимает место, ничего не добавляя:
183
+ род правки и область уже видны метками.
184
+
185
+ ## PR прикрепляется к задаче
186
+
187
+ Тело начинается со строки связи — по ней на борде заполняется поле «Linked pull requests».
188
+ Ревьювер, исполнитель и метки задаются той же командой, и PR без них не открывается:
189
+
190
+ ```bash
191
+ GH_TOKEN="$TOKEN" gh pr create --title '[<КЛЮЧ>-86] Письмо владельцу с незаполненным адресом попадает в логи' \
192
+ --reviewer <владелец> --assignee <бот> --label bug --label area:api \
193
+ --body 'Closes #86
194
+
195
+ …'
196
+ ```
197
+
198
+ Ревьювер — всегда владелец: без запроса разбора PR не показывается ему в очереди. Исполнитель —
199
+ та же учётная запись, от которой идёт машинная работа. Метки берутся у задачи целиком — и род
200
+ правки, и все её области; читаются они у задачи, а не выбираются по памяти:
201
+
202
+ ```bash
203
+ /opt/homebrew/bin/gh issue view 86 --json labels --jq '.labels | map(.name) | join(",")'
204
+ ```
205
+
206
+ Строка `Closes #<номер>` обязательна: без неё PR не прикрепляется к задаче, и сверка очереди
207
+ это находит. Она же и означает, что задача закрывается целиком — половину задачи одним PR не
208
+ выкатывают: у задачи одна ветка, и работа, которая в неё не влезает, делится на задачи до
209
+ того, как ветка заводится.
210
+
211
+ У уже открытого PR то же ставится тремя вызовами REST. `gh pr edit` здесь не годится: он
212
+ запрашивает карточки Projects (classic), получает отказ о снятом API и до правки не доходит.
213
+
214
+ ```bash
215
+ GH=/opt/homebrew/bin/gh
216
+ REPO=<владелец>/<репозиторий>
217
+
218
+ $GH api -X POST "repos/$REPO/issues/205/labels" -f 'labels[]=bug' -f 'labels[]=area:api'
219
+ $GH api -X POST "repos/$REPO/issues/205/assignees" -f 'assignees[]=<бот>'
220
+ $GH api -X POST "repos/$REPO/pulls/205/requested_reviewers" -f 'reviewers[]=<владелец>'
221
+ ```
222
+
223
+ Тем же вызовом правится и само тело: `-f body=` переписывает его целиком, поэтому строка
224
+ `Closes #<номер>` пишется заново вместе с остальным текстом.
225
+
226
+ ```bash
227
+ $GH api -X PATCH "repos/$REPO/pulls/205" -f body="$(cat тело.md)"
228
+ ```
229
+
230
+ Тело перечитывается всякий раз, когда в ветку что-то влилось после публикации: отчёт
231
+ утверждает про дерево, а дерево с тех пор изменилось.
232
+
233
+ ## Состояние PR читается, а не додумывается
234
+
235
+ Вызовы, которыми ставятся ревьювер, метки и исполнитель, отвечают нулевым кодом и тогда, когда
236
+ ничего не сделали: запрос разбора на автора PR GitHub молча выбрасывает. Поэтому после них PR
237
+ перечитывают:
238
+
239
+ ```bash
240
+ # Ключи латиницей: кириллический ключ без кавычек `jq` не разбирает и падает на нём
241
+ $GH api "repos/$REPO/pulls/321" \
242
+ --jq '{author: .user.login, reviewers: [.requested_reviewers[].login], labels: [.labels[].name]}'
243
+ ```
244
+
245
+ Автор здесь — `<бот>`. Если им оказался владелец, ревьювера у PR не будет вовсе:
246
+ назначить автора ревьювером нельзя, а отказа на такой запрос не приходит. Владельцу называют
247
+ то, что прочитали, а не то, что заказывали.
248
+
249
+ Учётная запись, из-под которой пришлось пушить, в этот вызов не переносится: пуш и авторство
250
+ PR выбираются отдельно, и `GH_TOKEN` для публикации — всегда токен бота.
251
+
252
+ Открытый PR означает, что задача ждёт разбора, — колонка переставляется тем же движением:
253
+
254
+ ```bash
255
+ npm run task:move -- 86 in-review
256
+ ```
257
+
258
+ Голым GraphQL по идентификаторам проекта, элемента и варианта поля это не пишется: команда
259
+ знает их сама, а собранный по памяти запрос молча ставит не ту колонку — отказа у борды на
260
+ это нет.
261
+
262
+ ## Что проверяется до публикации PR
263
+
264
+ Проверок на самом PR нет: выкатка запускается пушем в главную ветку, и до мержа никто не
265
+ гоняет ничего. Линтеры, юниты и сценарии хуков снимает гейт пуша — ниже то, чего он не знает.
266
+
267
+ 1. **В ветке только та правка, за которой её заводили** — `git diff main...HEAD --stat`. Чужой
268
+ домен в списке файлов означает, что правка расползлась, и её надо вернуть в свои границы.
269
+ 2. **Ни мока, ни подменённого ответа, ни отладочной строки** — `git diff main...HEAD` читается
270
+ целиком, а не по именам файлов. На прод они уезжают молча и портят настоящие данные.
271
+ 3. **Документ едет тем же коммитом.** Пару называет `docs-guard`, но спек домена и правку его
272
+ поведения он не знает — это остаётся за автором.
273
+ 4. **Проверки текстов и раскладки зелёные** — те, что дерево завело в `tools/`: сверка
274
+ сценариев с тестами, путей в документах, раскладки либ, повторов и классов без правила.
275
+ Какие именно есть здесь — `implementation.md` правила.
276
+ 5. **Все приложения дерева собираются** — `nx build` по каждому. Гейт пуша сборку не гоняет:
277
+ она длиннее всего, что он успевает сделать между командой и пушем.
278
+ 6. **Видимый текст заведён во всех локалях перевода** — тестом полноты словарей, если дерево
279
+ переводится.
280
+ 7. **Правка вёрстки подтверждена замером**, а не взглядом, и снята при узком экране — паттерн
281
+ `browser-verification-measure`.
282
+ 8. **Правка разметки публичного сайта проверена на прод-сборке по всем локалям перевода** — паттерн
283
+ `seo-verify`.
284
+ 9. **Тело PR начинается строкой `Closes #<номер>`**, а метки, ревьювер и исполнитель стоят.
285
+ 10. **Заголовок PR несёт номер задачи и называет её сделанной:** `[<КЛЮЧ>-<номер>] <Что сделано>`,
286
+ тем же номером, что стоит у задачи и в имени ветки. Инфинитив из задачи в него не
287
+ переносится, тип и область коммита — тоже.
288
+ 11. **Очередь работ сходится** — `npm run check:board`. Задача на борде, с исполнителем и
289
+ номером в заголовке; PR один на задачу, и закрывает он её целиком.
290
+ 12. **Состояние PR прочитано, а не выведено из кодов возврата** — автор `<бот>`,
291
+ ревьювер — владелец, метки те же, что у задачи. Владельцу называют прочитанное.
292
+
293
+ Сразу после публикации задача переставляется в разбор — `npm run task:move -- <номер>
294
+ in-review`, — и `npm run check:board` прогоняется ещё раз: до открытия PR колонку он не судит,
295
+ а после открытия расхождение видит.
296
+
297
+ Сделанное рассуждением и сделанное замером в теле PR разводятся прямо: непроверенное,
298
+ названное проверенным, ревьювер принимает за проверенное.
299
+
300
+ ## Частые промахи
301
+
302
+ - `gh` в оболочке пользователя подменён — звать `/opt/homebrew/bin/gh` напрямую.
303
+ - `git add` с несколькими путями не добавляет ничего, если хоть один путь не существует:
304
+ команда обрывается на первом промахе целиком, а не пропускает его. Следующий
305
+ `git commit --amend` при этом уносит в коммит всё, что осталось в индексе, — так в коммит
306
+ уехало удаление файла, принадлежавшее соседней ветке. Состав коммита читается
307
+ `git show --stat` сразу после него, а не на разборе PR.
308
+ - `gh project` с `--owner` отвечает `unknown owner type`: владелец борды — другая учётная
309
+ запись, и правка идёт только через GraphQL.
310
+ - Заведённый тикет на борду сама она не забирает: репозиторий с ней не связан, и добавление
311
+ идёт отдельным вызовом. Два тикета так и остались вне очереди работ — поэтому все четыре
312
+ шага и делает `npm run task:new`, а не рука.
313
+ - Исполнитель у задачи не проставляется сам ни при заведении через веб, ни при добавлении на
314
+ борду: из девяноста девяти открытых задач он стоял у двух.
315
+ - Задача, заведённая через веб, мимо команды, на борду не попадает и гардом не отбивается —
316
+ он смотрит команду, а не тикет. Ловится это только сверкой очереди.
317
+ - Колонка задачи сама не двигается ни от заведения ветки, ни от открытия PR: борда ветки не
318
+ видит вовсе, а связь с PR заполняет только поле «Linked pull requests». Взятие в работу не
319
+ ловит и сверка — ей ветка тоже не видна.
320
+ - `gh api graphql --paginate` на запросе элементов борды уходит в повтор первой страницы:
321
+ курсор берётся из ответа руками, а полнота сверяется с `items(first: 1) { totalCount }`.
322
+ - Второй строкой `Closes` в одном PR задача больше не закрывается: две задачи в одной ветке
323
+ откатываются только вместе. Либо это одна задача — и вторая поглощается, — либо две ветки.
324
+ - Половина задачи, уехавшая своим PR, тоже промах: тело такого PR начинается со слов «Часть
325
+ #<номер>» вместо `Closes`, задача остаётся открытой, и после отката видно её целой. Работа,
326
+ которая в одну ветку не влезает, делится на задачи до того, как ветка заводится.
327
+ - PR открыт без ревьювера: он не попадает во входящие владельца вовсе, и очередь стоит,
328
+ выглядя работающей. Так шестнадцать PR ждали разбора, которого никто не запрашивал.
329
+ - Метки поставлены по названию PR, а не прочитаны у задачи: область теряется, и по борде не
330
+ видно, что правка задела ещё и сайт.
331
+ - Задача закрыта не полностью, а метки перенесены целиком: тикет остаётся открытым, и это
332
+ говорится в теле PR, а не подразумевается строкой `Closes`.
333
+ - Правка владельца ни токена, ни переменных не берёт — они только для машинной работы.