@rt-tools/agent-kit 0.23.0 → 0.24.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.
- package/README.md +8 -2
- package/assets/checks/board.github.mjs +1 -1
- package/assets/checks/check-board.github.mjs +14 -0
- package/assets/checks/check-doc-paths.mjs +24 -5
- package/assets/checks/check-file-size.mjs +8 -2
- package/assets/checks/check-prose-style.mjs +10 -1
- package/assets/defaults/gate-map.sh +13 -0
- package/assets/defaults/project.sh +10 -0
- package/assets/defaults/shell.sh +18 -3
- package/assets/hooks/browser-guard-device-id.sh +42 -12
- package/assets/hooks/dispatch.sh +40 -9
- package/assets/hooks/docs-guard.sh +10 -0
- package/assets/hooks/exam-guard.sh +66 -16
- package/assets/hooks/git-guard-delivery-draft.sh +78 -0
- package/assets/hooks/git-guard-delivery.sh +49 -111
- package/assets/hooks/git-guard-main.sh +39 -4
- package/assets/hooks/git-guard-push-tests.sh +49 -3
- package/assets/hooks/hook-input.sh +17 -6
- package/assets/hooks/rule-source-guard.sh +11 -0
- package/assets/hooks/stand-login-guard.sh +101 -0
- package/assets/hooks/write-targets.sh +37 -4
- package/assets/laws/verifiability.md +12 -2
- package/assets/laws/work-conduct.md +59 -65
- package/assets/patterns/browser-verification-measure.md +41 -1
- package/assets/patterns/browser-verification-stand.md +56 -15
- package/assets/patterns/doc-style-human.md +75 -0
- package/assets/patterns/git-workflow-commit.azure.md +12 -0
- package/assets/patterns/git-workflow-commit.github.md +16 -3
- package/assets/patterns/git-workflow-commit.gitlab.md +12 -0
- package/assets/patterns/git-workflow-merge.md +8 -0
- package/assets/patterns/task-flow-start.md +1 -1
- package/assets/patterns/testing-e2e.md +18 -8
- package/assets/pitfalls/task-flow.md +40 -40
- package/assets/rules/browser-verification.md +29 -3
- package/assets/rules/doc-style.md +36 -0
- package/assets/rules/git-workflow.github.md +20 -23
- package/assets/rules/reuse-first.md +8 -2
- package/assets/rules/styling-bem.md +8 -1
- package/assets/rules/task-flow.md +73 -73
- package/assets/rules/testing.md +21 -0
- package/assets/skills/agent-kit.md +60 -70
- package/lib/commands.d.ts.map +1 -1
- package/lib/commands.js +63 -2
- package/lib/commands.js.map +1 -1
- package/lib/enroll.d.ts.map +1 -1
- package/lib/enroll.js +1 -1
- package/lib/enroll.js.map +1 -1
- package/lib/observations.d.ts +10 -1
- package/lib/observations.d.ts.map +1 -1
- package/lib/observations.js +1 -0
- package/lib/observations.js.map +1 -1
- package/lib/override-marks.d.ts +24 -0
- package/lib/override-marks.d.ts.map +1 -0
- package/lib/override-marks.js +98 -0
- package/lib/override-marks.js.map +1 -0
- package/lib/shipment.d.ts.map +1 -1
- package/lib/shipment.js +1 -1
- package/lib/shipment.js.map +1 -1
- package/package.json +1 -1
- package/rt-tools-agent-kit-0.24.0.tgz +0 -0
- package/rt-tools-agent-kit-0.23.0.tgz +0 -0
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Снятие черновика для гарда поставки: есть ли у заявки разбор, не конфликтует ли она и той ли
|
|
3
|
+
# записью открыта.
|
|
4
|
+
#
|
|
5
|
+
# Строки `# rt-hook:` здесь нет намеренно: событие и образец вызова объявляет сам гард, а
|
|
6
|
+
# помощник рядом хуком не регистрируется и в одиночку ничего не решает. Он зовётся из гарда и
|
|
7
|
+
# пользуется его же переменными — командой, корнем дерева, машинной записью — и его отказом.
|
|
8
|
+
#
|
|
9
|
+
# Вынесен он отсюда потому, что гард дорос до предела длины: снятие черновика среди его
|
|
10
|
+
# предметов самое отдельное — своя точка проверки, свой сетевой ярус, свои три отказа.
|
|
11
|
+
|
|
12
|
+
. "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/utf8.sh" 2>/dev/null || true
|
|
13
|
+
. "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/hook-input.sh" 2>/dev/null || true
|
|
14
|
+
|
|
15
|
+
rt_delivery_draft_ready() {
|
|
16
|
+
# --- снятие черновика --------------------------------------------------------------------
|
|
17
|
+
#
|
|
18
|
+
# Ревьювера до этой правки не спрашивал никто. Он стоял прозой в правиле, а хостинг запрос
|
|
19
|
+
# разбора на самого себя принимает молча и не создаёт: разбор при этом выглядит запрошенным.
|
|
20
|
+
# Раньше снятия черновика спросить негде — до открытия заявки ревьювера нет вовсе, — а само
|
|
21
|
+
# снятие и есть тот ход, которым работа объявляется готовой.
|
|
22
|
+
#
|
|
23
|
+
# Ярус сетевой, и молчит он так же, как ярус состояния задачи: нет ответа — нет требования.
|
|
24
|
+
#
|
|
25
|
+
# Возврат заявки в черновик под требование не подпадает: он делает ровно то, чего гард и
|
|
26
|
+
# добивается, — снимает с работы вид готовой.
|
|
27
|
+
if printf '%s' "$cmd" | grep -qE "${RT_CMD_BOUND}(gh[[:space:]]+pr[[:space:]]+ready|glab[[:space:]]+mr[[:space:]]+update[^|;&]*--ready)([[:space:]]|\$)" \
|
|
28
|
+
&& ! printf '%s' "$cmd" | grep -q -- '--undo'; then
|
|
29
|
+
# Ссылка на заявку необязательна: без неё клиент берёт заявку текущей ветки, и это самая
|
|
30
|
+
# короткая форма вызова. Требовать номер значило бы снимать всё требование одним пробелом.
|
|
31
|
+
# Поэтому берётся первый довод, каким бы он ни был — номер, адрес или имя ветки, — а его
|
|
32
|
+
# отсутствие означает «спроси про текущую ветку».
|
|
33
|
+
pull_ref="$(printf '%s' "$cmd" | sed -nE 's/.*(gh[[:space:]]+pr[[:space:]]+ready|glab[[:space:]]+mr[[:space:]]+update)[[:space:]]+([^[:space:];&|-][^[:space:];&|]*).*/\2/p' | head -1)"
|
|
34
|
+
if rt_needs rt_pull_state git-guard-delivery; then
|
|
35
|
+
pull="$(cd "$root" && rt_pull_state "$pull_ref" 2>/dev/null)" || pull=''
|
|
36
|
+
if [ -n "$pull" ] && printf '%s' "$pull" | jq -e '.exists' >/dev/null 2>&1; then
|
|
37
|
+
# Номер берётся из ответа, а если его там нет — из самой команды: заявка,
|
|
38
|
+
# названная адресом или именем ветки, в отказе должна остаться узнаваемой.
|
|
39
|
+
pull_name="$(printf '%s' "$pull" | jq -r '.number // empty' 2>/dev/null)"
|
|
40
|
+
# Номер пишется с решёткой, а имя ветки или адрес — в кавычках: решётка перед
|
|
41
|
+
# адресом читается как опечатка, а не как ссылка на заявку.
|
|
42
|
+
[ -z "$pull_name" ] && pull_name="$pull_ref"
|
|
43
|
+
case "$pull_name" in
|
|
44
|
+
'') ;;
|
|
45
|
+
*[!0-9]*) pull_name=" «${pull_name}»" ;;
|
|
46
|
+
*) pull_name=" #${pull_name}" ;;
|
|
47
|
+
esac
|
|
48
|
+
printf '%s' "$pull" | jq -e '.reviewed' >/dev/null 2>&1 \
|
|
49
|
+
|| fault "у заявки${pull_name} нет разбора: ревьювер не запрошен и отзыва никто не оставлял. Снятый черновик читается как «можно вливать», а вливать некому — назначь ревьювера и повтори."
|
|
50
|
+
|
|
51
|
+
# Конфликт приезжает в отданную заявку чужим слиянием, без единого действия её
|
|
52
|
+
# автора: основание, проверенное на открытии, к моменту снятия черновика уже
|
|
53
|
+
# вчерашнее. Снятый черновик читается как «можно вливать», а вливать нечего —
|
|
54
|
+
# владелец открывает заявку и находит там конфликт. Молчание при неизвестной
|
|
55
|
+
# сливаемости остаётся: хостинг считает её заново после каждой правки главной ветки,
|
|
56
|
+
# и «ещё не посчитано» — не «конфликтует».
|
|
57
|
+
printf '%s' "$pull" | jq -e '.conflicting' >/dev/null 2>&1 \
|
|
58
|
+
&& fault "заявка${pull_name} конфликтует с главной веткой. Влей её в свою ветку, разбери конфликт и повтори: снятый черновик читается как «можно вливать», а слить эту заявку нельзя."
|
|
59
|
+
|
|
60
|
+
# Автор заявки. На открытии судить было нечем, кроме текста команды: личность вызова
|
|
61
|
+
# приходит окружением. Здесь она уже названа хостингом, и это последний ход, где
|
|
62
|
+
# промах ещё исправим — после снятия черновика заявку вливают, а переоткрыть влитую
|
|
63
|
+
# нельзя. Дерево, не назвавшее машинной записи, автора не судит.
|
|
64
|
+
if [ -n "$task_bot" ]; then
|
|
65
|
+
pull_author="$(printf '%s' "$pull" | jq -r '.author // empty' 2>/dev/null)"
|
|
66
|
+
[ -n "$pull_author" ] && [ "$pull_author" != "$task_bot" ] \
|
|
67
|
+
&& fault "заявку${pull_name} открыла запись «${pull_author}», а не машинная «${task_bot}». Автор заявки её ревьювером не бывает, и разбор ей назначить нечем. Автора не сменить — закрой заявку и открой заново${pull_token_hint:+, подставив токен: ${pull_token_hint} …}."
|
|
68
|
+
fi
|
|
69
|
+
fi
|
|
70
|
+
fi
|
|
71
|
+
|
|
72
|
+
# Папка задачи: тот же предмет, что на открытии и на слиянии, третьим рубежом. Условие
|
|
73
|
+
# местное — оно читает ветку, а не хостинг, — и потому стоит вне сетевого яруса выше.
|
|
74
|
+
command -v rt_delivery_ready_folder >/dev/null 2>&1 && rt_delivery_ready_folder
|
|
75
|
+
|
|
76
|
+
deny_faults
|
|
77
|
+
fi
|
|
78
|
+
}
|
|
@@ -122,15 +122,12 @@ tasks_dir="${RT_TASKS_DIR:-}"
|
|
|
122
122
|
archive_dir="${RT_ARCHIVE_DIR:-}"
|
|
123
123
|
main_branch="${RT_MAIN_BRANCH:-main}"
|
|
124
124
|
|
|
125
|
-
# Обход требования: строка с причиной.
|
|
126
|
-
#
|
|
127
|
-
# же, что у гарда документа: если сделать по-разному, две формы одного обхода разойдутся.
|
|
125
|
+
# Обход требования: строка с причиной. Причину видит тот, кто вливает, поэтому обход разрешён;
|
|
126
|
+
# без причины это молчаливый пропуск. Порог в три знака — тот же, что у гарда документа.
|
|
128
127
|
#
|
|
129
|
-
# Строка обхода
|
|
130
|
-
# подстановки не принимает.
|
|
131
|
-
#
|
|
132
|
-
# собой, и тот же пример гард печатает в своём отказе ниже. Нашлось это приёмкой такого же
|
|
133
|
-
# признака — прогон обязан был покраснеть на настоящем нарушении и остался зелёным.
|
|
128
|
+
# Строка обхода стоит в начале строки — своей в теле PR или комментария в конце команды — и
|
|
129
|
+
# подстановки не принимает. Иначе текст, который объясняет, как называется обход, неотличим от
|
|
130
|
+
# самого обхода: тело PR со строкой-примером снимало требование само.
|
|
134
131
|
folder_skip_re='(^|#)[[:space:]]*Task-folder-skip:[[:space:]]*[^[:space:]<"'"'"'][^[:space:]"'"'"']{2,}'
|
|
135
132
|
|
|
136
133
|
deny() {
|
|
@@ -234,18 +231,13 @@ if [ -n "$branch_arg" ]; then
|
|
|
234
231
|
|| deny "BLOCKED: имя ветки «${branch_arg}» не той формы, что принята здесь. Номер у ветки тот же, что у задачи и у заголовка заявки на слияние."
|
|
235
232
|
check_task "$number_arg" "ветка «${branch_arg}»"
|
|
236
233
|
|
|
237
|
-
# Условия поставки, известные уже здесь,
|
|
238
|
-
#
|
|
239
|
-
#
|
|
240
|
-
# начале работы обе стоят одну команду.
|
|
234
|
+
# Условия поставки, известные уже здесь, проверяются здесь. После того как работа
|
|
235
|
+
# сделана, основание чинится слиянием с разбором конфликта, а подпись коммитов —
|
|
236
|
+
# переписыванием ветки; в начале работы обе стоят одну команду.
|
|
241
237
|
#
|
|
242
238
|
# Основание: вершина главной ветки обязана лежать в том, от чего растёт новая ветка.
|
|
243
|
-
#
|
|
244
|
-
#
|
|
245
|
-
#
|
|
246
|
-
# Судится названное основание, а не вершина рабочей копии: команда, которой основание
|
|
247
|
-
# как раз и берут свежим — `git checkout -b <ветка> origin/<главная>`, — иначе
|
|
248
|
-
# отбивалась бы наравне с той, у которой основание вчерашнее.
|
|
239
|
+
# Проверяется названное основание, а не вершина рабочей копии: иначе команда, которой
|
|
240
|
+
# основание берут свежим — `git checkout -b <ветка> origin/<главная>`, — запрещалась бы.
|
|
249
241
|
base_arg="$(printf '%s' "$cmd" | sed -nE 's/.*git[[:space:]]+(checkout[[:space:]]+-b|switch[[:space:]]+-c)[[:space:]]+[^[:space:];&|]+[[:space:]]+([^[:space:];&|-][^[:space:];&|]*).*/\2/p' | head -1)"
|
|
250
242
|
base_ref="${base_arg:-HEAD}"
|
|
251
243
|
if git rev-parse --verify --quiet "refs/remotes/origin/${main_branch}" >/dev/null 2>&1 \
|
|
@@ -289,68 +281,13 @@ fi
|
|
|
289
281
|
&& . "$rt_hooks_dir/git-guard-delivery-signature.sh" 2>/dev/null
|
|
290
282
|
command -v rt_delivery_signature >/dev/null 2>&1 && rt_delivery_signature
|
|
291
283
|
|
|
292
|
-
# --- снятие черновика
|
|
293
|
-
#
|
|
294
|
-
#
|
|
295
|
-
#
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
# Ярус сетевой, и молчит он так же, как ярус состояния задачи: нет ответа — нет требования.
|
|
300
|
-
#
|
|
301
|
-
# Возврат заявки в черновик под требование не подпадает: он делает ровно то, чего гард и
|
|
302
|
-
# добивается, — снимает с работы вид готовой.
|
|
303
|
-
if printf '%s' "$cmd" | grep -qE "${RT_CMD_BOUND}(gh[[:space:]]+pr[[:space:]]+ready|glab[[:space:]]+mr[[:space:]]+update[^|;&]*--ready)([[:space:]]|\$)" \
|
|
304
|
-
&& ! printf '%s' "$cmd" | grep -q -- '--undo'; then
|
|
305
|
-
# Ссылка на заявку необязательна: без неё клиент берёт заявку текущей ветки, и это самая
|
|
306
|
-
# короткая форма вызова. Требовать номер значило бы снимать всё требование одним пробелом.
|
|
307
|
-
# Поэтому берётся первый довод, каким бы он ни был — номер, адрес или имя ветки, — а его
|
|
308
|
-
# отсутствие означает «спроси про текущую ветку».
|
|
309
|
-
pull_ref="$(printf '%s' "$cmd" | sed -nE 's/.*(gh[[:space:]]+pr[[:space:]]+ready|glab[[:space:]]+mr[[:space:]]+update)[[:space:]]+([^[:space:];&|-][^[:space:];&|]*).*/\2/p' | head -1)"
|
|
310
|
-
if rt_needs rt_pull_state git-guard-delivery; then
|
|
311
|
-
pull="$(cd "$root" && rt_pull_state "$pull_ref" 2>/dev/null)" || pull=''
|
|
312
|
-
if [ -n "$pull" ] && printf '%s' "$pull" | jq -e '.exists' >/dev/null 2>&1; then
|
|
313
|
-
# Номер берётся из ответа, а если его там нет — из самой команды: заявка,
|
|
314
|
-
# названная адресом или именем ветки, в отказе должна остаться узнаваемой.
|
|
315
|
-
pull_name="$(printf '%s' "$pull" | jq -r '.number // empty' 2>/dev/null)"
|
|
316
|
-
# Номер пишется с решёткой, а имя ветки или адрес — в кавычках: решётка перед
|
|
317
|
-
# адресом читается как опечатка, а не как ссылка на заявку.
|
|
318
|
-
[ -z "$pull_name" ] && pull_name="$pull_ref"
|
|
319
|
-
case "$pull_name" in
|
|
320
|
-
'') ;;
|
|
321
|
-
*[!0-9]*) pull_name=" «${pull_name}»" ;;
|
|
322
|
-
*) pull_name=" #${pull_name}" ;;
|
|
323
|
-
esac
|
|
324
|
-
printf '%s' "$pull" | jq -e '.reviewed' >/dev/null 2>&1 \
|
|
325
|
-
|| fault "у заявки${pull_name} нет разбора: ревьювер не запрошен и отзыва никто не оставлял. Снятый черновик читается как «можно вливать», а вливать некому — назначь ревьювера и повтори."
|
|
326
|
-
|
|
327
|
-
# Конфликт приезжает в отданную заявку чужим слиянием, без единого действия её
|
|
328
|
-
# автора: основание, проверенное на открытии, к моменту снятия черновика уже
|
|
329
|
-
# вчерашнее. Снятый черновик читается как «можно вливать», а вливать нечего —
|
|
330
|
-
# владелец открывает заявку и находит там конфликт. Молчание при неизвестной
|
|
331
|
-
# сливаемости остаётся: хостинг считает её заново после каждой правки главной ветки,
|
|
332
|
-
# и «ещё не посчитано» — не «конфликтует».
|
|
333
|
-
printf '%s' "$pull" | jq -e '.conflicting' >/dev/null 2>&1 \
|
|
334
|
-
&& fault "заявка${pull_name} конфликтует с главной веткой. Влей её в свою ветку, разбери конфликт и повтори: снятый черновик читается как «можно вливать», а слить эту заявку нельзя."
|
|
335
|
-
|
|
336
|
-
# Автор заявки. На открытии судить было нечем, кроме текста команды: личность вызова
|
|
337
|
-
# приходит окружением. Здесь она уже названа хостингом, и это последний ход, где
|
|
338
|
-
# промах ещё исправим — после снятия черновика заявку вливают, а переоткрыть влитую
|
|
339
|
-
# нельзя. Дерево, не назвавшее машинной записи, автора не судит.
|
|
340
|
-
if [ -n "$task_bot" ]; then
|
|
341
|
-
pull_author="$(printf '%s' "$pull" | jq -r '.author // empty' 2>/dev/null)"
|
|
342
|
-
[ -n "$pull_author" ] && [ "$pull_author" != "$task_bot" ] \
|
|
343
|
-
&& fault "заявку${pull_name} открыла запись «${pull_author}», а не машинная «${task_bot}». Автор заявки её ревьювером не бывает, и разбор ей назначить нечем. Автора не сменить — закрой заявку и открой заново${pull_token_hint:+, подставив токен: ${pull_token_hint} …}."
|
|
344
|
-
fi
|
|
345
|
-
fi
|
|
346
|
-
fi
|
|
347
|
-
|
|
348
|
-
# Папка задачи: тот же предмет, что на открытии и на слиянии, третьим рубежом. Условие
|
|
349
|
-
# местное — оно читает ветку, а не хостинг, — и потому стоит вне сетевого яруса выше.
|
|
350
|
-
command -v rt_delivery_ready_folder >/dev/null 2>&1 && rt_delivery_ready_folder
|
|
351
|
-
|
|
352
|
-
deny_faults
|
|
353
|
-
fi
|
|
284
|
+
# --- снятие черновика ----------------------------------------------------------------------
|
|
285
|
+
# Предмет живёт помощником рядом — `git-guard-delivery-draft.sh`, тем же приёмом, что и подпись.
|
|
286
|
+
# Нет помощника — снятие черновика не судится, а работа идёт дальше.
|
|
287
|
+
# shellcheck disable=SC1090
|
|
288
|
+
[ -f "$rt_hooks_dir/git-guard-delivery-draft.sh" ] \
|
|
289
|
+
&& . "$rt_hooks_dir/git-guard-delivery-draft.sh" 2>/dev/null
|
|
290
|
+
command -v rt_delivery_draft_ready >/dev/null 2>&1 && rt_delivery_draft_ready
|
|
354
291
|
|
|
355
292
|
# --- слияние заявки ----------------------------------------------------------------------
|
|
356
293
|
#
|
|
@@ -404,17 +341,13 @@ if [ -n "$title" ]; then
|
|
|
404
341
|
fi
|
|
405
342
|
fi
|
|
406
343
|
|
|
407
|
-
# Главная ветка влита до открытия PR
|
|
408
|
-
#
|
|
409
|
-
#
|
|
410
|
-
# Ярусов два, и порядок между ними такой же, как у проверки задачи. Первый читает локальную
|
|
411
|
-
# вершину и работает без сети. Второй спрашивает удалённую ссылку — без него молчание гарда
|
|
412
|
-
# значит лишь «твоя ссылка не старше твоей ветки», а читается как «главная ветка влита»: ровно
|
|
413
|
-
# так открытый PR и оказался конфликтующим, и узнал об этом владелец.
|
|
344
|
+
# Главная ветка влита до открытия PR: иначе ревьювер видит чужую правку вперемешку со своей, а
|
|
345
|
+
# проверки идут от устаревшего основания.
|
|
414
346
|
#
|
|
415
|
-
#
|
|
416
|
-
#
|
|
417
|
-
#
|
|
347
|
+
# Ярусов два. Первый читает локальную вершину и работает без сети. Второй спрашивает удалённую
|
|
348
|
+
# ссылку: без него молчание значит лишь «локальная ссылка не старше ветки», а читается как
|
|
349
|
+
# «главная влита». Нет ответа сети — пропуск молча; предел ожидания задаётся переменными git,
|
|
350
|
+
# внешний `timeout` есть не на всякой машине.
|
|
418
351
|
if git rev-parse --verify --quiet "refs/remotes/origin/${main_branch}" >/dev/null 2>&1 \
|
|
419
352
|
&& ! git merge-base --is-ancestor "origin/${main_branch}" HEAD 2>/dev/null; then
|
|
420
353
|
behind="$(git rev-list --count "HEAD..origin/${main_branch}" 2>/dev/null)"
|
|
@@ -438,27 +371,36 @@ if [ -n "$remote_main" ] && [ -n "$local_main" ] && [ "$remote_main" != "$local_
|
|
|
438
371
|
fault "твоя ссылка origin/${main_branch} отстала от удалённой — ${local_main:0:8} против ${remote_main:0:8}.${age} Гард сравнивает ветку с тем, что лежит в дереве, поэтому молчание первого яруса значит «ссылка не старше ветки», а не «главная ветка влита». Влей и повтори: git fetch origin && git merge origin/${main_branch}."
|
|
439
372
|
fi
|
|
440
373
|
|
|
441
|
-
# Личность вызова. Клиент хостинга держит две записи
|
|
442
|
-
#
|
|
443
|
-
#
|
|
444
|
-
# таким же обычным; промах всплывает шагом позже, на назначении ревьювера: автор заявки её
|
|
445
|
-
# ревьювером не бывает. Чинится это только переоткрытием — автора у заявки не сменить.
|
|
374
|
+
# Личность вызова. Клиент хостинга держит две записи — залогиненную и ту, чей токен стоит в
|
|
375
|
+
# окружении вызова; какая откроет заявку, из команды видно только по явной подстановке. Промах
|
|
376
|
+
# всплывает на назначении ревьювера: автор заявки её ревьювером не бывает, а автора не сменить.
|
|
446
377
|
#
|
|
447
|
-
#
|
|
448
|
-
# исполнения нельзя. Дерево, не назвавшее переменной токена, требования не получает.
|
|
378
|
+
# Проверяется текст команды. Дерево, не назвавшее переменной токена, требования не получает.
|
|
449
379
|
if [ -n "$pull_token_var" ] \
|
|
450
380
|
&& ! printf '%s' "$cmd" | grep -qE "(^|[;&|(]|&&|\|\||[[:space:]])${pull_token_var}="; then
|
|
451
381
|
fault "заявка открывается без токена машинной записи: в команде нет подстановки «${pull_token_var}». Открытая залогиненной записью, она выйдет от владельца — ревьювером его тогда не назначить, и чинится это только переоткрытием.${pull_token_hint:+ Подставь токен: ${pull_token_hint} …}"
|
|
452
382
|
fi
|
|
453
383
|
|
|
454
|
-
#
|
|
455
|
-
#
|
|
456
|
-
#
|
|
457
|
-
# уехала без него, а владелец влил её кнопкой, пока шёл прогон.
|
|
384
|
+
# Второй ярус той же личности: кто на самом деле придёт по этому токену. Подстановка говорит
|
|
385
|
+
# о намерении, а не о результате: она читала файл, которого на машине нет, пустую строку клиент
|
|
386
|
+
# принял за незаданный токен, и заявка вышла от владельца при верной с виду команде.
|
|
458
387
|
#
|
|
459
|
-
#
|
|
460
|
-
#
|
|
461
|
-
#
|
|
388
|
+
# У хостинга спрашивает дерево, а не пакет: хостинг, клиент и путь к токену у каждого свои.
|
|
389
|
+
# Пустой ответ означает «спросить не удалось»: вызов пропускается, и об этом сообщается —
|
|
390
|
+
# молчаливый пропуск неотличим от сошедшейся сверки.
|
|
391
|
+
if [ -n "$task_bot" ] && command -v rt_pull_token_login >/dev/null 2>&1; then
|
|
392
|
+
token_login="$(rt_pull_token_login 2>/dev/null)"
|
|
393
|
+
if [ -z "$token_login" ]; then
|
|
394
|
+
printf 'гард поставки: кто придёт по токену, спросить не удалось — сверка по ответу хостинга пропущена.\n' >&2
|
|
395
|
+
elif [ "$token_login" != "$task_bot" ]; then
|
|
396
|
+
fault "по токену вызова хостинг отвечает записью «${token_login}», а не машинной «${task_bot}»: подстановка в команде есть, но значение пустое или чужое — так заявка выходит от владельца. Проверь, что файл токена на месте и читается${pull_token_hint:+: ${pull_token_hint}}."
|
|
397
|
+
fi
|
|
398
|
+
fi
|
|
399
|
+
|
|
400
|
+
# Тело заявки несёт раздел об оставшемся шаге с минуты открытия: без него владелец вливает
|
|
401
|
+
# заявку кнопкой, пока идёт прогон. Тело приходит доводом либо файлом, оба читаются здесь; файл
|
|
402
|
+
# к моменту разбора уже написан. Нет ни того ни другого — требования нет: заявка без тела
|
|
403
|
+
# проверяется строкой выше.
|
|
462
404
|
if [ -n "$pull_body_section" ]; then
|
|
463
405
|
body=''
|
|
464
406
|
if command -v perl >/dev/null 2>&1; then
|
|
@@ -482,13 +424,9 @@ fi
|
|
|
482
424
|
|
|
483
425
|
check_task "$number" "заявка с ветки «${branch}»" да
|
|
484
426
|
|
|
485
|
-
# Папка задачи разбирается до открытия заявки, а не после
|
|
486
|
-
#
|
|
487
|
-
#
|
|
488
|
-
# вливает, как только видит зелёное, и закрывающему коммиту места не остаётся — трижды подряд
|
|
489
|
-
# папка уехала в главную неразобранной, а гард слияния этого не видел вовсе: кнопку нажимает
|
|
490
|
-
# человек на хостинге, и до него гард не достаёт. Разбор переехал сюда, а замысел после уборки
|
|
491
|
-
# гард хода работы берёт из истории ветки, а не с диска.
|
|
427
|
+
# Папка задачи разбирается до открытия заявки, а не после одобрения: владелец вливает, как
|
|
428
|
+
# только видит зелёное, и закрывающему коммиту места не остаётся — трижды подряд папка уехала в
|
|
429
|
+
# главную неразобранной. Замысел после уборки гард хода работы берёт из истории ветки.
|
|
492
430
|
rt_delivery_open_folder
|
|
493
431
|
|
|
494
432
|
# Всё несошедшееся названо здесь, разом: до этой строки собирались условия, каждое из которых
|
|
@@ -44,10 +44,45 @@ if [ "$tool" = "mcp__webstorm__execute_tool" ] && command -v perl >/dev/null 2>&
|
|
|
44
44
|
' 2>/dev/null)"
|
|
45
45
|
[ -n "$inner" ] && cmd="$inner"
|
|
46
46
|
fi
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
47
|
+
# Глагол ищется в позиции команды, а не подстрокой в строке.
|
|
48
|
+
#
|
|
49
|
+
# Голый поиск «git commit» промахивается в обе стороны. Мимо него уходит вызов, у которого между
|
|
50
|
+
# `git` и глаголом стоит ключ — `git -c user.name=… commit`, `git -C <дерево> commit`, — и ровно
|
|
51
|
+
# так коммитят машинной учётной записью. В него же попадает строка, где эти два слова стоят
|
|
52
|
+
# рядом по другому поводу: `git log --grep 'git commit'`, разбор чужого вывода, текст сообщения.
|
|
53
|
+
# Отбой на чтении истории стоит дороже пропуска: гард, мешающий читать, выключают в первый день.
|
|
54
|
+
#
|
|
55
|
+
# Разбор простой: у каждого слова `git` в строке пропускаются ключи — сами по себе и вместе со
|
|
56
|
+
# значением, если ключ его берёт, — и первое слово без дефиса и есть глагол. Слов `git` в строке
|
|
57
|
+
# бывает несколько (`git add . && git commit`), поэтому печатаются глаголы всех, а судится
|
|
58
|
+
# список целиком.
|
|
59
|
+
if command -v awk >/dev/null 2>&1; then
|
|
60
|
+
verbs="$(printf '%s\n' "$cmd" | awk '
|
|
61
|
+
{
|
|
62
|
+
for (i = 1; i <= NF; i++) {
|
|
63
|
+
word = $i
|
|
64
|
+
sub(/^.*\//, "", word)
|
|
65
|
+
if (word != "git") continue
|
|
66
|
+
for (j = i + 1; j <= NF; j++) {
|
|
67
|
+
arg = $j
|
|
68
|
+
if (arg == "-c" || arg == "-C" || arg == "--git-dir" || arg == "--work-tree" \
|
|
69
|
+
|| arg == "--namespace" || arg == "--exec-path") { j++; continue }
|
|
70
|
+
if (substr(arg, 1, 1) == "-") continue
|
|
71
|
+
print arg
|
|
72
|
+
break
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
' 2>/dev/null)"
|
|
77
|
+
printf '%s\n' "$verbs" | grep -qx 'commit' || exit 0
|
|
78
|
+
else
|
|
79
|
+
# Разборщика нет — остаётся прежний признак: он врёт в обе стороны, но гард без него не
|
|
80
|
+
# судит вовсе.
|
|
81
|
+
case "$cmd" in
|
|
82
|
+
*git\ commit*) ;;
|
|
83
|
+
*) exit 0 ;;
|
|
84
|
+
esac
|
|
85
|
+
fi
|
|
51
86
|
|
|
52
87
|
# Коммит выполнится в рабочем каталоге вызова, поэтому и ветку смотрим там же; корень проекта
|
|
53
88
|
# — запасной вариант, и он важен для отдельного рабочего дерева, где ветка своя.
|
|
@@ -114,7 +114,30 @@ done
|
|
|
114
114
|
# shellcheck disable=SC1090
|
|
115
115
|
[ -f "$rt_hooks_dir/profile-check.sh" ] && . "$rt_hooks_dir/profile-check.sh"
|
|
116
116
|
command -v rt_needs >/dev/null 2>&1 || rt_needs() { command -v "$1" >/dev/null 2>&1; }
|
|
117
|
-
|
|
117
|
+
|
|
118
|
+
# Строка наблюдения на каждый исход. Гард, пишущий только отбои, отвечает на один вопрос из
|
|
119
|
+
# трёх: сколько пушей он остановил. «Набор прогнан и зелёный» и «набора не нашлось» выглядят в
|
|
120
|
+
# записи одинаково — молчанием, — и гейт, не гонявший ни одной проверки за неделю, неотличим от
|
|
121
|
+
# гейта, у которого всё зелено. Пишется это той же записью наблюдений, что и отбои гардов, и
|
|
122
|
+
# гаснет тем же выключателем дерева.
|
|
123
|
+
rt_push_gate_note() {
|
|
124
|
+
local outcome="$1" sid
|
|
125
|
+
# shellcheck disable=SC1090
|
|
126
|
+
[ -f "$rt_hooks_dir/observe.sh" ] && . "$rt_hooks_dir/observe.sh" 2>/dev/null
|
|
127
|
+
command -v rt_note >/dev/null 2>&1 || return 0
|
|
128
|
+
sid="$(printf '%s' "$input" | jq -r '.session_id // empty' 2>/dev/null)"
|
|
129
|
+
if [ -n "$sid" ]; then
|
|
130
|
+
rt_note push-gate "res=$outcome" "sid=$sid"
|
|
131
|
+
else
|
|
132
|
+
rt_note push-gate "res=$outcome"
|
|
133
|
+
fi
|
|
134
|
+
return 0
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if ! rt_needs rt_push_checks git-guard-push-tests; then
|
|
138
|
+
rt_push_gate_note no-checks
|
|
139
|
+
exit 0
|
|
140
|
+
fi
|
|
118
141
|
|
|
119
142
|
main_branch="${RT_MAIN_BRANCH:-main}"
|
|
120
143
|
base=''
|
|
@@ -131,8 +154,10 @@ rt_skip_code="${RT_SKIP_CODE:-7}"
|
|
|
131
154
|
failed=""
|
|
132
155
|
output=""
|
|
133
156
|
skipped=""
|
|
157
|
+
ran=0
|
|
134
158
|
while IFS= read -r check; do
|
|
135
159
|
[ -z "$check" ] && continue
|
|
160
|
+
ran=$((ran + 1))
|
|
136
161
|
out="$(eval "$check" 2>&1)"
|
|
137
162
|
status=$?
|
|
138
163
|
[ "$status" -eq 0 ] && continue
|
|
@@ -150,6 +175,13 @@ EOF
|
|
|
150
175
|
|
|
151
176
|
# Пропущенное называется вслух и тогда, когда набор прошёл: молчание о нём и есть та самая
|
|
152
177
|
# неотличимость, ради которой код заведён. Пуш при этом идёт — отказа здесь нет.
|
|
178
|
+
# Функция профиля есть, а печатать ей в этом дереве нечего: исход тот же, что и без неё, —
|
|
179
|
+
# набора не нашлось, и молчание о нём читалось бы как зелёный прогон.
|
|
180
|
+
if [ "$ran" -eq 0 ]; then
|
|
181
|
+
rt_push_gate_note no-checks
|
|
182
|
+
exit 0
|
|
183
|
+
fi
|
|
184
|
+
|
|
153
185
|
if [ -z "$failed" ] && [ -n "$skipped" ]; then
|
|
154
186
|
printf 'гейт пуша: набор прошёл, но эти проверки смотреть было не на что:\n%s\n' "$skipped" >&2
|
|
155
187
|
fi
|
|
@@ -176,11 +208,25 @@ if [ ! -f "$gap_mark" ]; then
|
|
|
176
208
|
: >"$gap_mark" 2>/dev/null || true
|
|
177
209
|
fi
|
|
178
210
|
|
|
179
|
-
[ -z "$failed" ]
|
|
211
|
+
if [ -z "$failed" ]; then
|
|
212
|
+
rt_push_gate_note green
|
|
213
|
+
exit 0
|
|
214
|
+
fi
|
|
215
|
+
|
|
216
|
+
rt_push_gate_note red
|
|
180
217
|
|
|
181
218
|
# Хвост вывода, а не весь: у прогонщика он длинный, а нужна причина отказа.
|
|
182
219
|
tail_out="$(printf '%s' "$output" | tail -n 40 | tr -d '\000')"
|
|
183
|
-
|
|
220
|
+
# Красное бывает двух родов, и гард их не различает: он проверяет только код возврата. Когда
|
|
221
|
+
# падает проверка кода, «почини и пушь снова» верно. Когда ошибается сама проверка, тот же текст
|
|
222
|
+
# велит чинить код, которого никто не трогал: разобранный однажды отказ целиком лежал в
|
|
223
|
+
# документах, не тронутых ни одним коммитом ветки. Закон о проверяемости говорит, что сломанная
|
|
224
|
+
# проверка работу не останавливает, и до этой строки такого варианта в отказе не было.
|
|
225
|
+
reason="BLOCKED: пуш без зелёного локального прогона. «${failed}» упала — почини и пушь снова, обходить гард нельзя. Пуш — вход в конвейер, и красное отсюда проверяется уже на проде.
|
|
226
|
+
|
|
227
|
+
Ходов отсюда три: починить названное и повторить вызов; починить саму проверку, если ошибается она, — разобрать отказы поимённо, показать разбор владельцу и поправить проверку; либо принести владельцу цену обхода и ждать его слова. Спорное в список известного не вносится: он хранит принятое, а не результаты сломанной проверки.
|
|
228
|
+
|
|
229
|
+
Хвост вывода:
|
|
184
230
|
|
|
185
231
|
${tail_out}"
|
|
186
232
|
|
|
@@ -58,17 +58,28 @@ rt_hook_field() {
|
|
|
58
58
|
# прошли мимо своих гардов пуш, открытие заявки, снятие черновика и слияние: коммит с чужой
|
|
59
59
|
# подписью уехал в главную ветку, а запрет на слияние агентом снимался подстановкой токена.
|
|
60
60
|
#
|
|
61
|
-
# Значение присваивания
|
|
62
|
-
#
|
|
63
|
-
#
|
|
64
|
-
#
|
|
65
|
-
#
|
|
61
|
+
# Значение присваивания бывает трёх видов: без пробелов, подстановкой `$( … )` и в кавычках.
|
|
62
|
+
# Раньше принимался только первый, а заявки открывают командой `GH_TOKEN=$(cat <файл>) gh pr
|
|
63
|
+
# create …` — путь в подстановке содержит пробел, и гард поставки такую команду не проверял:
|
|
64
|
+
# ни номер ветки, ни тело заявки, ни разбор папки задачи. Молчание гарда неотличимо от
|
|
65
|
+
# разрешения.
|
|
66
|
+
#
|
|
67
|
+
# Подстановка берётся до первой закрывающей скобки, строка в кавычках — до закрывающей кавычки
|
|
68
|
+
# того же вида. Вложенные скобки и кавычки образец не разбирает: признак остаётся образцом, а не
|
|
69
|
+
# разборщиком оболочки. Ошибаться признак должен в сторону лишнего срабатывания: гард, не
|
|
70
|
+
# распознавший вызов, молчит и выглядит исправным; сработавший лишний раз виден сразу и правится.
|
|
66
71
|
#
|
|
67
72
|
# Само по себе присваивание вызовом не считается: за ним обязано стоять имя команды.
|
|
68
73
|
#
|
|
74
|
+
# Каталог перед именем — та же команда. Клиент, у которого своё имя занято псевдонимом оболочки,
|
|
75
|
+
# зовут полным путём, и признак, знавший только голое имя, такой вызов не узнавал вовсе: гард
|
|
76
|
+
# выходил нулём, а молчание его неотличимо от разрешения. Так заявки и уехали открытыми не
|
|
77
|
+
# машинной записью. Часть пути не переходит через пробел, поэтому она остаётся внутри одного
|
|
78
|
+
# слова и лишнего к вызову не приклеивает.
|
|
79
|
+
#
|
|
69
80
|
# Объявлено это одним местом, а не литералом в каждом гарде: разойдясь, копии чинятся по одной
|
|
70
81
|
# и молчат о том, что остальные остались слепыми.
|
|
71
|
-
RT_CMD_BOUND='(^|[;&|(]|&&|\|\|)[[:space:]]*([A-Za-z_][A-Za-z0-9_]*=[^[:space:]]*[[:space:]]+)*'
|
|
82
|
+
RT_CMD_BOUND='(^|[;&|(]|&&|\|\|)[[:space:]]*([A-Za-z_][A-Za-z0-9_]*=([^[:space:]]*|\$\([^)]*\)|"[^"]*"|'"'"'[^'"'"']*'"'"')[[:space:]]+)*([^[:space:]]*/)?'
|
|
72
83
|
|
|
73
84
|
# Поля, которые спрашивают все гарды.
|
|
74
85
|
rt_hook_tool() { rt_hook_field RT_HOOK_TOOL '.tool_name'; }
|
|
@@ -107,6 +107,17 @@ while IFS= read -r candidate; do
|
|
|
107
107
|
resource="$(head -12 "$candidate" 2>/dev/null | sed -nE 's/.*rt-kit v[^ ]+ · ([^ ]+) · [0-9a-f]+.*/\1/p' | head -1)"
|
|
108
108
|
[ -z "$resource" ] && continue
|
|
109
109
|
|
|
110
|
+
# Файл в конфликте пропускается наравне со снятым. Разрешение конфликта содержания копии не
|
|
111
|
+
# меняет — раскладка кладёт её заново, — а отбитый здесь исполнитель остаётся с наполовину
|
|
112
|
+
# слитой веткой и без законного хода: источник править нечего, надстройка конфликта не
|
|
113
|
+
# снимает. Признаков два, и хватает любого: запись индекса о неслитом файле и маркеры
|
|
114
|
+
# слияния в самом файле — второй нужен там, где слияние ведёт не git, а сторонний
|
|
115
|
+
# инструмент, оставляющий маркеры без записи в индексе.
|
|
116
|
+
if [ -n "$(git -C "$root" ls-files -u -- "$candidate" 2>/dev/null)" ] \
|
|
117
|
+
|| grep -qE '^(<<<<<<< |>>>>>>> )' "$candidate" 2>/dev/null; then
|
|
118
|
+
continue
|
|
119
|
+
fi
|
|
120
|
+
|
|
110
121
|
if [ -n "$sources" ] && [ -f "$root/$sources/$resource" ]; then
|
|
111
122
|
deny "BLOCKED by rule-source-guard: «${candidate#"$root"/}» разложен пакетом, и правка на его месте теряется на следующей раскладке — а до тех пор раскладка отказывает по этому файлу целиком, и цену платит тот, кто в этот день правит соседний ресурс. Ресурс — «${resource}». Правь источник: ${sources}/${resource} — потом собери пакет и разложи." \
|
|
112
123
|
"правка, верная только этому дереву, идёт в надстройку .claude/rt-kit/overrides/${resource} — она сливается по разделу «## » и раскладку переживает"
|