@rt-tools/agent-kit 0.11.0 → 0.13.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/assets/checks/board-runs.github.mjs +87 -0
- package/assets/checks/board.github.mjs +0 -40
- package/assets/checks/check-board.github.mjs +39 -8
- package/assets/checks/check-file-size.mjs +19 -4
- package/assets/checks/check-schema-drift.mjs +28 -5
- package/assets/checks/check-state-next.mjs +10 -2
- package/assets/checks/rt-kit-checks.config.mjs +28 -2
- package/assets/commands/feedback.md +8 -0
- package/assets/defaults/project.sh +59 -6
- package/assets/defaults/turn-map.md +8 -6
- package/assets/hooks/browser-guard-device-id.sh +3 -1
- package/assets/hooks/browser-guard-no-asking.sh +3 -1
- package/assets/hooks/browser-guard-no-other-drivers.sh +6 -4
- package/assets/hooks/browser-guard-require-select.sh +4 -2
- package/assets/hooks/claim-guard.sh +3 -1
- package/assets/hooks/conscience-guard.sh +3 -1
- package/assets/hooks/dev-server-guard.sh +6 -4
- package/assets/hooks/dispatch.sh +69 -0
- package/assets/hooks/docs-guard.sh +6 -4
- package/assets/hooks/exam-guard.sh +5 -3
- package/assets/hooks/git-guard-delivery-folder.sh +99 -0
- package/assets/hooks/git-guard-delivery-signature.sh +10 -4
- package/assets/hooks/git-guard-delivery.sh +58 -74
- package/assets/hooks/git-guard-main.sh +6 -4
- package/assets/hooks/git-guard-push-tests.sh +8 -6
- package/assets/hooks/grill-gate.sh +4 -2
- package/assets/hooks/handoff-entry-guard.sh +4 -2
- package/assets/hooks/handoff-write.sh +27 -6
- package/assets/hooks/hook-input.sh +77 -0
- package/assets/hooks/lint-after-edit.sh +5 -3
- package/assets/hooks/override-write-guard.sh +107 -0
- package/assets/hooks/postmortem-guard.sh +3 -1
- package/assets/hooks/proposal-guard.sh +3 -1
- package/assets/hooks/prose-style-guard.sh +5 -3
- package/assets/hooks/qa-dataid-guard.sh +4 -2
- package/assets/hooks/rerun-guard.sh +6 -4
- package/assets/hooks/reuse-first-guard.sh +5 -3
- package/assets/hooks/rule-article.sh +99 -0
- package/assets/hooks/rule-source-guard.sh +126 -0
- package/assets/hooks/skill-gate-rearm.sh +3 -1
- package/assets/hooks/skill-gate.sh +23 -2
- package/assets/hooks/skill-loaded.sh +3 -1
- package/assets/hooks/sql-guard-request.sh +2 -1
- package/assets/hooks/sql-guard.sh +4 -2
- package/assets/hooks/task-flow-guard.sh +24 -4
- package/assets/hooks/turn-exit-guard.sh +59 -17
- package/assets/hooks/waiting-turn-guard.sh +3 -1
- package/assets/hooks/window-fill-guard.sh +6 -4
- package/assets/laws/work-conduct.md +5 -9
- package/assets/patterns/dependencies-upgrade.md +1 -1
- package/assets/patterns/doc-style-write.md +3 -3
- package/assets/patterns/git-workflow-commit.azure.md +2 -202
- package/assets/patterns/git-workflow-commit.github.md +2 -258
- package/assets/patterns/git-workflow-commit.gitlab.md +1 -217
- package/assets/patterns/git-workflow-docker.md +3 -3
- package/assets/patterns/git-workflow-merge.md +3 -2
- package/assets/patterns/git-workflow-migration.md +3 -3
- package/assets/patterns/git-workflow-pr.azure.md +224 -0
- package/assets/patterns/git-workflow-pr.github.md +280 -0
- package/assets/patterns/git-workflow-pr.gitlab.md +240 -0
- package/assets/patterns/git-workflow-restart.md +3 -3
- package/assets/patterns/git-workflow-secrets.md +3 -3
- package/assets/patterns/task-flow-archive.md +195 -0
- package/assets/patterns/task-flow-close.md +72 -240
- package/assets/patterns/task-flow-handoff.md +4 -4
- package/assets/patterns/task-flow-resume.md +5 -3
- package/assets/pitfalls/agent-kit.md +80 -0
- package/assets/pitfalls/doc-style.md +80 -0
- package/assets/pitfalls/git-workflow.azure.md +50 -0
- package/assets/pitfalls/git-workflow.github.md +78 -0
- package/assets/pitfalls/git-workflow.gitlab.md +49 -0
- package/assets/pitfalls/spec-driven.md +36 -0
- package/assets/pitfalls/styling-bem.md +45 -0
- package/assets/pitfalls/task-flow.md +62 -0
- package/assets/pitfalls/testing.md +70 -0
- package/assets/rules/deploy-flow.azure.md +114 -0
- package/assets/rules/deploy-flow.github.md +122 -0
- package/assets/rules/deploy-flow.gitlab.md +116 -0
- package/assets/rules/doc-style.md +25 -76
- package/assets/rules/git-workflow.azure.md +6 -92
- package/assets/rules/git-workflow.github.md +20 -128
- package/assets/rules/git-workflow.gitlab.md +6 -93
- package/assets/rules/spec-driven.md +39 -30
- package/assets/rules/styling-bem.md +20 -39
- package/assets/rules/task-flow.md +56 -222
- package/assets/rules/testing.md +3 -64
- package/assets/rules/turn-conduct.md +210 -0
- package/assets/rules/typescript-conventions.md +15 -0
- package/assets/skills/agent-kit.md +67 -89
- package/assets/templates/pitfalls.md +10 -0
- package/assets/templates/proposal.md +16 -1
- package/assets/templates/rule.md +5 -3
- package/bin/agent-kit.d.ts.map +1 -1
- package/bin/agent-kit.js +1 -42
- package/bin/agent-kit.js.map +1 -1
- package/lib/assets.d.ts.map +1 -1
- package/lib/assets.js +6 -1
- package/lib/assets.js.map +1 -1
- package/lib/cascade.d.ts.map +1 -1
- package/lib/cascade.js +19 -1
- package/lib/cascade.js.map +1 -1
- package/lib/commands.d.ts.map +1 -1
- package/lib/commands.js +1 -0
- package/lib/commands.js.map +1 -1
- package/lib/config.d.ts +16 -1
- package/lib/config.d.ts.map +1 -1
- package/lib/config.js +8 -0
- package/lib/config.js.map +1 -1
- package/lib/hooks-map.d.ts +13 -0
- package/lib/hooks-map.d.ts.map +1 -1
- package/lib/hooks-map.js +33 -1
- package/lib/hooks-map.js.map +1 -1
- package/lib/proposals.d.ts +5 -1
- package/lib/proposals.d.ts.map +1 -1
- package/lib/proposals.js +74 -5
- package/lib/proposals.js.map +1 -1
- package/lib/ship.d.ts +1 -2
- package/lib/ship.d.ts.map +1 -1
- package/lib/ship.js +0 -54
- package/lib/ship.js.map +1 -1
- package/lib/shipment.d.ts.map +1 -1
- package/lib/shipment.fixture.d.ts +39 -0
- package/lib/shipment.fixture.d.ts.map +1 -0
- package/lib/shipment.fixture.js +99 -0
- package/lib/shipment.fixture.js.map +1 -0
- package/lib/shipment.js +59 -7
- package/lib/shipment.js.map +1 -1
- package/package.json +1 -1
- package/rt-tools-agent-kit-0.13.0.tgz +0 -0
- package/assets/commands/agent-kit-digest.md +0 -89
- package/assets/commands/rules-review.md +0 -98
- package/assets/patterns/cargo-triage-mark.md +0 -119
- package/assets/rules/cargo-triage.md +0 -126
- package/lib/cargo-state.d.ts +0 -62
- package/lib/cargo-state.d.ts.map +0 -1
- package/lib/cargo-state.js +0 -118
- package/lib/cargo-state.js.map +0 -1
- package/rt-tools-agent-kit-0.11.0.tgz +0 -0
|
@@ -26,8 +26,10 @@
|
|
|
26
26
|
# тексте ход РАЗРЕШАЕТСЯ (exit 0). Сломанный гард не имеет права заклинить разговор.
|
|
27
27
|
|
|
28
28
|
. "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/utf8.sh" 2>/dev/null || true
|
|
29
|
+
. "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/hook-input.sh" 2>/dev/null || true
|
|
29
30
|
|
|
30
|
-
|
|
31
|
+
rt_hook_read
|
|
32
|
+
input="$RT_HOOK_INPUT"
|
|
31
33
|
[ -z "$input" ] && exit 0
|
|
32
34
|
command -v jq >/dev/null 2>&1 || exit 0
|
|
33
35
|
|
|
@@ -20,8 +20,10 @@
|
|
|
20
20
|
# разрешается. Сломанная совесть не имеет права заклинить разговор.
|
|
21
21
|
|
|
22
22
|
. "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/utf8.sh" 2>/dev/null || true
|
|
23
|
+
. "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/hook-input.sh" 2>/dev/null || true
|
|
23
24
|
|
|
24
|
-
|
|
25
|
+
rt_hook_read
|
|
26
|
+
input="$RT_HOOK_INPUT"
|
|
25
27
|
[ -z "$input" ] && exit 0
|
|
26
28
|
command -v jq >/dev/null 2>&1 || exit 0
|
|
27
29
|
|
|
@@ -15,10 +15,12 @@
|
|
|
15
15
|
# RT_STANDS. Нет профиля — текст отказа остаётся общим, сам гард работает.
|
|
16
16
|
|
|
17
17
|
. "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/utf8.sh" 2>/dev/null || true
|
|
18
|
+
. "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/hook-input.sh" 2>/dev/null || true
|
|
18
19
|
|
|
19
|
-
|
|
20
|
+
rt_hook_read
|
|
21
|
+
input="$RT_HOOK_INPUT"
|
|
20
22
|
|
|
21
|
-
tool="$(
|
|
23
|
+
tool="$(rt_hook_tool)"
|
|
22
24
|
case "$tool" in
|
|
23
25
|
Bash | mcp__webstorm__execute_terminal_command | mcp__webstorm__execute_tool) ;;
|
|
24
26
|
# Готовая конфигурация запуска командной строки не показывает — видно только её имя.
|
|
@@ -48,7 +50,7 @@ case "$tool" in
|
|
|
48
50
|
*) exit 0 ;;
|
|
49
51
|
esac
|
|
50
52
|
|
|
51
|
-
cmd="$(
|
|
53
|
+
cmd="$(rt_hook_cmd)"
|
|
52
54
|
[ -z "$cmd" ] && exit 0
|
|
53
55
|
|
|
54
56
|
# Универсальный исполнитель среды передаёт настоящую команду вложенной строкой. Разбирать надо
|
|
@@ -98,7 +100,7 @@ RUNNER='((npx|pnpm|yarn|bun|npm)([[:space:]]+(exec|run|dlx))?[[:space:]]+)?'
|
|
|
98
100
|
|
|
99
101
|
# Начало вызова: начало строки или разделитель команд. Кавычку в границы вносить нельзя — тогда
|
|
100
102
|
# поиск по тексту и снятие процесса по шаблону читаются как запуск.
|
|
101
|
-
BOUND=
|
|
103
|
+
BOUND="$RT_CMD_BOUND"
|
|
102
104
|
|
|
103
105
|
printf '%s' "$cmd" | grep -qE "${BOUND}${RUNNER}(nx|ng)[[:space:]]+(run[[:space:]]+[^[:space:]]*:)?(serve|dev)" \
|
|
104
106
|
&& deny "Запуск ещё одного сервера разработки через каркас."
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Диспетчер событий агента. НЕ гард: объявления `rt-hook:` у него нет — наоборот, он читает
|
|
3
|
+
# такие объявления у остальных. В настройке агента он стоит один на событие вместо списка.
|
|
4
|
+
#
|
|
5
|
+
# Зачем он есть. Агент зовёт каждый гард своим процессом и каждому подаёт один и тот же ввод.
|
|
6
|
+
# Гардов на вызове инструмента восемнадцать, и каждый разбирает ввод заново — по шесть-семь
|
|
7
|
+
# вызовов разборщика. Замер этого дерева: восемьсот двадцать шесть миллисекунд на вызов, из
|
|
8
|
+
# которых восемьдесят пять — запуск оболочек, а остальное — повторный разбор одного текста.
|
|
9
|
+
#
|
|
10
|
+
# Что он делает. Читает ввод один раз, разбирает его один раз, кладёт поля в окружение и зовёт
|
|
11
|
+
# ветки события по порядку. Первый ненулевой код возврата отдаётся агенту вместе с выводом
|
|
12
|
+
# гарда — остальные ветки не зовутся: отказ и раньше кончал вызов на первом же гарде.
|
|
13
|
+
#
|
|
14
|
+
# Чего он не делает. Он не судит ничего сам и ни одного гарда не заменяет: файлы остаются как
|
|
15
|
+
# были, меняется точка входа. Событие и образец вызова гард несёт сам, строкой `# rt-hook:` в
|
|
16
|
+
# своей шапке, и карта собирается из них — список, выписанный отдельно, разошёлся бы с набором
|
|
17
|
+
# файлов на первом же добавленном.
|
|
18
|
+
#
|
|
19
|
+
# ОТКАЗ В ПОЛЬЗУ РАБОТЫ: нет события в доводе, нет каталога гардов, нет разборщика — выход нулём.
|
|
20
|
+
# Сломанный диспетчер не имеет права заклинить работу.
|
|
21
|
+
|
|
22
|
+
set -u
|
|
23
|
+
|
|
24
|
+
here="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
25
|
+
. "$here/utf8.sh" 2>/dev/null || true
|
|
26
|
+
|
|
27
|
+
event="${1:-}"
|
|
28
|
+
[ -z "$event" ] && exit 0
|
|
29
|
+
|
|
30
|
+
input="$(cat 2>/dev/null)"
|
|
31
|
+
[ -z "$input" ] && exit 0
|
|
32
|
+
|
|
33
|
+
# Разбор один на все ветки: четыре поля одним вызовом разборщика вместо шести на каждый гард.
|
|
34
|
+
# Значения приходят уже закавыченными для оболочки — `@sh` в разборщике для того и сделан:
|
|
35
|
+
# командная строка держит и кавычки, и переводы строк, и подставить её иначе нельзя.
|
|
36
|
+
assignments="$(printf '%s' "$input" | jq -r '@sh "RT_HOOK_TOOL=\(.tool_name // "") RT_HOOK_CMD=\(.tool_input.command // "") RT_HOOK_FILE=\(.tool_input.file_path // "") RT_HOOK_CWD=\(.cwd // "")"' 2>/dev/null)"
|
|
37
|
+
if [ -n "$assignments" ]; then
|
|
38
|
+
eval "$assignments" 2>/dev/null || true
|
|
39
|
+
export RT_HOOK_TOOL RT_HOOK_CMD RT_HOOK_FILE RT_HOOK_CWD
|
|
40
|
+
# Признак разбора: по нему ветки отличают готовое поле от пустой переменной, случайно
|
|
41
|
+
# оказавшейся в окружении прогона. Без него пустое значение читается как «поля нет».
|
|
42
|
+
export RT_HOOK_PARSED=1
|
|
43
|
+
fi
|
|
44
|
+
export RT_HOOK_INPUT="$input"
|
|
45
|
+
|
|
46
|
+
branches="$(grep -l '^# rt-hook:' "$here"/*.sh 2>/dev/null | sort)"
|
|
47
|
+
[ -z "$branches" ] && exit 0
|
|
48
|
+
|
|
49
|
+
for branch in $branches; do
|
|
50
|
+
declaration="$(sed -n 's/^# rt-hook:[[:space:]]*//p' "$branch" 2>/dev/null | head -1)"
|
|
51
|
+
[ -z "$declaration" ] && continue
|
|
52
|
+
|
|
53
|
+
branch_event="${declaration%% *}"
|
|
54
|
+
[ "$branch_event" = "$event" ] || continue
|
|
55
|
+
|
|
56
|
+
# Образец вызова: его нет вовсе — гард зовётся на любом; есть — сверяется с именем
|
|
57
|
+
# инструмента целиком, а не куском. Звёздочка и точка со звёздочкой значат одно: любой вызов.
|
|
58
|
+
matcher="${declaration#"$branch_event"}"
|
|
59
|
+
matcher="${matcher#"${matcher%%[![:space:]]*}"}"
|
|
60
|
+
if [ -n "$matcher" ] && [ "$matcher" != '*' ] && [ "$matcher" != '.*' ]; then
|
|
61
|
+
[[ "${RT_HOOK_TOOL:-}" =~ ^(${matcher})$ ]] || continue
|
|
62
|
+
fi
|
|
63
|
+
|
|
64
|
+
printf '%s' "$input" | bash "$branch"
|
|
65
|
+
code=$?
|
|
66
|
+
[ "$code" -ne 0 ] && exit "$code"
|
|
67
|
+
done
|
|
68
|
+
|
|
69
|
+
exit 0
|
|
@@ -27,12 +27,14 @@
|
|
|
27
27
|
# пропуск.
|
|
28
28
|
|
|
29
29
|
. "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/utf8.sh" 2>/dev/null || true
|
|
30
|
+
. "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/hook-input.sh" 2>/dev/null || true
|
|
30
31
|
|
|
31
|
-
|
|
32
|
+
rt_hook_read
|
|
33
|
+
input="$RT_HOOK_INPUT"
|
|
32
34
|
[ -z "$input" ] && exit 0
|
|
33
35
|
command -v jq >/dev/null 2>&1 || exit 0
|
|
34
36
|
|
|
35
|
-
tool="$(
|
|
37
|
+
tool="$(rt_hook_tool)"
|
|
36
38
|
|
|
37
39
|
decide() {
|
|
38
40
|
# Наблюдение пишется только на отказе: подсказку гард раздаёт и там, где всё в порядке, и
|
|
@@ -117,7 +119,7 @@ case "$tool" in
|
|
|
117
119
|
*) exit 0 ;;
|
|
118
120
|
esac
|
|
119
121
|
|
|
120
|
-
cmd="$(
|
|
122
|
+
cmd="$(rt_hook_cmd)"
|
|
121
123
|
|
|
122
124
|
# Универсальный исполнитель передаёт настоящую команду вложенной строкой. Разбирать надо её,
|
|
123
125
|
# иначе имя команды стоит сразу за кавычкой и ни одно правило до него не дотягивается.
|
|
@@ -146,7 +148,7 @@ if printf '%s' "$cmd" | grep -qiE '(^|#)[[:space:]]*Docs-skip:[[:space:]]*[^[:sp
|
|
|
146
148
|
exit 0
|
|
147
149
|
fi
|
|
148
150
|
|
|
149
|
-
workdir="$(
|
|
151
|
+
workdir="$(rt_hook_cwd)"
|
|
150
152
|
[ -z "$workdir" ] && workdir="${CLAUDE_PROJECT_DIR:-.}"
|
|
151
153
|
cd "$workdir" 2>/dev/null || exit 0
|
|
152
154
|
git rev-parse --is-inside-work-tree >/dev/null 2>&1 || exit 0
|
|
@@ -19,8 +19,10 @@
|
|
|
19
19
|
# мешать работать.
|
|
20
20
|
|
|
21
21
|
. "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/utf8.sh" 2>/dev/null || true
|
|
22
|
+
. "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/hook-input.sh" 2>/dev/null || true
|
|
22
23
|
|
|
23
|
-
|
|
24
|
+
rt_hook_read
|
|
25
|
+
input="$RT_HOOK_INPUT"
|
|
24
26
|
[ -z "$input" ] && exit 0
|
|
25
27
|
command -v jq >/dev/null 2>&1 || exit 0
|
|
26
28
|
|
|
@@ -32,14 +34,14 @@ rt_hooks_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
|
32
34
|
[ -f "$rt_hooks_dir/roles.sh" ] && . "$rt_hooks_dir/roles.sh" 2>/dev/null
|
|
33
35
|
command -v rt_role_off >/dev/null 2>&1 && rt_role_off strict-teacher && exit 0
|
|
34
36
|
|
|
35
|
-
tool="$(
|
|
37
|
+
tool="$(rt_hook_tool)"
|
|
36
38
|
# Второй экзамен спрашивается на снятии черновика: работа кончилась, и правила поставки к этому
|
|
37
39
|
# моменту читались давно — между их чтением и этой минутой прошёл весь заход.
|
|
38
40
|
ready=0
|
|
39
41
|
case "$tool" in
|
|
40
42
|
Edit | Write | MultiEdit | mcp__webstorm__create_new_file) ;;
|
|
41
43
|
Bash)
|
|
42
|
-
cmd="$(
|
|
44
|
+
cmd="$(rt_hook_cmd)"
|
|
43
45
|
printf '%s' "$cmd" | grep -qE 'pr[[:space:]]+ready|mr[[:space:]]+update[^|;&]*--ready' || exit 0
|
|
44
46
|
ready=1
|
|
45
47
|
;;
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Условия поставки о папке задачи. НЕ гард: объявления `rt-hook:` у него нет, к событиям агента
|
|
3
|
+
# он не подключается. Его источает гард поставки — тем же приёмом, каким источает хвост отказа и
|
|
4
|
+
# запись наблюдений.
|
|
5
|
+
#
|
|
6
|
+
# Зачем он отдельным файлом. Условий о папке два, и живут они в разных местах вызова: одно на
|
|
7
|
+
# открытии заявки, другое на слиянии. Вместе с разбором команды, подписью, основанием и очередью
|
|
8
|
+
# работ они переросли предел длины файла, а делить гард по событиям нельзя — он одно целое,
|
|
9
|
+
# и разбор команды у него общий. Делится он по предмету: папка задачи — свой предмет, у неё свой
|
|
10
|
+
# обход и своя запись в архиве.
|
|
11
|
+
#
|
|
12
|
+
# ЧТО ЗДЕСЬ. Три вызова, и все три зовутся из гарда поставки, где уже объявлены `deny`, `fault`
|
|
13
|
+
# и переменные профиля: `tasks_dir`, `archive_dir`, `main_branch`, `folder_skip_re`, `root`.
|
|
14
|
+
# Своих у помощника нет — он не читает ни ввода, ни настроек.
|
|
15
|
+
|
|
16
|
+
# Есть ли папка задачи в ветке. Смотрим содержимое ветки, а не рабочее дерево: если папку
|
|
17
|
+
# удалили, но не закоммитили, проверка прошла бы, а папка всё равно уехала бы в главную. Имя
|
|
18
|
+
# ветки подставляем целиком, вместе с косой: у ветки вида `chore/312-slug` папка лежит во
|
|
19
|
+
# вложенном каталоге.
|
|
20
|
+
rt_folder_in_branch() {
|
|
21
|
+
git ls-tree -d --name-only HEAD -- "$1" 2>/dev/null | head -1
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
# Была ли папка задачи в этой ветке вообще. Спрашивается у ветки, которая её удалила: иначе
|
|
25
|
+
# требование записи в архиве цеплялось бы к работе, у которой папки и не было.
|
|
26
|
+
rt_folder_was_in_branch() {
|
|
27
|
+
_base="$1"
|
|
28
|
+
_folder="$2"
|
|
29
|
+
_had="$(git ls-tree -d --name-only "$_base" -- "$_folder" 2>/dev/null | head -1)"
|
|
30
|
+
[ -z "$_had" ] && _had="$(git log "$_base..HEAD" --diff-filter=A --name-only --pretty=format: -- "$_folder" 2>/dev/null | head -1)"
|
|
31
|
+
[ -n "$_had" ]
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
# Условие открытия заявки: папка разобрана, и запись в архиве от ветки прибыла. Отказ копится
|
|
35
|
+
# вместе с прочими условиями поставки и печатается разом.
|
|
36
|
+
#
|
|
37
|
+
# Разбор стоит здесь, а не на слиянии. Прежде было наоборот: считалось, что папка нужна на диске
|
|
38
|
+
# всё время разбора, потому что правку по замечаниям без замысла отбивает гард хода работы.
|
|
39
|
+
# Владелец же вливает, как только видит зелёное, и закрывающему коммиту места не остаётся —
|
|
40
|
+
# трижды подряд папка уехала в главную неразобранной, а гард слияния этого не видел вовсе:
|
|
41
|
+
# кнопку нажимает человек на хостинге, и до него гард не достаёт. Замысел после уборки гард хода
|
|
42
|
+
# работы берёт из истории ветки, а не с диска.
|
|
43
|
+
rt_delivery_open_folder() {
|
|
44
|
+
[ -n "$tasks_dir" ] || return 0
|
|
45
|
+
printf '%s' "$cmd" | grep -qiE "$folder_skip_re" && return 0
|
|
46
|
+
|
|
47
|
+
_folder="$tasks_dir/$branch"
|
|
48
|
+
_lying="$(rt_folder_in_branch "$_folder")"
|
|
49
|
+
if [ -n "$_lying" ]; then
|
|
50
|
+
fault "в ветке лежит папка задачи «${_lying}» — заявка открывается после уборки, а не до неё. Перенеси в «${archive_dir:-архив}» то, что объясняет принятые решения, остальное удали, закоммить последним коммитом и повтори. Если работа вливается частями, поставь в команду комментарий «# Task-folder-skip: <причина>»."
|
|
51
|
+
return 0
|
|
52
|
+
fi
|
|
53
|
+
|
|
54
|
+
[ -n "$archive_dir" ] || return 0
|
|
55
|
+
_base="$(git merge-base "$main_branch" HEAD 2>/dev/null)"
|
|
56
|
+
[ -z "$_base" ] && return 0
|
|
57
|
+
rt_folder_was_in_branch "$_base" "$_folder" || return 0
|
|
58
|
+
|
|
59
|
+
_gained="$(git diff --name-only --diff-filter=A "$_base" HEAD -- "$archive_dir" 2>/dev/null | head -1)"
|
|
60
|
+
[ -z "$_gained" ] \
|
|
61
|
+
&& fault "папку задачи удалили, но в «${archive_dir}» ветка ничего не добавила. Удалить проще, чем разобрать, — и вместе с папкой пропадает разбор просьбы, единственная запись слов владельца. Перенеси то, что объясняет принятые решения, одним файлом с понятным именем и повтори."
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
# Условие слияния: тот же предмет вторым рубежом. Он ловит слияние, идущее командой, — то, до
|
|
65
|
+
# которого гард достаёт. Отсюда выходят только отказом или молчанием: слияние решается целиком.
|
|
66
|
+
rt_delivery_merge_folder() {
|
|
67
|
+
[ -n "$tasks_dir" ] || exit 0 # ведения работы папкой в дереве нет
|
|
68
|
+
|
|
69
|
+
_branch="$(git branch --show-current 2>/dev/null)"
|
|
70
|
+
[ -z "$_branch" ] && exit 0
|
|
71
|
+
rt_task_branch_ok "$_branch" || exit 0 # за беззадачной веткой папки не стоит
|
|
72
|
+
|
|
73
|
+
_folder="$tasks_dir/$_branch"
|
|
74
|
+
|
|
75
|
+
# Сначала ищем обход в самой команде — это работает и без сети. Если читать только
|
|
76
|
+
# тело заявки, то без сети гард отбил бы слияние, причина которого в этом теле и написана.
|
|
77
|
+
printf '%s' "$cmd" | grep -qiE "$folder_skip_re" && exit 0
|
|
78
|
+
|
|
79
|
+
_number="$(printf '%s' "$cmd" | sed -nE 's/.*(pr|mr)[[:space:]]+(merge|update)[[:space:]]+([0-9]+).*/\3/p' | head -1)"
|
|
80
|
+
if [ -n "$_number" ] && rt_needs rt_report_body git-guard-delivery; then
|
|
81
|
+
_body="$(cd "$root" && rt_report_body "$_number" 2>/dev/null)"
|
|
82
|
+
[ -n "$_body" ] && printf '%s' "$_body" | grep -qiE "$folder_skip_re" && exit 0
|
|
83
|
+
fi
|
|
84
|
+
|
|
85
|
+
_lying="$(rt_folder_in_branch "$_folder")"
|
|
86
|
+
[ -n "$_lying" ] \
|
|
87
|
+
&& deny "BLOCKED: в ветке осталась папка задачи «${_lying}» — она уедет в главную. Разобрать её потом будет некому: работа перейдёт к следующей задаче, а эта заявка закроется. Перенеси в «${archive_dir:-архив}» то, что объясняет принятые решения, остальное удали и повтори. Если работа вливается частями, поставь в тело заявки строку «Task-folder-skip: <причина>»."
|
|
88
|
+
|
|
89
|
+
[ -n "$archive_dir" ] || exit 0
|
|
90
|
+
_base="$(git merge-base "$main_branch" HEAD 2>/dev/null)"
|
|
91
|
+
[ -z "$_base" ] && exit 0
|
|
92
|
+
rt_folder_was_in_branch "$_base" "$_folder" || exit 0
|
|
93
|
+
|
|
94
|
+
_gained="$(git diff --name-only --diff-filter=A "$_base" HEAD -- "$archive_dir" 2>/dev/null | head -1)"
|
|
95
|
+
[ -z "$_gained" ] \
|
|
96
|
+
&& deny "BLOCKED: папку задачи удалили, но в «${archive_dir}» ветка ничего не добавила. Удалить проще, чем разобрать, — и вместе с папкой пропадает разбор просьбы, единственная запись слов владельца. Перенеси то, что объясняет принятые решения, одним файлом с понятным именем и повтори."
|
|
97
|
+
|
|
98
|
+
exit 0
|
|
99
|
+
}
|
|
@@ -11,6 +11,10 @@
|
|
|
11
11
|
# среди них самая отдельная — своя точка проверки, свои условия молчания, свой отказ.
|
|
12
12
|
|
|
13
13
|
. "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/utf8.sh" 2>/dev/null || true
|
|
14
|
+
# Начало вызова помощник берёт оттуда же, откуда все гарды. Источается он и здесь, хотя зовущий
|
|
15
|
+
# гард это уже сделал: пустой образец превратил бы признак в поиск слова по всей строке, то есть
|
|
16
|
+
# в отказ на каждом упоминании команды.
|
|
17
|
+
. "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/hook-input.sh" 2>/dev/null || true
|
|
14
18
|
|
|
15
19
|
rt_delivery_signature() {
|
|
16
20
|
# --- подпись машинного коммита ------------------------------------------------------------
|
|
@@ -33,13 +37,15 @@ rt_delivery_signature() {
|
|
|
33
37
|
# логина разошлись бы молча. Исполнителем задачи он тоже не бывает — в дереве, где хостинг
|
|
34
38
|
# ограничил машинную запись, исполнителем ставят человека, а коммит остаётся машинным.
|
|
35
39
|
#
|
|
36
|
-
# Вызов пуша узнаётся по двум признакам сразу —
|
|
37
|
-
#
|
|
38
|
-
#
|
|
40
|
+
# Вызов пуша узнаётся по двум признакам сразу — начало вызова перед словом `git` и слово `push`
|
|
41
|
+
# отдельным словом. Одной подстрокой «git push» его не поймать: помощник учётных данных и
|
|
42
|
+
# заголовок запроса ставятся ключами `-c` между ними. Начало вызова считает присваивания
|
|
43
|
+
# переменных частью команды: пуш подписанного коммита набирают с подстановкой токена, и признак
|
|
44
|
+
# без них пропускал ровно тот вызов, ради которого этот помощник и заведён.
|
|
39
45
|
# Пробный пуш не отправляет ничего, и подпись у него не спрашивают. Отложенная правка — тоже:
|
|
40
46
|
# `git stash push` кладёт правку в тайник этой же машины, поэтому тайник вырезается из строки, а
|
|
41
47
|
# признак считается по остатку.
|
|
42
|
-
if printf '%s' "$cmd" | grep -qE
|
|
48
|
+
if printf '%s' "$cmd" | grep -qE "${RT_CMD_BOUND}git([[:space:]]|\$)" \
|
|
43
49
|
&& printf '%s' "$cmd" | sed -E 's/git[[:space:]]+stash[[:space:]]+push/git stash/g' | grep -qE '(^|[[:space:]])push([[:space:]]|$)' \
|
|
44
50
|
&& ! printf '%s' "$cmd" | grep -q -- '--dry-run'; then
|
|
45
51
|
# Два условия молчания. Дерево, не назвавшее почты машинной записи, требования не получает:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
# rt-hook: PreToolUse Bash|mcp__webstorm__execute_terminal_command|mcp__webstorm__execute_tool
|
|
3
|
-
# Требует: hooks/profile-check.sh, hooks/deny-tail.sh
|
|
3
|
+
# Требует: hooks/git-guard-delivery-folder.sh, hooks/profile-check.sh, hooks/deny-tail.sh
|
|
4
4
|
# Гард поставки. PreToolUse на заведении ветки, пуше и открытии заявки на слияние.
|
|
5
5
|
#
|
|
6
6
|
# Закон о поставке требует трёх вещей, которых обычно не проверяет ничто: правка начинается с
|
|
@@ -30,7 +30,9 @@
|
|
|
30
30
|
# numbered); молчание значит «спросить некого»;
|
|
31
31
|
# RT_TASK_NEW_CMD — чем заводится задача;
|
|
32
32
|
# RT_BOARD_CHECK_CMD — чем сверяется очередь работ;
|
|
33
|
-
# RT_TASK_BOT — учётная запись, которую ставят исполнителем;
|
|
33
|
+
# RT_TASK_BOT — учётная запись, которую ставят исполнителем; ею же открывается заявка;
|
|
34
|
+
# RT_PULL_TOKEN_VAR — переменная, которой вызову подставляют её токен;
|
|
35
|
+
# RT_PULL_TOKEN_HINT — готовая подстановка этого токена, целиком;
|
|
34
36
|
# RT_COMMIT_EMAIL — почта, которой подписан машинный коммит; её же левой частью он и
|
|
35
37
|
# опознаётся.
|
|
36
38
|
# Отказ называет и то, что не так, и чем это чинится: отказ без действия обходят, а не исполняют.
|
|
@@ -38,12 +40,14 @@
|
|
|
38
40
|
# ОТКАЗ В ПОЛЬЗУ РАБОТЫ: не репозиторий, нет разборщика, битый ввод, нет профиля — пропуск.
|
|
39
41
|
|
|
40
42
|
. "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/utf8.sh" 2>/dev/null || true
|
|
43
|
+
. "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/hook-input.sh" 2>/dev/null || true
|
|
41
44
|
|
|
42
|
-
|
|
45
|
+
rt_hook_read
|
|
46
|
+
input="$RT_HOOK_INPUT"
|
|
43
47
|
[ -z "$input" ] && exit 0
|
|
44
48
|
command -v jq >/dev/null 2>&1 || exit 0
|
|
45
49
|
|
|
46
|
-
tool="$(
|
|
50
|
+
tool="$(rt_hook_tool)"
|
|
47
51
|
sid="$(printf '%s' "$input" | jq -r '.session_id // "nosession"' 2>/dev/null)"
|
|
48
52
|
case "$tool" in
|
|
49
53
|
# Терминал среды и универсальный исполнитель кладут команду в то же поле.
|
|
@@ -51,7 +55,7 @@ case "$tool" in
|
|
|
51
55
|
*) exit 0 ;;
|
|
52
56
|
esac
|
|
53
57
|
|
|
54
|
-
cmd="$(
|
|
58
|
+
cmd="$(rt_hook_cmd)"
|
|
55
59
|
[ -z "$cmd" ] && exit 0
|
|
56
60
|
|
|
57
61
|
# Универсальный исполнитель передаёт настоящую команду вложенной строкой. Разбирать надо её,
|
|
@@ -65,7 +69,7 @@ if [ "$tool" = "mcp__webstorm__execute_tool" ] && command -v perl >/dev/null 2>&
|
|
|
65
69
|
[ -n "$inner" ] && cmd="$inner"
|
|
66
70
|
fi
|
|
67
71
|
|
|
68
|
-
workdir="$(
|
|
72
|
+
workdir="$(rt_hook_cwd)"
|
|
69
73
|
[ -z "$workdir" ] && workdir="${CLAUDE_PROJECT_DIR:-.}"
|
|
70
74
|
cd "$workdir" 2>/dev/null || exit 0
|
|
71
75
|
git rev-parse --is-inside-work-tree >/dev/null 2>&1 || exit 0
|
|
@@ -98,6 +102,11 @@ task_move="${RT_TASK_MOVE_CMD:-npm run task:move}"
|
|
|
98
102
|
# молча выключило бы проверку.
|
|
99
103
|
backlog_column="${RT_BOARD_BACKLOG:-}"
|
|
100
104
|
task_bot="${RT_TASK_BOT:-}"
|
|
105
|
+
# Личность вызова приходит окружением, а не словом в строке: из текста команды видно только то,
|
|
106
|
+
# подставлен ли токен явно. Дерево, не назвавшее переменной, автора заявки не судит вовсе —
|
|
107
|
+
# у него может не быть отдельной машинной записи.
|
|
108
|
+
pull_token_var="${RT_PULL_TOKEN_VAR:-}"
|
|
109
|
+
pull_token_hint="${RT_PULL_TOKEN_HINT:-}"
|
|
101
110
|
commit_email="${RT_COMMIT_EMAIL:-}"
|
|
102
111
|
tasks_dir="${RT_TASKS_DIR:-}"
|
|
103
112
|
archive_dir="${RT_ARCHIVE_DIR:-}"
|
|
@@ -143,6 +152,12 @@ fault() {
|
|
|
143
152
|
}— $1"
|
|
144
153
|
}
|
|
145
154
|
|
|
155
|
+
# Условия о папке задачи вынесены соседним файлом: у папки свой предмет, свой обход и своя
|
|
156
|
+
# запись в архиве, а вместе с разбором команды и очередью работ они переросли предел длины.
|
|
157
|
+
# Подключается он после `deny` и `fault` — обе зовутся изнутри.
|
|
158
|
+
# shellcheck disable=SC1090
|
|
159
|
+
[ -f "$rt_hooks_dir/git-guard-delivery-folder.sh" ] && . "$rt_hooks_dir/git-guard-delivery-folder.sh" 2>/dev/null
|
|
160
|
+
|
|
146
161
|
# Отказ по накопленному. Пусто — вызывающая сторона идёт дальше.
|
|
147
162
|
deny_faults() {
|
|
148
163
|
[ -z "$faults" ] && return 0
|
|
@@ -152,21 +167,6 @@ deny_faults() {
|
|
|
152
167
|
${faults}"
|
|
153
168
|
}
|
|
154
169
|
|
|
155
|
-
# Подсказка вместо отказа: на открытии PR папка ещё нужна. Решения подсказка не несёт,
|
|
156
|
-
# команда идёт дальше своим ходом.
|
|
157
|
-
hint() {
|
|
158
|
-
jq -n --arg c "$1" '{hookSpecificOutput:{hookEventName:"PreToolUse",additionalContext:$c}}' 2>/dev/null
|
|
159
|
-
exit 0
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
# Есть ли папка задачи в ветке. Смотрим содержимое ветки, а не рабочее дерево: если папку
|
|
163
|
-
# удалили, но не закоммитили, проверка прошла бы, а папка всё равно уехала бы в main. Имя
|
|
164
|
-
# ветки подставляем целиком, вместе с косой: у ветки вида `chore/312-slug` папка лежит во
|
|
165
|
-
# вложенном каталоге.
|
|
166
|
-
folder_in_branch() {
|
|
167
|
-
git ls-tree -d --name-only HEAD -- "$1" 2>/dev/null | head -1
|
|
168
|
-
}
|
|
169
|
-
|
|
170
170
|
check_task() {
|
|
171
171
|
number="$1"
|
|
172
172
|
where="$2"
|
|
@@ -283,7 +283,7 @@ command -v rt_delivery_signature >/dev/null 2>&1 && rt_delivery_signature
|
|
|
283
283
|
#
|
|
284
284
|
# Возврат заявки в черновик под требование не подпадает: он делает ровно то, чего гард и
|
|
285
285
|
# добивается, — снимает с работы вид готовой.
|
|
286
|
-
if printf '%s' "$cmd" | grep -qE
|
|
286
|
+
if printf '%s' "$cmd" | grep -qE "${RT_CMD_BOUND}(gh[[:space:]]+pr[[:space:]]+ready|glab[[:space:]]+mr[[:space:]]+update[^|;&]*--ready)([[:space:]]|\$)" \
|
|
287
287
|
&& ! printf '%s' "$cmd" | grep -q -- '--undo'; then
|
|
288
288
|
# Ссылка на заявку необязательна: без неё клиент берёт заявку текущей ветки, и это самая
|
|
289
289
|
# короткая форма вызова. Требовать номер значило бы снимать всё требование одним пробелом.
|
|
@@ -315,6 +315,16 @@ if printf '%s' "$cmd" | grep -qE '(^|[;&|(]|&&|\|\|)[[:space:]]*(gh[[:space:]]+p
|
|
|
315
315
|
# и «ещё не посчитано» — не «конфликтует».
|
|
316
316
|
printf '%s' "$pull" | jq -e '.conflicting' >/dev/null 2>&1 \
|
|
317
317
|
&& fault "заявка${pull_name} конфликтует с главной веткой. Влей её в свою ветку, разбери конфликт и повтори: снятый черновик читается как «можно вливать», а слить эту заявку нельзя."
|
|
318
|
+
|
|
319
|
+
# Автор заявки. На открытии судить было нечем, кроме текста команды: личность вызова
|
|
320
|
+
# приходит окружением. Здесь она уже названа хостингом, и это последний ход, где
|
|
321
|
+
# промах ещё исправим — после снятия черновика заявку вливают, а переоткрыть влитую
|
|
322
|
+
# нельзя. Дерево, не назвавшее машинной записи, автора не судит.
|
|
323
|
+
if [ -n "$task_bot" ]; then
|
|
324
|
+
pull_author="$(printf '%s' "$pull" | jq -r '.author // empty' 2>/dev/null)"
|
|
325
|
+
[ -n "$pull_author" ] && [ "$pull_author" != "$task_bot" ] \
|
|
326
|
+
&& fault "заявку${pull_name} открыла запись «${pull_author}», а не машинная «${task_bot}». Автор заявки её ревьювером не бывает, и разбор ей назначить нечем. Автора не сменить — закрой заявку и открой заново${pull_token_hint:+, подставив токен: ${pull_token_hint} …}."
|
|
327
|
+
fi
|
|
318
328
|
fi
|
|
319
329
|
fi
|
|
320
330
|
deny_faults
|
|
@@ -322,59 +332,20 @@ fi
|
|
|
322
332
|
|
|
323
333
|
# --- слияние заявки ----------------------------------------------------------------------
|
|
324
334
|
#
|
|
325
|
-
#
|
|
326
|
-
# работа перешла к следующей задаче, а PR закрыт. Раньше слияния требовать нельзя — пока идёт
|
|
327
|
-
# ревью, plan.md нужен на диске, иначе гард хода работы не даст править код.
|
|
335
|
+
# Второй рубеж того же условия, что стоит на открытии заявки: он ловит слияние, идущее командой.
|
|
328
336
|
# Команду ищем от начала строки или после разделителя, а не где угодно в тексте. Иначе гард
|
|
329
337
|
# отбивает сообщение, где `gh pr merge` просто упомянут в кавычках, — так он и сработал на
|
|
330
338
|
# правке этого же текста. Полностью подстроку в кавычках так не отсечь, но случайное упоминание
|
|
331
339
|
# внутри слова или пути мимо уже не пройдёт.
|
|
332
|
-
if printf '%s' "$cmd" | grep -qE
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
merge_branch="$(git branch --show-current 2>/dev/null)"
|
|
336
|
-
[ -z "$merge_branch" ] && exit 0
|
|
337
|
-
rt_task_branch_ok "$merge_branch" || exit 0 # за беззадачной веткой папки не стоит
|
|
338
|
-
|
|
339
|
-
folder="$tasks_dir/$merge_branch"
|
|
340
|
-
|
|
341
|
-
# Сначала ищем обход в самой команде — это работает и без сети. Если читать только
|
|
342
|
-
# тело PR, то без сети гард отбил бы слияние, причина которого в этом теле и написана.
|
|
343
|
-
printf '%s' "$cmd" | grep -qiE "$folder_skip_re" && exit 0
|
|
344
|
-
|
|
345
|
-
merge_number="$(printf '%s' "$cmd" | sed -nE 's/.*(pr|mr)[[:space:]]+(merge|update)[[:space:]]+([0-9]+).*/\3/p' | head -1)"
|
|
346
|
-
if [ -n "$merge_number" ] && rt_needs rt_report_body git-guard-delivery; then
|
|
347
|
-
body="$(cd "$root" && rt_report_body "$merge_number" 2>/dev/null)"
|
|
348
|
-
[ -n "$body" ] && printf '%s' "$body" | grep -qiE "$folder_skip_re" && exit 0
|
|
349
|
-
fi
|
|
350
|
-
|
|
351
|
-
lying="$(folder_in_branch "$folder")"
|
|
352
|
-
[ -n "$lying" ] \
|
|
353
|
-
&& deny "BLOCKED: в ветке осталась папка задачи «${lying}» — она уедет в главную. Разобрать её потом будет некому: работа перейдёт к следующей задаче, а этот PR закроется. Перенеси в «${archive_dir:-архив}» то, что объясняет принятые решения, остальное удали и повтори. Если работа вливается частями, поставь в тело PR строку «Task-folder-skip: <причина>»."
|
|
354
|
-
|
|
355
|
-
# Запись в архиве спрашиваем только у ветки, которая папку удалила. Иначе проверка
|
|
356
|
-
# цеплялась бы к работе, у которой папки и не было. Без общего предка с главной веткой
|
|
357
|
-
# сравнивать не с чем — тогда молчим.
|
|
358
|
-
[ -n "$archive_dir" ] || exit 0
|
|
359
|
-
base="$(git merge-base "$main_branch" HEAD 2>/dev/null)"
|
|
360
|
-
[ -z "$base" ] && exit 0
|
|
361
|
-
|
|
362
|
-
had="$(git ls-tree -d --name-only "$base" -- "$folder" 2>/dev/null | head -1)"
|
|
363
|
-
[ -z "$had" ] && had="$(git log "$base..HEAD" --diff-filter=A --name-only --pretty=format: -- "$folder" 2>/dev/null | head -1)"
|
|
364
|
-
[ -z "$had" ] && exit 0
|
|
365
|
-
|
|
366
|
-
gained="$(git diff --name-only --diff-filter=A "$base" HEAD -- "$archive_dir" 2>/dev/null | head -1)"
|
|
367
|
-
[ -z "$gained" ] \
|
|
368
|
-
&& deny "BLOCKED: папку задачи удалили, но в «${archive_dir}» ветка ничего не добавила. Удалить проще, чем разобрать, — и вместе с папкой пропадает разбор просьбы, единственная запись слов владельца. Перенеси то, что объясняет принятые решения, одним файлом с понятным именем и повтори."
|
|
369
|
-
|
|
370
|
-
exit 0
|
|
340
|
+
if printf '%s' "$cmd" | grep -qE "${RT_CMD_BOUND}(gh[[:space:]]+pr[[:space:]]+merge|glab[[:space:]]+mr[[:space:]]+merge|az[[:space:]]+repos[[:space:]]+pr[[:space:]]+update)([[:space:]]|\$)"; then
|
|
341
|
+
rt_delivery_merge_folder
|
|
371
342
|
fi
|
|
372
343
|
|
|
373
344
|
# --- открытие заявки на слияние ----------------------------------------------------------
|
|
374
345
|
# Команду ищем от начала строки или после разделителя — по той же причине, что и слияние:
|
|
375
346
|
# упоминание в кавычках командой не является.
|
|
376
347
|
printf '%s' "$cmd" \
|
|
377
|
-
| grep -qE
|
|
348
|
+
| grep -qE "${RT_CMD_BOUND}(gh[[:space:]]+pr[[:space:]]+create|glab[[:space:]]+mr[[:space:]]+create|az[[:space:]]+repos[[:space:]]+pr[[:space:]]+create)([[:space:]]|\$)" \
|
|
378
349
|
|| exit 0
|
|
379
350
|
|
|
380
351
|
branch="$(git branch --show-current 2>/dev/null)"
|
|
@@ -440,19 +411,32 @@ if [ -n "$remote_main" ] && [ -n "$local_main" ] && [ "$remote_main" != "$local_
|
|
|
440
411
|
fault "твоя ссылка origin/${main_branch} отстала от удалённой — ${local_main:0:8} против ${remote_main:0:8}.${age} Гард сравнивает ветку с тем, что лежит в дереве, поэтому молчание первого яруса значит «ссылка не старше ветки», а не «главная ветка влита». Влей и повтори: git fetch origin && git merge origin/${main_branch}."
|
|
441
412
|
fi
|
|
442
413
|
|
|
414
|
+
# Личность вызова. Клиент хостинга держит две записи сразу — залогиненную и ту, чей токен стоит
|
|
415
|
+
# в окружении вызова, — и какая из них откроет заявку, из текста команды не видно ничем, кроме
|
|
416
|
+
# явной подстановки. Читающие вызовы идут от залогиненной и работают, поэтому пишущий выглядит
|
|
417
|
+
# таким же обычным; промах всплывает шагом позже, на назначении ревьювера: автор заявки её
|
|
418
|
+
# ревьювером не бывает. Чинится это только переоткрытием — автора у заявки не сменить.
|
|
419
|
+
#
|
|
420
|
+
# Судится текст команды, а не ответ хостинга: спросить, чьим токеном пойдёт вызов, до его
|
|
421
|
+
# исполнения нельзя. Дерево, не назвавшее переменной токена, требования не получает.
|
|
422
|
+
if [ -n "$pull_token_var" ] \
|
|
423
|
+
&& ! printf '%s' "$cmd" | grep -qE "(^|[;&|(]|&&|\|\||[[:space:]])${pull_token_var}="; then
|
|
424
|
+
fault "заявка открывается без токена машинной записи: в команде нет подстановки «${pull_token_var}». Открытая залогиненной записью, она выйдет от владельца — ревьювером его тогда не назначить, и чинится это только переоткрытием.${pull_token_hint:+ Подставь токен: ${pull_token_hint} …}"
|
|
425
|
+
fi
|
|
426
|
+
|
|
443
427
|
check_task "$number" "заявка с ветки «${branch}»" да
|
|
444
428
|
|
|
429
|
+
# Папка задачи разбирается до открытия заявки, а не после одобрения. Прежде здесь стояло
|
|
430
|
+
# напоминание, а требование — на слиянии: считалось, что папка нужна на диске всё время
|
|
431
|
+
# разбора, потому что правку по замечаниям без замысла отбивает гард хода работы. Владелец же
|
|
432
|
+
# вливает, как только видит зелёное, и закрывающему коммиту места не остаётся — трижды подряд
|
|
433
|
+
# папка уехала в главную неразобранной, а гард слияния этого не видел вовсе: кнопку нажимает
|
|
434
|
+
# человек на хостинге, и до него гард не достаёт. Разбор переехал сюда, а замысел после уборки
|
|
435
|
+
# гард хода работы берёт из истории ветки, а не с диска.
|
|
436
|
+
rt_delivery_open_folder
|
|
437
|
+
|
|
445
438
|
# Всё несошедшееся названо здесь, разом: до этой строки собирались условия, каждое из которых
|
|
446
439
|
# прежде отбивало вызов в одиночку.
|
|
447
440
|
deny_faults
|
|
448
441
|
|
|
449
|
-
# Сейчас папка ещё нужна: правки по замечаниям ревью идут в эту же ветку, а без plan.md их не
|
|
450
|
-
# пропустит гард хода работы. Поэтому здесь только напоминание. Требование стоит на слиянии —
|
|
451
|
-
# там папка уже не нужна, а вред от неё как раз и наступает.
|
|
452
|
-
if [ -n "$tasks_dir" ]; then
|
|
453
|
-
lying="$(folder_in_branch "$tasks_dir/$branch")"
|
|
454
|
-
[ -n "$lying" ] \
|
|
455
|
-
&& hint "В ветке лежит папка задачи «${lying}». Разбери её до слияния, этим же PR: потом за неё уже никто не возьмётся. На слиянии это будет отказ, а не напоминание."
|
|
456
|
-
fi
|
|
457
|
-
|
|
458
442
|
exit 0
|
|
@@ -14,11 +14,13 @@
|
|
|
14
14
|
# Сломанный гард не должен мешать работать.
|
|
15
15
|
|
|
16
16
|
. "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/utf8.sh" 2>/dev/null || true
|
|
17
|
+
. "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/hook-input.sh" 2>/dev/null || true
|
|
17
18
|
|
|
18
|
-
|
|
19
|
+
rt_hook_read
|
|
20
|
+
input="$RT_HOOK_INPUT"
|
|
19
21
|
[ -z "$input" ] && exit 0
|
|
20
22
|
|
|
21
|
-
tool="$(
|
|
23
|
+
tool="$(rt_hook_tool)"
|
|
22
24
|
# Терминал среды разработки исполняет ту же командную строку и кладёт её в то же поле. Пока
|
|
23
25
|
# гард проверял только оболочку, весь его смысл обходился сменой инструмента.
|
|
24
26
|
case "$tool" in
|
|
@@ -26,7 +28,7 @@ case "$tool" in
|
|
|
26
28
|
*) exit 0 ;;
|
|
27
29
|
esac
|
|
28
30
|
|
|
29
|
-
cmd="$(
|
|
31
|
+
cmd="$(rt_hook_cmd)"
|
|
30
32
|
|
|
31
33
|
# Универсальный исполнитель среды передаёт настоящую команду вложенной строкой. Разбирать надо
|
|
32
34
|
# её, а не обёртку: иначе имя команды стоит сразу за кавычкой и ни одно правило до него не
|
|
@@ -46,7 +48,7 @@ esac
|
|
|
46
48
|
|
|
47
49
|
# Коммит выполнится в рабочем каталоге вызова, поэтому и ветку смотрим там же; корень проекта
|
|
48
50
|
# — запасной вариант, и он важен для отдельного рабочего дерева, где ветка своя.
|
|
49
|
-
workdir="$(
|
|
51
|
+
workdir="$(rt_hook_cwd)"
|
|
50
52
|
[ -z "$workdir" ] && workdir="${CLAUDE_PROJECT_DIR:-.}"
|
|
51
53
|
cd "$workdir" 2>/dev/null || exit 0
|
|
52
54
|
|