@rt-tools/agent-kit 0.16.1 → 0.18.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 (136) hide show
  1. package/assets/checks/board-epics.github.mjs +123 -0
  2. package/assets/checks/board-gh.github.mjs +121 -0
  3. package/assets/checks/board-paths.github.mjs +88 -0
  4. package/assets/checks/board-runs.github.mjs +8 -0
  5. package/assets/checks/board-titles.github.mjs +66 -0
  6. package/assets/checks/board.github.mjs +77 -73
  7. package/assets/checks/check-board.github.mjs +56 -7
  8. package/assets/checks/check-file-size.mjs +10 -2
  9. package/assets/checks/check-glossary.mjs +170 -0
  10. package/assets/checks/check-hook-scope.mjs +126 -0
  11. package/assets/checks/check-profile-drift.mjs +195 -0
  12. package/assets/checks/check-push-gate.mjs +59 -1
  13. package/assets/checks/check-schema-drift.mjs +12 -5
  14. package/assets/checks/check-specs.mjs +1 -1
  15. package/assets/checks/rt-kit-checks.config.mjs +13 -0
  16. package/assets/checks/spec-anchors.mjs +2 -2
  17. package/assets/checks/spec-contract.mjs +9 -0
  18. package/assets/defaults/gate-map.sh +13 -4
  19. package/assets/defaults/project.sh +31 -108
  20. package/assets/defaults/shell.sh +139 -0
  21. package/assets/docs/GLOSSARY.md +1 -1
  22. package/assets/hooks/browser-device-id.sh +20 -4
  23. package/assets/hooks/browser-guard-device-id.sh +5 -2
  24. package/assets/hooks/browser-guard-no-other-drivers.sh +41 -2
  25. package/assets/hooks/claim-guard.sh +22 -1
  26. package/assets/hooks/docs-guard.sh +1 -1
  27. package/assets/hooks/exam-guard.sh +85 -12
  28. package/assets/hooks/git-guard-delivery-conflict.sh +85 -0
  29. package/assets/hooks/git-guard-delivery-folder.sh +19 -0
  30. package/assets/hooks/git-guard-delivery-signature.sh +14 -5
  31. package/assets/hooks/git-guard-delivery.sh +54 -2
  32. package/assets/hooks/git-guard-push-tests.sh +21 -1
  33. package/assets/hooks/grill-gate-ask.sh +25 -0
  34. package/assets/hooks/grill-gate.sh +75 -11
  35. package/assets/hooks/lint-after-edit.sh +44 -16
  36. package/assets/hooks/proposal-guard.sh +11 -4
  37. package/assets/hooks/rule-source-guard.sh +8 -13
  38. package/assets/hooks/skill-gate-layers.sh +7 -0
  39. package/assets/hooks/skill-gate.sh +29 -0
  40. package/assets/hooks/task-context-load.sh +32 -0
  41. package/assets/hooks/task-flow-context.sh +194 -0
  42. package/assets/hooks/task-flow-draft-guard.sh +109 -0
  43. package/assets/hooks/task-flow-guard.sh +27 -159
  44. package/assets/hooks/turn-exit-guard.sh +53 -88
  45. package/assets/hooks/waiting-turn-guard.sh +65 -2
  46. package/assets/hooks/window-fill-guard.sh +5 -1
  47. package/assets/hooks/work-start-guard.sh +166 -0
  48. package/assets/hooks/write-targets.sh +24 -0
  49. package/assets/laws/delivery.md +19 -0
  50. package/assets/laws/frontend-application.md +4 -0
  51. package/assets/laws/verifiability.md +44 -0
  52. package/assets/laws/work-conduct.md +15 -0
  53. package/assets/patterns/browser-verification-measure.md +3 -1
  54. package/assets/patterns/browser-verification-stand.md +17 -4
  55. package/assets/patterns/doc-style-write.md +62 -2
  56. package/assets/patterns/git-workflow-commit.github.md +33 -5
  57. package/assets/patterns/git-workflow-docker.md +22 -0
  58. package/assets/patterns/git-workflow-freshness.md +87 -0
  59. package/assets/patterns/git-workflow-merge.md +41 -0
  60. package/assets/patterns/git-workflow-migration.md +11 -0
  61. package/assets/patterns/git-workflow-pr.github.md +12 -1
  62. package/assets/patterns/git-workflow-restart.md +18 -0
  63. package/assets/patterns/git-workflow-secrets.md +14 -0
  64. package/assets/patterns/git-workflow-stack.md +93 -1
  65. package/assets/patterns/lib-layers-move.md +4 -0
  66. package/assets/patterns/spec-driven-domain.md +47 -2
  67. package/assets/patterns/spec-driven-rule.md +16 -4
  68. package/assets/patterns/spec-driven-sweep.md +57 -0
  69. package/assets/patterns/status-report-table.github.md +1 -1
  70. package/assets/patterns/task-flow-archive.md +58 -16
  71. package/assets/patterns/task-flow-close.md +106 -23
  72. package/assets/patterns/task-flow-handoff.md +14 -2
  73. package/assets/patterns/task-flow-resume.md +35 -8
  74. package/assets/patterns/task-flow-start.md +60 -22
  75. package/assets/patterns/testing-e2e.md +23 -0
  76. package/assets/patterns/turn-entry-map.md +1 -1
  77. package/assets/pitfalls/agent-kit.md +71 -3
  78. package/assets/pitfalls/doc-style.md +15 -0
  79. package/assets/pitfalls/git-workflow.github.md +88 -0
  80. package/assets/pitfalls/spec-driven.md +22 -0
  81. package/assets/pitfalls/task-flow.md +113 -0
  82. package/assets/pitfalls/testing.md +6 -0
  83. package/assets/pitfalls/turn-conduct.md +49 -0
  84. package/assets/rules/browser-verification.md +58 -0
  85. package/assets/rules/deploy-flow.azure.md +8 -0
  86. package/assets/rules/deploy-flow.github.md +27 -0
  87. package/assets/rules/deploy-flow.gitlab.md +8 -0
  88. package/assets/rules/doc-style.md +20 -0
  89. package/assets/rules/git-workflow.azure.md +22 -2
  90. package/assets/rules/git-workflow.github.md +127 -118
  91. package/assets/rules/git-workflow.gitlab.md +17 -4
  92. package/assets/rules/lists.md +5 -0
  93. package/assets/rules/observability.needs-app.md +4 -0
  94. package/assets/rules/reuse-first.md +14 -0
  95. package/assets/rules/shared-code.md +5 -0
  96. package/assets/rules/spec-driven.md +33 -15
  97. package/assets/rules/task-flow.md +132 -126
  98. package/assets/rules/testing.md +41 -2
  99. package/assets/rules/turn-conduct.md +73 -57
  100. package/assets/rules/turn-entry.md +6 -0
  101. package/assets/samples/tasks/_template/grill.md +5 -0
  102. package/assets/samples/tasks/_template/plan.md +3 -0
  103. package/assets/skills/agent-kit.md +108 -76
  104. package/assets/templates/postmortem.md +5 -1
  105. package/bin/agent-kit.d.ts.map +1 -1
  106. package/bin/agent-kit.js +30 -6
  107. package/bin/agent-kit.js.map +1 -1
  108. package/lib/catalog.d.ts.map +1 -1
  109. package/lib/catalog.js +2 -1
  110. package/lib/catalog.js.map +1 -1
  111. package/lib/commands.d.ts.map +1 -1
  112. package/lib/commands.js +96 -7
  113. package/lib/commands.js.map +1 -1
  114. package/lib/hooks-map.d.ts +26 -0
  115. package/lib/hooks-map.d.ts.map +1 -1
  116. package/lib/hooks-map.js +58 -2
  117. package/lib/hooks-map.js.map +1 -1
  118. package/lib/sections.d.ts +6 -0
  119. package/lib/sections.d.ts.map +1 -1
  120. package/lib/sections.js +19 -0
  121. package/lib/sections.js.map +1 -1
  122. package/lib/shipment.d.ts +2 -0
  123. package/lib/shipment.d.ts.map +1 -1
  124. package/lib/shipment.fixture.d.ts +5 -0
  125. package/lib/shipment.fixture.d.ts.map +1 -1
  126. package/lib/shipment.fixture.js +7 -0
  127. package/lib/shipment.fixture.js.map +1 -1
  128. package/lib/shipment.js +13 -1
  129. package/lib/shipment.js.map +1 -1
  130. package/lib/sync.d.ts +35 -3
  131. package/lib/sync.d.ts.map +1 -1
  132. package/lib/sync.js +59 -8
  133. package/lib/sync.js.map +1 -1
  134. package/package.json +1 -1
  135. package/rt-tools-agent-kit-0.18.0.tgz +0 -0
  136. package/rt-tools-agent-kit-0.16.1.tgz +0 -0
