@rt-tools/agent-kit 0.22.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-long-work.github.mjs +101 -0
- package/assets/checks/board-runs.github.mjs +34 -0
- package/assets/checks/board.github.mjs +1 -1
- package/assets/checks/check-board.github.mjs +30 -2
- 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/checks/check-reuse.mjs +4 -1
- package/assets/checks/check-schema-drift.mjs +65 -6
- package/assets/checks/rt-kit-checks.config.mjs +13 -0
- package/assets/checks/signals.mjs +41 -1
- package/assets/commands/next-session.md +16 -5
- package/assets/defaults/gate-map.sh +13 -0
- package/assets/defaults/project.sh +26 -0
- package/assets/defaults/shell.sh +18 -3
- package/assets/hooks/browser-guard-device-id.sh +42 -12
- package/assets/hooks/browser-guard-no-asking.sh +5 -1
- 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 +64 -122
- package/assets/hooks/git-guard-main.sh +39 -4
- package/assets/hooks/git-guard-push-tests.sh +71 -3
- package/assets/hooks/glossary-load.sh +23 -2
- package/assets/hooks/grill-gate.sh +62 -0
- 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/autonomous-work.md +30 -0
- package/assets/laws/project-documentation.md +8 -0
- package/assets/laws/verifiability.md +12 -2
- package/assets/laws/work-conduct.md +59 -65
- package/assets/patterns/autonomous-work-run.md +105 -0
- package/assets/patterns/browser-verification-measure.md +41 -1
- package/assets/patterns/browser-verification-stand.md +57 -16
- package/assets/patterns/doc-style-human.md +75 -0
- package/assets/patterns/doc-style-write.md +16 -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/git-workflow-pr-ready.md +93 -0
- package/assets/patterns/git-workflow-pr.azure.md +1 -1
- package/assets/patterns/git-workflow-pr.github.md +1 -1
- package/assets/patterns/git-workflow-pr.gitlab.md +1 -1
- package/assets/patterns/task-flow-start.md +48 -48
- package/assets/patterns/testing-e2e.md +18 -8
- package/assets/patterns/ts-procedure.md +3 -2
- package/assets/pitfalls/task-flow.md +40 -40
- package/assets/rules/autonomous-work.md +92 -0
- package/assets/rules/browser-verification.md +35 -3
- package/assets/rules/deploy-flow.azure.md +7 -0
- package/assets/rules/deploy-flow.github.md +7 -0
- package/assets/rules/deploy-flow.gitlab.md +7 -0
- package/assets/rules/doc-style.md +54 -0
- package/assets/rules/git-workflow.azure.md +8 -0
- package/assets/rules/git-workflow.github.md +59 -63
- package/assets/rules/git-workflow.gitlab.md +8 -0
- package/assets/rules/reuse-first.md +25 -5
- package/assets/rules/styling-bem.md +8 -1
- package/assets/rules/task-flow.md +108 -109
- package/assets/rules/testing.md +21 -0
- package/assets/skills/agent-kit.md +72 -82
- package/lib/commands.d.ts.map +1 -1
- package/lib/commands.js +69 -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/push-gate.d.ts +14 -0
- package/lib/push-gate.d.ts.map +1 -0
- package/lib/push-gate.js +93 -0
- package/lib/push-gate.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/assets/laws/application/access.md +0 -34
- package/assets/laws/application/locales.md +0 -33
- package/assets/laws/application/search-visibility.md +0 -24
- package/assets/patterns/permissions-procedure.md +0 -71
- package/assets/patterns/seo-page.md +0 -104
- package/assets/patterns/seo-verify.md +0 -83
- package/assets/patterns/translations-content.md +0 -107
- package/assets/patterns/translations-key.md +0 -64
- package/assets/rules/permissions.md +0 -116
- package/assets/rules/seo.md +0 -139
- package/assets/rules/translations.md +0 -96
- package/rt-tools-agent-kit-0.22.0.tgz +0 -0
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
#
|
|
26
26
|
# Что здесь чем зовётся, знает профиль дерева:
|
|
27
27
|
# rt_task_branch_ok — форма имени ветки под задачу;
|
|
28
|
+
# rt_task_branch_number — номер задачи из этого имени: приставкой бывает и род правки;
|
|
28
29
|
# RT_TASK_TITLE_RE — форма номера в заголовке заявки;
|
|
29
30
|
# rt_task_state — состояние задачи одним объектом (existsize, open, onBoard, assigned,
|
|
30
31
|
# numbered); молчание значит «спросить некого»;
|
|
@@ -95,6 +96,7 @@ done
|
|
|
95
96
|
[ -f "$rt_hooks_dir/profile-check.sh" ] && . "$rt_hooks_dir/profile-check.sh"
|
|
96
97
|
command -v rt_needs >/dev/null 2>&1 || rt_needs() { command -v "$1" >/dev/null 2>&1; }
|
|
97
98
|
rt_needs rt_task_branch_ok git-guard-delivery || exit 0
|
|
99
|
+
rt_needs rt_task_branch_number git-guard-delivery || exit 0
|
|
98
100
|
|
|
99
101
|
title_re="${RT_TASK_TITLE_RE:-^\[[A-Za-z]+-[0-9]+\][[:space:]]+[^[:space:]]}"
|
|
100
102
|
task_new="${RT_TASK_NEW_CMD:-npm run task:new}"
|
|
@@ -120,15 +122,12 @@ tasks_dir="${RT_TASKS_DIR:-}"
|
|
|
120
122
|
archive_dir="${RT_ARCHIVE_DIR:-}"
|
|
121
123
|
main_branch="${RT_MAIN_BRANCH:-main}"
|
|
122
124
|
|
|
123
|
-
# Обход требования: строка с причиной.
|
|
124
|
-
#
|
|
125
|
-
# же, что у гарда документа: если сделать по-разному, две формы одного обхода разойдутся.
|
|
125
|
+
# Обход требования: строка с причиной. Причину видит тот, кто вливает, поэтому обход разрешён;
|
|
126
|
+
# без причины это молчаливый пропуск. Порог в три знака — тот же, что у гарда документа.
|
|
126
127
|
#
|
|
127
|
-
# Строка обхода
|
|
128
|
-
# подстановки не принимает.
|
|
129
|
-
#
|
|
130
|
-
# собой, и тот же пример гард печатает в своём отказе ниже. Нашлось это приёмкой такого же
|
|
131
|
-
# признака — прогон обязан был покраснеть на настоящем нарушении и остался зелёным.
|
|
128
|
+
# Строка обхода стоит в начале строки — своей в теле PR или комментария в конце команды — и
|
|
129
|
+
# подстановки не принимает. Иначе текст, который объясняет, как называется обход, неотличим от
|
|
130
|
+
# самого обхода: тело PR со строкой-примером снимало требование само.
|
|
132
131
|
folder_skip_re='(^|#)[[:space:]]*Task-folder-skip:[[:space:]]*[^[:space:]<"'"'"'][^[:space:]"'"'"']{2,}'
|
|
133
132
|
|
|
134
133
|
deny() {
|
|
@@ -146,10 +145,9 @@ deny() {
|
|
|
146
145
|
exit 0
|
|
147
146
|
}
|
|
148
147
|
|
|
149
|
-
# Несошедшиеся условия копятся и называются
|
|
150
|
-
#
|
|
151
|
-
#
|
|
152
|
-
# было известно уже на первом. Готовность к поставке — одно состояние, и называется оно целиком.
|
|
148
|
+
# Несошедшиеся условия копятся и называются разом: отказ по первому промаху заставляет чинить их
|
|
149
|
+
# по одному — правка основания, повтор, заголовок, снова повтор, задача, — хотя всё несошедшееся
|
|
150
|
+
# известно уже на первом круге. Готовность к поставке — одно состояние, и называется оно целиком.
|
|
153
151
|
faults=''
|
|
154
152
|
|
|
155
153
|
fault() {
|
|
@@ -163,10 +161,9 @@ fault() {
|
|
|
163
161
|
# shellcheck disable=SC1090
|
|
164
162
|
[ -f "$rt_hooks_dir/git-guard-delivery-folder.sh" ] && . "$rt_hooks_dir/git-guard-delivery-folder.sh" 2>/dev/null
|
|
165
163
|
|
|
166
|
-
# Конфликтующая своя заявка: тот же приём, что у папки и
|
|
167
|
-
#
|
|
168
|
-
#
|
|
169
|
-
# ярус не судится, а работа идёт дальше.
|
|
164
|
+
# Конфликтующая своя заявка: тот же приём, что у папки и подписи. Зовётся помощник до всех ярусов
|
|
165
|
+
# ниже и судит не готовность этой работы, а право брать следующую: пока отданное конфликтует, его
|
|
166
|
+
# чинят первым действием хода. Нет помощника — ярус не судится, а работа идёт дальше.
|
|
170
167
|
# shellcheck disable=SC1090
|
|
171
168
|
[ -f "$rt_hooks_dir/git-guard-delivery-conflict.sh" ] && . "$rt_hooks_dir/git-guard-delivery-conflict.sh" 2>/dev/null
|
|
172
169
|
command -v rt_delivery_conflict >/dev/null 2>&1 && rt_delivery_conflict
|
|
@@ -225,23 +222,22 @@ fi
|
|
|
225
222
|
if [ -n "$branch_arg" ]; then
|
|
226
223
|
# Имя, притворяющееся веткой под задачу, но не совпадающее с формой, — это промах в имени,
|
|
227
224
|
# а не осознанная беззадачная ветка. Ловится до первого коммита.
|
|
228
|
-
|
|
225
|
+
# Номер вынимает профиль: своя регулярка знала одну приставку — ключ задач, — и ветка вида
|
|
226
|
+
# `feat/88-slug`, законная по форме того же профиля, номера не давала вовсе. Проверка формы
|
|
227
|
+
# при заведении не срабатывала на ней ни разу, а сверка номеров пропускалась молча.
|
|
228
|
+
number_arg="$(rt_task_branch_number "$branch_arg")"
|
|
229
|
+
if [ -n "$number_arg" ]; then
|
|
229
230
|
rt_task_branch_ok "$branch_arg" \
|
|
230
231
|
|| deny "BLOCKED: имя ветки «${branch_arg}» не той формы, что принята здесь. Номер у ветки тот же, что у задачи и у заголовка заявки на слияние."
|
|
231
|
-
check_task "$
|
|
232
|
+
check_task "$number_arg" "ветка «${branch_arg}»"
|
|
232
233
|
|
|
233
|
-
# Условия поставки, известные уже здесь,
|
|
234
|
-
#
|
|
235
|
-
#
|
|
236
|
-
# начале работы обе стоят одну команду.
|
|
234
|
+
# Условия поставки, известные уже здесь, проверяются здесь. После того как работа
|
|
235
|
+
# сделана, основание чинится слиянием с разбором конфликта, а подпись коммитов —
|
|
236
|
+
# переписыванием ветки; в начале работы обе стоят одну команду.
|
|
237
237
|
#
|
|
238
238
|
# Основание: вершина главной ветки обязана лежать в том, от чего растёт новая ветка.
|
|
239
|
-
#
|
|
240
|
-
#
|
|
241
|
-
#
|
|
242
|
-
# Судится названное основание, а не вершина рабочей копии: команда, которой основание
|
|
243
|
-
# как раз и берут свежим — `git checkout -b <ветка> origin/<главная>`, — иначе
|
|
244
|
-
# отбивалась бы наравне с той, у которой основание вчерашнее.
|
|
239
|
+
# Проверяется названное основание, а не вершина рабочей копии: иначе команда, которой
|
|
240
|
+
# основание берут свежим — `git checkout -b <ветка> origin/<главная>`, — запрещалась бы.
|
|
245
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)"
|
|
246
242
|
base_ref="${base_arg:-HEAD}"
|
|
247
243
|
if git rev-parse --verify --quiet "refs/remotes/origin/${main_branch}" >/dev/null 2>&1 \
|
|
@@ -285,68 +281,13 @@ fi
|
|
|
285
281
|
&& . "$rt_hooks_dir/git-guard-delivery-signature.sh" 2>/dev/null
|
|
286
282
|
command -v rt_delivery_signature >/dev/null 2>&1 && rt_delivery_signature
|
|
287
283
|
|
|
288
|
-
# --- снятие черновика
|
|
289
|
-
#
|
|
290
|
-
#
|
|
291
|
-
#
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
# Ярус сетевой, и молчит он так же, как ярус состояния задачи: нет ответа — нет требования.
|
|
296
|
-
#
|
|
297
|
-
# Возврат заявки в черновик под требование не подпадает: он делает ровно то, чего гард и
|
|
298
|
-
# добивается, — снимает с работы вид готовой.
|
|
299
|
-
if printf '%s' "$cmd" | grep -qE "${RT_CMD_BOUND}(gh[[:space:]]+pr[[:space:]]+ready|glab[[:space:]]+mr[[:space:]]+update[^|;&]*--ready)([[:space:]]|\$)" \
|
|
300
|
-
&& ! printf '%s' "$cmd" | grep -q -- '--undo'; then
|
|
301
|
-
# Ссылка на заявку необязательна: без неё клиент берёт заявку текущей ветки, и это самая
|
|
302
|
-
# короткая форма вызова. Требовать номер значило бы снимать всё требование одним пробелом.
|
|
303
|
-
# Поэтому берётся первый довод, каким бы он ни был — номер, адрес или имя ветки, — а его
|
|
304
|
-
# отсутствие означает «спроси про текущую ветку».
|
|
305
|
-
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)"
|
|
306
|
-
if rt_needs rt_pull_state git-guard-delivery; then
|
|
307
|
-
pull="$(cd "$root" && rt_pull_state "$pull_ref" 2>/dev/null)" || pull=''
|
|
308
|
-
if [ -n "$pull" ] && printf '%s' "$pull" | jq -e '.exists' >/dev/null 2>&1; then
|
|
309
|
-
# Номер берётся из ответа, а если его там нет — из самой команды: заявка,
|
|
310
|
-
# названная адресом или именем ветки, в отказе должна остаться узнаваемой.
|
|
311
|
-
pull_name="$(printf '%s' "$pull" | jq -r '.number // empty' 2>/dev/null)"
|
|
312
|
-
# Номер пишется с решёткой, а имя ветки или адрес — в кавычках: решётка перед
|
|
313
|
-
# адресом читается как опечатка, а не как ссылка на заявку.
|
|
314
|
-
[ -z "$pull_name" ] && pull_name="$pull_ref"
|
|
315
|
-
case "$pull_name" in
|
|
316
|
-
'') ;;
|
|
317
|
-
*[!0-9]*) pull_name=" «${pull_name}»" ;;
|
|
318
|
-
*) pull_name=" #${pull_name}" ;;
|
|
319
|
-
esac
|
|
320
|
-
printf '%s' "$pull" | jq -e '.reviewed' >/dev/null 2>&1 \
|
|
321
|
-
|| fault "у заявки${pull_name} нет разбора: ревьювер не запрошен и отзыва никто не оставлял. Снятый черновик читается как «можно вливать», а вливать некому — назначь ревьювера и повтори."
|
|
322
|
-
|
|
323
|
-
# Конфликт приезжает в отданную заявку чужим слиянием, без единого действия её
|
|
324
|
-
# автора: основание, проверенное на открытии, к моменту снятия черновика уже
|
|
325
|
-
# вчерашнее. Снятый черновик читается как «можно вливать», а вливать нечего —
|
|
326
|
-
# владелец открывает заявку и находит там конфликт. Молчание при неизвестной
|
|
327
|
-
# сливаемости остаётся: хостинг считает её заново после каждой правки главной ветки,
|
|
328
|
-
# и «ещё не посчитано» — не «конфликтует».
|
|
329
|
-
printf '%s' "$pull" | jq -e '.conflicting' >/dev/null 2>&1 \
|
|
330
|
-
&& fault "заявка${pull_name} конфликтует с главной веткой. Влей её в свою ветку, разбери конфликт и повтори: снятый черновик читается как «можно вливать», а слить эту заявку нельзя."
|
|
331
|
-
|
|
332
|
-
# Автор заявки. На открытии судить было нечем, кроме текста команды: личность вызова
|
|
333
|
-
# приходит окружением. Здесь она уже названа хостингом, и это последний ход, где
|
|
334
|
-
# промах ещё исправим — после снятия черновика заявку вливают, а переоткрыть влитую
|
|
335
|
-
# нельзя. Дерево, не назвавшее машинной записи, автора не судит.
|
|
336
|
-
if [ -n "$task_bot" ]; then
|
|
337
|
-
pull_author="$(printf '%s' "$pull" | jq -r '.author // empty' 2>/dev/null)"
|
|
338
|
-
[ -n "$pull_author" ] && [ "$pull_author" != "$task_bot" ] \
|
|
339
|
-
&& fault "заявку${pull_name} открыла запись «${pull_author}», а не машинная «${task_bot}». Автор заявки её ревьювером не бывает, и разбор ей назначить нечем. Автора не сменить — закрой заявку и открой заново${pull_token_hint:+, подставив токен: ${pull_token_hint} …}."
|
|
340
|
-
fi
|
|
341
|
-
fi
|
|
342
|
-
fi
|
|
343
|
-
|
|
344
|
-
# Папка задачи: тот же предмет, что на открытии и на слиянии, третьим рубежом. Условие
|
|
345
|
-
# местное — оно читает ветку, а не хостинг, — и потому стоит вне сетевого яруса выше.
|
|
346
|
-
command -v rt_delivery_ready_folder >/dev/null 2>&1 && rt_delivery_ready_folder
|
|
347
|
-
|
|
348
|
-
deny_faults
|
|
349
|
-
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
|
|
350
291
|
|
|
351
292
|
# --- слияние заявки ----------------------------------------------------------------------
|
|
352
293
|
#
|
|
@@ -374,7 +315,7 @@ branch="$(git branch --show-current 2>/dev/null)"
|
|
|
374
315
|
rt_task_branch_ok "$branch" \
|
|
375
316
|
|| deny "BLOCKED: заявка с ветки «${branch}», за которой не стоит задачи. Правка начинается с задачи, видимой в очереди работ: заведи её — ${task_new} — и перенеси работу в ветку с её номером."
|
|
376
317
|
|
|
377
|
-
number="$(
|
|
318
|
+
number="$(rt_task_branch_number "$branch")"
|
|
378
319
|
|
|
379
320
|
title=''
|
|
380
321
|
if command -v perl >/dev/null 2>&1; then
|
|
@@ -400,17 +341,13 @@ if [ -n "$title" ]; then
|
|
|
400
341
|
fi
|
|
401
342
|
fi
|
|
402
343
|
|
|
403
|
-
# Главная ветка влита до открытия PR
|
|
404
|
-
#
|
|
405
|
-
#
|
|
406
|
-
# Ярусов два, и порядок между ними такой же, как у проверки задачи. Первый читает локальную
|
|
407
|
-
# вершину и работает без сети. Второй спрашивает удалённую ссылку — без него молчание гарда
|
|
408
|
-
# значит лишь «твоя ссылка не старше твоей ветки», а читается как «главная ветка влита»: ровно
|
|
409
|
-
# так открытый PR и оказался конфликтующим, и узнал об этом владелец.
|
|
344
|
+
# Главная ветка влита до открытия PR: иначе ревьювер видит чужую правку вперемешку со своей, а
|
|
345
|
+
# проверки идут от устаревшего основания.
|
|
410
346
|
#
|
|
411
|
-
#
|
|
412
|
-
#
|
|
413
|
-
#
|
|
347
|
+
# Ярусов два. Первый читает локальную вершину и работает без сети. Второй спрашивает удалённую
|
|
348
|
+
# ссылку: без него молчание значит лишь «локальная ссылка не старше ветки», а читается как
|
|
349
|
+
# «главная влита». Нет ответа сети — пропуск молча; предел ожидания задаётся переменными git,
|
|
350
|
+
# внешний `timeout` есть не на всякой машине.
|
|
414
351
|
if git rev-parse --verify --quiet "refs/remotes/origin/${main_branch}" >/dev/null 2>&1 \
|
|
415
352
|
&& ! git merge-base --is-ancestor "origin/${main_branch}" HEAD 2>/dev/null; then
|
|
416
353
|
behind="$(git rev-list --count "HEAD..origin/${main_branch}" 2>/dev/null)"
|
|
@@ -434,27 +371,36 @@ if [ -n "$remote_main" ] && [ -n "$local_main" ] && [ "$remote_main" != "$local_
|
|
|
434
371
|
fault "твоя ссылка origin/${main_branch} отстала от удалённой — ${local_main:0:8} против ${remote_main:0:8}.${age} Гард сравнивает ветку с тем, что лежит в дереве, поэтому молчание первого яруса значит «ссылка не старше ветки», а не «главная ветка влита». Влей и повтори: git fetch origin && git merge origin/${main_branch}."
|
|
435
372
|
fi
|
|
436
373
|
|
|
437
|
-
# Личность вызова. Клиент хостинга держит две записи
|
|
438
|
-
#
|
|
439
|
-
#
|
|
440
|
-
# таким же обычным; промах всплывает шагом позже, на назначении ревьювера: автор заявки её
|
|
441
|
-
# ревьювером не бывает. Чинится это только переоткрытием — автора у заявки не сменить.
|
|
374
|
+
# Личность вызова. Клиент хостинга держит две записи — залогиненную и ту, чей токен стоит в
|
|
375
|
+
# окружении вызова; какая откроет заявку, из команды видно только по явной подстановке. Промах
|
|
376
|
+
# всплывает на назначении ревьювера: автор заявки её ревьювером не бывает, а автора не сменить.
|
|
442
377
|
#
|
|
443
|
-
#
|
|
444
|
-
# исполнения нельзя. Дерево, не назвавшее переменной токена, требования не получает.
|
|
378
|
+
# Проверяется текст команды. Дерево, не назвавшее переменной токена, требования не получает.
|
|
445
379
|
if [ -n "$pull_token_var" ] \
|
|
446
380
|
&& ! printf '%s' "$cmd" | grep -qE "(^|[;&|(]|&&|\|\||[[:space:]])${pull_token_var}="; then
|
|
447
381
|
fault "заявка открывается без токена машинной записи: в команде нет подстановки «${pull_token_var}». Открытая залогиненной записью, она выйдет от владельца — ревьювером его тогда не назначить, и чинится это только переоткрытием.${pull_token_hint:+ Подставь токен: ${pull_token_hint} …}"
|
|
448
382
|
fi
|
|
449
383
|
|
|
450
|
-
#
|
|
451
|
-
#
|
|
452
|
-
#
|
|
453
|
-
# уехала без него, а владелец влил её кнопкой, пока шёл прогон.
|
|
384
|
+
# Второй ярус той же личности: кто на самом деле придёт по этому токену. Подстановка говорит
|
|
385
|
+
# о намерении, а не о результате: она читала файл, которого на машине нет, пустую строку клиент
|
|
386
|
+
# принял за незаданный токен, и заявка вышла от владельца при верной с виду команде.
|
|
454
387
|
#
|
|
455
|
-
#
|
|
456
|
-
#
|
|
457
|
-
#
|
|
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
|
+
# проверяется строкой выше.
|
|
458
404
|
if [ -n "$pull_body_section" ]; then
|
|
459
405
|
body=''
|
|
460
406
|
if command -v perl >/dev/null 2>&1; then
|
|
@@ -478,13 +424,9 @@ fi
|
|
|
478
424
|
|
|
479
425
|
check_task "$number" "заявка с ветки «${branch}»" да
|
|
480
426
|
|
|
481
|
-
# Папка задачи разбирается до открытия заявки, а не после
|
|
482
|
-
#
|
|
483
|
-
#
|
|
484
|
-
# вливает, как только видит зелёное, и закрывающему коммиту места не остаётся — трижды подряд
|
|
485
|
-
# папка уехала в главную неразобранной, а гард слияния этого не видел вовсе: кнопку нажимает
|
|
486
|
-
# человек на хостинге, и до него гард не достаёт. Разбор переехал сюда, а замысел после уборки
|
|
487
|
-
# гард хода работы берёт из истории ветки, а не с диска.
|
|
427
|
+
# Папка задачи разбирается до открытия заявки, а не после одобрения: владелец вливает, как
|
|
428
|
+
# только видит зелёное, и закрывающему коммиту места не остаётся — трижды подряд папка уехала в
|
|
429
|
+
# главную неразобранной. Замысел после уборки гард хода работы берёт из истории ветки.
|
|
488
430
|
rt_delivery_open_folder
|
|
489
431
|
|
|
490
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,15 +175,58 @@ 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
|
|
156
188
|
|
|
157
|
-
|
|
189
|
+
# Чем набор гейта уже набора конвейера — сказанное один раз за сессию.
|
|
190
|
+
#
|
|
191
|
+
# Правило поставки требует, чтобы гейт не был уже конвейера, а собрать это требование нечем:
|
|
192
|
+
# файл конвейера у каждого дерева свой, и вывести из него шаги набора пакет не может. Молчание
|
|
193
|
+
# при этом читается как «проверено всё»: расхождение узнаётся из красного конвейера после
|
|
194
|
+
# заявки, когда правка уже отдана человеку.
|
|
195
|
+
#
|
|
196
|
+
# Отметка живёт в каталоге временных файлов, как у слова о нехватке функции профиля: на каждый
|
|
197
|
+
# пуш та же строка повторялась бы за заход десятки раз и перестала бы читаться.
|
|
198
|
+
gap_key="$(printf '%s' "$input" | jq -r '.session_id // empty' 2>/dev/null)"
|
|
199
|
+
[ -z "$gap_key" ] && gap_key="$(date +%Y%m%d 2>/dev/null || printf 'nosession')"
|
|
200
|
+
gap_mark="${TMPDIR:-/tmp}/rt-kit-push-gate-gap-$gap_key"
|
|
201
|
+
if [ ! -f "$gap_mark" ]; then
|
|
202
|
+
printf 'гейт пуша: набор гейта — не набор конвейера. Сборка образов, снимки показа и проверка
|
|
203
|
+
' >&2
|
|
204
|
+
printf 'собранных пакетов в него не входят: что в нём стоит, показывает разбор состояния
|
|
205
|
+
' >&2
|
|
206
|
+
printf '(agent-kit doctor, раздел «набор перед пушем»), — прогони недостающее до заявки.
|
|
207
|
+
' >&2
|
|
208
|
+
: >"$gap_mark" 2>/dev/null || true
|
|
209
|
+
fi
|
|
210
|
+
|
|
211
|
+
if [ -z "$failed" ]; then
|
|
212
|
+
rt_push_gate_note green
|
|
213
|
+
exit 0
|
|
214
|
+
fi
|
|
215
|
+
|
|
216
|
+
rt_push_gate_note red
|
|
158
217
|
|
|
159
218
|
# Хвост вывода, а не весь: у прогонщика он длинный, а нужна причина отказа.
|
|
160
219
|
tail_out="$(printf '%s' "$output" | tail -n 40 | tr -d '\000')"
|
|
161
|
-
|
|
220
|
+
# Красное бывает двух родов, и гард их не различает: он проверяет только код возврата. Когда
|
|
221
|
+
# падает проверка кода, «почини и пушь снова» верно. Когда ошибается сама проверка, тот же текст
|
|
222
|
+
# велит чинить код, которого никто не трогал: разобранный однажды отказ целиком лежал в
|
|
223
|
+
# документах, не тронутых ни одним коммитом ветки. Закон о проверяемости говорит, что сломанная
|
|
224
|
+
# проверка работу не останавливает, и до этой строки такого варианта в отказе не было.
|
|
225
|
+
reason="BLOCKED: пуш без зелёного локального прогона. «${failed}» упала — почини и пушь снова, обходить гард нельзя. Пуш — вход в конвейер, и красное отсюда проверяется уже на проде.
|
|
226
|
+
|
|
227
|
+
Ходов отсюда три: починить названное и повторить вызов; починить саму проверку, если ошибается она, — разобрать отказы поимённо, показать разбор владельцу и поправить проверку; либо принести владельцу цену обхода и ждать его слова. Спорное в список известного не вносится: он хранит принятое, а не результаты сломанной проверки.
|
|
228
|
+
|
|
229
|
+
Хвост вывода:
|
|
162
230
|
|
|
163
231
|
${tail_out}"
|
|
164
232
|
|
|
@@ -15,11 +15,32 @@ GLOSSARY="${CLAUDE_PROJECT_DIR:-.}/docs/GLOSSARY.md"
|
|
|
15
15
|
[ -f "$GLOSSARY" ] || exit 0
|
|
16
16
|
command -v jq >/dev/null 2>&1 || exit 0
|
|
17
17
|
|
|
18
|
+
# Где слово заводят. Словарь бывает разложен пакетом — тогда правка на его месте теряется на
|
|
19
|
+
# следующей раскладке, а до тех пор раскладка отказывает по нему целиком. Вводная, которую читает
|
|
20
|
+
# каждая сессия, обязана вести в надстройку: она — единственный текст о словаре, доезжающий до
|
|
21
|
+
# читателя гарантированно, и, ведя в собранный файл, она отменяет собой всё сказанное на других
|
|
22
|
+
# слоях. Опровержение стоит строкой ниже, в шапке раскладки, но читается служебной строкой сборки:
|
|
23
|
+
# начинается оно с версии и суммы, а не со слова.
|
|
24
|
+
#
|
|
25
|
+
# Адрес выводится из идентификатора ресурса в самой шапке, а не зашивается: каталог надстроек у
|
|
26
|
+
# каждого дерева свой, и зашитый путь врал бы в первом же, которое держит их иначе. Шапки нет —
|
|
27
|
+
# словарь принадлежит дереву целиком, и «заводится здесь же» верно как есть.
|
|
28
|
+
header="$(head -1 "$GLOSSARY" 2>/dev/null)"
|
|
29
|
+
resource="$(printf '%s' "$header" | sed -n 's/.*rt-kit v[^ ]* · \([^ ]*\) · .*/\1/p')"
|
|
30
|
+
|
|
31
|
+
if [ -n "$resource" ]; then
|
|
32
|
+
where="$(printf 'Словарь разложен пакетом и на месте не правится: новое слово идёт в надстройку\n.claude/rt-kit/overrides/%s — она сливается по разделу «## » и раскладку переживает.\n' "$resource")"
|
|
33
|
+
else
|
|
34
|
+
where='Словарь принадлежит дереву целиком: новое слово заводится в нём же той же правкой.
|
|
35
|
+
'
|
|
36
|
+
fi
|
|
37
|
+
|
|
18
38
|
{
|
|
19
39
|
printf 'СЛОВАРЬ ПРОЕКТА — `docs/GLOSSARY.md`, ниже целиком.\n\n'
|
|
20
40
|
printf 'Слово отсюда употребляется в том значении, что здесь: и в файлах, и в ответах владельцу.\n'
|
|
21
41
|
printf 'Раздел «Так не пишем» действует так же — левая колонка не пишется и не произносится нигде.\n'
|
|
22
|
-
printf 'Слова, которого здесь нет, у читателя нет тоже: оно
|
|
23
|
-
printf 'либо заменяется тем, что уже есть.\n
|
|
42
|
+
printf 'Слова, которого здесь нет, у читателя нет тоже: оно вводится тем же изменением, которым\n'
|
|
43
|
+
printf 'впервые употреблено, либо заменяется тем, что уже есть.\n'
|
|
44
|
+
printf '%s\n' "$where"
|
|
24
45
|
cat "$GLOSSARY"
|
|
25
46
|
} | jq -Rs '{hookSpecificOutput:{hookEventName:"SessionStart",additionalContext:.}}'
|
|
@@ -165,6 +165,68 @@ verdict="$(tail -n 400 "$transcript" 2>/dev/null | jq -s -r --arg re "$read_re"
|
|
|
165
165
|
| if ($asked_prose or $asking_now) and ($read | not) then "ask" else "pass" end
|
|
166
166
|
' 2>/dev/null)"
|
|
167
167
|
|
|
168
|
+
# Второй признак того же гарда: на этот вопрос владелец уже отвечал.
|
|
169
|
+
#
|
|
170
|
+
# Первый признак судит, читались ли правила, и на разрешённой работе молчит. А промах бывает
|
|
171
|
+
# другой: владелец дал указание прямой репликой, исполнитель нашёл факт, который меняет цену
|
|
172
|
+
# указания, но не его смысл, — и вместо строки о цене задал меню, где два варианта из трёх
|
|
173
|
+
# предлагали отменить решение владельца. Работа встала до ответа, разрешённая минутой раньше.
|
|
174
|
+
#
|
|
175
|
+
# Судится пересечение слов: вопрос, который сейчас уходит, против последней реплики владельца —
|
|
176
|
+
# и только там, где в записи уже был вызов инструмента вопроса, то есть владелец на вопрос
|
|
177
|
+
# отвечал. Понимания текста здесь нет и не нужно: три общих значимых слова означают тот же
|
|
178
|
+
# предмет, а разбор из шести вопросов идёт по разным предметам и порога не набирает.
|
|
179
|
+
#
|
|
180
|
+
# ОТКАЗ В ПОЛЬЗУ РАБОТЫ: нет вопроса в вызове, нет реплики владельца, нет прошлого вопроса —
|
|
181
|
+
# признак молчит.
|
|
182
|
+
if [ -n "$tool" ]; then
|
|
183
|
+
asked_json="$(printf '%s' "$input" | jq -r '(.tool_input.questions // []) | tostring' 2>/dev/null)"
|
|
184
|
+
seen="$(tail -n 400 "$transcript" 2>/dev/null | jq -s -r --arg now "$asked_json" '
|
|
185
|
+
def is_input:
|
|
186
|
+
.type == "user"
|
|
187
|
+
and ((.isCompactSummary // false) | not)
|
|
188
|
+
and (((.message.content // []) | if type == "array"
|
|
189
|
+
then ([.[] | select(.type == "tool_result")] | length)
|
|
190
|
+
else 0 end) == 0);
|
|
191
|
+
|
|
192
|
+
def words: [splits("[^\\p{L}\\p{N}]+")] | map(select(length >= 5)) | unique;
|
|
193
|
+
|
|
194
|
+
(map(is_input) | rindex(true)) as $i
|
|
195
|
+
| if $i == null then "нет" else
|
|
196
|
+
(.[$i] | (.message.content // []) | if type == "array"
|
|
197
|
+
then ([.[] | select(.type == "text") | .text] | join(" "))
|
|
198
|
+
else (. // "") end) as $said
|
|
199
|
+
| ([.[:$i][] | select(.type == "assistant") | (.message.content // [])[]
|
|
200
|
+
| select(.type == "tool_use") | select(.name == "AskUserQuestion")] | length) as $before
|
|
201
|
+
| if $before == 0 or ($said | length) == 0 then "нет" else
|
|
202
|
+
(($now | words) - (($now | words) - ($said | words))) as $common
|
|
203
|
+
| if ($common | length) >= 3 then "было" else "нет" end
|
|
204
|
+
end
|
|
205
|
+
end
|
|
206
|
+
' 2>/dev/null)"
|
|
207
|
+
|
|
208
|
+
if [ "$seen" = "было" ]; then
|
|
209
|
+
reason="BLOCKED by grill-gate: на этот вопрос владелец уже отвечал в этом разговоре — продолжай работу, а не переспрашивай.
|
|
210
|
+
|
|
211
|
+
Указание владельца действует до его отмены. Новый факт против действующего указания — это строка в ответе о цене, а не новый вопрос: переспрашивают только то, чего указание не покрывает. Промах здесь не в форме вопроса, а в остановке работы, которая уже разрешена.
|
|
212
|
+
|
|
213
|
+
Вопрос всё-таки о другом предмете — назови в нём то, чего в прежнем ответе владельца нет: признак судит общие слова вопроса и последней реплики владельца, а не смысл."
|
|
214
|
+
|
|
215
|
+
# Общий хвост отказа: два законных хода и законная форма обхода, если она у отказа есть.
|
|
216
|
+
# shellcheck disable=SC1090
|
|
217
|
+
[ -f "$rt_hooks_dir/deny-tail.sh" ] && . "$rt_hooks_dir/deny-tail.sh" 2>/dev/null
|
|
218
|
+
command -v rt_deny_tail >/dev/null 2>&1 || rt_deny_tail() { :; }
|
|
219
|
+
deny_tail_text="$(rt_deny_tail "")"
|
|
220
|
+
[ -n "$deny_tail_text" ] && reason="${reason}
|
|
221
|
+
|
|
222
|
+
${deny_tail_text}"
|
|
223
|
+
|
|
224
|
+
jq -n --arg r "$reason" '{hookSpecificOutput:{hookEventName:"PreToolUse",permissionDecision:"deny",permissionDecisionReason:$r}}' 2>/dev/null \
|
|
225
|
+
|| printf '{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"deny","permissionDecisionReason":"grill-gate: на этот вопрос уже отвечали."}}\n'
|
|
226
|
+
exit 0
|
|
227
|
+
fi
|
|
228
|
+
fi
|
|
229
|
+
|
|
168
230
|
[ "$verdict" = "ask" ] || exit 0
|
|
169
231
|
|
|
170
232
|
if [ -n "$tool" ]; then
|
|
@@ -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'; }
|