@rt-tools/agent-kit 0.9.1 → 0.11.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 +5 -0
- package/assets/agents/conscience.md +58 -0
- package/assets/agents/prose-editor.md +44 -0
- package/assets/agents/strict-teacher.md +59 -0
- package/assets/checks/board.github.mjs +56 -1
- package/assets/checks/check-board.github.mjs +24 -0
- package/assets/checks/check-doc-paths.mjs +4 -15
- package/assets/checks/check-dupes.mjs +5 -6
- package/assets/checks/check-file-size.mjs +6 -20
- package/assets/checks/check-prose-style.mjs +137 -0
- package/assets/checks/check-reuse.mjs +5 -5
- package/assets/checks/check-state-next.mjs +194 -0
- package/assets/checks/check-states.mjs +142 -0
- package/assets/checks/check-styles.mjs +5 -5
- package/assets/checks/check-turn-map.mjs +146 -0
- package/assets/checks/lib-common.mjs +3 -3
- package/assets/checks/rt-kit-checks.config.mjs +85 -1
- package/assets/commands/agent-kit-digest.md +6 -5
- package/assets/defaults/project.sh +108 -6
- package/assets/defaults/turn-map.md +46 -0
- package/assets/docs/GLOSSARY.md +17 -15
- package/assets/hooks/browser-device-id.sh +2 -0
- package/assets/hooks/browser-guard-device-id.sh +11 -1
- package/assets/hooks/browser-guard-no-asking.sh +11 -1
- package/assets/hooks/browser-guard-no-listing.sh +11 -1
- package/assets/hooks/browser-guard-no-other-drivers.sh +9 -1
- package/assets/hooks/browser-guard-require-select.sh +13 -2
- package/assets/hooks/claim-guard.sh +115 -0
- package/assets/hooks/commit-msg.sh +2 -0
- package/assets/hooks/conscience-guard.sh +100 -0
- package/assets/hooks/constitution-index.sh +2 -0
- package/assets/hooks/deny-tail.sh +32 -0
- package/assets/hooks/dev-server-guard.sh +10 -2
- package/assets/hooks/docs-guard.sh +16 -2
- package/assets/hooks/exam-guard.sh +123 -0
- package/assets/hooks/git-guard-delivery-signature.sh +77 -0
- package/assets/hooks/git-guard-delivery.sh +156 -68
- package/assets/hooks/git-guard-main.sh +14 -0
- package/assets/hooks/git-guard-push-tests.sh +51 -2
- package/assets/hooks/glossary-load.sh +2 -0
- package/assets/hooks/grill-gate.sh +14 -0
- package/assets/hooks/handoff-entry-guard.sh +73 -0
- package/assets/hooks/handoff-write.sh +103 -0
- package/assets/hooks/lint-after-edit.sh +2 -0
- package/assets/hooks/observe.sh +2 -0
- package/assets/hooks/postmortem-guard.sh +14 -0
- package/assets/hooks/proposal-guard.sh +14 -0
- package/assets/hooks/prose-style-guard.sh +75 -0
- package/assets/hooks/qa-dataid-guard.sh +14 -1
- package/assets/hooks/rerun-guard.sh +88 -0
- package/assets/hooks/reuse-first-guard.sh +14 -1
- package/assets/hooks/roles.sh +34 -0
- package/assets/hooks/skill-gate-layers.sh +2 -0
- package/assets/hooks/skill-gate-rearm.sh +2 -0
- package/assets/hooks/skill-gate.sh +14 -0
- package/assets/hooks/skill-loaded.sh +2 -0
- package/assets/hooks/sql-guard-parse.sh +2 -0
- package/assets/hooks/sql-guard-request.sh +2 -0
- package/assets/hooks/sql-guard-target.sh +2 -0
- package/assets/hooks/sql-guard-write.sh +4 -1
- package/assets/hooks/sql-guard.sh +14 -1
- package/assets/hooks/task-context-load.sh +2 -0
- package/assets/hooks/task-flow-guard.sh +73 -7
- package/assets/hooks/turn-entry-load.sh +62 -0
- package/assets/hooks/turn-exit-guard.sh +191 -0
- package/assets/hooks/utf8.sh +35 -0
- package/assets/hooks/waiting-turn-guard.sh +14 -0
- package/assets/hooks/window-fill-guard.sh +43 -2
- package/assets/laws/work-conduct.md +29 -0
- package/assets/patterns/cargo-triage-mark.md +119 -0
- package/assets/patterns/task-flow-close.md +29 -8
- package/assets/patterns/task-flow-handoff.md +19 -1
- package/assets/patterns/task-flow-resume.md +36 -4
- package/assets/patterns/task-flow-start.md +56 -6
- package/assets/patterns/turn-entry-map.md +81 -0
- package/assets/rules/cargo-triage.md +126 -0
- package/assets/rules/git-workflow.azure.md +36 -9
- package/assets/rules/git-workflow.github.md +74 -9
- package/assets/rules/git-workflow.gitlab.md +40 -12
- package/assets/rules/task-flow.md +136 -71
- package/assets/rules/testing.md +15 -1
- package/assets/rules/turn-entry.md +93 -0
- package/assets/samples/tasks/_template/plan.md +4 -1
- package/assets/samples/tasks/_template/progress.md +1 -0
- package/assets/skills/agent-kit.md +33 -0
- package/assets/templates/project.sh +16 -0
- package/bin/agent-kit.d.ts.map +1 -1
- package/bin/agent-kit.js +42 -1
- package/bin/agent-kit.js.map +1 -1
- package/lib/cargo-state.d.ts +62 -0
- package/lib/cargo-state.d.ts.map +1 -0
- package/lib/cargo-state.js +118 -0
- package/lib/cargo-state.js.map +1 -0
- package/lib/cargo.d.ts +42 -0
- package/lib/cargo.d.ts.map +1 -1
- package/lib/cargo.js +2 -0
- package/lib/cargo.js.map +1 -1
- package/lib/commands.d.ts.map +1 -1
- package/lib/commands.js +64 -4
- package/lib/commands.js.map +1 -1
- package/lib/config.d.ts +8 -0
- package/lib/config.d.ts.map +1 -1
- package/lib/config.js +1 -0
- package/lib/config.js.map +1 -1
- package/lib/observations.d.ts +35 -1
- package/lib/observations.d.ts.map +1 -1
- package/lib/observations.js +14 -2
- package/lib/observations.js.map +1 -1
- package/lib/ship.d.ts +3 -0
- package/lib/ship.d.ts.map +1 -1
- package/lib/ship.js +56 -0
- package/lib/ship.js.map +1 -1
- package/lib/thresholds.d.ts +49 -0
- package/lib/thresholds.d.ts.map +1 -0
- package/lib/thresholds.js +151 -0
- package/lib/thresholds.js.map +1 -0
- package/package.json +1 -1
- package/rt-tools-agent-kit-0.11.0.tgz +0 -0
- package/rt-tools-agent-kit-0.9.1.tgz +0 -0
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# rt-hook: PreCompact .*
|
|
3
|
+
# Требует: hooks/profile-check.sh
|
|
4
|
+
# Передача захода пишется перед сжатием контекста, а не рукой исполнителя.
|
|
5
|
+
#
|
|
6
|
+
# Заход кончается двумя способами: исполнитель доводит работу до точки и пишет передачу сам —
|
|
7
|
+
# либо контекст переполняется, и сжатие приходит от инструмента. Второй случай раньше терял
|
|
8
|
+
# передачу целиком: писать её в этот момент уже некому, а после сжатия пересказывать нечего.
|
|
9
|
+
# Ночью, когда напомнить некому, так кончается каждый заход.
|
|
10
|
+
#
|
|
11
|
+
# Хук собирает передачу из того, что лежит на диске: ход работы даёт состояние и следующий шаг,
|
|
12
|
+
# дерево — ветку, незакоммиченное и коммиты сверх главной. Ничего от себя он не добавляет:
|
|
13
|
+
# передача пересказывает записанное, а не заменяет его.
|
|
14
|
+
#
|
|
15
|
+
# Написанное руками не затирается молча: файл один, и последняя запись побеждает. Исполнитель,
|
|
16
|
+
# закрывающий заход по правилу, пишет поверх — его передача полнее, потому что он знает то,
|
|
17
|
+
# чего на диске нет.
|
|
18
|
+
#
|
|
19
|
+
# FAIL-OPEN: нет разборщика, пустой ввод, не репозиторий, нет каталога передачи — хук выходит
|
|
20
|
+
# нулём и молчит. Сжатие он не отбивает никогда: остановленное сжатие оставит заход без места.
|
|
21
|
+
|
|
22
|
+
. "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/utf8.sh" 2>/dev/null || true
|
|
23
|
+
|
|
24
|
+
input="$(cat 2>/dev/null)"
|
|
25
|
+
[ -z "$input" ] && exit 0
|
|
26
|
+
|
|
27
|
+
command -v jq >/dev/null 2>&1 || exit 0
|
|
28
|
+
|
|
29
|
+
rt_hooks_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
30
|
+
for profile in "$rt_hooks_dir/../rt-kit/defaults/project.sh" "$rt_hooks_dir/../defaults/project.sh" \
|
|
31
|
+
"${CLAUDE_PROJECT_DIR:-.}/.claude/rt-kit/defaults/project.sh" "${CLAUDE_PROJECT_DIR:-.}/.claude/rt-kit/project.sh"; do
|
|
32
|
+
# shellcheck disable=SC1090
|
|
33
|
+
[ -f "$profile" ] && . "$profile" 2>/dev/null
|
|
34
|
+
done
|
|
35
|
+
|
|
36
|
+
workdir="$(printf '%s' "$input" | jq -r '.cwd // empty' 2>/dev/null)"
|
|
37
|
+
[ -z "$workdir" ] && workdir="${CLAUDE_PROJECT_DIR:-.}"
|
|
38
|
+
cd "$workdir" 2>/dev/null || exit 0
|
|
39
|
+
git rev-parse --is-inside-work-tree >/dev/null 2>&1 || exit 0
|
|
40
|
+
|
|
41
|
+
branch="$(git branch --show-current 2>/dev/null)"
|
|
42
|
+
[ -z "$branch" ] && exit 0
|
|
43
|
+
|
|
44
|
+
root="$(git rev-parse --show-toplevel 2>/dev/null)"
|
|
45
|
+
[ -z "$root" ] && exit 0
|
|
46
|
+
|
|
47
|
+
handoff_dir="${RT_HANDOFF_DIR:-.claude/handoff}"
|
|
48
|
+
tasks_dir="${RT_TASKS_DIR:-docs/tasks}"
|
|
49
|
+
|
|
50
|
+
mkdir -p "$root/$handoff_dir" 2>/dev/null || exit 0
|
|
51
|
+
[ -d "$root/$handoff_dir" ] || exit 0
|
|
52
|
+
|
|
53
|
+
# Ручное сжатие от автоматического отличается одним: при ручном исполнитель у клавиатуры и
|
|
54
|
+
# может дописать передачу сам. Пишется она в обоих случаях — заход, сжатый руками, теряет
|
|
55
|
+
# контекст ровно так же.
|
|
56
|
+
trigger="$(printf '%s' "$input" | jq -r '.trigger // "auto"' 2>/dev/null)"
|
|
57
|
+
[ -z "$trigger" ] && trigger='auto'
|
|
58
|
+
|
|
59
|
+
progress="$root/$tasks_dir/$branch/progress.md"
|
|
60
|
+
|
|
61
|
+
# Строка раздела «Где стоим» по её названию. Пусто — работа идёт вне папки задачи, и выдумывать
|
|
62
|
+
# за неё состояние нельзя: в передаче тогда стоит то, что известно дереву.
|
|
63
|
+
line_of() {
|
|
64
|
+
[ -f "$progress" ] || return 0
|
|
65
|
+
sed -n "s/^[[:space:]]*[-*][[:space:]]*\*\*$1:\*\*[[:space:]]*\(.*\)/\1/p" "$progress" 2>/dev/null | head -1
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
state="$(line_of 'Состояние')"
|
|
69
|
+
stage="$(line_of 'Этап')"
|
|
70
|
+
next_step="$(line_of 'Следующий шаг')"
|
|
71
|
+
pull="$(line_of 'PR')"
|
|
72
|
+
|
|
73
|
+
uncommitted="$(git status --short 2>/dev/null | head -20)"
|
|
74
|
+
[ -z "$uncommitted" ] && uncommitted='нет'
|
|
75
|
+
|
|
76
|
+
ahead="$(git log --oneline origin/main..HEAD 2>/dev/null | head -20)"
|
|
77
|
+
[ -z "$ahead" ] && ahead='нет коммитов сверх главной'
|
|
78
|
+
|
|
79
|
+
target="$root/$handoff_dir/$branch.md"
|
|
80
|
+
|
|
81
|
+
{
|
|
82
|
+
printf '# Передача захода — сжатие контекста (%s)\n\n' "$trigger"
|
|
83
|
+
printf '**Рабочее дерево:** %s\n' "$root"
|
|
84
|
+
printf '**Ветка:** %s\n\n' "$branch"
|
|
85
|
+
|
|
86
|
+
if [ -n "$state" ]; then
|
|
87
|
+
printf '## Где стоим\n\n'
|
|
88
|
+
printf -- '- **Состояние:** %s\n' "$state"
|
|
89
|
+
[ -n "$stage" ] && printf -- '- **Этап:** %s\n' "$stage"
|
|
90
|
+
[ -n "$next_step" ] && printf -- '- **Следующий шаг:** %s\n' "$next_step"
|
|
91
|
+
[ -n "$pull" ] && printf -- '- **PR:** %s\n' "$pull"
|
|
92
|
+
printf '\nХод работы целиком — `%s/%s/progress.md`; замысел рядом с ним.\n\n' "$tasks_dir" "$branch"
|
|
93
|
+
else
|
|
94
|
+
printf '## Где стоим\n\nПапки задачи у этой ветки нет: состояние работы взять неоткуда.\n\n'
|
|
95
|
+
fi
|
|
96
|
+
|
|
97
|
+
printf '## Незакоммиченное\n\n```\n%s\n```\n\n' "$uncommitted"
|
|
98
|
+
printf '## Коммиты сверх главной\n\n```\n%s\n```\n\n' "$ahead"
|
|
99
|
+
printf 'Написана хуком перед сжатием контекста. Всё, что здесь стоит, проверяется деревом:\n'
|
|
100
|
+
printf 'передача пересказывает записанное и описывает минуту, когда её собрали.\n'
|
|
101
|
+
} > "$target" 2>/dev/null
|
|
102
|
+
|
|
103
|
+
exit 0
|
|
@@ -29,6 +29,8 @@
|
|
|
29
29
|
|
|
30
30
|
# Сколько файлов линтуется за один перенос. Переезд либы трогает десятки файлов, и прогон по
|
|
31
31
|
# каждому превратил бы хук в минутную паузу; на нарушение границы хватает первых.
|
|
32
|
+
. "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/utf8.sh" 2>/dev/null || true
|
|
33
|
+
|
|
32
34
|
MAX_FILES=12
|
|
33
35
|
|
|
34
36
|
input="$(cat 2>/dev/null)"
|
package/assets/hooks/observe.sh
CHANGED
|
@@ -19,6 +19,8 @@
|
|
|
19
19
|
|
|
20
20
|
# Значение, годное к записи. Путь не выносится ни при каких обстоятельствах, остальное чистится
|
|
21
21
|
# до имени и обрезается: длинное значение в наблюдении не значит ничего, кроме утечки.
|
|
22
|
+
. "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/utf8.sh" 2>/dev/null || true
|
|
23
|
+
|
|
22
24
|
rt_observe_clean() {
|
|
23
25
|
case "$1" in
|
|
24
26
|
*/*) return 0 ;;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
# rt-hook: Stop
|
|
3
|
+
# Требует: hooks/deny-tail.sh
|
|
3
4
|
# Гард происшествия: ход, в котором исполнитель признал промах, не заканчивается, пока записи о
|
|
4
5
|
# происшествии нет. Stop.
|
|
5
6
|
#
|
|
@@ -20,6 +21,8 @@
|
|
|
20
21
|
# ОТКАЗ В ПОЛЬЗУ РАБОТЫ: при любой ошибке, отсутствии записи хода и повторном заходе ход
|
|
21
22
|
# РАЗРЕШАЕТСЯ (exit 0). Сломанный гард не имеет права заклинить разговор.
|
|
22
23
|
|
|
24
|
+
. "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/utf8.sh" 2>/dev/null || true
|
|
25
|
+
|
|
23
26
|
input="$(cat 2>/dev/null)"
|
|
24
27
|
[ -z "$input" ] && exit 0
|
|
25
28
|
|
|
@@ -86,6 +89,17 @@ reason="BLOCKED by postmortem-guard: в ответе признан промах
|
|
|
86
89
|
|
|
87
90
|
Гард судит один ход: следующий заход не отбивается."
|
|
88
91
|
|
|
92
|
+
# Общий хвост отказа: два законных хода. Файл может быть не разложен — тогда хвоста нет,
|
|
93
|
+
# а причина отказа остаётся прежней.
|
|
94
|
+
# shellcheck disable=SC1090
|
|
95
|
+
[ -f "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/deny-tail.sh" ] \
|
|
96
|
+
&& . "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/deny-tail.sh" 2>/dev/null
|
|
97
|
+
command -v rt_deny_tail >/dev/null 2>&1 || rt_deny_tail() { :; }
|
|
98
|
+
deny_tail_text="$(rt_deny_tail "")"
|
|
99
|
+
[ -n "$deny_tail_text" ] && reason="${reason}
|
|
100
|
+
|
|
101
|
+
${deny_tail_text}"
|
|
102
|
+
|
|
89
103
|
jq -n --arg r "$reason" '{decision:"block",reason:$r}' 2>/dev/null \
|
|
90
104
|
|| printf '{"decision":"block","reason":"postmortem-guard: признан промах — запиши разбор происшествия."}\n'
|
|
91
105
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
# rt-hook: Stop
|
|
3
|
+
# Требует: hooks/deny-tail.sh
|
|
3
4
|
# Гард предложения: ход, в котором владелец сказал завести или отправить предложение слою правил,
|
|
4
5
|
# не заканчивается, пока отправки не было. Stop.
|
|
5
6
|
#
|
|
@@ -19,6 +20,8 @@
|
|
|
19
20
|
# ОТКАЗ В ПОЛЬЗУ РАБОТЫ: при любой ошибке, нехватке `jq`, отсутствии записи хода и повторном
|
|
20
21
|
# заходе ход РАЗРЕШАЕТСЯ (exit 0). Сломанный гард не имеет права заклинить разговор.
|
|
21
22
|
|
|
23
|
+
. "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/utf8.sh" 2>/dev/null || true
|
|
24
|
+
|
|
22
25
|
input="$(cat 2>/dev/null)"
|
|
23
26
|
[ -z "$input" ] && exit 0
|
|
24
27
|
|
|
@@ -87,6 +90,17 @@ reason="BLOCKED by proposal-guard: владелец сказал завести
|
|
|
87
90
|
|
|
88
91
|
Гард судит один ход: следующий заход не отбивается."
|
|
89
92
|
|
|
93
|
+
# Общий хвост отказа: два законных хода. Файл может быть не разложен — тогда хвоста нет,
|
|
94
|
+
# а причина отказа остаётся прежней.
|
|
95
|
+
# shellcheck disable=SC1090
|
|
96
|
+
[ -f "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/deny-tail.sh" ] \
|
|
97
|
+
&& . "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/deny-tail.sh" 2>/dev/null
|
|
98
|
+
command -v rt_deny_tail >/dev/null 2>&1 || rt_deny_tail() { :; }
|
|
99
|
+
deny_tail_text="$(rt_deny_tail "")"
|
|
100
|
+
[ -n "$deny_tail_text" ] && reason="${reason}
|
|
101
|
+
|
|
102
|
+
${deny_tail_text}"
|
|
103
|
+
|
|
90
104
|
jq -n --arg r "$reason" '{decision:"block",reason:$r}' 2>/dev/null \
|
|
91
105
|
|| printf '{"decision":"block","reason":"proposal-guard: владелец просил предложение — отправь его командой пакета."}\n'
|
|
92
106
|
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# rt-hook: PreToolUse Edit|Write|MultiEdit
|
|
3
|
+
# Требует: checks/check-prose-style.mjs, hooks/deny-tail.sh
|
|
4
|
+
# Гард слога: канцелярит и слова, которых в этом дереве не пишут, не уезжают в файл.
|
|
5
|
+
#
|
|
6
|
+
# Правило о текстах требует простых слов, а держалось это памятью того, кто пишет: ни одна
|
|
7
|
+
# формулировочная договорённость не проверялась. Владелец читает написанное и видит машинный
|
|
8
|
+
# слог там, где договорённость требует человеческого.
|
|
9
|
+
#
|
|
10
|
+
# Судится только новый текст правки, а не файл целиком: накопленное чинится отдельной работой, и
|
|
11
|
+
# отбивать за него правку соседней строки — значит сделать гард обходимым по необходимости.
|
|
12
|
+
#
|
|
13
|
+
# FAIL-OPEN: нет узла, нет проверки, чужой инструмент, не `.md` → пропуск.
|
|
14
|
+
|
|
15
|
+
. "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/utf8.sh" 2>/dev/null || true
|
|
16
|
+
|
|
17
|
+
input="$(cat 2>/dev/null)"
|
|
18
|
+
[ -z "$input" ] && exit 0
|
|
19
|
+
command -v jq >/dev/null 2>&1 || exit 0
|
|
20
|
+
command -v node >/dev/null 2>&1 || exit 0
|
|
21
|
+
|
|
22
|
+
tool="$(printf '%s' "$input" | jq -r '.tool_name // empty' 2>/dev/null)"
|
|
23
|
+
case "$tool" in
|
|
24
|
+
Edit | Write | MultiEdit) ;;
|
|
25
|
+
*) exit 0 ;;
|
|
26
|
+
esac
|
|
27
|
+
|
|
28
|
+
path="$(printf '%s' "$input" | jq -r '.tool_input.file_path // empty' 2>/dev/null)"
|
|
29
|
+
case "$path" in
|
|
30
|
+
*.md) ;;
|
|
31
|
+
*) exit 0 ;;
|
|
32
|
+
esac
|
|
33
|
+
|
|
34
|
+
# Описание прошлого и папки задач не судятся: архив не правится вовсе, а ход работы пишется
|
|
35
|
+
# наспех и живёт до слияния.
|
|
36
|
+
case "$path" in
|
|
37
|
+
*/docs/archive/* | */docs/tasks/*) exit 0 ;;
|
|
38
|
+
esac
|
|
39
|
+
|
|
40
|
+
added="$(printf '%s' "$input" | jq -r '.tool_input.new_string // .tool_input.content // ([.tool_input.edits[]?.new_string] | join("\n")) // empty' 2>/dev/null)"
|
|
41
|
+
[ -z "$added" ] && exit 0
|
|
42
|
+
|
|
43
|
+
rt_hooks_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
44
|
+
check=""
|
|
45
|
+
for candidate in "$rt_hooks_dir/../checks/check-prose-style.mjs" "$rt_hooks_dir/../rt-kit/checks/check-prose-style.mjs" "${CLAUDE_PROJECT_DIR:-.}/tools/check-prose-style.mjs"; do
|
|
46
|
+
[ -f "$candidate" ] && check="$candidate" && break
|
|
47
|
+
done
|
|
48
|
+
[ -z "$check" ] && exit 0
|
|
49
|
+
|
|
50
|
+
tmp="$(mktemp -t prose)" || exit 0
|
|
51
|
+
printf '%s\n' "$added" > "$tmp"
|
|
52
|
+
found="$(node "$check" "$tmp" 2>&1 | grep -- '—' | sed 's|.*proba*[^:]*:| строка |' | head -8)"
|
|
53
|
+
rm -f "$tmp"
|
|
54
|
+
[ -z "$found" ] && exit 0
|
|
55
|
+
|
|
56
|
+
reason="BLOCKED by prose-style-guard: в новом тексте канцелярит или слово, которого в этом дереве не пишут.
|
|
57
|
+
|
|
58
|
+
${found}
|
|
59
|
+
|
|
60
|
+
Правь текст, а не обходи находку: замена названа у каждой. Слог — правило о текстах, и проверка видит перечисленные признаки, а не стиль вообще: чистый по ней абзац может быть плохим, но грязный плохой точно."
|
|
61
|
+
|
|
62
|
+
# Общий хвост отказа: два законных хода и законная форма обхода, если она у отказа есть.
|
|
63
|
+
# Файл может быть не разложен — тогда хвоста нет, а причина отказа остаётся прежней.
|
|
64
|
+
# shellcheck disable=SC1090
|
|
65
|
+
[ -f "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/deny-tail.sh" ] \
|
|
66
|
+
&& . "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/deny-tail.sh" 2>/dev/null
|
|
67
|
+
command -v rt_deny_tail >/dev/null 2>&1 || rt_deny_tail() { :; }
|
|
68
|
+
deny_tail_text="$(rt_deny_tail "")"
|
|
69
|
+
[ -n "$deny_tail_text" ] && reason="${reason}
|
|
70
|
+
|
|
71
|
+
${deny_tail_text}"
|
|
72
|
+
|
|
73
|
+
jq -n --arg r "$reason" '{hookSpecificOutput:{hookEventName:"PreToolUse",permissionDecision:"deny",permissionDecisionReason:$r}}' 2>/dev/null \
|
|
74
|
+
|| printf '{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"deny","permissionDecisionReason":"prose-style-guard: канцелярит в новом тексте."}}\n'
|
|
75
|
+
exit 0
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
# rt-hook: PreToolUse Edit|Write|MultiEdit|mcp__webstorm__create_new_file
|
|
3
|
-
# Требует: hooks/profile-check.sh
|
|
3
|
+
# Требует: hooks/profile-check.sh, hooks/deny-tail.sh
|
|
4
4
|
# Гард якоря для спек. PreToolUse на правке разметки.
|
|
5
5
|
#
|
|
6
6
|
# Спеки адресуют элементы только через этот атрибут. Классы оформления меняются вместе с
|
|
@@ -25,6 +25,8 @@
|
|
|
25
25
|
#
|
|
26
26
|
# ОТКАЗ В ПОЛЬЗУ РАБОТЫ: нет разборщика, битый ввод, чужой инструмент — пропуск.
|
|
27
27
|
|
|
28
|
+
. "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/utf8.sh" 2>/dev/null || true
|
|
29
|
+
|
|
28
30
|
input="$(cat 2>/dev/null)"
|
|
29
31
|
[ -z "$input" ] && exit 0
|
|
30
32
|
command -v jq >/dev/null 2>&1 || exit 0
|
|
@@ -122,6 +124,17 @@ missing="$(printf '%s' "$added" | RT_QA_DECORATIVE="$decorative" RT_QA_COMPONENT
|
|
|
122
124
|
|
|
123
125
|
reason="BLOCKED: интерактивные элементы без якоря для спек в ${path##*/}: ${missing}. Спеки адресуют элементы только через этот атрибут: классы оформления меняются вместе с вёрсткой, а поиск по роли и тексту ломается на переводах — оба вида селекторов делают сквозные спеки хрупкими. Проставь якорь через дефис по смыслу элемента; повторяющиеся элементы списка носят ОДИН якорь и различаются атрибутами данных. Если элемент чисто декоративный и спека его никогда не тронет — поставь атрибут пропуска НА САМ ЭТОТ ТЕГ, соседние элементы правки при этом продолжают проверяться."
|
|
124
126
|
|
|
127
|
+
# Общий хвост отказа: два законных хода и законная форма обхода, если она у отказа есть.
|
|
128
|
+
# Файл может быть не разложен — тогда хвоста нет, а причина отказа остаётся прежней.
|
|
129
|
+
# shellcheck disable=SC1090
|
|
130
|
+
[ -f "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/deny-tail.sh" ] \
|
|
131
|
+
&& . "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/deny-tail.sh" 2>/dev/null
|
|
132
|
+
command -v rt_deny_tail >/dev/null 2>&1 || rt_deny_tail() { :; }
|
|
133
|
+
deny_tail_text="$(rt_deny_tail "атрибут пропуска на самом декоративном теге")"
|
|
134
|
+
[ -n "$deny_tail_text" ] && reason="${reason}
|
|
135
|
+
|
|
136
|
+
${deny_tail_text}"
|
|
137
|
+
|
|
125
138
|
jq -n --arg r "$reason" '{hookSpecificOutput:{hookEventName:"PreToolUse",permissionDecision:"deny",permissionDecisionReason:$r}}' 2>/dev/null \
|
|
126
139
|
|| printf '{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"deny","permissionDecisionReason":"Интерактивный элемент без якоря для спек."}}\n'
|
|
127
140
|
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# rt-hook: PreToolUse Bash|mcp__webstorm__execute_terminal_command|mcp__webstorm__execute_tool
|
|
3
|
+
# Требует: hooks/deny-tail.sh
|
|
4
|
+
# Гард перезапуска прогона: упавшее задание не перезапускается, пока его журнал не прочитан.
|
|
5
|
+
#
|
|
6
|
+
# Красное на прогоне бывает двух родов, и со стороны списка они выглядят одинаково: отказ
|
|
7
|
+
# хостинга на шаге подготовки — раннер не скачал действие, ответ `429` — и дефект самой ветки.
|
|
8
|
+
# Первый лечится перезапуском, второй перезапуском не лечится вовсе: та же ветка падает тем же
|
|
9
|
+
# местом, и круг повторяется, пока кто-нибудь не откроет журнал. Три прогона одного дня так и
|
|
10
|
+
# перезапускались подряд.
|
|
11
|
+
#
|
|
12
|
+
# Гард судит ПОРЯДОК, а не причину падения: журнал раньше перезапуска. Что в журнале написано,
|
|
13
|
+
# он не читает и читать не может — красное по существу судит человек.
|
|
14
|
+
#
|
|
15
|
+
# Признак чтения — вызов за тот же ход, показывающий журнал этого задания: номер задания в
|
|
16
|
+
# команде тот же, что и в перезапуске. Номер сверяется, потому что прочитанный журнал соседнего
|
|
17
|
+
# задания о нашем не говорит ничего, а по списку прогонов они стоят рядом.
|
|
18
|
+
#
|
|
19
|
+
# ОТКАЗ В ПОЛЬЗУ РАБОТЫ: нет `jq`, нет записи хода, номер задания в команде не назван, вызов не
|
|
20
|
+
# похож на перезапуск — пропуск. Гард без номера судить не берётся: перезапуск последнего
|
|
21
|
+
# упавшего прогона зовут и без него, а угадывать, о каком задании речь, значит отбивать наугад.
|
|
22
|
+
|
|
23
|
+
. "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/utf8.sh" 2>/dev/null || true
|
|
24
|
+
|
|
25
|
+
input="$(cat 2>/dev/null)"
|
|
26
|
+
[ -z "$input" ] && exit 0
|
|
27
|
+
command -v jq >/dev/null 2>&1 || exit 0
|
|
28
|
+
|
|
29
|
+
tool="$(printf '%s' "$input" | jq -r '.tool_name // empty' 2>/dev/null)"
|
|
30
|
+
case "$tool" in
|
|
31
|
+
# Терминал среды и универсальный исполнитель кладут команду в то же поле.
|
|
32
|
+
Bash | mcp__webstorm__execute_terminal_command | mcp__webstorm__execute_tool) ;;
|
|
33
|
+
*) exit 0 ;;
|
|
34
|
+
esac
|
|
35
|
+
|
|
36
|
+
cmd="$(printf '%s' "$input" | jq -r '.tool_input.command // empty' 2>/dev/null)"
|
|
37
|
+
[ -z "$cmd" ] && exit 0
|
|
38
|
+
|
|
39
|
+
# Профиль дерева: сперва умолчание пакета, поверх него — надстройка проекта, если она есть.
|
|
40
|
+
# Имя клиента хостинга дерево называет само: у каждого вида оно своё, а угаданное не совпадает
|
|
41
|
+
# ни с чем.
|
|
42
|
+
rt_hooks_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
43
|
+
for profile in "$rt_hooks_dir/../rt-kit/defaults/project.sh" "$rt_hooks_dir/../defaults/project.sh" "${CLAUDE_PROJECT_DIR:-.}/.claude/rt-kit/defaults/project.sh" "${CLAUDE_PROJECT_DIR:-.}/.claude/rt-kit/project.sh"; do
|
|
44
|
+
# shellcheck disable=SC1090
|
|
45
|
+
[ -f "$profile" ] && . "$profile" 2>/dev/null
|
|
46
|
+
done
|
|
47
|
+
|
|
48
|
+
# Вызов перезапуска: слово перезапуска отдельным словом рядом со словом прогона.
|
|
49
|
+
host_cli="${RT_HOST_CLI:-gh}"
|
|
50
|
+
printf '%s' "$cmd" | grep -qE "(^|[;&|(]|&&|\|\|)[[:space:]]*${host_cli}([[:space:]]|$)" || exit 0
|
|
51
|
+
printf '%s' "$cmd" | grep -qE '(^|[[:space:]])(run|workflow)([[:space:]]|$)' || exit 0
|
|
52
|
+
printf '%s' "$cmd" | grep -qE '(^|[[:space:]])rerun([[:space:]]|$)' || exit 0
|
|
53
|
+
|
|
54
|
+
# Номер задания. Не назван — гард молчит: судить не о чем.
|
|
55
|
+
run_id="$(printf '%s' "$cmd" | grep -oE '(^|[[:space:]])[0-9]{6,}([[:space:]]|$)' | tr -d ' ' | head -1)"
|
|
56
|
+
[ -z "$run_id" ] && exit 0
|
|
57
|
+
|
|
58
|
+
transcript="$(printf '%s' "$input" | jq -r '.transcript_path // empty' 2>/dev/null)"
|
|
59
|
+
[ -z "$transcript" ] && exit 0
|
|
60
|
+
[ -f "$transcript" ] || exit 0
|
|
61
|
+
|
|
62
|
+
# Читался ли журнал ЭТОГО задания за тот же ход. Ход — вызовы после последней реплики владельца:
|
|
63
|
+
# журнал, прочитанный вчера, о сегодняшнем состоянии прогона не говорит ничего.
|
|
64
|
+
seen="$(jq -s -r --arg id "$run_id" '
|
|
65
|
+
[.[] | select(.type == "assistant") | (.message.content // [])[] | select(.type == "tool_use")
|
|
66
|
+
| ((.input.command // "") | tostring)] as $used
|
|
67
|
+
| (($used | join("\n")) | test("run[[:space:]]+view[^\\n]*" + $id + "|" + $id + "[^\\n]*--log")) as $read
|
|
68
|
+
| if $read then "читал" else "не-читал" end
|
|
69
|
+
' "$transcript" 2>/dev/null)"
|
|
70
|
+
|
|
71
|
+
[ "$seen" = "читал" ] && exit 0
|
|
72
|
+
|
|
73
|
+
reason="BLOCKED by rerun-guard: перезапуск задания ${run_id} без прочитанного журнала. Красное на прогоне бывает двух родов, и в списке они выглядят одинаково: отказ хостинга на шаге подготовки лечится перезапуском, дефект ветки — не лечится им вовсе, и круг повторяется, пока журнал не открыт. Прочитай журнал этого задания — ${host_cli} run view ${run_id} --log-failed — и повтори вызов. Гард судит порядок, а не причину падения: что в журнале написано, судишь ты."
|
|
74
|
+
|
|
75
|
+
# Общий хвост отказа: два законных хода и законная форма обхода, если она у отказа есть.
|
|
76
|
+
# Файл может быть не разложен — тогда хвоста нет, а причина отказа остаётся прежней.
|
|
77
|
+
# shellcheck disable=SC1090
|
|
78
|
+
[ -f "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/deny-tail.sh" ] \
|
|
79
|
+
&& . "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/deny-tail.sh" 2>/dev/null
|
|
80
|
+
command -v rt_deny_tail >/dev/null 2>&1 || rt_deny_tail() { :; }
|
|
81
|
+
deny_tail_text="$(rt_deny_tail "")"
|
|
82
|
+
[ -n "$deny_tail_text" ] && reason="${reason}
|
|
83
|
+
|
|
84
|
+
${deny_tail_text}"
|
|
85
|
+
|
|
86
|
+
jq -n --arg r "$reason" '{hookSpecificOutput:{hookEventName:"PreToolUse",permissionDecision:"deny",permissionDecisionReason:$r}}' 2>/dev/null \
|
|
87
|
+
|| printf '{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"deny","permissionDecisionReason":"rerun-guard: журнал задания за этот ход не читался."}}\n'
|
|
88
|
+
exit 0
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
# rt-hook: PreToolUse Edit|Write|MultiEdit|Bash|mcp__webstorm__create_new_file|mcp__webstorm__execute_terminal_command|mcp__webstorm__execute_tool
|
|
3
|
-
# Требует: hooks/profile-check.sh
|
|
3
|
+
# Требует: hooks/profile-check.sh, hooks/deny-tail.sh
|
|
4
4
|
# Гард «ничего не пишется с нуля». PreToolUse на правке кода и разметки.
|
|
5
5
|
#
|
|
6
6
|
# Линтеры знают правила, но не знают ИНВЕНТАРЬ: линтер стилей поймает сырой цвет, линтер кода —
|
|
@@ -32,6 +32,8 @@
|
|
|
32
32
|
#
|
|
33
33
|
# ОТКАЗ В ПОЛЬЗУ РАБОТЫ: нет разборщика, битый ввод, чужой инструмент — пропуск.
|
|
34
34
|
|
|
35
|
+
. "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/utf8.sh" 2>/dev/null || true
|
|
36
|
+
|
|
35
37
|
input="$(cat 2>/dev/null)"
|
|
36
38
|
[ -z "$input" ] && exit 0
|
|
37
39
|
command -v jq >/dev/null 2>&1 || exit 0
|
|
@@ -312,6 +314,17 @@ ${found}
|
|
|
312
314
|
Порядок действий: 1) открой готовое — барель кита или базовый класс — и используй его; 2) найди в дереве экран, где этот случай уже собран, и повтори сборку; 3) если готового правда не хватает — расширяй его на месте, у готового, а не клонируй рядом: клон забирает правки на себя и расходится с оригиналом с первой же.
|
|
313
315
|
Свой примитив, своя основа и свои инлайновые стили заводятся только с явного одобрения владельца, и спрашивается это до первого написанного файла. Разовое исключение помечается маркером отступления в той же строке, с объяснением, чего именно нет в готовом. Переименованием файла это не обходится."
|
|
314
316
|
|
|
317
|
+
# Общий хвост отказа: два законных хода и законная форма обхода, если она у отказа есть.
|
|
318
|
+
# Файл может быть не разложен — тогда хвоста нет, а причина отказа остаётся прежней.
|
|
319
|
+
# shellcheck disable=SC1090
|
|
320
|
+
[ -f "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/deny-tail.sh" ] \
|
|
321
|
+
&& . "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/deny-tail.sh" 2>/dev/null
|
|
322
|
+
command -v rt_deny_tail >/dev/null 2>&1 || rt_deny_tail() { :; }
|
|
323
|
+
deny_tail_text="$(rt_deny_tail "маркер отступления в той же строке, с объяснением, чего именно нет в готовом")"
|
|
324
|
+
[ -n "$deny_tail_text" ] && reason="${reason}
|
|
325
|
+
|
|
326
|
+
${deny_tail_text}"
|
|
327
|
+
|
|
315
328
|
jq -n --arg r "$reason" '{hookSpecificOutput:{hookEventName:"PreToolUse",permissionDecision:"deny",permissionDecisionReason:$r}}' 2>/dev/null \
|
|
316
329
|
|| printf '{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"deny","permissionDecisionReason":"Написано своё там, где готовое уже есть."}}\n'
|
|
317
330
|
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Состояние роли слоя правил. НЕ гард: объявления `rt-hook:` у него нет, к событиям агента он не
|
|
3
|
+
# подключается. Его источают гарды, стоящие при ролях, — тем же приёмом, каким гейт источает
|
|
4
|
+
# карту, а гарды — профиль дерева.
|
|
5
|
+
#
|
|
6
|
+
# Зачем он есть. Роль, при которой стоит гард, зовётся не по усмотрению исполнителя: гард держит
|
|
7
|
+
# работу, пока роль не сказала своё. Выключить её было нечем — отказ от файла роли в настройке
|
|
8
|
+
# убирал роль и оставлял гард, и дереву, которому роль в эту минуту мешает, оставалось вырезать
|
|
9
|
+
# гард из настроек руками. Теперь дерево называет выключенные роли одним списком, а гард при
|
|
10
|
+
# такой роли выходит молча.
|
|
11
|
+
#
|
|
12
|
+
# Выключается обязательность вызова, а не сама роль: файл роли остаётся разложенным, и позвать
|
|
13
|
+
# её руками можно в любую минуту.
|
|
14
|
+
#
|
|
15
|
+
# ОТКАЗ В ПОЛЬЗУ РАБОТЫ наоборот: всё, чего не хватило, читается как «роль включена». Нет `jq`,
|
|
16
|
+
# нет настройки, настройка не разбирается — гард работает, как работал. Сломанное чтение,
|
|
17
|
+
# гасящее роль, выключало бы слой правил молча, и заметить это было бы нечем.
|
|
18
|
+
|
|
19
|
+
# Выключена ли роль. Успех — выключена.
|
|
20
|
+
#
|
|
21
|
+
# rt_role_off strict-teacher && exit 0
|
|
22
|
+
. "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/utf8.sh" 2>/dev/null || true
|
|
23
|
+
|
|
24
|
+
rt_role_off() {
|
|
25
|
+
local role="$1" config
|
|
26
|
+
[ -n "$role" ] || return 1
|
|
27
|
+
command -v jq >/dev/null 2>&1 || return 1
|
|
28
|
+
config="${CLAUDE_PROJECT_DIR:-.}/.claude/rt-kit.json"
|
|
29
|
+
[ -f "$config" ] || return 1
|
|
30
|
+
[ "$(jq -r --arg role "$role" '
|
|
31
|
+
if ((.rolesOff // []) | type) == "array" and (((.rolesOff // []) | index($role)) != null)
|
|
32
|
+
then "off" else "on" end
|
|
33
|
+
' "$config" 2>/dev/null)" = "off" ]
|
|
34
|
+
}
|
|
@@ -20,6 +20,8 @@
|
|
|
20
20
|
|
|
21
21
|
# Уже названное вторым разом не требуется: отказ, перечисляющий одно правило дважды, читается
|
|
22
22
|
# как два разных требования.
|
|
23
|
+
. "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/utf8.sh" 2>/dev/null || true
|
|
24
|
+
|
|
23
25
|
rt_layer_add() {
|
|
24
26
|
case " $req " in
|
|
25
27
|
*" $1 "*) return 0 ;;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
# rt-hook: PreToolUse Edit|Write|MultiEdit|Bash|mcp__webstorm__create_new_file|mcp__webstorm__execute_terminal_command|mcp__webstorm__execute_tool|mcp__claude-in-chrome__.*
|
|
3
|
+
# Требует: hooks/deny-tail.sh
|
|
3
4
|
# Гейт правил: не даёт править файл, пока не загружено правило, под которое он подпадает.
|
|
4
5
|
#
|
|
5
6
|
# Закон и правило, которых никто не открывает, не действуют. Напоминание в подсказке помогает
|
|
@@ -22,6 +23,8 @@
|
|
|
22
23
|
# ОТКАЗ В ПОЛЬЗУ РАБОТЫ: любая ошибка и любой неопознанный путь пропускают правку (exit 0).
|
|
23
24
|
# Сломанный гейт не имеет права остановить работу совсем.
|
|
24
25
|
|
|
26
|
+
. "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/utf8.sh" 2>/dev/null || true
|
|
27
|
+
|
|
25
28
|
input="$(cat 2>/dev/null)"
|
|
26
29
|
[ -z "$input" ] && exit 0
|
|
27
30
|
|
|
@@ -172,6 +175,17 @@ if [ -n "$law" ]; then
|
|
|
172
175
|
&& reason="Отбито гейтом правил: загрузи правило «${req}» инструментом Skill — оно применяет закон ${law_path} к этому дереву — и повтори действие. ${fallback} Для этой области это происходит один раз за сессию."
|
|
173
176
|
fi
|
|
174
177
|
|
|
178
|
+
# Общий хвост отказа: два законных хода и законная форма обхода, если она у отказа есть.
|
|
179
|
+
# Файл может быть не разложен — тогда хвоста нет, а причина отказа остаётся прежней.
|
|
180
|
+
# shellcheck disable=SC1090
|
|
181
|
+
[ -f "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/deny-tail.sh" ] \
|
|
182
|
+
&& . "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/deny-tail.sh" 2>/dev/null
|
|
183
|
+
command -v rt_deny_tail >/dev/null 2>&1 || rt_deny_tail() { :; }
|
|
184
|
+
deny_tail_text="$(rt_deny_tail "")"
|
|
185
|
+
[ -n "$deny_tail_text" ] && reason="${reason}
|
|
186
|
+
|
|
187
|
+
${deny_tail_text}"
|
|
188
|
+
|
|
175
189
|
jq -n --arg r "$reason" '{hookSpecificOutput:{hookEventName:"PreToolUse",permissionDecision:"deny",permissionDecisionReason:$r}}' 2>/dev/null \
|
|
176
190
|
|| printf '{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"deny","permissionDecisionReason":"Загрузи правило %s и повтори."}}\n' "$req"
|
|
177
191
|
|
|
@@ -13,6 +13,8 @@
|
|
|
13
13
|
# команда становилась одним сегментом, доказательство чтения из первой строки покрывало
|
|
14
14
|
# неразобранный вызов из второй, и запись на боевую базу проходила молча — тот же обход,
|
|
15
15
|
# что чинили для `&&`, только набранный с новой строки.
|
|
16
|
+
. "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/utf8.sh" 2>/dev/null || true
|
|
17
|
+
|
|
16
18
|
normalize() {
|
|
17
19
|
perl -0ne '
|
|
18
20
|
# Продолжение длинной команды обратным слешем — это одна строка, а не две: без
|
|
@@ -10,6 +10,8 @@
|
|
|
10
10
|
|
|
11
11
|
# Три вида ввода: запрос через подключение редактора, команда оболочки и та же команда,
|
|
12
12
|
# завёрнутая в универсальный исполнитель. Инструмент, не названный здесь, гарда не касается.
|
|
13
|
+
. "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/utf8.sh" 2>/dev/null || true
|
|
14
|
+
|
|
13
15
|
sql_read_request() {
|
|
14
16
|
sql=""
|
|
15
17
|
context=""
|
|
@@ -18,6 +18,8 @@
|
|
|
18
18
|
# подключения, и по одному SQL отличить прод от локальной копии невозможно. Поэтому
|
|
19
19
|
# подключения опознаются в лицо. Список сверяется вызовом list_database_connections;
|
|
20
20
|
# добавили новое — допишите сюда, иначе оно попадёт в «неизвестные» ниже.
|
|
21
|
+
. "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/utf8.sh" 2>/dev/null || true
|
|
22
|
+
|
|
21
23
|
sql_resolve_target() {
|
|
22
24
|
is_prod=""
|
|
23
25
|
PROD_CONNECTIONS="${RT_PROD_CONNECTIONS:-}"
|
|
@@ -7,6 +7,8 @@
|
|
|
7
7
|
|
|
8
8
|
# Глаголы ищутся в сегментах ВЫЗОВА, а не по всей строке: иначе слово из шаблона поиска в
|
|
9
9
|
# соседнем звене цепочки объявляло записью читающую команду.
|
|
10
|
+
. "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/utf8.sh" 2>/dev/null || true
|
|
11
|
+
|
|
10
12
|
sql_detect_write() {
|
|
11
13
|
is_write=""
|
|
12
14
|
write_scope="$segments"
|
|
@@ -67,7 +69,8 @@ verdict() {
|
|
|
67
69
|
if [ -n "$soft" ]; then
|
|
68
70
|
ask "Запрос помечен маркером destructive-ok, но остаётся разрушительным: $1 Подтверди выполнение, если это осознанно."
|
|
69
71
|
fi
|
|
70
|
-
deny "BLOCKED: $1 Адресуй строки по первичному ключу — \`WHERE id IN ('…','…')\`: так затрагивается ровно столько строк, сколько перечислено, и промах виден до выполнения. Удаление по маске (email LIKE '%test%') однажды унесло вместе с тестовыми записями демонстрационные брони владельца. Если адресация по id действительно не подходит — сначала выполни SELECT с тем же
|
|
72
|
+
deny "BLOCKED: $1 Адресуй строки по первичному ключу — \`WHERE id IN ('…','…')\`: так затрагивается ровно столько строк, сколько перечислено, и промах виден до выполнения. Удаление по маске (email LIKE '%test%') однажды унесло вместе с тестовыми записями демонстрационные брони владельца. Если адресация по id действительно не подходит — сначала выполни SELECT с тем же условием и покажи пользователю, что попадает под удаление." \
|
|
73
|
+
"маркер destructive-ok при запросе, с объяснением"
|
|
71
74
|
}
|
|
72
75
|
|
|
73
76
|
sql_check_destructive() {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
# rt-hook: PreToolUse Bash|mcp__webstorm__execute_sql_query|mcp__webstorm__execute_terminal_command|mcp__webstorm__execute_tool
|
|
3
|
+
# Требует: hooks/deny-tail.sh
|
|
3
4
|
# Гард пишущих запросов к хранилищу. PreToolUse.
|
|
4
5
|
#
|
|
5
6
|
# Правка данных — единственное действие, которое нельзя откатить правкой кода. Удаление по
|
|
@@ -39,6 +40,8 @@
|
|
|
39
40
|
# пропускаются целиком — иначе слова «drop» и «update» в описании коммита читались бы как
|
|
40
41
|
# запрос. Доставки запроса внутри такой команды не бывает, так что цена послабления нулевая.
|
|
41
42
|
|
|
43
|
+
. "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/utf8.sh" 2>/dev/null || true
|
|
44
|
+
|
|
42
45
|
input="$(cat 2>/dev/null)"
|
|
43
46
|
[ -z "$input" ] && exit 0
|
|
44
47
|
command -v jq >/dev/null 2>&1 || exit 0
|
|
@@ -69,8 +72,18 @@ done
|
|
|
69
72
|
|
|
70
73
|
PROD_DSN="${RT_PROD_DSN:-}"
|
|
71
74
|
|
|
75
|
+
# Общий хвост отказа: два законных хода и законная форма обхода, если она у отказа есть. Файл
|
|
76
|
+
# может быть не разложен — тогда хвоста нет, а причина отказа остаётся прежней.
|
|
77
|
+
# shellcheck disable=SC1090
|
|
78
|
+
[ -f "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/deny-tail.sh" ] \
|
|
79
|
+
&& . "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/deny-tail.sh" 2>/dev/null
|
|
80
|
+
command -v rt_deny_tail >/dev/null 2>&1 || rt_deny_tail() { :; }
|
|
81
|
+
|
|
72
82
|
deny() {
|
|
73
|
-
|
|
83
|
+
reason="$1"
|
|
84
|
+
tail_text="$(rt_deny_tail "$2")"
|
|
85
|
+
[ -n "$tail_text" ] && reason="$1 ${tail_text}"
|
|
86
|
+
jq -n --arg r "$reason" '{hookSpecificOutput:{hookEventName:"PreToolUse",permissionDecision:"deny",permissionDecisionReason:$r}}' 2>/dev/null \
|
|
74
87
|
|| printf '{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"deny","permissionDecisionReason":"Destructive SQL blocked. Address rows by id."}}\n'
|
|
75
88
|
exit 0
|
|
76
89
|
}
|