@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
|
@@ -50,6 +50,27 @@ RT_HOOKS_TESTS="${RT_HOOKS_TESTS:-.claude/hooks/tests/run.sh}"
|
|
|
50
50
|
# её делают, а всплывает на чужой работе — раскладка отказывает по правленому файлу целиком и не
|
|
51
51
|
# кладёт ни одного другого. Признак — сама настройка раскладки: дерево без неё пакета не ставит,
|
|
52
52
|
# и звать в нём нечего.
|
|
53
|
+
# Тронула ли ветка только тексты. Нулевой код — только тексты, иначе — код.
|
|
54
|
+
#
|
|
55
|
+
# Признак нужен тому дереву, которое дописало в набор гейта тяжёлый шаг: стенд, снимки витрин,
|
|
56
|
+
# сборку образов. Без него такой шаг идёт подряд, чем бы ветка ни была, и пуш коммита, менявшего
|
|
57
|
+
# одну строку в таблице markdown, занимает минуты — за которые владелец успевает прочитать его
|
|
58
|
+
# как зависший и отбить. Правило поставки требует того же от конвейера: ветка, не тронувшая ни
|
|
59
|
+
# строки кода, не поднимает стенда, не снимает кадров и не собирает образов.
|
|
60
|
+
#
|
|
61
|
+
# Текстовой считается ветка, у которой каждый задетый файл — либо `.md`, либо лежит под каталогом
|
|
62
|
+
# текстов. Всё остальное, включая обвязку и настройки, считается кодом: признак обязан ошибаться
|
|
63
|
+
# в сторону лишнего прогона, а не пропущенного. Пустая база означает, что сравнивать не с чем, —
|
|
64
|
+
# тогда признак молчит и гоняется всё.
|
|
65
|
+
rt_push_docs_only_default() {
|
|
66
|
+
[ -z "$1" ] && return 1
|
|
67
|
+
changed="$(git diff --name-only "$1"...HEAD 2>/dev/null)"
|
|
68
|
+
[ -z "$changed" ] && return 1
|
|
69
|
+
printf '%s\n' "$changed" | grep -qvE "(^${RT_DOCS_DIR:-docs}/|\.md\$)" && return 1
|
|
70
|
+
|
|
71
|
+
return 0
|
|
72
|
+
}
|
|
73
|
+
|
|
53
74
|
rt_push_checks_default() {
|
|
54
75
|
runner="$(rt_runner)"
|
|
55
76
|
root="${CLAUDE_PROJECT_DIR:-.}"
|
|
@@ -66,7 +87,8 @@ rt_push_checks_default() {
|
|
|
66
87
|
[ -x "$root/$RT_HOOKS_TESTS" ] && printf '%s\n' "bash $RT_HOOKS_TESTS"
|
|
67
88
|
|
|
68
89
|
for check in check-doc-paths check-specs check-file-size check-dupes check-styles \
|
|
69
|
-
check-lib-layers check-reuse check-schema-drift check-
|
|
90
|
+
check-lib-layers check-reuse check-schema-drift check-states check-state-next \
|
|
91
|
+
check-turn-map check-push-gate; do
|
|
70
92
|
[ -f "$root/$RT_CHECKS_DIR/$check.mjs" ] && printf '%s\n' "node $RT_CHECKS_DIR/$check.mjs"
|
|
71
93
|
done
|
|
72
94
|
|
|
@@ -101,6 +123,9 @@ rt_lint_for_default() {
|
|
|
101
123
|
}
|
|
102
124
|
|
|
103
125
|
# Каталог папок задач. Пусто — ведения работы папкой в дереве нет, и гард замысла молчит.
|
|
126
|
+
# Каталог текстов проекта: по нему признак состава правки отличает текстовую ветку от кодовой.
|
|
127
|
+
RT_DOCS_DIR="${RT_DOCS_DIR:-docs}"
|
|
128
|
+
|
|
104
129
|
RT_TASKS_DIR="${RT_TASKS_DIR:-docs/tasks}"
|
|
105
130
|
|
|
106
131
|
# Каталог записей о законченных работах. Туда переносят то, что объясняет решения закрытой
|
|
@@ -111,9 +136,27 @@ RT_ARCHIVE_DIR="${RT_ARCHIVE_DIR:-docs/archive}"
|
|
|
111
136
|
# Размер окна захода в токенах и пороги стража. Пусто — стража нет: считать долю не от чего, а
|
|
112
137
|
# выведенный из записи захода размер врал бы — модель записана там без пометки о расширенном
|
|
113
138
|
# окне. Дерево задаёт его в настройке агента, переменной окружения того же имени.
|
|
139
|
+
#
|
|
140
|
+
# Теми же двумя числами задаётся порог, на котором инструмент сжимает контекст сам, — а значит и
|
|
141
|
+
# порог, на котором пишется передача захода. В настройке агента им отвечает пара: размер окна
|
|
142
|
+
# автосжатия и доля в процентах, при которой оно приходит. Разъехавшись, они дают заход, который
|
|
143
|
+
# либо сжимается до того, как передача написана, либо доживает до предела окна. Сведены они или
|
|
144
|
+
# нет — говорит разбор состояния раскладки; здесь у пакета своих значений нет, потому что окно
|
|
145
|
+
# принадлежит дереву, а не ему.
|
|
146
|
+
#
|
|
147
|
+
# Сведёнными считаются не совпавшие числа, а разведённые. Порог сжатия обязан стоять НИЖЕ порога
|
|
148
|
+
# остановки: первым срабатывает то, что заход продолжает, а не то, что его останавливает.
|
|
149
|
+
# Совпавшая пара — гонка, и выигрывает её страж: он стоит на вызове инструмента, а сжатие
|
|
150
|
+
# приходит между ходами. Ровно так заход и вставал на пороге вместо того, чтобы продолжиться
|
|
151
|
+
# сжатым, — при том что сверка обе стороны считала настроенными.
|
|
152
|
+
#
|
|
153
|
+
# Насколько ниже — своё число дерева: сжатие идёт не мгновенно, и разница в один процент
|
|
154
|
+
# требование «ниже» удовлетворяет, а работу не спасает. Запас объявляется, а не выводится
|
|
155
|
+
# разницей.
|
|
114
156
|
RT_WINDOW_TOKENS="${RT_WINDOW_TOKENS:-}"
|
|
115
157
|
RT_WINDOW_WARN_PCT="${RT_WINDOW_WARN_PCT:-40}"
|
|
116
158
|
RT_WINDOW_STOP_PCT="${RT_WINDOW_STOP_PCT:-50}"
|
|
159
|
+
RT_WINDOW_MARGIN_PCT="${RT_WINDOW_MARGIN_PCT:-5}"
|
|
117
160
|
|
|
118
161
|
# Куда кладётся передача захода. Вне дерева: состояние работы живёт в ходе работы и коммитится,
|
|
119
162
|
# а передача его пересказывает для вставки в новый заход и в историю не едет.
|
|
@@ -144,6 +187,11 @@ RT_SPECS_DIR="${RT_SPECS_DIR:-docs/specs}"
|
|
|
144
187
|
# папкой задачи и с архивом. Если общего предка нет, сравнивать не с чем — проверка молчит.
|
|
145
188
|
RT_MAIN_BRANCH="${RT_MAIN_BRANCH:-main}"
|
|
146
189
|
|
|
190
|
+
# Имя клиента хостинга в строке запуска. По нему гард перезапуска узнаёт вызов: у каждого вида
|
|
191
|
+
# хостинга свой клиент, и угаданное имя не совпадает ни с чем. Умолчание записано под самый
|
|
192
|
+
# распространённый вид; дерево с другим хостингом называет своё.
|
|
193
|
+
RT_HOST_CLI="${RT_HOST_CLI:-gh}"
|
|
194
|
+
|
|
147
195
|
# Тело PR по его номеру. Обход требования пишут в PR, а в команде слияния его нет — там только
|
|
148
196
|
# номер. Пустой ответ значит «спросить не у кого»: тогда обход ищется только в тексте команды.
|
|
149
197
|
rt_report_body_default() {
|
|
@@ -185,19 +233,52 @@ rt_is_app_code_default() {
|
|
|
185
233
|
# Список намеренно широк, и цена этого названа: команда чтения, в которой стоит имя
|
|
186
234
|
# интерпретатора, будет отбита наравне с командой правки. Узкий список стоил бы дороже —
|
|
187
235
|
# пропущенная форма записи возвращает обход целиком, а найти её можно только промахом.
|
|
236
|
+
#
|
|
237
|
+
# Перенаправление в пустое устройство и в поток ошибок снимается до разбора: файла оно не
|
|
238
|
+
# пишет, а выглядит как перенаправление в файл. Так глушат вывод команды чтения, и без этого
|
|
239
|
+
# `grep -rn x libs/ 2>/dev/null` судится наравне с записью — правило требуется на чтение, а
|
|
240
|
+
# отбитий, пришедшихся не на правку файла, набирается большинство. Настоящая запись рядом с
|
|
241
|
+
# заглушённым потоком остаётся видной: снимается перенаправление, а не команда целиком.
|
|
188
242
|
rt_shell_writes_default() {
|
|
189
|
-
printf '%s' "$1"
|
|
190
|
-
'
|
|
243
|
+
printf '%s' "$1" \
|
|
244
|
+
| sed -E 's#(&|[0-9]*)>>?[[:space:]]*/dev/(null|stderr)##g; s#[0-9]*>&[0-9-]##g' \
|
|
245
|
+
| grep -Eq \
|
|
246
|
+
'>>?[[:space:]]*[^|&>[:space:]]|\btee\b|\bsed\b[^|]*-i|\bperl\b[^|]*-i|\bpython3?\b|\bnode\b|\bruby\b|\bdd\b[^|]*of=|\bcp\b|\bmv\b|\brm\b|\btouch\b|\btruncate\b|\binstall\b|\bpatch\b|\bgit[[:space:]]+(checkout|restore|apply|stash)\b'
|
|
191
247
|
}
|
|
192
248
|
|
|
193
249
|
# Пути, названные командой оболочки. Печатает по одному в строке; судит их зовущий.
|
|
194
250
|
#
|
|
195
251
|
# Разбирать оболочку по-настоящему нечем — здесь и не разбирают: из текста вынимается всё, что
|
|
196
252
|
# похоже на путь, и каждое отдаётся признаку. Лишнее он отсеет сам, а пропущенное вернуло бы
|
|
197
|
-
# обход. Кавычки
|
|
253
|
+
# обход. Кавычки снимаются заменой на пробел: путь внутри них тот же самый.
|
|
254
|
+
#
|
|
255
|
+
# Тело документа на месте путей не даёт: там лежит текст, который команда кладёт в файл, а
|
|
256
|
+
# чужой путь, названный в нём словами, требовал бы правила под запись, которой нет. Запись
|
|
257
|
+
# замысла так отбивалась трижды подряд, пока пути под каталогом кода не были названы иначе.
|
|
258
|
+
# Судится заголовок команды — именно в нём стоит тот путь, куда команда пишет.
|
|
259
|
+
#
|
|
260
|
+
# Исключение — интерпретатор: ему код приходит телом, и путь записи стоит именно там. Признак
|
|
261
|
+
# ошибается в сторону лишнего чтения тела: имя интерпретатора, стоящее в команде где угодно,
|
|
262
|
+
# возвращает разбор тела целиком.
|
|
198
263
|
rt_shell_paths_default() {
|
|
199
|
-
printf '%s' "$1" \
|
|
200
|
-
|
|
264
|
+
text="$(printf '%s' "$1" | tr "\"'\`" ' ')"
|
|
265
|
+
if ! printf '%s' "$text" \
|
|
266
|
+
| grep -Eq '(^|[|;&(]|[[:space:]])(python3?|node|ruby|perl|php|deno|bun|bash|sh|zsh)([[:space:]]|$)'; then
|
|
267
|
+
text="$(printf '%s' "$text" | awk '
|
|
268
|
+
function trim(s) { sub(/^[ \t]+/, "", s); sub(/[ \t]+$/, "", s); return s }
|
|
269
|
+
tag != "" { if (trim($0) == tag) tag = ""; next }
|
|
270
|
+
{
|
|
271
|
+
print
|
|
272
|
+
if (match($0, /<<-?[ \t]*[A-Za-z_][A-Za-z0-9_]*/)) {
|
|
273
|
+
t = substr($0, RSTART, RLENGTH)
|
|
274
|
+
sub(/^<<-?[ \t]*/, "", t)
|
|
275
|
+
tag = t
|
|
276
|
+
}
|
|
277
|
+
}')"
|
|
278
|
+
fi
|
|
279
|
+
|
|
280
|
+
printf '%s' "$text" \
|
|
281
|
+
| tr "(),;=" ' ' \
|
|
201
282
|
| tr '[:space:]' '\n' \
|
|
202
283
|
| grep -E '^[A-Za-z0-9_@.-]*/[A-Za-z0-9_@./-]+$' \
|
|
203
284
|
| sed 's|^\./||' \
|
|
@@ -239,6 +320,12 @@ RT_BOARD_CHECK_CMD="${RT_BOARD_CHECK_CMD:-npm run check:board}"
|
|
|
239
320
|
# Учётная запись, которую ставят исполнителем. Умолчание молчит: у каждого дерева она своя.
|
|
240
321
|
RT_TASK_BOT="${RT_TASK_BOT:-}"
|
|
241
322
|
|
|
323
|
+
# Команда перевода задачи между колонками очереди работ и имя первой колонки — той, из которой
|
|
324
|
+
# задача уходит, когда её берут в работу. Имя без умолчания: колонки дерево называет своими
|
|
325
|
+
# словами, а выдуманное не совпало бы ни с чем и молча выключило бы проверку колонки.
|
|
326
|
+
RT_TASK_MOVE_CMD="${RT_TASK_MOVE_CMD:-npm run task:move}"
|
|
327
|
+
RT_BOARD_BACKLOG="${RT_BOARD_BACKLOG:-}"
|
|
328
|
+
|
|
242
329
|
# Почта, которой подписан коммит машинной учётной записи. Целым значением, а не образцом:
|
|
243
330
|
# служебный адрес хостинга состоит из числа, логина и домена, а сопоставляется по числу — логин
|
|
244
331
|
# рядом с ним не сверяет никто. Образец «число, плюс, логин» прошёл бы с чужим числом, то есть
|
|
@@ -268,6 +355,18 @@ rt_task_state_default() {
|
|
|
268
355
|
printf '%s' "$state"
|
|
269
356
|
}
|
|
270
357
|
|
|
358
|
+
# Состояние заявки одним объектом: exists, draft, author, reviewers, reviewed. Спрашивает того
|
|
359
|
+
# же помощника очереди работ, что и состояние задачи, — чтобы гард и сверка одинаково понимали
|
|
360
|
+
# «у заявки есть разбор». Нет узла, нет помощника, нет сети — молчание, и ярус пропускается.
|
|
361
|
+
rt_pull_state_default() {
|
|
362
|
+
command -v node >/dev/null 2>&1 || return 1
|
|
363
|
+
[ -f "${RT_BOARD_HELPER:-tools/board.mjs}" ] || return 1
|
|
364
|
+
state="$(node "${RT_BOARD_HELPER:-tools/board.mjs}" pr "$1" 2>/dev/null)" || return 1
|
|
365
|
+
[ -z "$state" ] && return 1
|
|
366
|
+
printf '%s' "$state" | jq -e 'has("offline") | not' >/dev/null 2>&1 || return 1
|
|
367
|
+
printf '%s' "$state"
|
|
368
|
+
}
|
|
369
|
+
|
|
271
370
|
# Что в этом дереве считается переизобретением. По строке «образец<таб>чем заменить».
|
|
272
371
|
# Образцы узкие намеренно: гард сверяет только НОВЫЙ текст, и широкий образец отбивал бы
|
|
273
372
|
# правку, которая ничего нового не заводит.
|
|
@@ -306,6 +405,8 @@ rt_qa_decorative_default() {
|
|
|
306
405
|
# Умолчания под общими именами. Надстройка проекта объявит любое из них заново — и позовёт
|
|
307
406
|
# отсюда то же имя с суффиксом `_default` для всего, чего не назвала сама.
|
|
308
407
|
rt_push_checks() { rt_push_checks_default "$@"; }
|
|
408
|
+
rt_push_docs_only() { rt_push_docs_only_default "$@"; }
|
|
409
|
+
|
|
309
410
|
rt_docs_pair_for() { rt_docs_pair_for_default "$@"; }
|
|
310
411
|
rt_lint_for() { rt_lint_for_default "$@"; }
|
|
311
412
|
rt_task_branch_ok() { rt_task_branch_ok_default "$@"; }
|
|
@@ -315,5 +416,6 @@ rt_shell_writes() { rt_shell_writes_default "$@"; }
|
|
|
315
416
|
rt_shell_paths() { rt_shell_paths_default "$@"; }
|
|
316
417
|
rt_qa_decorative() { rt_qa_decorative_default "$@"; }
|
|
317
418
|
rt_task_state() { rt_task_state_default "$@"; }
|
|
419
|
+
rt_pull_state() { rt_pull_state_default "$@"; }
|
|
318
420
|
rt_report_body() { rt_report_body_default "$@"; }
|
|
319
421
|
rt_handoff_allowed_cmd() { rt_handoff_allowed_cmd_default "$@"; }
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# Карта хода
|
|
2
|
+
|
|
3
|
+
Это не правило, а его короткая выжимка: правило объясняет, карта называет. Полный текст —
|
|
4
|
+
скил `task-flow`; он же называет паттерн, который ведёт каждое состояние.
|
|
5
|
+
|
|
6
|
+
Состояние работы объявлено строкой в разделе «Где стоим» хода работы. Пока обязательное
|
|
7
|
+
действие не сделано, работа стоит в том же состоянии.
|
|
8
|
+
|
|
9
|
+
## Состояния и обязательные действия
|
|
10
|
+
|
|
11
|
+
| Состояние | Обязательное действие | Ведёт паттерн |
|
|
12
|
+
| ------------------------- | ------------------------------------------------------ | ------------------ |
|
|
13
|
+
| `просьба-не-разобрана` | разведка по дереву, затем вопросы | `task-flow-start` |
|
|
14
|
+
| `разбор-закрыт` | договорённость о продукте либо причина её отсутствия | `task-flow-start` |
|
|
15
|
+
| `договорённость-записана` | завести задачу, ветку и папку | `task-flow-start` |
|
|
16
|
+
| `задача-взята` | написать замысел | `task-flow-start` |
|
|
17
|
+
| `замысел-записан` | делать первый этап | `task-flow-start` |
|
|
18
|
+
| `этап-идёт` | доделать этап и отметить в ходе работы | `task-flow-resume` |
|
|
19
|
+
| `этапы-кончились` | прогнать набор и открыть PR черновиком | `task-flow-close` |
|
|
20
|
+
| `работа-отдана` | взять следующую задачу | `task-flow-resume` |
|
|
21
|
+
| `разбор-кончился` | влить договорённость, привести тексты, разобрать папку | `task-flow-close` |
|
|
22
|
+
| `папка-разобрана` | снять черновик и попросить влить | `task-flow-close` |
|
|
23
|
+
| `влито` | разбор работы правилами и сверка очереди | `task-flow-close` |
|
|
24
|
+
|
|
25
|
+
Ни у одного состояния обязательное действие не звучит как «ждать». Прогон, разбор владельцем и
|
|
26
|
+
слияние идут без исполнителя и от взгляда быстрее не становятся.
|
|
27
|
+
|
|
28
|
+
## Чем ход кончается
|
|
29
|
+
|
|
30
|
+
Способов четыре, и других нет.
|
|
31
|
+
|
|
32
|
+
| Выход | Чем подтверждается |
|
|
33
|
+
| -------------------------------------------------- | --------------------------------------------------------------- |
|
|
34
|
+
| вопрос владельцу, ответа на который в правилах нет | вопрос задан, и за тот же ход правила читались |
|
|
35
|
+
| отказ гарда | отказ назван владельцу, обход не искался |
|
|
36
|
+
| заполненное окно там, где сжатия нет | ход работы дописан, передача написана |
|
|
37
|
+
| работа отдана, и следующая начата | PR открыт, и по следующей задаче сделано действие, а не сказано |
|
|
38
|
+
|
|
39
|
+
Там, где дерево объявило порог сжатия ниже порога остановки, заполненное окно ход не кончает:
|
|
40
|
+
контекст сжимается, передача приходит входом, и работа идёт дальше тем же заходом. Порог
|
|
41
|
+
остановки там — страховка на случай, когда сжатие не пришло.
|
|
42
|
+
|
|
43
|
+
Всё остальное — продолжение хода. Ходом не кончаются: коммит, записанный замысел, закрытый
|
|
44
|
+
разбор просьбы, прочитанная договорённость, зелёная проверка, сводка о чужом шаге и объявление
|
|
45
|
+
намерения. Переход из состояния в состояние — тем более: обязательное действие сделано, и
|
|
46
|
+
следующее делается тем же ходом. Названо может быть только сделанное.
|
package/assets/docs/GLOSSARY.md
CHANGED
|
@@ -30,6 +30,7 @@
|
|
|
30
30
|
| Раскладка | Перенос ресурса из пакета в дерево по его роду и настройке слоя |
|
|
31
31
|
| Разложенный файл | Файл в дереве с шапкой пакета. Правится не на месте, а надстройкой: правка на месте теряется на следующей раскладке |
|
|
32
32
|
| Надстройка | Файл дерева, который сливается с разложенным по заголовкам разделов |
|
|
33
|
+
| Выключенная роль | Роль, вызов которой дерево перестало считать обязательным: гард при ней молчит. Названа списком в настройке дерева, из раскладки не убирается и зовётся руками |
|
|
33
34
|
| Наблюдение | Строка о событии слоя правил: правило загружено, гейт отбил, гард отказал. Пишет гард, живёт в дереве, наружу уезжает счётчиками. Журналом не называется |
|
|
34
35
|
| Сводка | Что наблюдения говорят за отрезок дней: чем пользовались, чем ни разу, обо что спотыкались |
|
|
35
36
|
| Предложение | Готовая формулировка правки правил с адресом: пакет, компаньон или дерево. Приносит разбор закрытой задачи или слово посреди работы, отправляет человек командой |
|
|
@@ -37,21 +38,22 @@
|
|
|
37
38
|
|
|
38
39
|
## Работа
|
|
39
40
|
|
|
40
|
-
| Термин
|
|
41
|
-
|
|
|
42
|
-
| Задача
|
|
43
|
-
| PR
|
|
44
|
-
| Очередь работ
|
|
45
|
-
| Папка задачи
|
|
46
|
-
| Разбор
|
|
47
|
-
| Замысел
|
|
48
|
-
| Ход работы
|
|
49
|
-
|
|
|
50
|
-
|
|
|
51
|
-
|
|
|
52
|
-
|
|
|
53
|
-
|
|
|
54
|
-
|
|
|
41
|
+
| Термин | Что это |
|
|
42
|
+
| ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
43
|
+
| Задача | Единица работы в очереди работ. Заводится до ветки, и номер её стоит в имени ветки и в заголовке PR |
|
|
44
|
+
| PR | Заявка на слияние: то же название, что у задачи, переведённое в сделанное. Отчётом, пул-реквестом и мёрдж-реквестом не называется — ни в файлах, ни в разговоре |
|
|
45
|
+
| Очередь работ | Доска, на которой видно состояние каждой задачи. Ветки она не видит |
|
|
46
|
+
| Папка задачи | Одна работа от разбора до слияния: разбор просьбы, замысел, ход работы. Умирает со слиянием — разбирается, и объясняющее решение уезжает в архив |
|
|
47
|
+
| Разбор | Расспрос владельца до первой правки. Записывается его словами и задним числом не переписывается |
|
|
48
|
+
| Замысел | Файл папки задачи: след задачи и этапы с признаками готовности. После написания не правится — с ним сверяют результат при приёмке |
|
|
49
|
+
| Ход работы | Файл папки задачи: «Где стоим», решения по ходу с причинами, записи заходов. Единственное место, где отмечается сделанное. Журналом не называется |
|
|
50
|
+
| Состояние работы | Единица, которой работа ведётся: у каждого состояния названы вход, обязательное действие и выход. Объявляется машиночитаемой строкой в разделе «Где стоим» хода работы — гард судит объявленный переход, а не наличие файлов |
|
|
51
|
+
| След задачи | Раздел замысла: какие спеки, законы, правила и части кода работа задевает |
|
|
52
|
+
| Заход | Одна сессия работы над задачей. Работа живёт дольше одного захода, и между ними её состояние держит только ход работы |
|
|
53
|
+
| Заполнение окна | Доля места захода, которую он уже занял: вход, запись в кэш, прочитанное из кэша и вывод последнего ответа, делённые на размер окна. Не «расход» и не «бюджет»: речь о месте, а не о деньгах |
|
|
54
|
+
| Передача | Текст, которым заход закрывается: рабочее дерево, ветка, задача, где лежит ход работы, что сделано, следующий шаг, особенности захода. Кладётся вне дерева и не коммитится |
|
|
55
|
+
| Эпик | Серия задач одной темы, выполняемых в назначенном порядке. Живёт в двух местах сразу: карточка в очереди работ с меткой эпика и замысел рядом с ней — что за возможность разрабатывается, какие задачи входят и в каком порядке. Шире одной ветки. Линией работ не называется |
|
|
56
|
+
| Архив | Записи о состоявшемся: что объясняет закрытое решение. После выкатки не правится |
|
|
55
57
|
|
|
56
58
|
## Проверки
|
|
57
59
|
|
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
#
|
|
9
9
|
# Файл с идентификатором в репозиторий не коммитится: у каждой машины он свой.
|
|
10
10
|
|
|
11
|
+
. "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/utf8.sh" 2>/dev/null || true
|
|
12
|
+
|
|
11
13
|
if [ -n "${RT_BROWSER_DEVICE_ID:-}" ]; then
|
|
12
14
|
printf '%s\n' "$RT_BROWSER_DEVICE_ID"
|
|
13
15
|
exit 0
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
# rt-hook: PreToolUse mcp__claude-in-chrome__select_browser
|
|
3
|
+
# Требует: hooks/deny-tail.sh
|
|
3
4
|
# Гард выбора браузера. PreToolUse на выборе браузера расширением.
|
|
4
5
|
#
|
|
5
6
|
# Отклоняет любой профиль, кроме закреплённого: чужой стоит лишнего круга и приводит в браузер,
|
|
@@ -10,6 +11,8 @@
|
|
|
10
11
|
#
|
|
11
12
|
# ОТКАЗ В ПОЛЬЗУ РАБОТЫ: помощник не назвал профиль — пропуск.
|
|
12
13
|
|
|
14
|
+
. "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/utf8.sh" 2>/dev/null || true
|
|
15
|
+
|
|
13
16
|
input="$(cat 2>/dev/null)"
|
|
14
17
|
|
|
15
18
|
device_id="$("${CLAUDE_PROJECT_DIR:-.}/.claude/hooks/browser-device-id.sh" 2>/dev/null)"
|
|
@@ -24,5 +27,12 @@ if [ "$requested" = "$device_id" ]; then
|
|
|
24
27
|
exit 0
|
|
25
28
|
fi
|
|
26
29
|
|
|
27
|
-
|
|
30
|
+
# Общий хвост отказа: два законных хода и законная форма обхода, если она у отказа есть. Файл
|
|
31
|
+
# может быть не разложен — тогда хвоста нет, а причина отказа остаётся прежней.
|
|
32
|
+
# shellcheck disable=SC1090
|
|
33
|
+
[ -f "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/deny-tail.sh" ] \
|
|
34
|
+
&& . "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/deny-tail.sh" 2>/dev/null
|
|
35
|
+
command -v rt_deny_tail >/dev/null 2>&1 || rt_deny_tail() { :; }
|
|
36
|
+
|
|
37
|
+
echo "Профиль «${requested}» не тот, что закреплён за проектом. Бери ${device_id} — единственный профиль, где сделан вход. $(rt_deny_tail)" >&2
|
|
28
38
|
exit 2
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
# rt-hook: PreToolUse AskUserQuestion
|
|
3
|
+
# Требует: hooks/deny-tail.sh
|
|
3
4
|
# PreToolUse guard for AskUserQuestion.
|
|
4
5
|
#
|
|
5
6
|
# The browser choice is already made and pinned in browser-device-id.sh — asking again is noise, and the
|
|
@@ -13,6 +14,8 @@
|
|
|
13
14
|
#
|
|
14
15
|
# FAIL-OPEN when browser-device-id.sh prints nothing.
|
|
15
16
|
|
|
17
|
+
. "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/utf8.sh" 2>/dev/null || true
|
|
18
|
+
|
|
16
19
|
input="$(cat 2>/dev/null)"
|
|
17
20
|
|
|
18
21
|
device_id="$("${CLAUDE_PROJECT_DIR:-.}/.claude/hooks/browser-device-id.sh" 2>/dev/null)"
|
|
@@ -23,5 +26,12 @@ questions="$(printf '%s' "$input" | jq -r '[.tool_input.questions[]? | .question
|
|
|
23
26
|
|
|
24
27
|
printf '%s' "$questions" | grep -qiE 'deviceid|device id|(which|what|pick|choose|select)[^.]{0,25}\bbrowser\b|\bbrowser\b[^.]{0,25}(profile|to use)|как(ой|ую)?[^.]{0,15}браузер|брауз[а-я]*[^.]{0,20}(использовать|выбрать|выбор|нужен|запустить)' || exit 0
|
|
25
28
|
|
|
26
|
-
|
|
29
|
+
# Общий хвост отказа: два законных хода и законная форма обхода, если она у отказа есть. Файл
|
|
30
|
+
# может быть не разложен — тогда хвоста нет, а причина отказа остаётся прежней.
|
|
31
|
+
# shellcheck disable=SC1090
|
|
32
|
+
[ -f "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/deny-tail.sh" ] \
|
|
33
|
+
&& . "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/deny-tail.sh" 2>/dev/null
|
|
34
|
+
command -v rt_deny_tail >/dev/null 2>&1 || rt_deny_tail() { :; }
|
|
35
|
+
|
|
36
|
+
echo "Do not ask which browser to use — the profile is pinned. Call select_browser with deviceId ${device_id} ('Main' profile). $(rt_deny_tail)" >&2
|
|
27
37
|
exit 2
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
# rt-hook: PreToolUse mcp__claude-in-chrome__(list_connected_browsers|switch_browser)
|
|
3
|
+
# Требует: hooks/deny-tail.sh
|
|
3
4
|
# Гард перечисления и переключения браузеров. PreToolUse.
|
|
4
5
|
#
|
|
5
6
|
# Сессии проекта живут в одном закреплённом профиле. Перечисление и переключение отдают общие
|
|
@@ -9,10 +10,19 @@
|
|
|
9
10
|
# ОТКАЗ В ПОЛЬЗУ РАБОТЫ: помощник не назвал профиль — пропуск. Гард, который не может назвать
|
|
10
11
|
# нужный профиль, ничего не предлагает взамен, и слепой отказ только заводил бы работу в тупик.
|
|
11
12
|
|
|
13
|
+
. "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/utf8.sh" 2>/dev/null || true
|
|
14
|
+
|
|
12
15
|
cat >/dev/null 2>&1
|
|
13
16
|
|
|
14
17
|
device_id="$("${CLAUDE_PROJECT_DIR:-.}/.claude/hooks/browser-device-id.sh" 2>/dev/null)"
|
|
15
18
|
[ -z "$device_id" ] && exit 0
|
|
16
19
|
|
|
17
|
-
|
|
20
|
+
# Общий хвост отказа: два законных хода и законная форма обхода, если она у отказа есть. Файл
|
|
21
|
+
# может быть не разложен — тогда хвоста нет, а причина отказа остаётся прежней.
|
|
22
|
+
# shellcheck disable=SC1090
|
|
23
|
+
[ -f "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/deny-tail.sh" ] \
|
|
24
|
+
&& . "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/deny-tail.sh" 2>/dev/null
|
|
25
|
+
command -v rt_deny_tail >/dev/null 2>&1 || rt_deny_tail() { :; }
|
|
26
|
+
|
|
27
|
+
echo "Не перечисляй и не переключай браузеры. Вызови выбор браузера с профилем ${device_id} — единственным, где сделан вход. $(rt_deny_tail)" >&2
|
|
18
28
|
exit 2
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
# rt-hook: PreToolUse mcp__playwright__.*|mcp__chrome-devtools__.*|Bash
|
|
3
|
+
# Требует: hooks/deny-tail.sh
|
|
3
4
|
# Гард обходных путей к браузеру. PreToolUse.
|
|
4
5
|
#
|
|
5
6
|
# Закрепление профиля чего-то стоит только тогда, когда дверь одна. Здесь перечислены двери,
|
|
@@ -12,6 +13,8 @@
|
|
|
12
13
|
#
|
|
13
14
|
# ОТКАЗ В ПОЛЬЗУ РАБОТЫ: помощник не назвал профиль — пропуск.
|
|
14
15
|
|
|
16
|
+
. "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/utf8.sh" 2>/dev/null || true
|
|
17
|
+
|
|
15
18
|
input="$(cat 2>/dev/null)"
|
|
16
19
|
|
|
17
20
|
device_id="$("${CLAUDE_PROJECT_DIR:-.}/.claude/hooks/browser-device-id.sh" 2>/dev/null)"
|
|
@@ -19,8 +22,13 @@ device_id="$("${CLAUDE_PROJECT_DIR:-.}/.claude/hooks/browser-device-id.sh" 2>/de
|
|
|
19
22
|
|
|
20
23
|
tool="$(printf '%s' "$input" | jq -r '.tool_name // empty' 2>/dev/null)"
|
|
21
24
|
|
|
25
|
+
# shellcheck disable=SC1090
|
|
26
|
+
[ -f "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/deny-tail.sh" ] \
|
|
27
|
+
&& . "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/deny-tail.sh" 2>/dev/null
|
|
28
|
+
command -v rt_deny_tail >/dev/null 2>&1 || rt_deny_tail() { :; }
|
|
29
|
+
|
|
22
30
|
deny() {
|
|
23
|
-
echo "$1 Води браузер закреплённым расширением: выбери профиль ${device_id} и работай его инструментами." >&2
|
|
31
|
+
echo "$1 Води браузер закреплённым расширением: выбери профиль ${device_id} и работай его инструментами. $(rt_deny_tail)" >&2
|
|
24
32
|
exit 2
|
|
25
33
|
}
|
|
26
34
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
# rt-hook: PreToolUse mcp__claude-in-chrome__.*
|
|
3
|
+
# Требует: hooks/deny-tail.sh
|
|
3
4
|
# Гард свежести выбора браузера. PreToolUse на всех остальных вызовах расширения.
|
|
4
5
|
#
|
|
5
6
|
# ЗАЧЕМ ОН ЕСТЬ — отказ, из которого он вырос: расширение действует на тот браузер, который
|
|
@@ -19,6 +20,8 @@
|
|
|
19
20
|
#
|
|
20
21
|
# ОТКАЗ В ПОЛЬЗУ РАБОТЫ: помощник не назвал профиль — пропуск.
|
|
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
|
|
|
24
27
|
device_id="$("${CLAUDE_PROJECT_DIR:-.}/.claude/hooks/browser-device-id.sh" 2>/dev/null)"
|
|
@@ -35,8 +38,16 @@ ttl=300
|
|
|
35
38
|
sid="$(printf '%s' "$input" | jq -r '.session_id // "nosession"' 2>/dev/null)"
|
|
36
39
|
marker="${TMPDIR:-/tmp}/claude-browser-guard/${sid}"
|
|
37
40
|
|
|
41
|
+
# Общий хвост отказа: два законных хода и законная форма обхода, если она у отказа есть. Файл
|
|
42
|
+
# может быть не разложен — тогда хвоста нет, а причина отказа остаётся прежней.
|
|
43
|
+
# shellcheck disable=SC1090
|
|
44
|
+
[ -f "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/deny-tail.sh" ] \
|
|
45
|
+
&& . "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/deny-tail.sh" 2>/dev/null
|
|
46
|
+
command -v rt_deny_tail >/dev/null 2>&1 || rt_deny_tail() { :; }
|
|
47
|
+
|
|
48
|
+
|
|
38
49
|
if [ ! -f "$marker" ]; then
|
|
39
|
-
echo "В этой сессии браузер не выбран. Вызови выбор браузера с профилем ${device_id} до любого другого вызова." >&2
|
|
50
|
+
echo "В этой сессии браузер не выбран. Вызови выбор браузера с профилем ${device_id} до любого другого вызова. $(rt_deny_tail)" >&2
|
|
40
51
|
exit 2
|
|
41
52
|
fi
|
|
42
53
|
|
|
@@ -46,7 +57,7 @@ age=$(( now - stamped ))
|
|
|
46
57
|
|
|
47
58
|
if [ "$age" -gt "$ttl" ]; then
|
|
48
59
|
rm -f "$marker" 2>/dev/null
|
|
49
|
-
echo "Последний выбор браузера был ${age} с назад (предел ${ttl} с) — на таких перерывах активный браузер расширения уплывает, и это может быть уже не закреплённый профиль. Вызови выбор с профилем ${device_id} заново и повтори." >&2
|
|
60
|
+
echo "Последний выбор браузера был ${age} с назад (предел ${ttl} с) — на таких перерывах активный браузер расширения уплывает, и это может быть уже не закреплённый профиль. Вызови выбор с профилем ${device_id} заново и повтори. $(rt_deny_tail)" >&2
|
|
50
61
|
exit 2
|
|
51
62
|
fi
|
|
52
63
|
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# rt-hook: Stop
|
|
3
|
+
# Гард утверждения: сказанное владельцу о состоянии дерева несёт команду, показавшую это. Stop.
|
|
4
|
+
#
|
|
5
|
+
# Зачем именно так. Правило текстов само говорит, что ответ владельцу не читает ни одна
|
|
6
|
+
# проверка: за файл отвечает гейт, за ответ — автор, и цену ошибки в ответе платит владелец.
|
|
7
|
+
# Восемь разборов подряд про одно и то же — готовым названо лежащее на диске, снятыми названы
|
|
8
|
+
# только локальные ветки, проверенным назван набор уже конвейерного, состоянием дерева названа
|
|
9
|
+
# отставшая локальная ссылка. Каждый раз в правило дописывалась статья, и каждый раз промах
|
|
10
|
+
# повторялся: текст читают в начале захода, а утверждение говорят в конце.
|
|
11
|
+
#
|
|
12
|
+
# Что судится: текст, сказанный владельцу за этот ход. Вывод инструмента и записанное в файл
|
|
13
|
+
# сюда не входят — их читают гейт и сверки.
|
|
14
|
+
#
|
|
15
|
+
# Как судится: у каждого слова-утверждения назван свой род команды. Утверждение о зелёном
|
|
16
|
+
# наборе подтверждает прогон набора, о запушенном — вызов пуша, о снятом — вызов удаления.
|
|
17
|
+
# Команда ищется в этом же ходе: состояние дерева меняется, и вывод прошлого хода о нынешнем
|
|
18
|
+
# уже не говорит.
|
|
19
|
+
#
|
|
20
|
+
# Чего гард не судит. Неверный вывод: об образце, судимом по одному его файлу, и о пути, которым
|
|
21
|
+
# человек не пойдёт, машине судить нечем — там нет ни слова-утверждения, ни команды, с которой
|
|
22
|
+
# сверять. Эти случаи держит правило ведения работы, и это известная граница гарда, а не
|
|
23
|
+
# обещание.
|
|
24
|
+
#
|
|
25
|
+
# ОТКАЗ В ПОЛЬЗУ РАБОТЫ: при любой ошибке, нехватке `jq`, отсутствии записи хода или пустом
|
|
26
|
+
# тексте ход РАЗРЕШАЕТСЯ (exit 0). Сломанный гард не имеет права заклинить разговор.
|
|
27
|
+
|
|
28
|
+
. "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/utf8.sh" 2>/dev/null || true
|
|
29
|
+
|
|
30
|
+
input="$(cat 2>/dev/null)"
|
|
31
|
+
[ -z "$input" ] && exit 0
|
|
32
|
+
command -v jq >/dev/null 2>&1 || exit 0
|
|
33
|
+
|
|
34
|
+
# Повторный заход по тому же ходу не судится: гард сказал своё один раз и отпускает.
|
|
35
|
+
active="$(printf '%s' "$input" | jq -r '.stop_hook_active // false' 2>/dev/null)"
|
|
36
|
+
[ "$active" = "true" ] && exit 0
|
|
37
|
+
|
|
38
|
+
transcript="$(printf '%s' "$input" | jq -r '.transcript_path // empty' 2>/dev/null)"
|
|
39
|
+
[ -z "$transcript" ] && exit 0
|
|
40
|
+
[ -f "$transcript" ] || exit 0
|
|
41
|
+
|
|
42
|
+
# Ход — всё, что записано после последней настоящей реплики владельца: ответ инструмента
|
|
43
|
+
# приходит той же ролью и репликой не считается.
|
|
44
|
+
turn="$(tail -n 400 "$transcript" 2>/dev/null | jq -s -r '
|
|
45
|
+
def is_input:
|
|
46
|
+
.type == "user"
|
|
47
|
+
and (((.message.content // []) | if type == "array"
|
|
48
|
+
then ([.[] | select(.type == "tool_result")] | length)
|
|
49
|
+
else 0 end) == 0);
|
|
50
|
+
|
|
51
|
+
(map(is_input) | rindex(true)) as $i
|
|
52
|
+
| (if $i == null then [] else .[$i:] end) as $turn
|
|
53
|
+
| [$turn[] | select(.type == "assistant") | (.message.content // [])[]
|
|
54
|
+
| select(.type == "text") | (.text // "")] as $said
|
|
55
|
+
| [$turn[] | select(.type == "assistant") | (.message.content // [])[]
|
|
56
|
+
| select(.type == "tool_use") | (.input.command // "")] as $ran
|
|
57
|
+
| { said: ($said | join("\n")), ran: ($ran | join("\n")) }
|
|
58
|
+
' 2>/dev/null)"
|
|
59
|
+
|
|
60
|
+
[ -z "$turn" ] && exit 0
|
|
61
|
+
|
|
62
|
+
said="$(printf '%s' "$turn" | jq -r '.said // ""' 2>/dev/null)"
|
|
63
|
+
ran="$(printf '%s' "$turn" | jq -r '.ran // ""' 2>/dev/null)"
|
|
64
|
+
[ -z "$said" ] && exit 0
|
|
65
|
+
|
|
66
|
+
# Карта утверждений: что сказано о дереве | чем это показывают | что назвать в отказе.
|
|
67
|
+
#
|
|
68
|
+
# Слово берётся в той форме, в какой его говорят владельцу. Будущее время сюда не идёт:
|
|
69
|
+
# «проверю» и «запущу» утверждениями не являются — они обещание, и врать им нечем.
|
|
70
|
+
claims=(
|
|
71
|
+
'проверено|прогнал[а]?|тесты (зелёные|прошли)|линт(ер)? (зелёный|прошёл|чистый)|сборка (зелёная|прошла)|набор зелёный|проверки зелёные|спеки зелёные|всё зелен(о|ое)§nx (test|lint|build|run|affected|run-many)|npm (run|test)|pnpm (run|exec|test)|jest|vitest|playwright|check:|run\.sh§команду набора — прогон тестов, линта или сборки'
|
|
72
|
+
'запушен[аоы]?|запушил[а]?|пуш прошёл|ветка уехала§git push§`git push`'
|
|
73
|
+
'(PR|правка|ветка|работа)[^.]{0,20}(влит|слит|смержен)|влит[оа] в|слит[оа] в§git merge|gh pr merge§`git merge` или `gh pr merge`'
|
|
74
|
+
'(ветки|ветка|файлы|файл|папка|каталог)[^.]{0,40}(снят|удал|почищ|вычищ)|снят[оыа] с§git branch|git push .*--delete|git rm|gh api|rm §команду удаления — `git branch -d`, `git push --delete` или `git rm`'
|
|
75
|
+
'прогон (зелёный|прошёл|кончился)|конвейер зелёный|проверки на PR зелёные§gh run§`gh run list` или `gh run view`'
|
|
76
|
+
'(работа|правка|задача) готова|можно вливать|PR открыт|черновик снят§gh pr §`gh pr create`, `gh pr view` или `gh pr ready`'
|
|
77
|
+
'задача заведена|задача (в|переведена в) колонк|колонка переведена§gh issue|gh api|task:new|task:move|board\.mjs§команду очереди работ — заведение задачи или перевод колонки'
|
|
78
|
+
'(в дереве|в репозитории|здесь|такого файла|такой команды)[^.]{0,30}(нет|не бывает)|не заводили|нигде не встречается§grep|rg |ls |find |git ls-files|git grep|git log|cat §команду поиска — `grep`, `git ls-files` или обход каталога'
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
for row in "${claims[@]}"; do
|
|
82
|
+
words="${row%%§*}"
|
|
83
|
+
rest="${row#*§}"
|
|
84
|
+
proof="${rest%%§*}"
|
|
85
|
+
name="${rest#*§}"
|
|
86
|
+
|
|
87
|
+
found="$(printf '%s' "$said" | grep -oiE "$words" 2>/dev/null | head -1)"
|
|
88
|
+
[ -z "$found" ] && continue
|
|
89
|
+
printf '%s' "$ran" | grep -qiE "$proof" 2>/dev/null && continue
|
|
90
|
+
|
|
91
|
+
reason="BLOCKED by claim-guard: владельцу сказано «${found}» — это утверждение о состоянии дерева, а команды, которая его показывает, за этот ход не было.
|
|
92
|
+
|
|
93
|
+
Утверждение о дереве стоит ровно столько, сколько команда, его показавшая: сказанное без команды владелец читает как проверенный факт и узнаёт о расхождении последним.
|
|
94
|
+
|
|
95
|
+
Запусти ${name} этим же ходом и назови её вывод — либо убери утверждение из ответа.
|
|
96
|
+
|
|
97
|
+
Гард судит один ход: следующий заход не отбивается."
|
|
98
|
+
|
|
99
|
+
# Общий хвост отказа: два законных хода. Файл может быть не разложен — тогда хвоста нет,
|
|
100
|
+
# а причина отказа остаётся прежней.
|
|
101
|
+
# shellcheck disable=SC1090
|
|
102
|
+
[ -f "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/deny-tail.sh" ] \
|
|
103
|
+
&& . "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/deny-tail.sh" 2>/dev/null
|
|
104
|
+
command -v rt_deny_tail >/dev/null 2>&1 || rt_deny_tail() { :; }
|
|
105
|
+
deny_tail_text="$(rt_deny_tail "")"
|
|
106
|
+
[ -n "$deny_tail_text" ] && reason="${reason}
|
|
107
|
+
|
|
108
|
+
${deny_tail_text}"
|
|
109
|
+
|
|
110
|
+
jq -n --arg r "$reason" '{decision:"block",reason:$r}' 2>/dev/null \
|
|
111
|
+
|| printf '{"decision":"block","reason":"claim-guard: утверждение о дереве не подтверждено командой."}\n'
|
|
112
|
+
exit 0
|
|
113
|
+
done
|
|
114
|
+
|
|
115
|
+
exit 0
|