@@ -41,6 +41,15 @@
41
41
  принимается заново.** Отменённое решение следа в работе не оставляет — по результату не видно ни
42
42
  того, что его принимали, ни того, что от него отказались. Принятое заново оно расходится с
43
43
  прежним молча и стоит той же работы второй раз.
44
+ - **Решение, которое переживёт задачу, записывается там, где оно переживёт.** Ход работы умирает
45
+ вместе с папкой задачи, а имя, адрес и выбранный способ, названные владельцем посреди серии
46
+ работ, нужны следующим её задачам. Не переехавшее решение существует только в переписке того
47
+ захода, в котором принято: следующий заход делает разведку честно, не находит ничего — и
48
+ задаёт владельцу вопрос, на который тот уже отвечал.
49
+ - **Инструмент, названный в просьбе, входит в просьбу.** Замена его на свой — отступление от
50
+ просьбы, даже когда свой даёт тот же ответ: равноценность инструментов решает тот, кто просит.
51
+ Одинаковый результат равноценностью не является — у названного инструмента бывает видно то,
52
+ чего у заменителя нет вовсе.
44
53
  - **Понимание записано там, где идёт работа.** Оставленное в переписке живёт у одного участника и
45
54
  до следующего дня; работу продолжает тот, у кого этой переписки нет.
46
55
  - **Сказанное владельцем записывается его словами и задним числом не переписывается.** Пересказ
@@ -197,6 +206,12 @@
197
206
  - **Уборка за работой идёт до того, как о готовности сказано.** Всё, что работа обязана убрать за
198
207
  собой, убирается раньше просьбы включить её в общее дерево, а не после согласия. После включения
199
208
  убирать уже некому: работа перешла к следующей задаче.
209
+ - **Постоянное указание среды исполнения слабее правила дерева.** Среда описывает своё
210
+ умолчание и о дереве не знает; дерево вправе его отменить и отменяет молча — тем, что говорит
211
+ иначе. Расхождение разрешается в пользу дерева, а не того из двух текстов, который строже
212
+ сформулирован или ближе стоит к делу. Опознаётся оно чтением обоих, а не проверкой: указание
213
+ среды приходит в заход текстом, и сличить его с правилом машине не по чему.
214
+
200
215
  - **Пока эпик не кончился, следующая работа не выбирается, а берётся.** Выбор, предложенный
201
216
  владельцу при назначенном порядке, — это просьба назначить его заново: он уже назначен, и
202
217
  предлагать его повторно значит отменять собственное планирование.
@@ -67,7 +67,9 @@ description: Паттерн правила browser-verification. Брать, к
67
67
  - Между кликами обязателен `await`: синхронный цикл «кликнул — прочитал DOM» читает состояние
68
68
  до перерисовки и возвращает устаревшие значения.
69
69
  - `select_browser` протухает через 300 секунд. На длинной проверке это срабатывает посреди
