@rt-tools/agent-kit 0.13.0 → 0.15.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 +17 -0
- package/assets/checks/board-runs.github.mjs +45 -0
- package/assets/checks/board.github.mjs +43 -1
- package/assets/checks/check-board.github.mjs +51 -1
- package/assets/checks/check-descriptions.mjs +123 -0
- package/assets/checks/check-dupes.mjs +31 -3
- package/assets/checks/check-file-size.mjs +47 -2
- package/assets/checks/check-turn-map.mjs +20 -3
- package/assets/checks/lib-common.mjs +12 -1
- package/assets/checks/lib-domains.mjs +1 -1
- package/assets/checks/rt-kit-checks.config.mjs +17 -1
- package/assets/checks/spec-anchors.mjs +18 -3
- package/assets/checks/spec-common.mjs +5 -1
- package/assets/checks/task-new.github.mjs +15 -3
- package/assets/defaults/project.sh +21 -16
- package/assets/defaults/turn-map.md +15 -19
- package/assets/docs/GLOSSARY.md +52 -58
- package/assets/hooks/claim-guard.sh +34 -0
- package/assets/hooks/dispatch.sh +48 -15
- package/assets/hooks/hook-input.sh +39 -0
- package/assets/hooks/rule-article.sh +12 -0
- package/assets/hooks/skill-gate.sh +5 -4
- package/assets/hooks/task-flow-guard.sh +20 -0
- package/assets/hooks/turn-exit-guard.sh +248 -8
- package/assets/hooks/waiting-turn-guard.sh +39 -3
- package/assets/laws/delivery.md +92 -104
- package/assets/laws/frontend-application.md +4 -0
- package/assets/laws/project-documentation.md +64 -68
- package/assets/laws/verifiability.md +32 -33
- package/assets/laws/work-conduct.md +167 -157
- package/assets/patterns/doc-style-sweep.md +1 -1
- package/assets/patterns/doc-style-trace.md +1 -1
- package/assets/patterns/git-workflow-commit.azure.md +1 -1
- package/assets/patterns/git-workflow-commit.github.md +7 -1
- package/assets/patterns/git-workflow-commit.gitlab.md +1 -1
- package/assets/patterns/git-workflow-docker.md +1 -1
- package/assets/patterns/git-workflow-merge.md +14 -3
- package/assets/patterns/git-workflow-pr.azure.md +1 -1
- package/assets/patterns/git-workflow-pr.github.md +1 -1
- package/assets/patterns/git-workflow-pr.gitlab.md +1 -1
- package/assets/patterns/git-workflow-restart.md +1 -1
- package/assets/patterns/git-workflow-secrets.md +1 -1
- package/assets/patterns/git-workflow-stack.md +93 -0
- package/assets/patterns/seo-page.md +1 -1
- package/assets/patterns/spec-driven-rule.md +55 -0
- package/assets/patterns/status-report-table.github.md +88 -0
- package/assets/patterns/task-flow-archive.md +3 -4
- package/assets/patterns/task-flow-close.md +6 -1
- package/assets/patterns/task-flow-start.md +17 -5
- package/assets/patterns/ts-procedure.md +1 -1
- package/assets/pitfalls/doc-style.md +5 -0
- package/assets/pitfalls/git-workflow.github.md +55 -0
- package/assets/pitfalls/task-flow.md +28 -0
- package/assets/pitfalls/testing.md +14 -0
- package/assets/pitfalls/turn-conduct.md +33 -0
- package/assets/rules/angular-patterns.md +1 -1
- package/assets/rules/api-layer.md +3 -3
- package/assets/rules/browser-verification.md +15 -1
- package/assets/rules/dependencies.md +1 -1
- package/assets/rules/deploy-flow.azure.md +1 -1
- package/assets/rules/deploy-flow.github.md +1 -1
- package/assets/rules/deploy-flow.gitlab.md +1 -1
- package/assets/rules/doc-style.md +7 -0
- package/assets/rules/entity-conventions.needs-admin.md +1 -1
- package/assets/rules/entity-models.md +1 -1
- package/assets/rules/git-workflow.azure.md +1 -1
- package/assets/rules/git-workflow.github.md +154 -181
- package/assets/rules/git-workflow.gitlab.md +1 -1
- package/assets/rules/lib-layers.md +1 -1
- package/assets/rules/observability.needs-app.md +1 -1
- package/assets/rules/platform-access.md +1 -1
- package/assets/rules/reuse-first.md +1 -1
- package/assets/rules/seo.md +4 -3
- package/assets/rules/shared-code.md +1 -1
- package/assets/rules/spec-driven.md +68 -1
- package/assets/rules/status-report.md +97 -0
- package/assets/rules/styling-bem.md +12 -0
- package/assets/rules/task-flow.md +102 -98
- package/assets/rules/testing.md +67 -66
- package/assets/rules/turn-conduct.md +155 -85
- package/assets/rules/turn-entry.md +7 -1
- package/assets/rules/typescript-conventions.md +1 -1
- package/assets/skills/agent-kit-extend.md +1 -1
- package/assets/skills/agent-kit.md +18 -1
- package/bin/agent-kit.d.ts.map +1 -1
- package/bin/agent-kit.js +25 -0
- package/bin/agent-kit.js.map +1 -1
- package/lib/cost.d.ts +44 -0
- package/lib/cost.d.ts.map +1 -0
- package/lib/cost.js +181 -0
- package/lib/cost.js.map +1 -0
- package/package.json +1 -1
- package/rt-tools-agent-kit-0.15.0.tgz +0 -0
- package/rt-tools-agent-kit-0.13.0.tgz +0 -0
|
@@ -12,7 +12,9 @@
|
|
|
12
12
|
# нельзя, и запрет должна держать машина.
|
|
13
13
|
#
|
|
14
14
|
# Что считается работой: правка файла и команда, меняющая дерево или его состояние. Чтение,
|
|
15
|
-
# поиск и разговор работой не считаются — именно ими и заполняется ход, который встал.
|
|
15
|
+
# поиск и разговор работой не считаются — именно ими и заполняется ход, который встал. Читающая
|
|
16
|
+
# подкоманда `git` и клиента хостинга работой не считается тоже, и судится это по частям
|
|
17
|
+
# составной команды: чтение, соединённое с правкой через `&&`, работой остаётся.
|
|
16
18
|
#
|
|
17
19
|
# Что отпускает ход:
|
|
18
20
|
# 1. Работа отдана либо влита — состояние работы говорит об этом само.
|
|
@@ -74,10 +76,53 @@ case "$state" in
|
|
|
74
76
|
работа-отдана | влито) exit 0 ;;
|
|
75
77
|
esac
|
|
76
78
|
|
|
79
|
+
# Записанный замысел концом хода не бывает вовсе. Обязательное действие этого состояния — делать
|
|
80
|
+
# первый этап, а начавший его переводит состояние той же правкой: ход, оставшийся в прежнем
|
|
81
|
+
# состоянии, первого этапа не начал по определению. Второй признак сюда не годится — заведение
|
|
82
|
+
# задачи, ветки, колонки и папки он считает работой, и ход, где сделана одна подготовка,
|
|
83
|
+
# проходит его насквозь. За один заход так вышло трижды; дважды это поймал соседний гард по
|
|
84
|
+
# открытой заявке, третий раз не поймало ничто, а владельцу отчёт о взятой задаче неотличим от
|
|
85
|
+
# остановки.
|
|
86
|
+
if [ "$state" = "замысел-записан" ]; then
|
|
87
|
+
plan="$root/$tasks_dir/$branch/plan.md"
|
|
88
|
+
first_stage="$(grep -m1 '^### ' "$plan" 2>/dev/null | sed 's/^### //')"
|
|
89
|
+
[ -z "$first_stage" ] && first_stage="первый этап замысла"
|
|
90
|
+
reason="BLOCKED by turn-exit-guard: работа стоит в состоянии 'замысел-записан', а обязательное действие этого состояния — делать первый этап — за ход не начато.
|
|
91
|
+
|
|
92
|
+
Заведение задачи, ветки, колонки и папки этим шагом не считается: всё это подготовка к работе, а не работа. Владельцу отчёт о взятой задаче неотличим от остановки — он видит исполнителя стоящим.
|
|
93
|
+
|
|
94
|
+
Первый этап замысла: ${first_stage}
|
|
95
|
+
|
|
96
|
+
Начни его этим же ходом и перепиши состояние на '- **Состояние:** \`этап-идёт\`'. Владелец сказал остановиться — так и напиши: страж читает его слово, а не пересказ.
|
|
97
|
+
|
|
98
|
+
Страж судит один ход: следующий заход не отбивается."
|
|
99
|
+
|
|
100
|
+
# Общий хвост отказа: два законных хода. Файл может быть не разложен — тогда хвоста нет,
|
|
101
|
+
# а причина отказа остаётся прежней.
|
|
102
|
+
# shellcheck disable=SC1090
|
|
103
|
+
[ -f "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/deny-tail.sh" ] \
|
|
104
|
+
&& . "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/deny-tail.sh" 2>/dev/null
|
|
105
|
+
command -v rt_deny_tail >/dev/null 2>&1 || rt_deny_tail() { :; }
|
|
106
|
+
deny_tail_text="$(rt_deny_tail "")"
|
|
107
|
+
[ -n "$deny_tail_text" ] && reason="${reason}
|
|
108
|
+
|
|
109
|
+
${deny_tail_text}"
|
|
110
|
+
|
|
111
|
+
jq -n --arg r "$reason" '{decision:"block",reason:$r}' 2>/dev/null \
|
|
112
|
+
|| printf '{"decision":"block","reason":"turn-exit-guard: замысел записан, а первый этап не начат."}\n'
|
|
113
|
+
exit 0
|
|
114
|
+
fi
|
|
115
|
+
|
|
77
116
|
# То же самое, но объявить это на диске уже нечем: папка задачи разбирается до открытия заявки,
|
|
78
117
|
# и ход работы уезжает вместе с ней. Признак берётся из истории ветки — папка, снятая её
|
|
79
|
-
# коммитом.
|
|
80
|
-
#
|
|
118
|
+
# коммитом.
|
|
119
|
+
#
|
|
120
|
+
# Отпускать по нему ход нельзя: папка снимается ДО заявки, и между этими двумя движениями работа
|
|
121
|
+
# не отдана никому. Ход, попавший в зазор, прежде выходил отсюда нулём — а до отдачи ему
|
|
122
|
+
# оставался ровно один шаг, и он его не сделал. Признак поэтому только снимает требование
|
|
123
|
+
# состояния: дальше ход судится вторым признаком, как всякий другой. Ход, в котором заявку
|
|
124
|
+
# открыли или прочитали, второй признак пропускает сам — вызов клиента хостинга он считает
|
|
125
|
+
# работой.
|
|
81
126
|
folder_archived() {
|
|
82
127
|
[ -n "$branch" ] || return 1
|
|
83
128
|
[ -n "$(git ls-tree -d --name-only HEAD -- "$tasks_dir/$branch" 2>/dev/null | head -1)" ] && return 1
|
|
@@ -89,7 +134,8 @@ folder_archived() {
|
|
|
89
134
|
[ -n "$had" ]
|
|
90
135
|
}
|
|
91
136
|
|
|
92
|
-
|
|
137
|
+
archived=false
|
|
138
|
+
[ -z "$progress" ] && folder_archived && archived=true
|
|
93
139
|
|
|
94
140
|
# Следующий шаг из хода работы — его страж и называет в отказе: исполнитель, которому сказано
|
|
95
141
|
# только «работа не кончена», перечитывает ту же строку сам.
|
|
@@ -101,7 +147,36 @@ next_step=""
|
|
|
101
147
|
# заполняется ход, который встал.
|
|
102
148
|
work_re='git (add|commit|push|checkout|merge|rm)|npm run|pnpm (run|exec)|nx (build|test|run)|gh (pr|issue|api|run)|task:(new|move)|mkdir|cp |mv |rm |sed -i|tee |>>?[[:space:]]*[^|&]'
|
|
103
149
|
|
|
104
|
-
|
|
150
|
+
# Разведка. Те же слова, что и в образце работы, но подкоманда читающая: переключение ветки,
|
|
151
|
+
# подтягивание, просмотр истории, чтение заявок и прогонов. Образец работы называет их работой,
|
|
152
|
+
# потому что знает только первое слово — `git` и `gh` стоят в нём целиком, — и ход, в котором
|
|
153
|
+
# исполнитель перешёл на главную ветку, прочитал историю и написал владельцу отчёт, выходил
|
|
154
|
+
# отсюда нулём. Разбор — `docs/postmortems/2026-08-25-read-only-turn-counted-as-work.md`.
|
|
155
|
+
#
|
|
156
|
+
# Разведка выглядит работой лучше всего остального: в ней команды, числа и точные ответы. Тем
|
|
157
|
+
# она и опасна — ход, набитый ею, читается как полный и владельцем, и самим заходом.
|
|
158
|
+
read_re='^[[:space:]]*(([^[:space:]]*/)?git[[:space:]]+(show|log|ls-tree|ls-files|ls-remote|diff|status|branch|tag|rev-parse|remote|describe|blame|fetch|pull|(checkout|switch)(?![[:space:]]+-[bc][[:space:]]))|([^[:space:]]*/)?gh[[:space:]]+(pr|issue|run|repo)[[:space:]]+(list|view|status|checks|diff|download|logs))([[:space:]]|$)'
|
|
159
|
+
|
|
160
|
+
# Части составной команды судятся по одной: ход собирает чтение и работу в одну строку через
|
|
161
|
+
# `&&`, и суждение целиком отпускало бы разведку по первой же меняющей части.
|
|
162
|
+
part_re='&&|\|\||;|\n'
|
|
163
|
+
|
|
164
|
+
# Ожидание чужого шага. Прогон, разбор владельцем и слияние идут без исполнителя и от взгляда
|
|
165
|
+
# быстрее не становятся — правило прямо говорит, что состоянием работы это не бывает. Судится
|
|
166
|
+
# только ПОСЛЕДНЕЕ действие хода: ожидание в середине законно, а запуск работы в фоне работой
|
|
167
|
+
# остаётся. Разбор — `docs/postmortems/2026-08-25-turn-ended-on-waiting.md`.
|
|
168
|
+
#
|
|
169
|
+
# Прежний признак спрашивал одно: была ли за ход работа. Ход, где разобран конфликт, сделаны
|
|
170
|
+
# коммит и пуш, а последним действием стал цикл до готовности прогона, проходил его целиком —
|
|
171
|
+
# работа была, и много. Именно эта полнота и обманывает: пустоты за таким ходом не видно.
|
|
172
|
+
wait_re='gh[[:space:]]+(run[[:space:]]+watch|pr[[:space:]]+checks[^|]*--watch)|until[[:space:]].*sleep|while[[:space:]].*sleep|^[[:space:]]*sleep[[:space:]]'
|
|
173
|
+
|
|
174
|
+
# Отдача работы и начало следующей. Правило зовёт законным концом хода отданную работу — но с
|
|
175
|
+
# условием: следующая начата, и по ней сделано ДЕЙСТВИЕ, а не сказано.
|
|
176
|
+
handover_re='gh[[:space:]]+pr[[:space:]]+create'
|
|
177
|
+
started_re='task:new|task:move|board\.mjs[[:space:]]+move|git[[:space:]]+checkout[[:space:]]+-b|git[[:space:]]+switch[[:space:]]+-c'
|
|
178
|
+
|
|
179
|
+
verdict="$(tail -n 400 "$transcript" 2>/dev/null | jq -s -r --arg work "$work_re" --arg read "$read_re" --arg part "$part_re" --arg wait "$wait_re" --arg handover "$handover_re" --arg started "$started_re" '
|
|
105
180
|
def is_input:
|
|
106
181
|
.type == "user"
|
|
107
182
|
and (((.message.content // []) | if type == "array"
|
|
@@ -115,7 +190,29 @@ verdict="$(tail -n 400 "$transcript" 2>/dev/null | jq -s -r --arg work "$work_re
|
|
|
115
190
|
| ($uses | map(.name // "") | any(test("^(Edit|Write|MultiEdit|NotebookEdit)$"))) as $edited
|
|
116
191
|
| ($uses | map(.name // "") | any(test("AskUserQuestion"))) as $asked
|
|
117
192
|
| ($uses | map((.input.command // "")) | join("\n")) as $ran
|
|
118
|
-
|
|
193
|
+
# Работой считается часть команды, совпавшая с образцом работы и не совпавшая с образцом
|
|
194
|
+
# разведки: переключение ветки и чтение истории тем же ходом работой не становятся.
|
|
195
|
+
| ([$ran | splits($part)] | map(test($work) and (test($read) | not)) | any) as $ran_work
|
|
196
|
+
# Последнее действие хода. Ожидание чужого шага концом хода не бывает, сколько бы работы ни
|
|
197
|
+
# было раньше: работа остаётся ровно там, где стояла.
|
|
198
|
+
| ([$uses[] | select((.name // "") == "Bash") | (.input.command // "")] | last // "") as $last
|
|
199
|
+
| ($last | test($wait)) as $waited
|
|
200
|
+
# Отдача работы: хвост хода после открытия заявки. Всё, что было до неё, сделано по сданной
|
|
201
|
+
# задаче и о следующей не говорит ничего.
|
|
202
|
+
| ([$uses[] | select((.name // "") == "Bash") | (.input.command // "")]) as $cmds
|
|
203
|
+
| (($cmds | map(test($handover)) | index(true))) as $handover_at
|
|
204
|
+
| ($handover_at != null) as $handed_over
|
|
205
|
+
| (if $handover_at == null then [] else $cmds[$handover_at:] end) as $tail
|
|
206
|
+
| (($tail | map(test($started)) | any)
|
|
207
|
+
or ($uses | map(.name // "") | any(test("^(Edit|Write|MultiEdit|NotebookEdit)$")))) as $started_next
|
|
208
|
+
# ПОСЛЕДНЕЕ ДЕЙСТВИЕ ХОДА — общий признак, из которого частные ярусы ниже только выводят
|
|
209
|
+
# понятный отказ. Девять разборов происшествий за сутки описывают девять разных остановок, и
|
|
210
|
+
# во всех девяти последним действием хода был текст владельцу: отчёт, сводка, объявление
|
|
211
|
+
# намерения. Ярус на каждый вид остановки — гонка без конца: видов столько, сколько бывает
|
|
212
|
+
# поводов заговорить. Признак поэтому один — работой должно быть ПОСЛЕДНЕЕ действие.
|
|
213
|
+
| ([$uses[] | (.name // "")] | last // "") as $last_tool
|
|
214
|
+
| (($last_tool | test("^(Edit|Write|MultiEdit|NotebookEdit)$"))
|
|
215
|
+
or ([$last | splits($part)] | map(test($work) and (test($read) | not)) | any)) as $ended_working
|
|
119
216
|
# Отказ гарда и передача захода — оба кончают ход по правилу.
|
|
120
217
|
| ([$turn[] | select(.type == "user") | .message.content // [] | select(type == "array") | .[]
|
|
121
218
|
| select(.type == "tool_result") | .content
|
|
@@ -129,17 +226,65 @@ verdict="$(tail -n 400 "$transcript" 2>/dev/null | jq -s -r --arg work "$work_re
|
|
|
129
226
|
| if type == "string" then . elif type == "array"
|
|
130
227
|
then (map(if type == "object" then (.text // "") else "" end) | join("\n")) else "" end] | join("\n")) as $said
|
|
131
228
|
| ($said | test("останов|стоп|хватит|подожди|не надо|прерв|отложи")) as $told_stop
|
|
132
|
-
| { worked: ($edited or $ran_work), released: ($asked or $denied or $handed or $told_stop), ran: $ran }
|
|
229
|
+
| { worked: ($edited or $ran_work), released: ($asked or $denied or $handed or $told_stop), waited: $waited, handed_over: $handed_over, started_next: $started_next, ended_working: $ended_working, ran: $ran }
|
|
133
230
|
' 2>/dev/null)"
|
|
134
231
|
|
|
135
232
|
[ -z "$verdict" ] && exit 0
|
|
136
233
|
|
|
137
234
|
worked="$(printf '%s' "$verdict" | jq -r '.worked // false' 2>/dev/null)"
|
|
235
|
+
waited="$(printf '%s' "$verdict" | jq -r '.waited // false' 2>/dev/null)"
|
|
236
|
+
handed_over="$(printf '%s' "$verdict" | jq -r '.handed_over // false' 2>/dev/null)"
|
|
237
|
+
started_next="$(printf '%s' "$verdict" | jq -r '.started_next // false' 2>/dev/null)"
|
|
238
|
+
ended_working="$(printf '%s' "$verdict" | jq -r '.ended_working // false' 2>/dev/null)"
|
|
138
239
|
released="$(printf '%s' "$verdict" | jq -r '.released // false' 2>/dev/null)"
|
|
139
240
|
commands="$(printf '%s' "$verdict" | jq -r '.ran // ""' 2>/dev/null)"
|
|
140
241
|
|
|
141
242
|
[ "$released" = "true" ] && exit 0
|
|
142
243
|
|
|
244
|
+
# Взятая, но не начатая работа. Ветка по номеру задачи заведена, а каталога задачи при ней нет
|
|
245
|
+
# вовсе — значит работа объявлена взятой и не начата ни одной строкой. Ход здесь не кончается, сколько бы
|
|
246
|
+
# работы в нём ни было: заведение ветки, перевод колонки и уборка соседних веток — всё это
|
|
247
|
+
# команды, меняющие дерево, и второй признак отпускает такой ход целиком.
|
|
248
|
+
#
|
|
249
|
+
# Ровно так ход и вставал: задача взята, номер назван владельцу, отчёт написан — и следующее
|
|
250
|
+
# действие состояния «задача-взята», написать замысел, не сделано. Отчёт выглядит работой лучше
|
|
251
|
+
# всякой другой, а страж, знающий только «была ли за ход работа», подтверждает это: работа была.
|
|
252
|
+
# Разбор — `docs/postmortems/2026-08-25-task-taken-and-turn-ended.md`.
|
|
253
|
+
#
|
|
254
|
+
# Папка, разобранная коммитом ветки, сюда не попадает: `archived` означает отданную работу, и её
|
|
255
|
+
# судит прежний ярус. Ветка без номера задачи не судится вовсе — под пробу заводят и такие.
|
|
256
|
+
if [ "$archived" != "true" ] && [ -z "$progress" ] && [ -n "$branch" ] && [ ! -d "$root/$tasks_dir/$branch" ]; then
|
|
257
|
+
task_key="${RT_TASK_KEY:-}"
|
|
258
|
+
if [ -z "$task_key" ] && [ -f "$root/.claude/rt-kit/checks.json" ]; then
|
|
259
|
+
task_key="$(jq -r '.board.taskKey // empty' "$root/.claude/rt-kit/checks.json" 2>/dev/null)"
|
|
260
|
+
fi
|
|
261
|
+
[ -z "$task_key" ] && task_key='[A-Za-z][A-Za-z0-9]*'
|
|
262
|
+
if printf '%s' "$branch" | grep -qE "^${task_key}-[0-9]+-" 2>/dev/null; then
|
|
263
|
+
reason="BLOCKED by turn-exit-guard: работа взята и не начата — ветка \`$branch\` заведена, а папки задачи при ней нет.
|
|
264
|
+
|
|
265
|
+
Заведённая ветка означает состояние «задача-взята», и обязательное действие у него одно — написать замысел. Ход, кончившийся здесь, оставляет работу объявленной и не начатой: номер назван, колонка сдвинута, а на диске нет ни разбора просьбы, ни этапов. Команды заведения ветки и перевода колонки этого не заменяют — ими такой ход и заполняется.
|
|
266
|
+
|
|
267
|
+
npm run task:new -- <номер> # если папки нет вовсе
|
|
268
|
+
|
|
269
|
+
Собери \`$tasks_dir/$branch/\` и напиши замысел этим же ходом.
|
|
270
|
+
|
|
271
|
+
Страж судит один ход: следующий заход не отбивается."
|
|
272
|
+
|
|
273
|
+
# shellcheck disable=SC1090
|
|
274
|
+
[ -f "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/deny-tail.sh" ] \
|
|
275
|
+
&& . "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/deny-tail.sh" 2>/dev/null
|
|
276
|
+
command -v rt_deny_tail >/dev/null 2>&1 || rt_deny_tail() { :; }
|
|
277
|
+
deny_tail_text="$(rt_deny_tail "")"
|
|
278
|
+
[ -n "$deny_tail_text" ] && reason="${reason}
|
|
279
|
+
|
|
280
|
+
${deny_tail_text}"
|
|
281
|
+
|
|
282
|
+
jq -n --arg r "$reason" '{decision:"block",reason:$r}' 2>/dev/null \
|
|
283
|
+
|| printf '{"decision":"block","reason":"turn-exit-guard: работа взята и не начата — напиши замысел."}\n'
|
|
284
|
+
exit 0
|
|
285
|
+
fi
|
|
286
|
+
fi
|
|
287
|
+
|
|
143
288
|
# Контракт этапа. Отметка «этап сделан» — утверждение о дереве, и подтверждается оно выводом
|
|
144
289
|
# команды, а не словами: этап, отмеченный по памяти, через заход неотличим от проверенного.
|
|
145
290
|
# Страж сравнивает номер этапа с тем, что лежит в истории ветки, и на выросшем номере требует
|
|
@@ -192,9 +337,104 @@ ${deny_tail_text}"
|
|
|
192
337
|
fi
|
|
193
338
|
fi
|
|
194
339
|
|
|
340
|
+
# Работа отдана, а следующая только названа. Работы в таком ходе больше, чем в любом другом, — и
|
|
341
|
+
# вся она по сданной задаче: отдача завершает прошлую работу, а не ход. Девять разборов
|
|
342
|
+
# происшествий за сутки описывают девять разных остановок, и во всех девяти последним действием
|
|
343
|
+
# хода был текст владельцу: отчёт, сводка, объявление намерения.
|
|
344
|
+
# Разбор — `docs/postmortems/2026-08-25-handover-turn-ends-on-intent.md`.
|
|
345
|
+
if [ "$handed_over" = "true" ] && [ "$started_next" != "true" ]; then
|
|
346
|
+
reason="BLOCKED by turn-exit-guard: заявка открыта, а по следующей работе за этот ход не сделано ничего.
|
|
347
|
+
|
|
348
|
+
Отданная работа кончает ход только вместе с начатой следующей — по ней должно быть сделано действие, а не сказано. «Беру такую-то» выходом не является: правило зовёт это объявлением намерения.
|
|
349
|
+
|
|
350
|
+
Всё, что было до открытия заявки, сделано по сданной задаче и о следующей не говорит ничего.
|
|
351
|
+
|
|
352
|
+
npm run task:new -- <заголовок> # завести следующую
|
|
353
|
+
git checkout -b <КЛЮЧ>-<номер>-<slug> # взять её в работу
|
|
354
|
+
|
|
355
|
+
Сделай первый шаг по следующей работе этим же ходом. Владелец сказал остановиться — так и напиши: страж читает его слово, а не пересказ.
|
|
356
|
+
|
|
357
|
+
Страж судит один ход: следующий заход не отбивается."
|
|
358
|
+
|
|
359
|
+
# shellcheck disable=SC1090
|
|
360
|
+
[ -f "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/deny-tail.sh" ] \
|
|
361
|
+
&& . "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/deny-tail.sh" 2>/dev/null
|
|
362
|
+
command -v rt_deny_tail >/dev/null 2>&1 || rt_deny_tail() { :; }
|
|
363
|
+
deny_tail_text="$(rt_deny_tail "")"
|
|
364
|
+
[ -n "$deny_tail_text" ] && reason="${reason}
|
|
365
|
+
|
|
366
|
+
${deny_tail_text}"
|
|
367
|
+
|
|
368
|
+
jq -n --arg r "$reason" '{decision:"block",reason:$r}' 2>/dev/null \
|
|
369
|
+
|| printf '{"decision":"block","reason":"turn-exit-guard: работа отдана, а следующая не начата."}\n'
|
|
370
|
+
exit 0
|
|
371
|
+
fi
|
|
372
|
+
|
|
373
|
+
# Ход кончился ожиданием чужого шага. Работа в нём была — тем он и обманчив: полон, и пустоты за
|
|
374
|
+
# ним не видно. Судится последнее действие, а не наличие работы.
|
|
375
|
+
if [ "$waited" = "true" ]; then
|
|
376
|
+
reason="BLOCKED by turn-exit-guard: последним действием хода стало ожидание чужого шага, а оно состоянием работы не бывает.
|
|
377
|
+
|
|
378
|
+
Прогон, разбор владельцем и слияние идут без исполнителя и от взгляда быстрее не становятся. Работы за ход могло быть много — она остаётся ровно там, где стояла, и владелец видит исполнителя стоящим.
|
|
379
|
+
|
|
380
|
+
Следующий шаг записан в ходе работы: ${next_step}
|
|
381
|
+
|
|
382
|
+
Сделай его этим же ходом либо возьми следующую задачу. Ожидание в середине хода законно — отбит именно конец.
|
|
383
|
+
|
|
384
|
+
Страж судит один ход: следующий заход не отбивается."
|
|
385
|
+
|
|
386
|
+
# shellcheck disable=SC1090
|
|
387
|
+
[ -f "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/deny-tail.sh" ] \
|
|
388
|
+
&& . "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/deny-tail.sh" 2>/dev/null
|
|
389
|
+
command -v rt_deny_tail >/dev/null 2>&1 || rt_deny_tail() { :; }
|
|
390
|
+
deny_tail_text="$(rt_deny_tail "")"
|
|
391
|
+
[ -n "$deny_tail_text" ] && reason="${reason}
|
|
392
|
+
|
|
393
|
+
${deny_tail_text}"
|
|
394
|
+
|
|
395
|
+
jq -n --arg r "$reason" '{decision:"block",reason:$r}' 2>/dev/null \
|
|
396
|
+
|| printf '{"decision":"block","reason":"turn-exit-guard: ход кончился ожиданием чужого шага."}\n'
|
|
397
|
+
exit 0
|
|
398
|
+
fi
|
|
399
|
+
|
|
400
|
+
# Общий рубеж. Работа за ход была — но последним действием стал не она, а текст владельцу.
|
|
401
|
+
# Частные ярусы выше называют вид остановки точнее; сюда доходит то, чего они не знают по имени.
|
|
402
|
+
if [ "$worked" = "true" ] && [ "$ended_working" != "true" ]; then
|
|
403
|
+
reason="BLOCKED by turn-exit-guard: работа за ход была, но последним действием хода стала не она.
|
|
404
|
+
|
|
405
|
+
Ход кончается работой, а не рассказом о ней. Отчёт, сводка и объявление намерения выходом не являются: они выглядят завершением тем убедительнее, чем больше сделано, — и ровно на это место встаёт следующее действие.
|
|
406
|
+
|
|
407
|
+
Следующий шаг записан в ходе работы: ${next_step}
|
|
408
|
+
|
|
409
|
+
Сделай его этим же ходом, а сказать о сделанном можно после. Владелец сказал остановиться — так и напиши: страж читает его слово, а не пересказ.
|
|
410
|
+
|
|
411
|
+
Страж судит один ход: следующий заход не отбивается."
|
|
412
|
+
|
|
413
|
+
# shellcheck disable=SC1090
|
|
414
|
+
[ -f "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/deny-tail.sh" ] \
|
|
415
|
+
&& . "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/deny-tail.sh" 2>/dev/null
|
|
416
|
+
command -v rt_deny_tail >/dev/null 2>&1 || rt_deny_tail() { :; }
|
|
417
|
+
deny_tail_text="$(rt_deny_tail "")"
|
|
418
|
+
[ -n "$deny_tail_text" ] && reason="${reason}
|
|
419
|
+
|
|
420
|
+
${deny_tail_text}"
|
|
421
|
+
|
|
422
|
+
jq -n --arg r "$reason" '{decision:"block",reason:$r}' 2>/dev/null \
|
|
423
|
+
|| printf '{"decision":"block","reason":"turn-exit-guard: последним действием хода стала не работа."}\n'
|
|
424
|
+
exit 0
|
|
425
|
+
fi
|
|
426
|
+
|
|
195
427
|
[ "$worked" = "true" ] && exit 0
|
|
196
428
|
|
|
197
|
-
if [
|
|
429
|
+
if [ "$archived" = "true" ]; then
|
|
430
|
+
reason="BLOCKED by turn-exit-guard: папка задачи снята коммитом ветки, а за этот ход не сделано ничего — ни правки, ни команды, меняющей дерево.
|
|
431
|
+
|
|
432
|
+
Снятая папка означает середину отдачи, а не её конец: убирают её ДО того, как открыта заявка, и между этими двумя движениями работу не видит никто, кроме того, кто её сделал. Состояние работы с этой минуты объявить нечем, поэтому ход судится по второму признаку — была ли за него работа.
|
|
433
|
+
|
|
434
|
+
Оставшийся шаг один: открыть заявку черновиком. Заявка уже открыта — возьми следующую задачу: ожидание чужого прогона работой не является и ходом не кончается.
|
|
435
|
+
|
|
436
|
+
Страж судит один ход: следующий заход не отбивается."
|
|
437
|
+
elif [ -z "$state" ]; then
|
|
198
438
|
reason="BLOCKED by turn-exit-guard: за этот ход не сделано ничего — ни правки, ни команды, меняющей дерево. Папки задачи у этой работы нет, и состояние взять неоткуда, но ход это не кончает.
|
|
199
439
|
|
|
200
440
|
Ход кончается четырьмя способами, и других нет: вопрос владельцу, ответа на который в правилах нет; отказ гарда; заполненное окно захода; отданная работа с начатой следующей. Названная и не запущенная команда выходом не является: строка «сейчас запущу» — объявление намерения, а оно прямо названо ложным концом хода.
|
|
@@ -63,12 +63,19 @@ red_re='completed[[:space:]]+failure|"conclusion"[[:space:]]*:[[:space:]]*"failu
|
|
|
63
63
|
# по следующей задаче не считается — она чинит прежнюю, а не двигает работу дальше.
|
|
64
64
|
moved_re='task:new|task:move|checkout[[:space:]]+-b|docs/tasks/'
|
|
65
65
|
|
|
66
|
+
# Чем ход показывает, что отданную работу он довёл до конца, а не бросил черновиком. Снятие
|
|
67
|
+
# черновика — очевидный случай; чтение прогона — тот, где снимать ещё нечего, но исполнитель
|
|
68
|
+
# посмотрел, а не сказал «жду». Две готовые заявки простояли черновиками именно потому, что
|
|
69
|
+
# следующая задача была взята вместо этого, а не сверх этого.
|
|
70
|
+
ready_re='pr[[:space:]]+ready|run[[:space:]]+(list|view|watch)|pr[[:space:]]+checks|check-runs|check:board|board\.mjs'
|
|
71
|
+
|
|
66
72
|
# Ход — это всё, что записано после последнего настоящего ввода владельца. Ответ инструмента
|
|
67
73
|
# приходит той же ролью, поэтому строки с `tool_result` вводом не считаются.
|
|
68
74
|
#
|
|
69
75
|
# Хвост в 400 строк: запись хода растёт всю сессию, а судится только последний ход.
|
|
70
76
|
verdict="$(tail -n 400 "$transcript" 2>/dev/null | jq -s -r \
|
|
71
|
-
--arg opened "$opened_re" --arg moved "$moved_re" --arg read "$read_re" --arg red "$red_re"
|
|
77
|
+
--arg opened "$opened_re" --arg moved "$moved_re" --arg read "$read_re" --arg red "$red_re" \
|
|
78
|
+
--arg ready "$ready_re" '
|
|
72
79
|
def is_input:
|
|
73
80
|
.type == "user"
|
|
74
81
|
and (((.message.content // []) | if type == "array"
|
|
@@ -90,12 +97,41 @@ verdict="$(tail -n 400 "$transcript" 2>/dev/null | jq -s -r \
|
|
|
90
97
|
| ($ran | test($opened; "i")) as $opened_pr
|
|
91
98
|
| (($ran | test($read; "i")) and ($out | test($red; "i"))) as $red_run
|
|
92
99
|
| ($ran | test($moved; "i")) as $went_on
|
|
93
|
-
|
|
|
94
|
-
|
|
100
|
+
| ($ran | test($ready; "i")) as $checked
|
|
101
|
+
| if $opened_pr and ($went_on | not) then "owe:pr"
|
|
102
|
+
elif $opened_pr and ($checked | not) then "owe:draft"
|
|
103
|
+
elif $went_on then "pass"
|
|
95
104
|
elif $red_run then "owe:run"
|
|
96
105
|
else "pass" end
|
|
97
106
|
' 2>/dev/null)"
|
|
98
107
|
|
|
108
|
+
# Черновик, оставленный при взятой следующей задаче, — отдельный отказ: там требование не про
|
|
109
|
+
# следующую задачу, а про доведение отданной.
|
|
110
|
+
if [ "$verdict" = "owe:draft" ]; then
|
|
111
|
+
reason="BLOCKED by waiting-turn-guard: в этом ходе открыт PR, следующая задача взята, а состояние отданной работы не спрошено ни одной командой.
|
|
112
|
+
|
|
113
|
+
Черновик читается владельцем как «работа не кончена»: кнопка слияния у него заблокирована самим хостингом, и по списку заявок готовое от недоделанного не отличить — серое и там и там. Довести отданное до снятого черновика обязан тот, кто его отдал.
|
|
114
|
+
|
|
115
|
+
Спроси прогон на вершине этим же ходом — `gh run list`, `gh pr checks` или сверку очереди работ — и сними черновик, когда он зелёный, а ветка сливается. Прогон ещё идёт — так и скажи владельцу, назвав его вывод.
|
|
116
|
+
|
|
117
|
+
Следующая задача берётся сверх этого, а не вместо: обе готовые заявки простояли черновиками ровно на такой подмене.
|
|
118
|
+
|
|
119
|
+
Гард судит один ход: следующий заход не отбивается."
|
|
120
|
+
|
|
121
|
+
# shellcheck disable=SC1090
|
|
122
|
+
[ -f "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/deny-tail.sh" ] \
|
|
123
|
+
&& . "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/deny-tail.sh" 2>/dev/null
|
|
124
|
+
command -v rt_deny_tail >/dev/null 2>&1 || rt_deny_tail() { :; }
|
|
125
|
+
deny_tail_text="$(rt_deny_tail "")"
|
|
126
|
+
[ -n "$deny_tail_text" ] && reason="${reason}
|
|
127
|
+
|
|
128
|
+
${deny_tail_text}"
|
|
129
|
+
|
|
130
|
+
jq -n --arg r "$reason" '{decision:"block",reason:$r}' 2>/dev/null \
|
|
131
|
+
|| printf '{"decision":"block","reason":"waiting-turn-guard: отданная работа осталась черновиком — спроси прогон и сними черновик."}\n'
|
|
132
|
+
exit 0
|
|
133
|
+
fi
|
|
134
|
+
|
|
99
135
|
case "$verdict" in
|
|
100
136
|
owe:pr) said="в этом ходе открыт PR" ;;
|
|
101
137
|
owe:run) said="в этом ходе прочитан красный прогон" ;;
|