@rt-tools/agent-kit 0.5.1 → 0.5.2
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 +68 -0
- package/assets/commands/agent-kit-digest.md +83 -0
- package/assets/commands/skill-curator.md +33 -1
- package/assets/defaults/gate-map.sh +2 -0
- package/assets/defaults/project.sh +12 -1
- package/assets/docs/GLOSSARY.md +3 -0
- package/assets/hooks/docs-guard.sh +18 -1
- package/assets/hooks/git-guard-delivery.sh +30 -3
- package/assets/hooks/git-guard-main.sh +8 -0
- package/assets/hooks/git-guard-push-tests.sh +8 -1
- package/assets/hooks/grill-gate.sh +38 -16
- package/assets/hooks/lint-after-edit.sh +10 -3
- package/assets/hooks/observe.sh +90 -0
- package/assets/hooks/postmortem-guard.sh +92 -0
- package/assets/hooks/profile-check.sh +43 -0
- package/assets/hooks/qa-dataid-guard.sh +9 -2
- package/assets/hooks/reuse-first-guard.sh +9 -2
- package/assets/hooks/skill-gate.sh +15 -0
- package/assets/hooks/skill-loaded.sh +7 -0
- package/assets/hooks/task-context-load.sh +16 -2
- package/assets/hooks/task-flow-guard.sh +9 -2
- package/assets/hooks/window-fill-guard.sh +8 -1
- package/assets/laws/project-documentation.md +5 -0
- package/assets/laws/verifiability.md +9 -0
- package/assets/laws/work-conduct.md +39 -0
- package/assets/patterns/git-workflow-commit.azure.md +16 -12
- package/assets/patterns/git-workflow-commit.github.md +16 -12
- package/assets/patterns/git-workflow-commit.gitlab.md +16 -12
- package/assets/patterns/task-flow-resume.md +12 -0
- package/assets/patterns/task-flow-start.md +36 -2
- package/assets/rules/git-workflow.azure.md +15 -0
- package/assets/rules/git-workflow.github.md +15 -0
- package/assets/rules/git-workflow.gitlab.md +15 -0
- package/assets/rules/spec-driven.md +6 -0
- package/assets/rules/task-flow.md +24 -3
- package/assets/skills/agent-kit.md +32 -0
- package/assets/templates/postmortem.md +32 -0
- package/assets/templates/proposal.md +39 -0
- package/bin/agent-kit.d.ts.map +1 -1
- package/bin/agent-kit.js +50 -1
- package/bin/agent-kit.js.map +1 -1
- package/lib/catalog.d.ts +33 -0
- package/lib/catalog.d.ts.map +1 -1
- package/lib/catalog.js +55 -1
- package/lib/catalog.js.map +1 -1
- package/lib/commands.d.ts +41 -0
- package/lib/commands.d.ts.map +1 -1
- package/lib/commands.js +314 -3
- 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 +4 -0
- package/lib/config.js.map +1 -1
- package/lib/observations.d.ts +72 -0
- package/lib/observations.d.ts.map +1 -0
- package/lib/observations.js +126 -0
- package/lib/observations.js.map +1 -0
- package/lib/proposals.d.ts +48 -0
- package/lib/proposals.d.ts.map +1 -0
- package/lib/proposals.js +111 -0
- package/lib/proposals.js.map +1 -0
- package/lib/submit.d.ts +24 -0
- package/lib/submit.d.ts.map +1 -0
- package/lib/submit.js +26 -0
- package/lib/submit.js.map +1 -0
- package/lib/sync.d.ts +9 -1
- package/lib/sync.d.ts.map +1 -1
- package/lib/sync.js +2 -1
- package/lib/sync.js.map +1 -1
- package/package.json +1 -1
- package/rt-tools-agent-kit-0.5.2.tgz +0 -0
- package/rt-tools-agent-kit-0.5.1.tgz +0 -0
package/README.md
CHANGED
|
@@ -26,6 +26,7 @@ npx agent-kit init # спросить законы галочками и з
|
|
|
26
26
|
npx agent-kit sync # разложить выбранное в docs/constitution/
|
|
27
27
|
npx agent-kit doctor # что разложено, что отстало, чего не хватает
|
|
28
28
|
npx agent-kit adopt # отдать пакету файлы, лежащие на его путях не от него
|
|
29
|
+
npx agent-kit stats # чем пользовались, чем ни разу, обо что спотыкались
|
|
29
30
|
```
|
|
30
31
|
|
|
31
32
|
`init` называет и то, чего пакет ждёт от дерева: значения дырок, которые придётся вписать в
|
|
@@ -238,6 +239,73 @@ npx agent-kit init --laws access,delivery,verifiability
|
|
|
238
239
|
|
|
239
240
|
Отказ хотя бы по одному файлу не пишет ничего: половина разложенного хуже целого.
|
|
240
241
|
|
|
242
|
+
## Наблюдения и сводка
|
|
243
|
+
|
|
244
|
+
Правило, которого никто не открывает, не действует, — и узнать об этом было нечем. Гарды пишут
|
|
245
|
+
наблюдения: правило загружено, гейт отбил правку, гард отказал. Строка несёт имя ресурса пакета,
|
|
246
|
+
род события, род правки, версию и признак сессии; путей дерева, имён его доменов и имени его
|
|
247
|
+
самого в ней нет — значение со слэшем не пишется вовсе.
|
|
248
|
+
|
|
249
|
+
```bash
|
|
250
|
+
npx agent-kit stats --days 3 # за отрезок; без довода — за три дня
|
|
251
|
+
npx agent-kit stats --json # то же машиночитаемо
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
Самая ценная строка сводки — не «чем пользовались», а **что разложено и не загружено ни разу**:
|
|
255
|
+
чем пользуются, видно и по работе, а мёртвый ресурс ничем себя не выдаёт.
|
|
256
|
+
|
|
257
|
+
Наблюдения лежат в `.claude/rt-kit/observations/` файлом на день и снимаются через тридцать
|
|
258
|
+
дней. Запись выключается ключом `"observe": false` в конфиге — целиком, а не частями. Каталог
|
|
259
|
+
просится в список игнорируемого; вписывает его проект — в чужие файлы дерева пакет не пишет.
|
|
260
|
+
|
|
261
|
+
## Предложения обратно в пакет
|
|
262
|
+
|
|
263
|
+
Разбор закрытой задачи и раньше ставил каждому предложению адрес — «пакет», «компаньон» или
|
|
264
|
+
«дерево», — но результат оставался в переписке, и правки переносили руками. Теперь предложения
|
|
265
|
+
ложатся файлом в `.claude/rt-kit/proposals/`, блоком на предложение:
|
|
266
|
+
|
|
267
|
+
```markdown
|
|
268
|
+
## пакет · rules/styling-bem.md
|
|
269
|
+
|
|
270
|
+
- **место:** раздел «Ловушки», в конец
|
|
271
|
+
- **повод:** что в этой задаче пошло не так без этого правила
|
|
272
|
+
|
|
273
|
+
> Готовый текст правки.
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
```bash
|
|
277
|
+
npx agent-kit propose --dry-run # что уехало бы
|
|
278
|
+
npx agent-kit propose # завести записи в очереди работ пакета
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
Наружу уезжает **только адрес «пакет»** и вместе с ним — сводка наблюдений: без цифр
|
|
282
|
+
предложение читается как мнение. Отправленное помечается ссылкой в том же файле и второй раз не
|
|
283
|
+
уезжает. Куда отправлять, пакет берёт из своего манифеста, а не из зашитого в код адреса.
|
|
284
|
+
|
|
285
|
+
Перед отправкой текст сверяется на адрес дерева — абсолютный путь, имя корня, адрес удалённого
|
|
286
|
+
репозитория. Нашлось — отказ с номером строки, и отбивается вся отправка целиком: «уехало два из
|
|
287
|
+
трёх» читается как «всё в порядке». Файл уезжает в чужой репозиторий, и запрет называть чужое
|
|
288
|
+
дерево держится проверкой, а не памятью того, кто пишет.
|
|
289
|
+
|
|
290
|
+
### Что для этого нужно
|
|
291
|
+
|
|
292
|
+
Своего сервера у механизма нет, ключей и учётных записей он не заводит. Наблюдения и сводка
|
|
293
|
+
живут целиком на машине; в сеть ходит только отправка, и только по команде человека.
|
|
294
|
+
|
|
295
|
+
| Где | Что нужно |
|
|
296
|
+
| --- | --- |
|
|
297
|
+
| у потребителя | `jq` — его требуют и сами гарды |
|
|
298
|
+
| у потребителя | помощник хостинга, вошедший в любую учётную запись: запись заводится от её имени |
|
|
299
|
+
| в репозитории пакета | метка, по которой сведение находит предложения, — заводится один раз |
|
|
300
|
+
|
|
301
|
+
```bash
|
|
302
|
+
gh label create agent-kit-feedback --description 'Предложение по слою правил, пришедшее из дерева'
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
Метки нет — отправка отказывает, и понять причину по сообщению помощника нельзя: про метку,
|
|
306
|
+
доступ и собственное отсутствие он говорит одинаково глухо. Поэтому отказ команды называет все
|
|
307
|
+
три сам.
|
|
308
|
+
|
|
241
309
|
## Роли и конвейеры
|
|
242
310
|
|
|
243
311
|
Пакет везёт шесть ролей субагентов и два конвейера из них.
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Свести накопленные предложения и наблюдения в правки ресурсов пакета правил
|
|
3
|
+
argument-hint: '[пусто | --days N]'
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Собери предложения, пришедшие из деревьев, и скажи, что из них становится правкой пакета.
|
|
7
|
+
Отрезок от пользователя: `$ARGUMENTS` (без довода — три дня).
|
|
8
|
+
|
|
9
|
+
Зовётся **в репозитории самого пакета**, а не в дереве, где он стоит: здесь лежат ресурсы,
|
|
10
|
+
которые предстоит править, и видно всех потребителей сразу. В чужом дереве команда бессмысленна
|
|
11
|
+
— там есть только своя половина картины.
|
|
12
|
+
|
|
13
|
+
## 1. Собери, что пришло
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
gh issue list --label agent-kit-feedback --state open --limit 100 \
|
|
17
|
+
--json number,title,body,createdAt
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Помощник хостинга и учётная запись машинной работы у каждого дерева свои — как их звать здесь,
|
|
21
|
+
сказано в компаньоне правила `git-workflow`.
|
|
22
|
+
|
|
23
|
+
Каждая запись заведена командой `agent-kit propose` из дерева, где пакет стоит. В теле — ресурс,
|
|
24
|
+
место, повод, готовый текст и сводка наблюдений того дерева. Имени дерева там нет намеренно:
|
|
25
|
+
различать их можно только по сводке и по времени.
|
|
26
|
+
|
|
27
|
+
Своё дерево тоже потребитель — его наблюдения читаются прямо:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
npx agent-kit stats --days <отрезок> --json
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## 2. Раздели повторившееся и разовое
|
|
34
|
+
|
|
35
|
+
Это и есть работа сведения; всё остальное — оформление.
|
|
36
|
+
|
|
37
|
+
- **Пришло об одном ресурсе из двух и более записей** — правка пакета. Разные деревья
|
|
38
|
+
споткнулись об одно и то же место, и чинить его надо там, откуда оно к ним приехало.
|
|
39
|
+
- **Пришло однажды и называет то, чего у других нет** — надстройка того дерева, а не пакет.
|
|
40
|
+
Скажи это прямо и назови, куда именно: `overrides/<ресурс>` для текстов, карта гейта и
|
|
41
|
+
профиль — для родов файлов и команд.
|
|
42
|
+
- **Пришло однажды, но верно любому дереву** — правка пакета. Числа записей мало: правило,
|
|
43
|
+
которое молчит о важном, молчит у всех, а споткнулся о него пока один.
|
|
44
|
+
|
|
45
|
+
Ресурс, о котором пришли **противоречащие** предложения, в правку не идёт вовсе: неси оба
|
|
46
|
+
владельцу. Слой правил, который говорит два разных, хуже слоя, который молчит.
|
|
47
|
+
|
|
48
|
+
## 3. Посмотри, что говорят наблюдения
|
|
49
|
+
|
|
50
|
+
Сводка отвечает на то, чего в предложениях нет:
|
|
51
|
+
|
|
52
|
+
- **правило, разложенное и не загруженное ни разу** — кандидат на сокращение или на запись в
|
|
53
|
+
карте гейта: правило, которого гейт не требует, никто и не откроет;
|
|
54
|
+
- **правило, которое гейт отбивает чаще прочих** — его либо забывают, либо оно требуется не
|
|
55
|
+
там; посмотри род правки в той же сводке;
|
|
56
|
+
- **гард, отказывающий чаще прочих** — либо место поставки раз за разом делают не так, либо
|
|
57
|
+
отказ не называет, чем он снимается.
|
|
58
|
+
|
|
59
|
+
## 4. Принеси правки
|
|
60
|
+
|
|
61
|
+
По каждой — ресурс, место, готовый текст и число записей, из которых она вышла. Не правь ничего
|
|
62
|
+
сам: решение принимает владелец, а работа идёт обычным ходом — задача, ветка, папка задачи.
|
|
63
|
+
|
|
64
|
+
Заведи задачу на то, что владелец принял:
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
npm run task:new -- --title '<что не так>' --slug <короткое-имя> --label enhancement < тело.md
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Записи, вошедшие в задачу, закрой ссылкой на неё:
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
gh issue close <номер> --comment 'Вошло в #<номер задачи>.'
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## 5. Черновик записи в журнал изменений
|
|
77
|
+
|
|
78
|
+
Собери его сразу, пока видно, из чего правка выросла: заголовок коммита по формату дерева и
|
|
79
|
+
одна фраза о том, что менялось и почему. Журнал изменений при выпуске собирается из заголовков,
|
|
80
|
+
и переписывать их задним числом — работа заново.
|
|
81
|
+
|
|
82
|
+
**Выпуск отсюда не запускается.** Это отдельное решение владельца: слияние отчёта пакета не
|
|
83
|
+
публикует. Скажи, что накопилось на выпуск, и остановись.
|
|
@@ -47,7 +47,39 @@ ls -t "${TMPDIR}claude-skill-gate/"*.loaded | head -5
|
|
|
47
47
|
Инструментом `Agent`, `subagent_type: 'skill-curator'`. В промпт — путь к `.loaded` и сводку
|
|
48
48
|
целиком.
|
|
49
49
|
|
|
50
|
-
## 4.
|
|
50
|
+
## 4. Выгрузи предложения файлом
|
|
51
|
+
|
|
52
|
+
Ответ роли живёт в переписке и умирает вместе с ней, а правки в пакет идут из другого дерева и
|
|
53
|
+
в другой день. Поэтому предложения ложатся на диск — их пишешь ты, не роль: файлов она не
|
|
54
|
+
пишет вовсе.
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
mkdir -p .claude/rt-kit/proposals
|
|
58
|
+
cp .claude/rt-kit/templates/proposal.md .claude/rt-kit/proposals/$(date +%F)-<ветка>.md
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Дальше — по блоку на предложение, заголовком `## <адрес> · <ресурс>`. Адрес роль уже поставила,
|
|
62
|
+
твоё дело — не потерять его и не переписать текст своими словами.
|
|
63
|
+
|
|
64
|
+
Файл читает `agent-kit propose`: по заголовку он отбирает то, что уезжает в репозиторий пакета.
|
|
65
|
+
Блок без адреса в заголовке не уедет никуда и останется лежать молча.
|
|
66
|
+
|
|
67
|
+
## 5. Отправь то, что адресовано пакету
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
npx agent-kit propose --dry-run # что уехало бы
|
|
71
|
+
npx agent-kit propose # завести запись в очереди работ пакета
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Уезжают только блоки с адресом «пакет», и вместе с ними — сводка наблюдений: без цифр
|
|
75
|
+
предложение читается как мнение. Отправленное помечается ссылкой в том же файле и второй раз
|
|
76
|
+
не уезжает.
|
|
77
|
+
|
|
78
|
+
Отправка отказывает, если в тексте предложения нашёлся адрес этого дерева — путь, имя корня,
|
|
79
|
+
чужой репозиторий. Это не придирка: файл уезжает в чужой репозиторий целиком. Правь текст, а не
|
|
80
|
+
обходи проверку.
|
|
81
|
+
|
|
82
|
+
## 6. Отдай результат владельцу
|
|
51
83
|
|
|
52
84
|
Покажи предложения агента **как есть**: он пишет готовый текст для вставки, и пересказ его
|
|
53
85
|
портит. По каждому скажи своё — согласен или нет и почему; правило, с которым ты не согласен,
|
|
@@ -111,6 +111,8 @@ skill_for_default() {
|
|
|
111
111
|
case "$target" in
|
|
112
112
|
*git\ commit* | *git\ push* | *git\ merge* | *git\ rebase* | *git\ cherry-pick* | *gh\ pr\ * | *glab\ mr\ * | *az\ repos\ *)
|
|
113
113
|
printf '%s\n' 'git-workflow' ;;
|
|
114
|
+
*git\ worktree\ add* | *git\ worktree\ remove*)
|
|
115
|
+
printf '%s\n' 'git-workflow' ;;
|
|
114
116
|
*prisma\ migrate* | *prisma\ db\ *) printf '%s\n' 'git-workflow' ;;
|
|
115
117
|
*curl\ *localhost* | *wget\ *localhost*) printf '%s\n' 'browser-verification' ;;
|
|
116
118
|
esac
|
|
@@ -125,9 +125,20 @@ rt_report_body_default() {
|
|
|
125
125
|
# Признак — путь, а не оценка на глаз: оценку назначает тот, кому она мешает, и порог плывёт.
|
|
126
126
|
# Правила, тексты, обвязка и зависимости под требование не попадают — иначе разбор задачи
|
|
127
127
|
# нельзя было бы вести до заведения ветки.
|
|
128
|
+
#
|
|
129
|
+
# Судится путь относительно корня дерева: каталог со словом `projects` в имени встречается и
|
|
130
|
+
# вне репозитория, а правка файла вне корня замыслом этой ветки не распоряжается вовсе. Гарды
|
|
131
|
+
# отдают сюда абсолютный путь целиком, и образец по подстроке совпадал бы с домашним каталогом
|
|
132
|
+
# агента ровно так же, как с кодом дерева.
|
|
128
133
|
rt_is_app_code_default() {
|
|
134
|
+
root="${CLAUDE_PROJECT_DIR:-$PWD}"
|
|
129
135
|
case "$1" in
|
|
130
|
-
|
|
136
|
+
"$root"/*) rel="${1#"$root"/}" ;;
|
|
137
|
+
/*) return 1 ;;
|
|
138
|
+
*) rel="$1" ;;
|
|
139
|
+
esac
|
|
140
|
+
case "$rel" in
|
|
141
|
+
apps/* | libs/* | projects/*) return 0 ;;
|
|
131
142
|
*) return 1 ;;
|
|
132
143
|
esac
|
|
133
144
|
}
|
package/assets/docs/GLOSSARY.md
CHANGED
|
@@ -30,6 +30,9 @@
|
|
|
30
30
|
| Раскладка | Перенос ресурса из пакета в дерево по его роду и настройке слоя |
|
|
31
31
|
| Разложенный файл | Файл в дереве с шапкой пакета. Правится не на месте, а надстройкой: правка на месте теряется на следующей раскладке |
|
|
32
32
|
| Надстройка | Файл дерева, который сливается с разложенным по заголовкам разделов |
|
|
33
|
+
| Наблюдение | Строка о событии слоя правил: правило загружено, гейт отбил, гард отказал. Пишет гард, живёт в дереве, наружу уезжает счётчиками. Журналом не называется |
|
|
34
|
+
| Сводка | Что наблюдения говорят за отрезок дней: чем пользовались, чем ни разу, обо что спотыкались |
|
|
35
|
+
| Предложение | Готовая формулировка правки правил с адресом: пакет, компаньон или дерево. Приносит разбор закрытой задачи, отправляет человек командой |
|
|
33
36
|
|
|
34
37
|
## Работа
|
|
35
38
|
|
|
@@ -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
|
|
3
|
+
# Требует: hooks/profile-check.sh
|
|
3
4
|
# Гард пары «правка и её документ». PreToolUse.
|
|
4
5
|
#
|
|
5
6
|
# Расхождение кода с текстом беззвучно. Ни линтер, ни сборка, ни тесты не читают правила,
|
|
@@ -32,6 +33,16 @@ command -v jq >/dev/null 2>&1 || exit 0
|
|
|
32
33
|
tool="$(printf '%s' "$input" | jq -r '.tool_name // empty' 2>/dev/null)"
|
|
33
34
|
|
|
34
35
|
decide() {
|
|
36
|
+
# Наблюдение пишется только на отказе: подсказку гард раздаёт и там, где всё в порядке, и
|
|
37
|
+
# счёт, в котором они смешаны, не значит ничего.
|
|
38
|
+
if [ "$1" = "deny" ]; then
|
|
39
|
+
# shellcheck disable=SC1090
|
|
40
|
+
[ -f "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/observe.sh" ] \
|
|
41
|
+
&& . "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/observe.sh" 2>/dev/null
|
|
42
|
+
command -v rt_note >/dev/null 2>&1 \
|
|
43
|
+
&& rt_note guard-deny res=docs-guard "sid=$(printf '%s' "$input" | jq -r '.session_id // "nosession"' 2>/dev/null)"
|
|
44
|
+
fi
|
|
45
|
+
|
|
35
46
|
jq -n --arg d "$1" --arg r "$2" \
|
|
36
47
|
'{hookSpecificOutput:{hookEventName:"PreToolUse",permissionDecision:$d,permissionDecisionReason:$r}}' 2>/dev/null \
|
|
37
48
|
|| printf '{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"deny","permissionDecisionReason":"Документ едет тем же коммитом."}}\n'
|
|
@@ -47,6 +58,12 @@ for profile in "$rt_hooks_dir/../rt-kit/defaults/project.sh" "$rt_hooks_dir/../d
|
|
|
47
58
|
[ -f "$profile" ] && . "$profile" 2>/dev/null
|
|
48
59
|
done
|
|
49
60
|
|
|
61
|
+
# Слово о нехватке функции профиля: хук, вышедший молча, неотличим от работающего. Файл может
|
|
62
|
+
# быть не разложен — тогда остаётся прежнее поведение, молчаливое.
|
|
63
|
+
# shellcheck disable=SC1090
|
|
64
|
+
[ -f "$rt_hooks_dir/profile-check.sh" ] && . "$rt_hooks_dir/profile-check.sh"
|
|
65
|
+
command -v rt_needs >/dev/null 2>&1 || rt_needs() { command -v "$1" >/dev/null 2>&1; }
|
|
66
|
+
|
|
50
67
|
laws_dir="${RT_LAWS_DIR:-docs/constitution}"
|
|
51
68
|
lib_marker="${RT_LIB_MARKER:-project.json}"
|
|
52
69
|
|
|
@@ -170,7 +187,7 @@ done
|
|
|
170
187
|
#
|
|
171
188
|
# Контракт и спек домена, гард и его сценарии — что именно, знает профиль: связь у каждого
|
|
172
189
|
# дерева своя, а механика одна.
|
|
173
|
-
if
|
|
190
|
+
if rt_needs rt_docs_pair_for docs-guard; then
|
|
174
191
|
while IFS= read -r file; do
|
|
175
192
|
[ -z "$file" ] && continue
|
|
176
193
|
want="$(rt_docs_pair_for "$file" 2>/dev/null)"
|
|
@@ -1,5 +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
|
|
3
4
|
# Гард поставки. PreToolUse на заведении ветки и открытии заявки на слияние.
|
|
4
5
|
#
|
|
5
6
|
# Закон о поставке требует трёх вещей, которых обычно не проверяет ничто: правка начинается с
|
|
@@ -37,6 +38,7 @@ input="$(cat 2>/dev/null)"
|
|
|
37
38
|
command -v jq >/dev/null 2>&1 || exit 0
|
|
38
39
|
|
|
39
40
|
tool="$(printf '%s' "$input" | jq -r '.tool_name // empty' 2>/dev/null)"
|
|
41
|
+
sid="$(printf '%s' "$input" | jq -r '.session_id // "nosession"' 2>/dev/null)"
|
|
40
42
|
case "$tool" in
|
|
41
43
|
# Терминал среды и универсальный исполнитель кладут команду в то же поле.
|
|
42
44
|
Bash | mcp__webstorm__execute_terminal_command | mcp__webstorm__execute_tool) ;;
|
|
@@ -73,7 +75,13 @@ for profile in "$rt_hooks_dir/../rt-kit/defaults/project.sh" "$rt_hooks_dir/../d
|
|
|
73
75
|
# shellcheck disable=SC1090
|
|
74
76
|
[ -f "$profile" ] && . "$profile" 2>/dev/null
|
|
75
77
|
done
|
|
76
|
-
|
|
78
|
+
|
|
79
|
+
# Слово о нехватке функции профиля: хук, вышедший молча, неотличим от работающего. Файл может
|
|
80
|
+
# быть не разложен — тогда остаётся прежнее поведение, молчаливое.
|
|
81
|
+
# shellcheck disable=SC1090
|
|
82
|
+
[ -f "$rt_hooks_dir/profile-check.sh" ] && . "$rt_hooks_dir/profile-check.sh"
|
|
83
|
+
command -v rt_needs >/dev/null 2>&1 || rt_needs() { command -v "$1" >/dev/null 2>&1; }
|
|
84
|
+
rt_needs rt_task_branch_ok git-guard-delivery || exit 0
|
|
77
85
|
|
|
78
86
|
title_re="${RT_TASK_TITLE_RE:-^\[[A-Za-z]+-[0-9]+\][[:space:]]+[^[:space:]]}"
|
|
79
87
|
task_new="${RT_TASK_NEW_CMD:-npm run task:new}"
|
|
@@ -89,6 +97,13 @@ main_branch="${RT_MAIN_BRANCH:-main}"
|
|
|
89
97
|
folder_skip_re='Task-folder-skip:[[:space:]]*[^[:space:]"'"'"']{3,}'
|
|
90
98
|
|
|
91
99
|
deny() {
|
|
100
|
+
# Отказ гарда — наблюдение: гард, отбивающий чаще прочих, говорит, какое место поставки
|
|
101
|
+
# раз за разом делают не так. Текст отказа в наблюдение не идёт: в нём стоят номера задач
|
|
102
|
+
# и имена веток этого дерева.
|
|
103
|
+
# shellcheck disable=SC1090
|
|
104
|
+
[ -f "$rt_hooks_dir/observe.sh" ] && . "$rt_hooks_dir/observe.sh" 2>/dev/null
|
|
105
|
+
command -v rt_note >/dev/null 2>&1 && rt_note guard-deny res=git-guard-delivery "sid=$sid"
|
|
106
|
+
|
|
92
107
|
jq -n --arg r "$1" '{hookSpecificOutput:{hookEventName:"PreToolUse",permissionDecision:"deny",permissionDecisionReason:$r}}' 2>/dev/null \
|
|
93
108
|
|| printf '{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"deny","permissionDecisionReason":"Гард поставки."}}\n'
|
|
94
109
|
exit 0
|
|
@@ -112,7 +127,7 @@ folder_in_branch() {
|
|
|
112
127
|
check_task() {
|
|
113
128
|
number="$1"
|
|
114
129
|
where="$2"
|
|
115
|
-
|
|
130
|
+
rt_needs rt_task_state git-guard-delivery || return 0
|
|
116
131
|
state="$(cd "$root" && rt_task_state "$number" 2>/dev/null)" || return 0
|
|
117
132
|
[ -z "$state" ] && return 0
|
|
118
133
|
|
|
@@ -173,7 +188,7 @@ if printf '%s' "$cmd" | grep -qE '(^|[;&|(]|&&|\|\|)[[:space:]]*(gh[[:space:]]+p
|
|
|
173
188
|
printf '%s' "$cmd" | grep -qiE "$folder_skip_re" && exit 0
|
|
174
189
|
|
|
175
190
|
merge_number="$(printf '%s' "$cmd" | sed -nE 's/.*(pr|mr)[[:space:]]+(merge|update)[[:space:]]+([0-9]+).*/\3/p' | head -1)"
|
|
176
|
-
if [ -n "$merge_number" ] &&
|
|
191
|
+
if [ -n "$merge_number" ] && rt_needs rt_report_body git-guard-delivery; then
|
|
177
192
|
body="$(cd "$root" && rt_report_body "$merge_number" 2>/dev/null)"
|
|
178
193
|
[ -n "$body" ] && printf '%s' "$body" | grep -qiE "$folder_skip_re" && exit 0
|
|
179
194
|
fi
|
|
@@ -236,6 +251,18 @@ if [ -n "$title" ]; then
|
|
|
236
251
|
fi
|
|
237
252
|
fi
|
|
238
253
|
|
|
254
|
+
# Главная ветка влита до открытия отчёта. Отчёт от разошедшейся ветки показывает ревьюверу свою
|
|
255
|
+
# правку вперемешку с чужой, а проверки на нём гоняются от устаревшего основания.
|
|
256
|
+
#
|
|
257
|
+
# Судится локальная вершина главной ветки, без сети: сетевой вызов в разборе команды падал бы
|
|
258
|
+
# вместе со связью и отбивал бы работу вместо промаха. Отсюда и граница — гард ловит ветку,
|
|
259
|
+
# отставшую заведомо; свежесть самой вершины держит `git fetch`, и требует его чеклист.
|
|
260
|
+
if git rev-parse --verify --quiet "refs/remotes/origin/${main_branch}" >/dev/null 2>&1 \
|
|
261
|
+
&& ! git merge-base --is-ancestor "origin/${main_branch}" HEAD 2>/dev/null; then
|
|
262
|
+
behind="$(git rev-list --count "HEAD..origin/${main_branch}" 2>/dev/null)"
|
|
263
|
+
deny "BLOCKED: «${main_branch}» ушла вперёд на ${behind:-несколько} коммитов, а в ветку не влита. Отчёт от разошедшейся ветки показывает ревьюверу правку вперемешку с чужой, а проверки на нём идут от устаревшего основания. Влей и повтори: git fetch origin && git merge origin/${main_branch} — порядок и разбор конфликта в паттерне git-workflow-merge."
|
|
264
|
+
fi
|
|
265
|
+
|
|
239
266
|
check_task "$number" "заявка с ветки «${branch}»"
|
|
240
267
|
|
|
241
268
|
# Сейчас папка ещё нужна: правки по замечаниям ревью идут в эту же ветку, а без plan.md их не
|
|
@@ -67,6 +67,14 @@ fi
|
|
|
67
67
|
|
|
68
68
|
reason="Отбито: коммит прямо в «${default}». Работа едет через ветку и PR — правило git-workflow. Заведи ветку отдельным вызовом и коммить в неё: подготовленные изменения при этом сохранятся. Если коммит в ${default} действительно нужен — спроси владельца, сам не обходи."
|
|
69
69
|
|
|
70
|
+
# Отказ — наблюдение. Имя главной ветки в него не идёт: у деревьев оно своё, а счёт отказов
|
|
71
|
+
# одинаков везде.
|
|
72
|
+
rt_hooks_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
73
|
+
# shellcheck disable=SC1090
|
|
74
|
+
[ -f "$rt_hooks_dir/observe.sh" ] && . "$rt_hooks_dir/observe.sh" 2>/dev/null
|
|
75
|
+
command -v rt_note >/dev/null 2>&1 \
|
|
76
|
+
&& rt_note guard-deny res=git-guard-main "sid=$(printf '%s' "$input" | jq -r '.session_id // "nosession"' 2>/dev/null)"
|
|
77
|
+
|
|
70
78
|
jq -n --arg r "$reason" '{hookSpecificOutput:{hookEventName:"PreToolUse",permissionDecision:"deny",permissionDecisionReason:$r}}' 2>/dev/null \
|
|
71
79
|
|| printf '{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"deny","permissionDecisionReason":"Коммит в главную ветку отбит. Заведи ветку."}}\n'
|
|
72
80
|
|
|
@@ -1,5 +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
|
|
3
4
|
# Гард проверок перед пушем. PreToolUse на вызове пуша.
|
|
4
5
|
#
|
|
5
6
|
# Пуш — это вход в конвейер: слияние в главную ветку запускает выкатку, и всё, что не
|
|
@@ -60,7 +61,13 @@ for profile in "$rt_hooks_dir/../rt-kit/defaults/project.sh" "$rt_hooks_dir/../d
|
|
|
60
61
|
# shellcheck disable=SC1090
|
|
61
62
|
[ -f "$profile" ] && . "$profile" 2>/dev/null
|
|
62
63
|
done
|
|
63
|
-
|
|
64
|
+
|
|
65
|
+
# Слово о нехватке функции профиля: хук, вышедший молча, неотличим от работающего. Файл может
|
|
66
|
+
# быть не разложен — тогда остаётся прежнее поведение, молчаливое.
|
|
67
|
+
# shellcheck disable=SC1090
|
|
68
|
+
[ -f "$rt_hooks_dir/profile-check.sh" ] && . "$rt_hooks_dir/profile-check.sh"
|
|
69
|
+
command -v rt_needs >/dev/null 2>&1 || rt_needs() { command -v "$1" >/dev/null 2>&1; }
|
|
70
|
+
rt_needs rt_push_checks git-guard-push-tests || exit 0
|
|
64
71
|
|
|
65
72
|
main_branch="${RT_MAIN_BRANCH:-main}"
|
|
66
73
|
base=''
|
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
|
+
# rt-hook: PreToolUse AskUserQuestion
|
|
2
3
|
# rt-hook: Stop
|
|
3
|
-
# Гард разговора:
|
|
4
|
-
#
|
|
4
|
+
# Гард разговора: вопрос владельцу не задаётся, пока за этот же ход не читались законы и
|
|
5
|
+
# правила. Стоит на двух событиях, и это не дублирование.
|
|
5
6
|
#
|
|
6
|
-
# Зачем именно так. Требование «правила читаются до разговора»
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
#
|
|
7
|
+
# Зачем именно так. Требование «правила читаются до разговора» исполнимо ровно до отправки
|
|
8
|
+
# вопроса. Проверка на завершении хода отбивает задним числом: к моменту отказа вопрос уже у
|
|
9
|
+
# владельца, и владелец видит его вместе с отбитым ходом — требование срабатывает, но работу не
|
|
10
|
+
# спасает. Поэтому ход с вопросом судится на инструменте вопроса, до отправки.
|
|
10
11
|
#
|
|
11
|
-
#
|
|
12
|
-
#
|
|
12
|
+
# Одним этим перехватом дыра не закрывается: вопрос чаще задаётся прозой, и ровно так был задан
|
|
13
|
+
# тот, из-за которого гард заведён. Прозаический вопрос инструментом не является, и поймать его
|
|
14
|
+
# можно только на завершении хода — событие получает путь к записи хода и видит его целиком.
|
|
15
|
+
# Отсюда два события: меню ловится до отправки, проза — после.
|
|
13
16
|
#
|
|
14
17
|
# Чтением правил считается любой из трёх путей: загрузка правила, чтение файла законов или
|
|
15
18
|
# правил, поиск по ним. Требовать именно загрузку значило бы гнать на неё там, где хватило
|
|
@@ -23,10 +26,13 @@ input="$(cat 2>/dev/null)"
|
|
|
23
26
|
|
|
24
27
|
command -v jq >/dev/null 2>&1 || exit 0
|
|
25
28
|
|
|
29
|
+
# Какое событие пришло. У вызова инструмента есть его имя, у завершения хода — нет.
|
|
30
|
+
tool="$(printf '%s' "$input" | jq -r '.tool_name // empty' 2>/dev/null)"
|
|
31
|
+
|
|
26
32
|
# Повторный заход по тому же ходу не судится: иначе ход не кончится никогда — гард сказал своё
|
|
27
|
-
# один раз и отпускает.
|
|
33
|
+
# один раз и отпускает. К вызову инструмента это не относится: там судится сам вызов.
|
|
28
34
|
active="$(printf '%s' "$input" | jq -r '.stop_hook_active // false' 2>/dev/null)"
|
|
29
|
-
[ "$active" = "true" ] && exit 0
|
|
35
|
+
[ -z "$tool" ] && [ "$active" = "true" ] && exit 0
|
|
30
36
|
|
|
31
37
|
transcript="$(printf '%s' "$input" | jq -r '.transcript_path // empty' 2>/dev/null)"
|
|
32
38
|
[ -z "$transcript" ] && exit 0
|
|
@@ -61,7 +67,10 @@ read_re="$(printf '%s' "$laws_dir|$rules_dir|$specs_dir" | sed 's/^|*//; s/|*$//
|
|
|
61
67
|
# бы.
|
|
62
68
|
#
|
|
63
69
|
# Хвост в 400 строк: запись хода растёт всю сессию, а судится только последний ход.
|
|
64
|
-
|
|
70
|
+
# На событии вызова инструмента вопрос уже известен — он и есть вызов; судится только то,
|
|
71
|
+
# читались ли за этот ход правила. На завершении хода вопрос ищется в тексте реплик: меню к
|
|
72
|
+
# этому моменту уже отбито раньше.
|
|
73
|
+
verdict="$(tail -n 400 "$transcript" 2>/dev/null | jq -s -r --arg re "$read_re" --arg tool "$tool" '
|
|
65
74
|
def is_input:
|
|
66
75
|
.type == "user"
|
|
67
76
|
and (((.message.content // []) | if type == "array"
|
|
@@ -78,19 +87,32 @@ verdict="$(tail -n 400 "$transcript" 2>/dev/null | jq -s -r --arg re "$read_re"
|
|
|
78
87
|
or ((.name == "Bash") and ((.input.command // "") | test($re)))
|
|
79
88
|
) | any) as $read
|
|
80
89
|
| (($texts | join("\n")) | test("\\?[[:space:]]*$"; "m")) as $asked_prose
|
|
81
|
-
| ($
|
|
82
|
-
| if ($asked_prose or $
|
|
90
|
+
| ($tool != "") as $asking_now
|
|
91
|
+
| if ($asked_prose or $asking_now) and ($read | not) then "ask" else "pass" end
|
|
83
92
|
' 2>/dev/null)"
|
|
84
93
|
|
|
85
94
|
[ "$verdict" = "ask" ] || exit 0
|
|
86
95
|
|
|
87
|
-
|
|
96
|
+
if [ -n "$tool" ]; then
|
|
97
|
+
head="BLOCKED by grill-gate: вопрос владельцу ещё не ушёл, и это единственный момент, когда требование исполнимо."
|
|
98
|
+
else
|
|
99
|
+
head="BLOCKED by grill-gate: в ответе есть вопрос владельцу, а законы и правила за этот ход не читались."
|
|
100
|
+
fi
|
|
101
|
+
|
|
102
|
+
reason="$head Вопрос, ответ на который уже записан, владельцу не задаётся — правило ведения работы. Прогони поиск по словам темы и ответь по найденному; спрашивай только то, что документацией не покрыто:
|
|
88
103
|
|
|
89
104
|
grep -rn -i \"<слово темы>\" $laws_dir $rules_dir $specs_dir
|
|
90
105
|
|
|
91
106
|
Гард судит один ход: следующий заход не отбивается."
|
|
92
107
|
|
|
93
|
-
|
|
94
|
-
|
|
108
|
+
# Форма отказа у двух событий разная: вызов инструмента отбивается решением о доступе, а
|
|
109
|
+
# завершение хода — решением о ходе. Одна форма на оба события молча не срабатывает.
|
|
110
|
+
if [ -n "$tool" ]; then
|
|
111
|
+
jq -n --arg r "$reason" '{hookSpecificOutput:{hookEventName:"PreToolUse",permissionDecision:"deny",permissionDecisionReason:$r}}' 2>/dev/null \
|
|
112
|
+
|| printf '{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"deny","permissionDecisionReason":"grill-gate: прочитай законы и правила по теме, прежде чем спрашивать владельца."}}\n'
|
|
113
|
+
else
|
|
114
|
+
jq -n --arg r "$reason" '{decision:"block",reason:$r}' 2>/dev/null \
|
|
115
|
+
|| printf '{"decision":"block","reason":"grill-gate: прочитай законы и правила по теме, прежде чем спрашивать владельца."}\n'
|
|
116
|
+
fi
|
|
95
117
|
|
|
96
118
|
exit 0
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
# rt-hook: PostToolUse Edit|Write|MultiEdit|Bash|mcp__webstorm__create_new_file
|
|
3
|
+
# Требует: hooks/profile-check.sh
|
|
3
4
|
# Линтер по следам правки. PostToolUse.
|
|
4
5
|
#
|
|
5
6
|
# Два входа. Правка файла — линтуется один изменённый файл. Перенос файла командой — линтуются
|
|
@@ -62,7 +63,13 @@ for profile in "$rt_hooks_dir/../rt-kit/defaults/project.sh" "$rt_hooks_dir/../d
|
|
|
62
63
|
# shellcheck disable=SC1090
|
|
63
64
|
[ -f "$profile" ] && . "$profile" 2>/dev/null
|
|
64
65
|
done
|
|
65
|
-
|
|
66
|
+
|
|
67
|
+
# Слово о нехватке функции профиля: хук, вышедший молча, неотличим от работающего. Файл может
|
|
68
|
+
# быть не разложен — тогда остаётся прежнее поведение, молчаливое.
|
|
69
|
+
# shellcheck disable=SC1090
|
|
70
|
+
[ -f "$rt_hooks_dir/profile-check.sh" ] && . "$rt_hooks_dir/profile-check.sh"
|
|
71
|
+
command -v rt_needs >/dev/null 2>&1 || rt_needs() { command -v "$1" >/dev/null 2>&1; }
|
|
72
|
+
rt_needs rt_lint_for lint-after-edit || exit 0
|
|
66
73
|
|
|
67
74
|
# --- какие файлы проверяем -------------------------------------------------------------
|
|
68
75
|
|
|
@@ -133,7 +140,7 @@ fi
|
|
|
133
140
|
# --- отсев того, что линтерами дерева не покрыто ----------------------------------------
|
|
134
141
|
|
|
135
142
|
lintable() {
|
|
136
|
-
if
|
|
143
|
+
if rt_needs rt_is_app_code lint-after-edit; then
|
|
137
144
|
rt_is_app_code "$1" || return 1
|
|
138
145
|
fi
|
|
139
146
|
if [ -n "${RT_LINT_SKIP_RE:-}" ] && printf '%s' "$1" | grep -qE "$RT_LINT_SKIP_RE"; then
|
|
@@ -156,7 +163,7 @@ linter_name() {
|
|
|
156
163
|
|
|
157
164
|
# --- прогон -----------------------------------------------------------------------------
|
|
158
165
|
|
|
159
|
-
push_checks="$(
|
|
166
|
+
push_checks="$(rt_needs rt_push_checks lint-after-edit && rt_push_checks 2>/dev/null)"
|
|
160
167
|
|
|
161
168
|
report=""
|
|
162
169
|
linters=""
|