70
- работы — это не сбой стенда, повторить вызов и продолжить. Закреплённый профиль — тот, что записан в дереве, идентификатор `062b17db-ae82-4264-927c-e9904d0dd5be`.
70
+ работы — это не сбой стенда, повторить вызов и продолжить. Закреплённый профиль называет само дерево переменной окружения либо своим файлом рядом с
71
+ настройками раскладки; спрашивается он у помощника, а не помнится: идентификатор локален для
72
+ машины и в пакет не едет вовсе.
71
73
 
72
74
  ## Поведение роутера воспроизводится нажатиями
73
75
 
@@ -40,6 +40,10 @@ PORT={{prodSitePort}} node dist/apps/site/server/server.mjs
40
40
  Это не дев-сервер: гард ловит `nx|ng serve`, пакетные раннеры и статические серверы, а запуск
41
41
  собранного сервера пропускает.
42
42
 
43
+ Поведение, зависящее от хоста — адрес владельца, увод, разный ответ на разных именах, —
44
+ проверяется прямо здесь одним запросом с заданным заголовком. Прокси с подменой для этого не
45
+ нужен: он добавляет ещё один процесс, чьё поведение придётся отделять от проверяемого.
46
+
43
47
  ## Стенд админки
44
48
 
45
49
  ```bash
@@ -107,8 +111,9 @@ docker run -d --name <префикс>-stand-nginx \
107
111
  `nginx.conf`, — файл с расширением `.conf` в смонтированном каталоге попал бы в `include`
108
112
  и уронил бы nginx на директиве `user`. Без него стенд поднимается на конфиге образа, и
109
113
  предел соединений на воркер там свой.
110
- - Сервер отдачи страниц отвечает `400` на чужой `Host`: все запросы идут с
111
- `-H "Host: localhost"`.
114
+ - Чужой `Host` отбивает **дев-сервер**, а не собранное приложение: прод-сборка отвечает на него
115
+ тем же, чем и на свой. За настоящим прокси запросы идут со своим заголовком ровно потому, что
116
+ прокси стоит перед дев-сервером, — оттуда и `-H "Host: localhost"`.
112
117
  - Переменные окружения стенда обязаны смотреть на процессы стенда. `CACHE_REFRESH_URL`,
113
118
  направленный на {{sitePort}}, сбрасывает кэш мимо того процесса, который держит справочник
114
119
  перенаправлений в памяти, — исправный механизм при этом выглядит сломанным.
@@ -128,8 +133,11 @@ pnpm install --frozen-lockfile # из ../<префикс>-base: node_mod
128
133
  коммит — проверяют сборкой, а не тем, что он в главной ветке.
129
134
  - Стенд второго дерева поднимают на своих портах: {{sitePort}}, {{adminPort}} и {{apiPort}} заняты владельцем, а {{ssrPort}}
130
135
  занимать нельзя — стенд разработчика ходит по имени `ssr:{{ssrPort}}`.
131
- - Оба стенда держат поднятыми одновременно: если сравнивать по памяти между двумя запусками,
132
- заметишь только то, что успел запомнить.
136
+ - Оба стенда держат поднятыми одновременно — ровно до конца сравнения: если сравнивать по памяти
137
+ между двумя запусками, заметишь только то, что успел запомнить. Со сравнением стенд второго
138
+ дерева гасится тем же ходом, а не оставляется до конца захода: оставленные стенды копятся
139
+ заходами и держат соединения к хранилищу, и правило говорит об этом ловушкой. Снятие отбито —
140
+ стенд называется владельцу в конце захода поимённо, с портом.
133
141
 
134
142
  ## Состояние портов снимается до первой сборки захода
135
143
 
@@ -155,6 +163,11 @@ done
155
163
  это по её длительности.
156
164
  - Свой дев-сервер не поднимать: сайт на {{sitePort}}, админка на {{adminPort}}, API на {{apiPort}} уже подняты
157
165
  владельцем, и второй экземпляр отбивается гардом.
166
+ - **Дев-сервер владельца — место, где смотрят, а не место, где проверяют.** Вывод о том, что
167
+ экран верен, делается на прод-сборке за настоящим прокси: разметка от сервера, локали, кэш,
168
+ перенаправления и заголовки на дев-сервере либо другие, либо отсутствуют вовсе. Взгляд на
169
+ поднятый дев-сервер годится, чтобы понять, что происходит на незнакомом экране, и не годится
170
+ подтверждением: подтверждение — замер на стенде из прод-сборки.
158
171
  - **Общая сборка глушит все три дев-сервера владельца, а не только API.** После
159
172
  `nx run-many -t build` ложатся и сайт на {{sitePort}}, и админка на {{adminPort}}. Собирать надо то, что
160
173
  проверяешь (`npx nx build site`), а не всё дерево. Если серверы легли, поднять их обратно
@@ -48,7 +48,7 @@ description: Паттерн правила doc-style. Брать при напи
48
48
 
49
49
  «Не планируется», «не будет», «отдельная фича по запросу» — это намерение владельца, а не
50
50
  свойство системы. Что не сделано — да, почему не сделано — да, что не будет сделано никогда —
51
- нет. Вместо приговора — открытый вопрос `Q-N` с тем, что решение изменит.
51
+ нет. Вместо приговора — открытый вопрос `Q-<буква закона>-<номер>` с тем, что решение изменит.
52
52
 
53
53
  Ошибка беззвучная: сверять утверждение о будущем не с чем, оно проходит любую проверку. Так в
54
54
  первый живой спек попало «онлайн-оплаты нет и не планируется», хотя оплата в планах.
@@ -69,7 +69,40 @@ description: Паттерн правила doc-style. Брать при напи
69
69
  ✓ у пустого списка должен быть текст, у отказа — кнопка повтора
70
70
  ```
71
71
 
