@rt-tools/agent-kit 0.11.0 → 0.12.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/check-file-size.mjs +19 -4
- package/assets/checks/check-state-next.mjs +10 -2
- package/assets/checks/rt-kit-checks.config.mjs +16 -2
- package/assets/defaults/project.sh +9 -1
- 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 +5 -3
- 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 +5 -3
- 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.sh +37 -5
- package/assets/hooks/git-guard-main.sh +6 -4
- package/assets/hooks/git-guard-push-tests.sh +6 -4
- 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 +54 -0
- package/assets/hooks/lint-after-edit.sh +5 -3
- 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 +5 -3
- package/assets/hooks/reuse-first-guard.sh +5 -3
- package/assets/hooks/rule-article.sh +99 -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 +6 -4
- package/assets/hooks/turn-exit-guard.sh +42 -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 +193 -0
- package/assets/patterns/task-flow-close.md +3 -173
- package/assets/patterns/task-flow-handoff.md +4 -4
- 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 +106 -0
- package/assets/rules/deploy-flow.github.md +113 -0
- package/assets/rules/deploy-flow.gitlab.md +108 -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 +14 -127
- 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 +17 -199
- package/assets/rules/testing.md +3 -64
- package/assets/rules/turn-conduct.md +206 -0
- package/assets/rules/typescript-conventions.md +15 -0
- package/assets/skills/agent-kit.md +35 -12
- package/assets/templates/pitfalls.md +10 -0
- 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/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/package.json +1 -1
- package/rt-tools-agent-kit-0.12.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
|
@@ -35,8 +35,18 @@ import { join } from 'node:path';
|
|
|
35
35
|
import { allowlistOf, baselineOf, CONFIG, ROOT, parseAllowlist } from './rt-kit-checks.config.mjs';
|
|
36
36
|
|
|
37
37
|
const ALLOWLIST = allowlistOf('file-size');
|
|
38
|
-
/**
|
|
38
|
+
/** Пределов два: код и текст слоя правил. Какой из них применён, каждая строка отказа называет. */
|
|
39
39
|
const LIMIT = CONFIG.fileSizeLimit;
|
|
40
|
+
const PROSE_LIMIT = CONFIG.proseSizeLimit ?? CONFIG.fileSizeLimit;
|
|
41
|
+
/** Корни текста слоя правил; дерево, их не назвавшее, судится одним пределом. */
|
|
42
|
+
const PROSE_ROOTS = CONFIG.proseRoots ?? [];
|
|
43
|
+
|
|
44
|
+
/** Предел для файла и имя предела для отказа: по корню, а не по расширению — код лежит и в `.md`. */
|
|
45
|
+
function limitOf(path) {
|
|
46
|
+
return PROSE_ROOTS.some((root) => root && path.startsWith(root))
|
|
47
|
+
? { limit: PROSE_LIMIT, title: 'предел текста' }
|
|
48
|
+
: { limit: LIMIT, title: 'предел кода' };
|
|
49
|
+
}
|
|
40
50
|
|
|
41
51
|
/** Роды файлов, которых не читает линтер. Код остаётся за ним. */
|
|
42
52
|
const JUDGED = ['.md', '.scss', '.html', '.js', '.mjs', '.sh'];
|
|
@@ -78,7 +88,7 @@ const tracked = trackedFiles().filter(judged);
|
|
|
78
88
|
|
|
79
89
|
for (const path of tracked) {
|
|
80
90
|
const lines = lineCount(path);
|
|
81
|
-
if (lines >
|
|
91
|
+
if (lines > limitOf(path).limit) {
|
|
82
92
|
tooLong.set(path, lines);
|
|
83
93
|
}
|
|
84
94
|
}
|
|
@@ -95,7 +105,10 @@ const gone = [...known.keys()].filter((path) => !existsSync(join(ROOT, path)));
|
|
|
95
105
|
const shrunk = [...known.keys()].filter((path) => !tooLong.has(path) && existsSync(join(ROOT, path)));
|
|
96
106
|
|
|
97
107
|
const problems = [
|
|
98
|
-
...fresh.map(([path, lines]) =>
|
|
108
|
+
...fresh.map(([path, lines]) => {
|
|
109
|
+
const { limit, title } = limitOf(path);
|
|
110
|
+
return `${path}: ${lines} строк, ${title} ${limit} — делить, а не дописывать строку в ${ALLOWLIST}`;
|
|
111
|
+
}),
|
|
99
112
|
...gone.map((path) => `${path}: строка в ${ALLOWLIST} устарела — файла в дереве нет`),
|
|
100
113
|
...shrunk.map((path) => `${path}: значится в ${ALLOWLIST}, но уже короче предела — строку убрать`),
|
|
101
114
|
];
|
|
@@ -107,7 +120,9 @@ if (problems.length > 0) {
|
|
|
107
120
|
process.exit(1);
|
|
108
121
|
}
|
|
109
122
|
|
|
123
|
+
const limits = PROSE_ROOTS.length > 0 ? `предел кода ${LIMIT}, предел текста ${PROSE_LIMIT}` : `предел ${LIMIT}`;
|
|
124
|
+
|
|
110
125
|
console.log(
|
|
111
|
-
`check-file-size: проверено ${tracked.length} файлов,
|
|
126
|
+
`check-file-size: проверено ${tracked.length} файлов, ${limits}, длиннее предела ${tooLong.size}, ` +
|
|
112
127
|
`из них принято ${accepted.size}, долг ${debt.size} — новых нет`
|
|
113
128
|
);
|
|
@@ -31,6 +31,13 @@ import { join } from 'node:path';
|
|
|
31
31
|
|
|
32
32
|
const ROOT = process.cwd();
|
|
33
33
|
const RULE = join(ROOT, '.claude/skills/task-flow/SKILL.md');
|
|
34
|
+
/**
|
|
35
|
+
* Правило хода захода: граница состояния живёт там, а не в правиле ведения работы.
|
|
36
|
+
* Разделились они, когда правило ведения работы вышло за предел длины: ход работы
|
|
37
|
+
* остался в одном, выходы хода уехали в другое. Дерево, где разложено только первое,
|
|
38
|
+
* судится по нему одному — второго файла у него нет.
|
|
39
|
+
*/
|
|
40
|
+
const TURN_RULE = join(ROOT, '.claude/skills/turn-conduct/SKILL.md');
|
|
34
41
|
const SKILLS = join(ROOT, '.claude/skills');
|
|
35
42
|
const MAP = join(ROOT, '.claude/rt-kit/defaults/turn-map.md');
|
|
36
43
|
const LAW = join(ROOT, 'docs/constitution/work-conduct.md');
|
|
@@ -167,8 +174,9 @@ if (counted === 0) {
|
|
|
167
174
|
problems.push('ни одного раздела состояния не нашлось: паттерны не разложены или заголовки в них другие');
|
|
168
175
|
}
|
|
169
176
|
|
|
170
|
-
|
|
171
|
-
|
|
177
|
+
const turnText = existsSync(TURN_RULE) ? readFileSync(TURN_RULE, 'utf8') : '';
|
|
178
|
+
if (!ruleText.includes(BOUNDARY) && !turnText.includes(BOUNDARY)) {
|
|
179
|
+
problems.push(`правило хода захода о границе состояния молчит: строки «${BOUNDARY}» в нём нет`);
|
|
172
180
|
}
|
|
173
181
|
|
|
174
182
|
for (const [file, what] of [
|
|
@@ -67,10 +67,24 @@ const DEFAULTS = {
|
|
|
67
67
|
*/
|
|
68
68
|
generatedDirs: [],
|
|
69
69
|
/**
|
|
70
|
-
* Предел длины файла
|
|
71
|
-
* числе на каждой
|
|
70
|
+
* Предел длины файла для кода и обвязки. Родов у него по-прежнему не два десятка, а два:
|
|
71
|
+
* код и текст слоя правил. Спорить о числе на каждой правке нечему — оба числа стоят
|
|
72
|
+
* здесь и меняются работой, а не по ходу.
|
|
72
73
|
*/
|
|
73
74
|
fileSizeLimit: 500,
|
|
75
|
+
/**
|
|
76
|
+
* Предел длины текста слоя правил — закона, правила, паттерна, холодной части и их
|
|
77
|
+
* источников. Он ниже предела кода вдвое, и это не строгость ради строгости: текст, не
|
|
78
|
+
* влезающий на экран целиком, дописывают в конец, не перечитав начала, — так в одном файле
|
|
79
|
+
* и оказываются два ответа на один вопрос. У кода от этого спасает линтер, у текста —
|
|
80
|
+
* только это число.
|
|
81
|
+
*/
|
|
82
|
+
proseSizeLimit: 300,
|
|
83
|
+
/**
|
|
84
|
+
* Корни, под которыми лежит текст слоя правил, и его источники. Файл отсюда судится
|
|
85
|
+
* пределом текста, всё остальное — пределом кода. Пусто — предел один на всё дерево.
|
|
86
|
+
*/
|
|
87
|
+
proseRoots: [],
|
|
74
88
|
/**
|
|
75
89
|
* Внешние пакеты, чьи перечисления считаются наравне с либами: своё перечисление под уже
|
|
76
90
|
* объявленный там набор — такая же копия, как и между двумя либами. Каждая запись — имя
|
|
@@ -317,9 +317,17 @@ RT_TASK_TITLE_RE="${RT_TASK_TITLE_RE:-^\[[A-Za-z]+-[0-9]+\][[:space:]]+[^[:space
|
|
|
317
317
|
RT_TASK_NEW_CMD="${RT_TASK_NEW_CMD:-npm run task:new}"
|
|
318
318
|
RT_BOARD_CHECK_CMD="${RT_BOARD_CHECK_CMD:-npm run check:board}"
|
|
319
319
|
|
|
320
|
-
# Учётная запись, которую ставят
|
|
320
|
+
# Учётная запись, которую ставят исполнителем и которой открывают заявку. Умолчание молчит: у
|
|
321
|
+
# каждого дерева она своя, а у иного её нет вовсе.
|
|
321
322
|
RT_TASK_BOT="${RT_TASK_BOT:-}"
|
|
322
323
|
|
|
324
|
+
# Чем вызову подставляют токен машинной записи и как эта подстановка выглядит целиком. Клиент
|
|
325
|
+
# хостинга держит две записи сразу — залогиненную и ту, чей токен стоит в окружении вызова, — и
|
|
326
|
+
# из текста команды видно только явную подстановку. Оба умолчания молчат: дерево без отдельной
|
|
327
|
+
# машинной записи автора заявки не судит вовсе.
|
|
328
|
+
RT_PULL_TOKEN_VAR="${RT_PULL_TOKEN_VAR:-}"
|
|
329
|
+
RT_PULL_TOKEN_HINT="${RT_PULL_TOKEN_HINT:-}"
|
|
330
|
+
|
|
323
331
|
# Команда перевода задачи между колонками очереди работ и имя первой колонки — той, из которой
|
|
324
332
|
# задача уходит, когда её берут в работу. Имя без умолчания: колонки дерево называет своими
|
|
325
333
|
# словами, а выдуманное не совпало бы ни с чем и молча выключило бы проверку колонки.
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
# Карта хода
|
|
2
2
|
|
|
3
3
|
Это не правило, а его короткая выжимка: правило объясняет, карта называет. Полный текст —
|
|
4
|
-
скил `task-flow`; он же называет паттерн, который ведёт каждое состояние.
|
|
4
|
+
скил `task-flow`; он же называет паттерн, который ведёт каждое состояние. Чем ход
|
|
5
|
+
кончается — скил `turn-conduct` под тем же законом.
|
|
5
6
|
|
|
6
7
|
Состояние работы объявлено строкой в разделе «Где стоим» хода работы. Пока обязательное
|
|
7
8
|
действие не сделано, работа стоит в том же состоянии.
|
|
@@ -19,8 +20,8 @@
|
|
|
19
20
|
| `этапы-кончились` | прогнать набор и открыть PR черновиком | `task-flow-close` |
|
|
20
21
|
| `работа-отдана` | взять следующую задачу | `task-flow-resume` |
|
|
21
22
|
| `разбор-кончился` | влить договорённость, привести тексты, разобрать папку | `task-flow-close` |
|
|
22
|
-
| `папка-разобрана` | снять черновик и попросить влить | `task-flow-
|
|
23
|
-
| `влито` | разбор работы правилами и сверка очереди | `task-flow-
|
|
23
|
+
| `папка-разобрана` | снять черновик и попросить влить | `task-flow-archive` |
|
|
24
|
+
| `влито` | разбор работы правилами и сверка очереди | `task-flow-archive` |
|
|
24
25
|
|
|
25
26
|
Ни у одного состояния обязательное действие не звучит как «ждать». Прогон, разбор владельцем и
|
|
26
27
|
слияние идут без исполнителя и от взгляда быстрее не становятся.
|
|
@@ -41,6 +42,7 @@
|
|
|
41
42
|
остановки там — страховка на случай, когда сжатие не пришло.
|
|
42
43
|
|
|
43
44
|
Всё остальное — продолжение хода. Ходом не кончаются: коммит, записанный замысел, закрытый
|
|
44
|
-
разбор просьбы, прочитанная договорённость, зелёная проверка, сводка о чужом
|
|
45
|
-
|
|
46
|
-
следующее делается тем же ходом. Названо может быть только
|
|
45
|
+
разбор просьбы, прочитанная договорённость, зелёная проверка, сводка о чужом шаге, объявление
|
|
46
|
+
намерения и названная, но не запущенная команда. Переход из состояния в состояние — тем более:
|
|
47
|
+
обязательное действие сделано, и следующее делается тем же ходом. Названо может быть только
|
|
48
|
+
сделанное.
|
|
@@ -12,8 +12,10 @@
|
|
|
12
12
|
# ОТКАЗ В ПОЛЬЗУ РАБОТЫ: помощник не назвал профиль — пропуск.
|
|
13
13
|
|
|
14
14
|
. "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/utf8.sh" 2>/dev/null || true
|
|
15
|
+
. "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/hook-input.sh" 2>/dev/null || true
|
|
15
16
|
|
|
16
|
-
|
|
17
|
+
rt_hook_read
|
|
18
|
+
input="$RT_HOOK_INPUT"
|
|
17
19
|
|
|
18
20
|
device_id="$("${CLAUDE_PROJECT_DIR:-.}/.claude/hooks/browser-device-id.sh" 2>/dev/null)"
|
|
19
21
|
[ -z "$device_id" ] && exit 0
|
|
@@ -15,8 +15,10 @@
|
|
|
15
15
|
# FAIL-OPEN when browser-device-id.sh prints nothing.
|
|
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
23
|
device_id="$("${CLAUDE_PROJECT_DIR:-.}/.claude/hooks/browser-device-id.sh" 2>/dev/null)"
|
|
22
24
|
[ -z "$device_id" ] && exit 0
|
|
@@ -14,13 +14,15 @@
|
|
|
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
|
|
|
20
22
|
device_id="$("${CLAUDE_PROJECT_DIR:-.}/.claude/hooks/browser-device-id.sh" 2>/dev/null)"
|
|
21
23
|
[ -z "$device_id" ] && exit 0
|
|
22
24
|
|
|
23
|
-
tool="$(
|
|
25
|
+
tool="$(rt_hook_tool)"
|
|
24
26
|
|
|
25
27
|
# shellcheck disable=SC1090
|
|
26
28
|
[ -f "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/deny-tail.sh" ] \
|
|
@@ -45,7 +47,7 @@ case "$tool" in
|
|
|
45
47
|
*) exit 0 ;;
|
|
46
48
|
esac
|
|
47
49
|
|
|
48
|
-
cmd="$(
|
|
50
|
+
cmd="$(rt_hook_cmd)"
|
|
49
51
|
[ -z "$cmd" ] && exit 0
|
|
50
52
|
|
|
51
53
|
if [ "$tool" = "mcp__webstorm__execute_tool" ] && command -v perl >/dev/null 2>&1; then
|
|
@@ -21,13 +21,15 @@
|
|
|
21
21
|
# ОТКАЗ В ПОЛЬЗУ РАБОТЫ: помощник не назвал профиль — пропуск.
|
|
22
22
|
|
|
23
23
|
. "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/utf8.sh" 2>/dev/null || true
|
|
24
|
+
. "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/hook-input.sh" 2>/dev/null || true
|
|
24
25
|
|
|
25
|
-
|
|
26
|
+
rt_hook_read
|
|
27
|
+
input="$RT_HOOK_INPUT"
|
|
26
28
|
|
|
27
29
|
device_id="$("${CLAUDE_PROJECT_DIR:-.}/.claude/hooks/browser-device-id.sh" 2>/dev/null)"
|
|
28
30
|
[ -z "$device_id" ] && exit 0
|
|
29
31
|
|
|
30
|
-
tool="$(
|
|
32
|
+
tool="$(rt_hook_tool)"
|
|
31
33
|
# У перечисления, переключения и самого выбора свои гарды.
|
|
32
34
|
case "$tool" in
|
|
33
35
|
*list_connected_browsers|*switch_browser|*select_browser) exit 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
|
# Универсальный исполнитель среды передаёт настоящую команду вложенной строкой. Разбирать надо
|
|
@@ -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
|
;;
|
|
@@ -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:-}"
|
|
@@ -315,6 +324,16 @@ if printf '%s' "$cmd" | grep -qE '(^|[;&|(]|&&|\|\|)[[:space:]]*(gh[[:space:]]+p
|
|
|
315
324
|
# и «ещё не посчитано» — не «конфликтует».
|
|
316
325
|
printf '%s' "$pull" | jq -e '.conflicting' >/dev/null 2>&1 \
|
|
317
326
|
&& fault "заявка${pull_name} конфликтует с главной веткой. Влей её в свою ветку, разбери конфликт и повтори: снятый черновик читается как «можно вливать», а слить эту заявку нельзя."
|
|
327
|
+
|
|
328
|
+
# Автор заявки. На открытии судить было нечем, кроме текста команды: личность вызова
|
|
329
|
+
# приходит окружением. Здесь она уже названа хостингом, и это последний ход, где
|
|
330
|
+
# промах ещё исправим — после снятия черновика заявку вливают, а переоткрыть влитую
|
|
331
|
+
# нельзя. Дерево, не назвавшее машинной записи, автора не судит.
|
|
332
|
+
if [ -n "$task_bot" ]; then
|
|
333
|
+
pull_author="$(printf '%s' "$pull" | jq -r '.author // empty' 2>/dev/null)"
|
|
334
|
+
[ -n "$pull_author" ] && [ "$pull_author" != "$task_bot" ] \
|
|
335
|
+
&& fault "заявку${pull_name} открыла запись «${pull_author}», а не машинная «${task_bot}». Автор заявки её ревьювером не бывает, и разбор ей назначить нечем. Автора не сменить — закрой заявку и открой заново${pull_token_hint:+, подставив токен: ${pull_token_hint} …}."
|
|
336
|
+
fi
|
|
318
337
|
fi
|
|
319
338
|
fi
|
|
320
339
|
deny_faults
|
|
@@ -440,6 +459,19 @@ if [ -n "$remote_main" ] && [ -n "$local_main" ] && [ "$remote_main" != "$local_
|
|
|
440
459
|
fault "твоя ссылка origin/${main_branch} отстала от удалённой — ${local_main:0:8} против ${remote_main:0:8}.${age} Гард сравнивает ветку с тем, что лежит в дереве, поэтому молчание первого яруса значит «ссылка не старше ветки», а не «главная ветка влита». Влей и повтори: git fetch origin && git merge origin/${main_branch}."
|
|
441
460
|
fi
|
|
442
461
|
|
|
462
|
+
# Личность вызова. Клиент хостинга держит две записи сразу — залогиненную и ту, чей токен стоит
|
|
463
|
+
# в окружении вызова, — и какая из них откроет заявку, из текста команды не видно ничем, кроме
|
|
464
|
+
# явной подстановки. Читающие вызовы идут от залогиненной и работают, поэтому пишущий выглядит
|
|
465
|
+
# таким же обычным; промах всплывает шагом позже, на назначении ревьювера: автор заявки её
|
|
466
|
+
# ревьювером не бывает. Чинится это только переоткрытием — автора у заявки не сменить.
|
|
467
|
+
#
|
|
468
|
+
# Судится текст команды, а не ответ хостинга: спросить, чьим токеном пойдёт вызов, до его
|
|
469
|
+
# исполнения нельзя. Дерево, не назвавшее переменной токена, требования не получает.
|
|
470
|
+
if [ -n "$pull_token_var" ] \
|
|
471
|
+
&& ! printf '%s' "$cmd" | grep -qE "(^|[;&|(]|&&|\|\||[[:space:]])${pull_token_var}="; then
|
|
472
|
+
fault "заявка открывается без токена машинной записи: в команде нет подстановки «${pull_token_var}». Открытая залогиненной записью, она выйдет от владельца — ревьювером его тогда не назначить, и чинится это только переоткрытием.${pull_token_hint:+ Подставь токен: ${pull_token_hint} …}"
|
|
473
|
+
fi
|
|
474
|
+
|
|
443
475
|
check_task "$number" "заявка с ветки «${branch}»" да
|
|
444
476
|
|
|
445
477
|
# Всё несошедшееся названо здесь, разом: до этой строки собирались условия, каждое из которых
|
|
@@ -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
|
|
|
@@ -26,18 +26,20 @@
|
|
|
26
26
|
# ОТКАЗ В ПОЛЬЗУ РАБОТЫ: не репозиторий, битый ввод, нет профиля — пропуск.
|
|
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
|
|
|
33
|
-
tool="$(
|
|
35
|
+
tool="$(rt_hook_tool)"
|
|
34
36
|
case "$tool" in
|
|
35
37
|
# Терминал среды и универсальный исполнитель кладут команду в то же поле.
|
|
36
38
|
Bash | mcp__webstorm__execute_terminal_command | mcp__webstorm__execute_tool) ;;
|
|
37
39
|
*) exit 0 ;;
|
|
38
40
|
esac
|
|
39
41
|
|
|
40
|
-
cmd="$(
|
|
42
|
+
cmd="$(rt_hook_cmd)"
|
|
41
43
|
|
|
42
44
|
# Вызов пуша узнаётся по двум признакам сразу — команда `git` в начале строки или за
|
|
43
45
|
# разделителем и слово `push` отдельным словом. Тем же приёмом, что у гарда поставки: одной
|
|
@@ -88,7 +90,7 @@ ${deny_tail_text}"
|
|
|
88
90
|
exit 0
|
|
89
91
|
fi
|
|
90
92
|
|
|
91
|
-
workdir="$(
|
|
93
|
+
workdir="$(rt_hook_cwd)"
|
|
92
94
|
[ -z "$workdir" ] && workdir="${CLAUDE_PROJECT_DIR:-.}"
|
|
93
95
|
cd "$workdir" 2>/dev/null || exit 0
|
|
94
96
|
git rev-parse --is-inside-work-tree >/dev/null 2>&1 || exit 0
|
|
@@ -23,14 +23,16 @@
|
|
|
23
23
|
# РАЗРЕШАЕТСЯ (exit 0). Сломанный гард не имеет права заклинить разговор.
|
|
24
24
|
|
|
25
25
|
. "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/utf8.sh" 2>/dev/null || true
|
|
26
|
+
. "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/hook-input.sh" 2>/dev/null || true
|
|
26
27
|
|
|
27
|
-
|
|
28
|
+
rt_hook_read
|
|
29
|
+
input="$RT_HOOK_INPUT"
|
|
28
30
|
[ -z "$input" ] && exit 0
|
|
29
31
|
|
|
30
32
|
command -v jq >/dev/null 2>&1 || exit 0
|
|
31
33
|
|
|
32
34
|
# Какое событие пришло. У вызова инструмента есть его имя, у завершения хода — нет.
|
|
33
|
-
tool="$(
|
|
35
|
+
tool="$(rt_hook_tool)"
|
|
34
36
|
|
|
35
37
|
# Повторный заход по тому же ходу не судится: иначе ход не кончится никогда — гард сказал своё
|
|
36
38
|
# один раз и отпускает. К вызову инструмента это не относится: там судится сам вызов.
|
|
@@ -15,12 +15,14 @@
|
|
|
15
15
|
# FAIL-OPEN: нет `jq`, нет записи хода, передачи в реплике нет → пропуск.
|
|
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
|
[ -z "$input" ] && exit 0
|
|
21
23
|
command -v jq >/dev/null 2>&1 || exit 0
|
|
22
24
|
|
|
23
|
-
tool="$(
|
|
25
|
+
tool="$(rt_hook_tool)"
|
|
24
26
|
case "$tool" in
|
|
25
27
|
Edit | Write | MultiEdit) ;;
|
|
26
28
|
*) exit 0 ;;
|