72
- Проверка: прочитать фразу вслух. Если так не говорят — переписать.
72
+ Проверка: прочитать фразу вслух. Если так не говорят — переписать. Слухом это ловится в той
73
+ фразе, которую читают, и не ловится в файле на восемьсот строк: оборот всплывает по всему
74
+ тексту, и глазами он не считается.
75
+
76
+ Берётся чужой оборот оттуда, что писавший только что читал, и сильнее всего это бьёт при письме
77
+ в том же роде: ни одного слова вне словаря в такой фразе нет, каждое проходит вычитку по
78
+ отдельности, а непонятной оказывается фраза целиком. Вводный абзац поэтому пишется последним —
79
+ когда источник уже выветрился.
80
+
81
+ Вторая половина проверки — счёт, и там, где дерево разложило проверку слога
82
+ `checks/check-prose-style.mjs`, набор оборотов накоплен в ней самой: каждый признак назван
83
+ вместе с заменой, а находка печатается файлом и строкой. Оборот, найденный вычиткой,
84
+ дописывается в набор тем же ходом — иначе следующий пишущий начинает с пустого места и
85
+ собирает его заново из своей памяти.
86
+
87
+ Читается не только слово, но и то, одно ли у фразы прочтение. Три причины нечитаемости приходят
88
+ по очереди, и правка одной заводит следующую — статью закона переписывали трижды, и каждый раз
89
+ владелец не понимал её по новой причине:
90
+
91
+ ```text
92
+ ✗ не различать их нельзя — двойное отрицание читается наоборот сказанному
93
+ ✗ у каждого видно, откуда он взялся — местоимение без хозяина, а «взялся» не называет действия
94
+ ✗ либо снимается ролью, либо остаётся навсегда — два исхода через «или» без правила, по которому наступает тот или другой
95
+
96
+ ✓ выданное лично снимается только лично: роль этого не трогает
97
+ ```
98
+
99
+ Довод «без такого устройства было бы так» в текст не идёт вовсе: он спорит с тем, чего нет, и
100
+ читается как «это к чему?».
101
+
102
+ Чего не видит ни слух, ни она: повтора законного оборота. Каждое его появление законно
103
+ поодиночке, а стоящий в тексте десятками он уже не приём, а тик — читатель перестаёт замечать
104
+ его вместе со смыслом. Считается это грепом по тексту, и число само по себе отказом не бывает:
105
+ судит его тот, кто правит следующим.
73
106
 
74
107
  ## Факт проверяется, а не вспоминается
75
108
 
@@ -79,6 +112,33 @@ description: Паттерн правила doc-style. Брать при напи
79
112
  Особенно это касается отказов: «вернётся `value out of range`» продержалось в двух документах,
80
113
  хотя такой отказ недостижим — в контракте и в колонке одна ширина.
81
114
 
115
+ **Утверждение о состоянии другой ветки читается из неё, а не из своей копии.** Копия, снятая при
116
+ отведении, отвечает на любой вопрос о содержимом файла и ни одним признаком не показывает,
117
+ насколько она отстала. Трижды за один заход состояние общей ветки вывели из файла на своей,
118
+ отставшей на семь коммитов; все три вывода неверны, один доехал до документа задачи и был назван
119
+ владельцу как факт. Поймано ребейзом, то есть случайно.
120
+
121
+ ```bash
122
+ git fetch origin
123
+ git show origin/main:<путь к файлу>
124
+ ```
125
+
126
+ **Утверждение «все N таких-то» пишется после перечисления маршрутов, которыми значение попадает в
127
+ место, а не после скана по одному образцу.** Скан подтверждает ровно тот маршрут, который в него
128
+ заложен, и молчит обо всех прочих — тем увереннее, чем точнее образец. Посчитали места по образцу
129
+ самого вызова, получили 22 и записали в план и в описание заявки, что порядок держится
130
+ устройством; мимо прошли десять вызовов обёртки, которая берёт значение аргументом. Мест
131
+ оказалось 32, и у десяти значение стояло числом.
132
+
133
+ **Команда, которой получено число, проверяется на том, что она мерит спрошенное.** Вывод команды
134
+ — не подтверждение сам по себе: инструмент отвечает на тот вопрос, который понял, и о непонятой
135
+ части образца молчит. Три числа подряд за один заход: граница слова, не реализованная в этой
136
+ сборке инструмента и не объявленная отказом, дала 278 вхождений вместо 79; счёт вызовов
137
+ регулярным выражением дал 32 и 42 на одном и том же, а по синтаксическому дереву их 27.
138
+
139
+ **Число, пришедшее из прошлой сессии, замером не считается.** Оно неотличимо от замеренного и в
140
+ документе выглядит так же уверенно; пересчитывается заново тем же ходом, которым пишется.
141
+
82
142
  ## Не пересказывать то, у чего есть источник
83
143
 
84
144
  - типы и поля контракта — `libs/common/proto/proto/<область>/v1/`, ссылкой;
@@ -26,6 +26,19 @@ description: Паттерн правила git-workflow. Брать на зав
26
26
 
27
27
  Все четыре шага делает одна команда дерева, а не рука: делить их значит забывать третий.
28
28
 
29
+ **Очередь работ спрашивается до заведения задачи, а не после.** Найденное собственной сверкой
30
+ ощущается новым, и это ощущение — единственное, что стоит за решением завести задачу: команда
31
+ заведения отвечает за свои вызовы и о содержании очереди не знает ничего. Спрашивается она
32
+ поиском по словам темы, вместе с закрытым за последний месяц: дефект, закрытый и вернувшийся, —
33
+ та же работа, а не новая. Дубль стоит дорого — по нему проходит вся работа целиком, а сводить
34
+ две задачи в одну потом приходится руками.
35
+
36
+ ```bash
37
+ /opt/homebrew/bin/gh issue list --state all --limit 200 --search '<слова темы>' \
38
+ --json number,title,state
39
+ ```
40
+
41
+
29
42
  ```bash
30
43
  npm run task:new -- --title 'Письма владельцу не уходят молча' \
31
44
  --label bug --label area:api --slug mail-owner-silence < описание.md
@@ -102,7 +115,7 @@ $GH api graphql -f query='mutation { deleteIssue(input: {issueId: "<node-id>"})
102
115
  составная команда отклоняется целиком — ветки в ней ещё нет:
103
116
 
104
117
  ```bash
105
- ✗ git checkout -b <КЛЮЧ>-85-guest-token && git commit -m 'feat(admin): …'
118
+ ✗ git checkout -b <КЛЮЧ>-85-guest-token && git commit -m 'feat(<область>): …'
106
119
  ✓ git checkout -b <КЛЮЧ>-85-guest-token
107
120
  ✓ git commit -F -
108
121
  ```
@@ -153,11 +166,25 @@ GIT_COMMITTER_NAME="<бот>" GIT_COMMITTER_EMAIL="<номер>+<бот>@users.n
153
166
  конце заголовка не принимается, длина — до 150 знаков.
154
167
 
155
168
  ```
156
- feat(site): availability calendar with season prices
157
- fix(api): reject overlapping booking dates
169
+ feat(<область>): availability calendar with season prices
170
+ fix(<область>): reject overlapping booking dates
158
171
  chore(deploy): docker-compose for vps
159
172
  ```
160
173
 
174
+ ## Файлы своей работы называются поимённо
175
+
176
+ Рабочее дерево одно, а работ в нём бывает несколько: чужая незакоммиченная папка, оставленный
177
+ черновик, правка соседнего захода. Каталог, добавленный целиком, уносит их с собой, и в главную
178
+ ветку уезжает то, о чём эта работа не просила.
179
+
180
+ ```bash
181
+ git add docs/tasks/<КЛЮЧ>-<номер>-<slug>/plan.md docs/tasks/<КЛЮЧ>-<номер>-<slug>/progress.md
182
+ git diff --cached --name-only # что действительно встало в индекс
183
+ ```
184
+
185
+ Проверки индекса после добавления мало: однажды добавленный чужой файл становится
186
+ отслеживаемым и дальше едет молча — следующий `git add` его уже не спрашивает.
187
+
161
188
  ## Документ едет тем же коммитом
162
189
 
163
190
  `docs-guard` требует пару и называет её сам. Обход — строка в теле, причина обязательна:
@@ -169,6 +196,7 @@ Docs-skip: правка только в тестах хука, зеркала у
169
196
  ## Частые промахи
170
197
 
171
198
  - `gh` в оболочке пользователя подменён — звать `/opt/homebrew/bin/gh` напрямую.
199
+ - Каталог добавлен целиком при чужом незакоммиченном рядом: чужая папка задачи уехала в главную ветку и стала отслеживаемой.
172
200
  - `git add` с несколькими путями не добавляет ничего, если хоть один путь не существует:
173
201
  команда обрывается на первом промахе целиком, а не пропускает его. Следующий
174
202
  `git commit --amend` при этом уносит в коммит всё, что осталось в индексе, — так в коммит
@@ -180,13 +208,13 @@ Docs-skip: правка только в тестах хука, зеркала у
180
208
  каталог диагностики.
181
209
  - `gh project` с `--owner` отвечает `unknown owner type`: владелец борды — другая учётная
182
210
  запись, и правка идёт только через GraphQL.
183
- - Заведённый тикет на борду сама она не забирает: репозиторий с ней не связан, и добавление
211
+ - Заведённую задачу на борду сама она не забирает: репозиторий с ней не связан, и добавление
184
212
  идёт отдельным вызовом. Два тикета так и остались вне очереди работ — поэтому все четыре
185
213
  шага и делает `npm run task:new`, а не рука.
186
214
  - Исполнитель у задачи не проставляется сам ни при заведении через веб, ни при добавлении на
187
215
  борду: из девяноста девяти открытых задач он стоял у двух.
188
216
  - Задача, заведённая через веб, мимо команды, на борду не попадает и гардом не отбивается —
189
- он смотрит команду, а не тикет. Ловится это только сверкой очереди.
217
+ он смотрит команду, а не задачу. Ловится это только сверкой очереди.
190
218
  - Колонка задачи сама не двигается ни от заведения ветки, ни от открытия PR: борда ветки не
191
219
  видит вовсе, а связь с PR заполняет только поле «Linked pull requests». Взятие в работу не
192
220
  ловит и сверка — ей ветка тоже не видна.
@@ -96,6 +96,14 @@ docker builder prune --force --filter until=24h # вчерашний кэ
96
96
  Освобождают отбором, а не общей чисткой: у сценария чистки образов сперва спрашивают, что он
97
97
  снял бы, и только потом дают снимать.
98
98
 
99
+ **Снятие образов отбивает не гейт, а режим захода.** В автоматическом режиме команда с
100
+ необратимым удалением — снятие образа, чистка слоёв — отклоняется независимо от разрешений, и
101
+ разрешающее правило на ту же команду отказа не снимает. Повторный вызов отбивается так же: отказ
102
+ не зависит от того, как команда набрана, поэтому переформулировка тут не путь. Ходов отсюда два —
103
+ освободить место тем, что удаления не требует, либо назвать это владельцу: режим переключает он,
104
+ и снимает образы тоже он. Названное в конце захода стоит целого прогона: конвейер всё это время
105
+ стоит красным по нехватке места.
106
+
99
107
  ## Демон поднимается своим CLI
100
108
 
101
109
  Открытие приложения виртуальную машину не поднимает: приложение считается запущенным, а демон
@@ -176,6 +184,20 @@ docker info --format 'демон: {{.ServerVersion}}'
176
184
  содержимым или `403`, но не `401`. Каталог снимается в конце — раннер живёт между прогонами, и
177
185
  пароль реестра остался бы лежать на диске владельца.
178
186
 
187
+ ## Права токена спрашиваются до того, как конвейер на них обопрётся
188
+
189
+ Токен, которым ходят руками, и токен, которым ходит выкатка, — один и тот же ровно до первой
190
+ записи в реестр образов: области у него могут кончаться на чтении. Узнаётся это отказом, когда
191
+ весь путь выкатки уже написан, поэтому спрашивается раньше — и не догадкой, а заголовком ответа
192
+ хостинга:
193
+
194
+ ```bash
195
+ curl -sI -H "Authorization: Bearer <токен>" '<адрес хостинга>' | grep -i '^x-oauth-scopes:'
196
+ ```
197
+
198
+ Первая выкатка, сделанная руками из-за такого отказа, путь выкатки не проверяет — она проверяет
199
+ образы. Об этом говорится владельцу прямо: иначе зелёный прод читается как пройденный конвейер.
200
+
179
201
  ## Образ собирается под платформу прод-сервера
180
202
 
181
203
  Машина владельца и прод-сервер бывают разной архитектуры. Без явной платформы собирается образ
@@ -0,0 +1,87 @@
1
+ ---
2
+ name: git-workflow-freshness
3
+ kind: pattern
4
+ rule: git-workflow
5
+ description: Паттерн правила git-workflow. Брать перед пушем, при взятии задачи и после каждого известного слияния: чтение всех своих открытых заявок разом, отделение отставания от спора в файлах, сверка локальной вершины с хостингом.
6
+ ---
7
+
8
+ # Свежесть открытых заявок
9
+
10
+ Паттерн правила `git-workflow`. Что при этом должно быть верно — закон
11
+ `docs/constitution/delivery.md`. Разбор одного конфликта — паттерн `git-workflow-merge`,
12
+ череда веток из одного основания — `git-workflow-stack`.
13
+
14
+ ## Когда брать
15
+
16
+ Три места, и в каждом чтение обязательно:
17
+
18
+ - перед пушем — вершина главной могла уйти, пока шла работа;
19
+ - при взятии новой задачи — прежние заявки остались открытыми и о себе не напомнят;
20
+ - после каждого слияния, о котором стало известно, — оно отставило все остальные разом.
21
+
22
+ ## Что читается
23
+
24
+ Одной командой по всем своим открытым заявкам, а не по той, чья ветка сейчас взята:
25
+
26
+ ```bash
27
+ /opt/homebrew/bin/gh pr list --author '<машинная запись>' --state open \
28
+ --json number,headRefName,isDraft,mergeable,mergeStateStatus,statusCheckRollup
29
+ ```
30
+
31
+ | Поле | Что говорит |
32
+ | -------------------- | --------------------------------------------------------------------------------- |
33
+ | `mergeable` | `MERGEABLE` — кнопку нажать можно; `CONFLICTING` — нельзя; `UNKNOWN` — ещё не посчитано |
34
+ | `mergeStateStatus` | `BEHIND` — отстала от главной; `DIRTY` — спор в файлах; `BLOCKED` — ждёт разбора |
35
+ | `isDraft` | черновик: кнопка слияния заблокирована хостингом при любом цвете прогона |
36
+ | `statusCheckRollup` | прогон на вершине: пустой список — прогона нет вовсе, а не «зелено» |
37
+
38
+ `UNKNOWN` означает «ещё не посчитано» и читается как «спросить снова через несколько секунд»,
39
+ а не как «конфликтов нет».
40
+
41
+ ## Спор в файлах отделяется от отставания
42
+
43
+ Метка хостинга говорит одно — «нажать нельзя». Что именно чинить, отвечает слияние деревьев,
44
+ и отвечает без сети:
45
+
46
+ ```bash
47
+ git fetch origin
48
+ git merge-tree "$(git merge-base origin/main <ветка>)" origin/main <ветка> | grep -c '^<<<<<<<'
49
+ ```
50
+
51
+ Ноль — ветка просто отстала, и лечится это вливанием главной. Больше нуля — спор в файлах, и
52
+ каждый разбирается по роду файла: паттерн `git-workflow-merge`.
53
+
54
+ ## Что делается с найденным
55
+
56
+ | Найдено | Что делается |
57
+ | -------------------------------------- | ------------------------------------------------------------------ |
58
+ | отстала, спора нет | главная вливается в ветку и отправляется тем же ходом |
59
+ | спор в файлах | разбор по роду файла, затем отправка |
60
+ | прогона на вершине нет | вершина перезапускается или дожидается — цвета нет ни у той, ни у другой |
61
+ | черновик при зелёном прогоне | черновик снимается — у него кнопка слияния заблокирована |
62
+ | заявка открыта человеком, а не машиной | ревьювера ей уже не поставить: заводится заново машинной записью |
63
+
64
+ Внутри одной ветки порядок один: влить главную → прогнать набор гейта → отправить → перечитать
65
+ состояние у хостинга. Перечитывание — часть работы, а не отчёт о ней.
66
+
67
+ ## Локальная вершина сверяется с той, что на хостинге
68
+
69
+ Догнанная в рабочем дереве и не отправленная ветка работой не считается: человек видит прежнее
70
+ состояние.
71
+
72
+ ```bash
73
+ git rev-parse HEAD
74
+ /opt/homebrew/bin/gh pr view <номер> --json headRefOid --jq .headRefOid
75
+ ```
76
+
77
+ Разошлись — отправка не сделана, и это первое, что чинится.
78
+
79
+ ## Частые промахи
80
+
81
+ - Состояние заявки прочитано по памяти прошлого хода: между ходами человек влил соседнюю работу.
82
+ - `UNKNOWN` прочитан как «конфликтов нет» — хостинг ещё считал.
83
+ - Читалась заявка текущей ветки, а брошенные остались отставшими: спрашиваются все свои открытые.
84
+ - Главная влита в рабочем дереве и не отправлена: снаружи это «не сделано ничего».
85
+ - Отставание лечили разбором конфликта: спора в файлах не было вовсе, хватило бы вливания.
86
+ - Спор разрешили выбором стороны целиком: род файла решает приём, и стороны у него разные.
87
+ - Круг догоняния начат по окрику человека, а не сам: к этой минуте отстали уже все заявки.
@@ -10,6 +10,10 @@ description: Паттерн правила git-workflow. Брать, когда
10
10
  Паттерн правила `git-workflow`. Что при этом должно быть верно — закон
11
11
  `docs/constitution/delivery.md`.
12
12
 
13
+ **Вызовы здесь даны клиентом GitHub.** Дерево на другом хостинге читает их как форму, а команду
14
+ берёт у своего клиента: имена полей и подкоманд у клиентов разные, а спрашиваемое — одно.
15
+ Соответствие называет редакция правила поставки, разложенная в этом дереве.
16
+
13
17
  ## Когда брать
14
18
 
15
19
  - PR отмечен конфликтующим, и его надо вернуть к сливаемому состоянию.
@@ -40,6 +44,23 @@ git diff --name-only --diff-filter=U # что встало конфликт
40
44
  | компаньон правила рядом со скилом | сохранением обеих сторон — те же две дописи в одну таблицу; после — `npm run check:specs` |
41
45
  | список работ (`docs/BACKLOG.md`) | признаком отбора — паттерн `doc-style-sweep` |
42
46
 
47
+ ## Коммит переносится черри-пиком
48
+
49
+ Ветка, которой коммит должен был уехать, ушла: влилась, была снята хостингом или заведена не под
50
+ ту задачу. Сам коммит при этом цел и лежит в отпавшей ветке.
51
+
52
+ ```bash
53
+ GIT_COMMITTER_NAME="<бот>" GIT_COMMITTER_EMAIL="<номер>+<бот>@users.noreply.github.com" \
54
+ git cherry-pick <sha>
55
+ ```
56
+
57
+ Переменные подписи стоят на самой команде переноса. Черри-пик сохраняет автора коммита и ставит
58
+ коммиттером того, кто его зовёт, — то есть человека; коммиттера читает набор проверок перед
59
+ пушем, и чинится это уже перебазированием, а не правкой одного коммита.
60
+
61
+ Отпавшая ветка снимается с обеих сторон тем же ходом — иначе она стоит в перечне как незакрытая
62
+ работа.
63
+
43
64
  ## Что дописала ветка, видно только от точки расхождения
44
65
 
45
66
  Конфликтный маркер показывает место, а не правку: сторона ветки в нём — её допись вместе со
@@ -98,14 +119,34 @@ PR описывал дерево на день, когда его написал
98
119
  /opt/homebrew/bin/gh api -X PATCH repos/<владелец>/<репозиторий>/pulls/<номер> -f body="$(cat тело.md)"
99
120
  ```
100
121
 
122
+ ## Зелёный прогон стареет вместе с главной веткой
123
+
124
+ Прогон говорит про то основание, на котором шёл. Пока он идёт, а заявка ждёт разбора, главная
125
+ ветка живёт своей жизнью, и локальная ссылка об этом молчит: она описывает день, когда её
126
+ подтянули. Перед тем как назвать заявку готовой к слиянию, отставание спрашивается у хранилища:
127
+
128
+ ```bash
129
+ /opt/homebrew/bin/gh api repos/<владелец>/<репозиторий>/compare/<главная>...<ветка> --jq .behind_by
130
+ ```
131
+
132
+ Ноль — заявка готова. Больше нуля — главная вливается, набор проверок пересматривается по тому,
133
+ что ветка везёт теперь, и прогон идёт заново: зелёные задания прошлого прогона после вливания не
134
+ значат ничего.
135
+
101
136
  ## Частые промахи
102
137
 
103
138
  - «Сохранить обе стороны» применено ко всем файлам одинаково: в спеке это верно, в коде и в
104
139
  списке работ — нет.
140
+ - Команда мержа взята без переменных подписи: коммит слияния подписан человеком, и отбивает его
141
+ набор пуша — на том шаге, где все проверки уже зелёные. Чинится это переписыванием ветки, а не
142
+ правкой одного коммита: за слиянием обычно уже лежат разрешения конфликтов.
105
143
  - Сторона ветки перенесена без сверки с очередью работ: одна работа стала двумя записями.
106
144
  - После разрешения прогнана сборка, а проверки текстов — нет: конфликта в них сборке не видно.
107
145
  - Тело PR оставлено прежним: ревьювер читает утверждение о дереве, которого больше нет.
146
+ - Заявка названа готовой по зелёному прогону: задания шли от основания, которого в главной ветке
147
+ уже нет.
108
148
  - Раздел, снятый главной веткой, вернулся «сохранением обеих сторон»: в спеке два экземпляра
109
149
  одного абзаца, и снятый читается как действующий.
150
+ - Черри-пик взят без переменной коммиттера: автор у коммита прежний, коммиттер — человек, и отправку отбивает набор проверок.
110
151
  - Мерж ушёл за подписью человека: переменных в команде слияния не было, а строка о подписи
111
152
  лежит ниже команды и читается уже после коммита.
@@ -74,6 +74,11 @@ npx prisma migrate resolve --applied <новое имя>
74
74
 
75
75
  ## Частые промахи
76
76
 
77
+ - **Накат в образе настраивается файлом настройки, а не адресом в окружении.** Седьмая редакция
78
+ читает адрес хранилища только из своего файла настройки: ни объявление в схеме, ни переменная
79
+ окружения в составе прода его не заменяют. Этот файл кладётся в образ явно, рядом со схемой и
80
+ миграциями. Без него сборка зелёная целиком — генерация клиента на стадии сборки проходит, — а
81
+ отказывает первый же накат на узле.
77
82
  - Метку времени ставит момент создания, а порядок применения лексикографический: миграция из
78
83
  ветки, начатой раньше, встаёт перед той, от которой зависит. На существующей базе это
79
84
  незаметно — падает только накат с нуля.
@@ -86,3 +91,9 @@ npx prisma migrate resolve --applied <новое имя>
86
91
  через деплой, данные — через админку.
87
92
  - Строки адресуются по первичному ключу, а не по маске: удаление по маске почты однажды унесло
88
93
  вместе с тестовыми записями демонстрационные брони владельца.
94
+ - **Формой запроса вопрос гарда не снимается.** Условие по идентификатору он судит одинаково в
95
+ любой записи — что по одному, что по списку, — и на обе отвечает вопросом владельцу; отказ
96
+ приходит только на условие не по идентификатору. Там, где вопрос читается отказом, ход один:
97
+ назвать владельцу отбитую команду и попросить режим, в котором вопрос дойдёт, — а не
98
+ переписывать запрос, пока он не пройдёт. Иначе из захода уносят вывод о требованиях гарда,
99
+ которых у него нет.
@@ -177,6 +177,12 @@ $GH api "repos/$REPO/pulls/321" \
177
177
  назначить автора ревьювером нельзя, а отказа на такой запрос не приходит. Владельцу называют
178
178
  то, что прочитали, а не то, что заказывали.
179
179
 
180
+ Прочитанное расхождение имеет назначенное действие, а не остаётся наблюдением: автора у заявки
181
+ не сменить, правкой это не чинится. Заявка закрывается и открывается заново токеном машинной
182
+ записи — с переносом тела, меток, ревьювера и исполнителя. Номер при этом сменится, и строки,
183
+ которые ссылались на прежний, переписываются тем же ходом. Развилка, вынесенная тут владельцу,
184
+ означает, что порядок поставки прочитан и не исполнен: ход отсюда один.
185
+
180
186
  Учётная запись, из-под которой пришлось пушить, в этот вызов не переносится: пуш и авторство
181
187
  PR выбираются отдельно, и `GH_TOKEN` для публикации — всегда токен бота.
182
188
 
@@ -185,6 +191,11 @@ PR выбираются отдельно, и `GH_TOKEN` для публикац
185
191
  фразой владелец успевает влить PR, и всё сказанное о нём после этого — про вчерашний день. Так
186
192
  владельцу и было предложено влить то, что он влил часом раньше.
187
193
 
194
+ Читается состояние и перед пушем в ветку, у которой есть заявка, а не только перед словом о
195
+ ней. Влитая заявка означает, что ветки на сервере уже нет: пуш её не обновит, а заведёт заново,
196
+ и вклад останется вне главной ветки. Ответ пуша говорит это одной строкой — отметкой о новой
197
+ ветке вместо перечня коммитов, — и её читают: на удачную отправку такой ответ похож целиком.
198
+
188
199
  Открытый PR означает, что задача ждёт разбора, — колонка переставляется тем же движением:
189
200
 
190
201
  ```bash
@@ -276,5 +287,5 @@ in-review`, — и `npm run check:board` прогоняется ещё раз:
276
287
  выглядя работающей. Так шестнадцать PR ждали разбора, которого никто не запрашивал.
277
288
  - Метки поставлены по названию PR, а не прочитаны у задачи: область теряется, и по борде не
278
289
  видно, что правка задела ещё и сайт.
279
- - Задача закрыта не полностью, а метки перенесены целиком: тикет остаётся открытым, и это
290
+ - Задача закрыта не полностью, а метки перенесены целиком: задача остаётся открытой, и это
280
291
  говорится в теле PR, а не подразумевается строкой `Closes`.
@@ -41,6 +41,24 @@ docker inspect <контейнер> --format '{{.Config.Image}}'
41
41
  `integrations` из логов исчезает, хотя `API is running` остаётся на месте. После перезапуска —
42
42
  тот же `inspect` и наличие ожидаемых строк в логе.
43
43
 
44
+ ## Откат: тот же вызов с прежним sha
45
+
46
+ Откат — не отдельный механизм, а тот же подъём по sha, только взятому на шаг назад. Прежний sha
47
+ берётся у реестра, где чистка оставляет три последних, — глубже отката нет:
48
+
49
+ ```bash
50
+ docker image ls '<реестр>/<образ>' --format '{{.Tag}}\t{{.CreatedAt}}' | sort -k2 -r | head -3
51
+ IMAGE_TAG='<прежний sha>' docker compose -f docker-compose.prod.yml --env-file .env.prod up -d --no-build
52
+ ```
53
+
54
+ Откат возвращает прежний образ, но не прежнюю схему хранилища: миграция, уехавшая с новой
55
+ версией, остаётся применённой, и прежний образ работает с изменённой схемой. Правка схемы
56
+ поэтому и делится на совместимую и несовместимую — паттерн миграции.
57
+
58
+ Откаченный прод сходится с главной веткой не сразу: главная везёт правку, которой на проде уже
59
+ нет. Строкой очереди работ это не видно вовсе, и владельцу называется словами, вместе с sha, на
60
+ который откатились.
61
+
44
62
  ## Частые промахи
45
63
 
46
64
  - Вывод «прод жив, значит выкатилось» — код ответа подмену образа не показывает.
@@ -17,6 +17,20 @@ description: Паттерн правила deploy-flow. Брать при раб
17
17
  - Разбирается, что именно выкачено и чего приложению не хватает для работы, — включая ключ,
18
18
  который живёт в окружении и экрана не имеет.
19
19
 
20
+ ## Секрет выкатки — не ключ внешней службы
21
+
22
+ Ключи из этого паттерна живут в хранилище или в составе прода, и читает их приложение. Секреты
23
+ выкатки — ключ доступа к узлу, пароль реестра, адрес и токен приёма — приложением не читаются
24
+ вовсе: их читает конвейер, лежат они в настройках репозитория, и хранилище о них не знает.
25
+
26
+ Отсюда и разный ход при промахе. Ключ службы, которого не хватает, молчит на экране: возможность
27
+ не работает, а приложение отвечает. Секрет выкатки, которого не хватает, роняет шаг конвейера, и
28
+ до приложения дело не доходит вовсе — разбирают его в журнале задания, а не в браузере.
29
+
30
+ Про то, как секрет выкатки заводится и чем проверяется его право, говорит паттерн работы с
31
+ образами — `git-workflow-docker`, если дерево его разложило. Здесь остаётся граница: пришедший
32
+ за секретом выкатки читает дальше не этот текст.
33
+
20
34
  ## Ключ, заводимый владельцем, живёт в хранилище, а не в окружении
21
35
 
22
36
  Такой ключ лежит строкой в хранилище, зашифрованной ключом шифрования секретов; рядом открытая