@rt-tools/agent-kit 0.14.0 → 0.16.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.
Files changed (90) hide show
  1. package/README.md +17 -0
  2. package/assets/checks/archive-age.mjs +107 -0
  3. package/assets/checks/archive-prune.mjs +46 -0
  4. package/assets/checks/check-archive-age.mjs +42 -0
  5. package/assets/checks/check-board.github.mjs +16 -0
  6. package/assets/checks/check-descriptions.mjs +123 -0
  7. package/assets/checks/check-dupes.mjs +31 -3
  8. package/assets/checks/check-file-size.mjs +47 -2
  9. package/assets/checks/check-turn-map.mjs +20 -3
  10. package/assets/checks/lib-common.mjs +12 -1
  11. package/assets/checks/lib-domains.mjs +1 -1
  12. package/assets/checks/rt-kit-checks.config.mjs +24 -1
  13. package/assets/checks/spec-anchors.mjs +18 -3
  14. package/assets/checks/spec-common.mjs +5 -1
  15. package/assets/defaults/project.sh +22 -17
  16. package/assets/defaults/turn-map.md +15 -19
  17. package/assets/docs/GLOSSARY.md +52 -58
  18. package/assets/hooks/rule-article.sh +12 -0
  19. package/assets/hooks/skill-gate.sh +5 -4
  20. package/assets/hooks/task-flow-guard.sh +20 -0
  21. package/assets/hooks/turn-exit-guard.sh +229 -4
  22. package/assets/laws/delivery.md +92 -104
  23. package/assets/laws/frontend-application.md +4 -0
  24. package/assets/laws/project-documentation.md +64 -68
  25. package/assets/laws/verifiability.md +32 -33
  26. package/assets/laws/work-conduct.md +167 -157
  27. package/assets/patterns/doc-style-sweep.md +1 -1
  28. package/assets/patterns/doc-style-trace.md +1 -1
  29. package/assets/patterns/git-workflow-commit.azure.md +1 -1
  30. package/assets/patterns/git-workflow-commit.github.md +7 -1
  31. package/assets/patterns/git-workflow-commit.gitlab.md +1 -1
  32. package/assets/patterns/git-workflow-docker.md +1 -1
  33. package/assets/patterns/git-workflow-merge.md +14 -3
  34. package/assets/patterns/git-workflow-pr.azure.md +1 -1
  35. package/assets/patterns/git-workflow-pr.github.md +1 -1
  36. package/assets/patterns/git-workflow-pr.gitlab.md +1 -1
  37. package/assets/patterns/git-workflow-restart.md +1 -1
  38. package/assets/patterns/git-workflow-secrets.md +1 -1
  39. package/assets/patterns/git-workflow-stack.md +93 -0
  40. package/assets/patterns/seo-page.md +1 -1
  41. package/assets/patterns/spec-driven-rule.md +55 -0
  42. package/assets/patterns/status-report-table.github.md +88 -0
  43. package/assets/patterns/task-flow-archive.md +3 -4
  44. package/assets/patterns/task-flow-close.md +6 -1
  45. package/assets/patterns/task-flow-start.md +17 -5
  46. package/assets/patterns/ts-procedure.md +1 -1
  47. package/assets/pitfalls/doc-style.md +5 -0
  48. package/assets/pitfalls/git-workflow.github.md +47 -0
  49. package/assets/pitfalls/task-flow.md +28 -0
  50. package/assets/pitfalls/testing.md +14 -0
  51. package/assets/pitfalls/turn-conduct.md +33 -0
  52. package/assets/rules/angular-patterns.md +1 -1
  53. package/assets/rules/api-layer.md +3 -3
  54. package/assets/rules/browser-verification.md +15 -1
  55. package/assets/rules/dependencies.md +1 -1
  56. package/assets/rules/deploy-flow.azure.md +1 -1
  57. package/assets/rules/deploy-flow.github.md +1 -1
  58. package/assets/rules/deploy-flow.gitlab.md +1 -1
  59. package/assets/rules/doc-style.md +18 -0
  60. package/assets/rules/entity-conventions.needs-admin.md +1 -1
  61. package/assets/rules/entity-models.md +1 -1
  62. package/assets/rules/git-workflow.azure.md +1 -1
  63. package/assets/rules/git-workflow.github.md +154 -181
  64. package/assets/rules/git-workflow.gitlab.md +1 -1
  65. package/assets/rules/lib-layers.md +1 -1
  66. package/assets/rules/observability.needs-app.md +1 -1
  67. package/assets/rules/platform-access.md +1 -1
  68. package/assets/rules/reuse-first.md +1 -1
  69. package/assets/rules/seo.md +4 -3
  70. package/assets/rules/shared-code.md +1 -1
  71. package/assets/rules/spec-driven.md +68 -1
  72. package/assets/rules/status-report.md +97 -0
  73. package/assets/rules/styling-bem.md +12 -0
  74. package/assets/rules/task-flow.md +102 -100
  75. package/assets/rules/testing.md +67 -66
  76. package/assets/rules/turn-conduct.md +146 -105
  77. package/assets/rules/turn-entry.md +7 -1
  78. package/assets/rules/typescript-conventions.md +1 -1
  79. package/assets/skills/agent-kit-extend.md +1 -1
  80. package/assets/skills/agent-kit.md +18 -1
  81. package/bin/agent-kit.d.ts.map +1 -1
  82. package/bin/agent-kit.js +25 -0
  83. package/bin/agent-kit.js.map +1 -1
  84. package/lib/cost.d.ts +44 -0
  85. package/lib/cost.d.ts.map +1 -0
  86. package/lib/cost.js +181 -0
  87. package/lib/cost.js.map +1 -0
  88. package/package.json +1 -1
  89. package/rt-tools-agent-kit-0.16.0.tgz +0 -0
  90. package/rt-tools-agent-kit-0.14.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,6 +76,43 @@ 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
118
  # коммитом.
@@ -108,7 +147,36 @@ next_step=""
108
147
  # заполняется ход, который встал.
109
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:]]*[^|&]'
110
149
 
111
- verdict="$(tail -n 400 "$transcript" 2>/dev/null | jq -s -r --arg work "$work_re" '
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" '
112
180
  def is_input:
113
181
  .type == "user"
114
182
  and (((.message.content // []) | if type == "array"
@@ -122,7 +190,29 @@ verdict="$(tail -n 400 "$transcript" 2>/dev/null | jq -s -r --arg work "$work_re
122
190
  | ($uses | map(.name // "") | any(test("^(Edit|Write|MultiEdit|NotebookEdit)$"))) as $edited
123
191
  | ($uses | map(.name // "") | any(test("AskUserQuestion"))) as $asked
124
192
  | ($uses | map((.input.command // "")) | join("\n")) as $ran
125
- | ($ran | test($work)) as $ran_work
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
126
216
  # Отказ гарда и передача захода — оба кончают ход по правилу.
127
217
  | ([$turn[] | select(.type == "user") | .message.content // [] | select(type == "array") | .[]
128
218
  | select(.type == "tool_result") | .content
@@ -136,17 +226,65 @@ verdict="$(tail -n 400 "$transcript" 2>/dev/null | jq -s -r --arg work "$work_re
136
226
  | if type == "string" then . elif type == "array"
137
227
  then (map(if type == "object" then (.text // "") else "" end) | join("\n")) else "" end] | join("\n")) as $said
138
228
  | ($said | test("останов|стоп|хватит|подожди|не надо|прерв|отложи")) as $told_stop
139
- | { 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 }
140
230
  ' 2>/dev/null)"
141
231
 
142
232
  [ -z "$verdict" ] && exit 0
143
233
 
144
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)"
145
239
  released="$(printf '%s' "$verdict" | jq -r '.released // false' 2>/dev/null)"
146
240
  commands="$(printf '%s' "$verdict" | jq -r '.ran // ""' 2>/dev/null)"
147
241
 
148
242
  [ "$released" = "true" ] && exit 0
149
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
+
150
288
  # Контракт этапа. Отметка «этап сделан» — утверждение о дереве, и подтверждается оно выводом
151
289
  # команды, а не словами: этап, отмеченный по памяти, через заход неотличим от проверенного.
152
290
  # Страж сравнивает номер этапа с тем, что лежит в истории ветки, и на выросшем номере требует
@@ -199,6 +337,93 @@ ${deny_tail_text}"
199
337
  fi
200
338
  fi
201
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
+
202
427
  [ "$worked" = "true" ] && exit 0
203
428
 
204
429
  if [ "$archived" = "true" ]; then
@@ -5,123 +5,111 @@
5
5
  приложение перестаёт отвечать, а причина этого выясняется по истории.
6
6
 
7
7
  ## Статьи
8
-
9
8
  - **Правка начинается с задачи, видимой в очереди работ.** Заведённой задачи мало: ту, что в
10
9
  очередь не попала, никто не видит, и работа за ней не планировалась.
11
- - **Правка попадает в главную ветку только через отдельную ветку.** Прямая запись в главную
12
- лишает правку и обсуждения, и возможности откатить её одним движением.
10
+ - **Правка попадает в главную ветку только через отдельную ветку.** Прямая запись в главную лишает
11
+ правку и обсуждения, и возможности откатить её одним движением.
13
12
  - **В очереди работ стоят задачи, а не PR о них.** У задачи и её PR один номер и одна судьба,
14
- поэтому вторая карточка о той же работе ничего не добавляет — она удваивает очередь и врёт о
15
- её длине. Читают очередь затем, чтобы увидеть, что сделано и что осталось; PR отвечает на
16
- другой вопрос как именно сделано, и попадают в него из карточки задачи, где связь с ним
17
- и так стоит. Карточка PR при этом живёт своей жизнью: закрывается позже задачи, висит в
18
- очереди после слияния и остаётся в ней навсегда, потому что колонки под неё нет.
13
+ поэтому вторая карточка о той же работе ничего не добавляет — она удваивает очередь и врёт о её
14
+ длине. Очередь читают затем, чтобы видеть сделанное и оставшееся; PR отвечает на другой вопрос и
15
+ открывается из карточки задачи, где связь с ним и так стоит. Карточка PR живёт своей жизнью:
16
+ висит в очереди после слияния навсегда, потому что колонки под неё нет.
19
17
  - **Проверка не гоняет того, что правка не может сломать.** Набор, одинаковый для любой правки,
20
- выглядит строгим, а работает наоборот: прогон, который длится вдесятеро дольше нужного, учат
21
- не ждать, а обходить. Состав набора выводится из состава правки — из того, что она задела, а
22
- не из того, кем она названа; правка, не тронувшая ни строки кода, не собирает образов и не
23
- снимает кадров. Пропущенное при этом называется пропущенным: молча выпавший шаг читается как
24
- пройденный.
25
- - **У задачи одна ветка, у ветки одна задача.** Откат снимает всё, что въехало этой веткой,
26
- разом: две задачи в ней откатятся только вместе, а задача, въехавшая двумя ветками, после
27
- отката одной останется наполовину сделанной и в очереди работ этого не видно. Работа,
28
- которая в одну ветку не влезает, делится на задачи до того, как ветка заводится. Признак
29
- деления раздельный откат, а не объём: числа файлов, строк или коммитов, за которым работа
30
- становится двумя задачами, нет. Правка одного рода остаётся одной задачей, сколько бы файлов
31
- она ни задела. Объём захода исполнителя признаком деления не является тоже: он говорит, какого
32
- размера задачу стоит заводить среди тех, что делятся законно, и не даёт делить то, что
33
- откатывается только вместе.
34
- - **Правка самой поставки проверяется её прогоном, а не рассуждением.** Проверить её иначе
35
- нечем: она исполняется только там, где выкатывает, и в среде, которой на месте работы нет — с
36
- чужими правами, чужим хранилищем ключей и чужой сетью. «Проверю после слияния» решением
37
- исполнителя не бывает: за этими словами стоит выкатка, которой уже не будет, если правка
38
- окажется неверной. Отложить проверку может только владелец, и он говорит это словами.
39
- - **Две задачи, которые чинятся одной правкой, одна задача.** Вторая стирается вместе со
40
- своим номером, а то, чего в первой не было, дописывается в неё до этого. Две строки об
41
- одной работе хуже дыры в нумерации: по ним потом не понять, что сделано, а что нет. Слить
42
- их можно, пока правка не въехала в главную ветку; после обе остаются как есть.
43
- - **Задача, ветка под неё и PR о сделанном несут один и тот же номер в своих названиях.**
44
- Иначе одну работу приходится узнавать по тексту названия, а в списке из полусотни строк это
45
- делается по памяти и с ошибками.
46
- - **Номер пишется всюду одинаково: ключ задач, дефис, номер.** Заголовок задачи и PR
47
- начинается с этой пары в квадратных скобках, имя ветки с неё же. Одна форма, а не три
48
- похожих, потому что номер читают не только глазами: из имени ветки его достаёт гард, из
49
- заголовка сверка очереди. Формы, выведенные порознь, расходятся молча и не отказывают, а
50
- перестают узнавать номер: проверка, которая должна была найти работу без задачи, пропускает
51
- всё подряд.
52
- - **Ключ задач дерево называет само, но назвать обязано.** В форме имени это единственное, что
53
- у каждого дерева своё, и единственное, что настраивается. Не названный ключ не даёт ни
54
- поблажки, ни умолчания: работа с очередью отказывает и говорит, где он задаётся. Пустой ключ
55
- хуже отсутствующей проверки от имени остаётся огрызок, которому ничто не отвечает, и
56
- правильно названной не выглядит ни одна задача.
57
- - **У задачи есть исполнитель с момента её заведения.** Задача без исполнителя выглядит
58
- ничьей: по очереди работ не видно, кто её взял, и заведённая по ходу правка теряется среди
59
- чужих.
60
- - **Состояние задачи в очереди работ отвечает тому, что с ней происходит.** Взятая в работу
61
- видна взятой, а та, PR по которой ждёт разбора, ждущей разбора. Иначе очередь показывает
62
- один и тот же вид у нетронутого, у делаемого прямо сейчас и у сделанного: работа берётся
63
- второй раз, а PR стоит неразобранным, пока про него не вспомнят. Состояние переставляется
64
- в тот момент, когда работа переходит на следующий шаг, а не приводится в порядок потом:
65
- очередь читают между этими моментами, а не после них.
66
- - **Попадание правки в главную ветку означает выкатку.** Всё, от чего правка зависит снаружи
67
- кода — переменные окружения, секреты, записи имён, — ставится до этого момента, а не после.
68
- - **Признак режима исполнения объявлен в самом артефакте развёртывания, а не только в составе
69
- его запуска.** Артефакт поднимают и мимо состава — руками, при разборе, на чужой машине, — и
70
- без объявления он в этот момент считает себя отладочным, не сказав об этом ничего.
18
+ выглядит строгим, а работает наоборот: прогон, который длится вдесятеро дольше нужного, учат не
19
+ ждать, а обходить. Состав набора выводится из состава правки — из того, что она задела, а не из
20
+ того, кем она названа; правка, не тронувшая ни строки кода, не собирает образов и не снимает
21
+ кадров. Пропущенное при этом называется пропущенным: молча выпавший шаг читается как пройденный.
22
+ - **У задачи одна ветка, у ветки одна задача.** Откат снимает всё, что въехало этой веткой, разом:
23
+ две задачи в ней откатятся только вместе, а задача, въехавшая двумя ветками, после отката одной
24
+ останется наполовину сделанной — и в очереди работ этого не видно. Работа, которая в одну ветку
25
+ не влезает, делится на задачи до заведения ветки. Признак деления раздельный откат, а не
26
+ объём: числа файлов, строк или коммитов, за которым работа становится двумя задачами, нет.
27
+ Правка одного рода остаётся одной задачей, сколько бы файлов она ни задела; объём захода
28
+ говорит, какого размера задачу заводить среди тех, что делятся законно, и делить неделимое не
29
+ даёт.
30
+ - **Правка самой поставки проверяется её прогоном, а не рассуждением.** Проверить её иначе нечем:
31
+ она исполняется только там, где выкатывает, и в среде, которой на месте работы нет — с чужими
32
+ правами, чужим хранилищем ключей и чужой сетью. «Проверю после слияния» решением исполнителя не
33
+ бывает: за этими словами стоит выкатка, которой уже не будет, если правка окажется неверной.
34
+ Отложить проверку может только владелец, и он говорит это словами.
35
+ - **Две задачи, которые чинятся одной правкой, одна задача.** Вторая стирается вместе со своим
36
+ номером, а то, чего в первой не было, дописывается в неё до этого. Две строки об одной работе
37
+ хуже дыры в нумерации: по ним потом не понять, что сделано, а что нет. Слить их можно, пока
38
+ правка не въехала в главную ветку; после обе остаются как есть.
39
+ - **Задача, ветка под неё и PR о сделанном несут один и тот же номер в своих названиях.** Иначе
40
+ одну работу приходится узнавать по тексту названия, а в списке из полусотни строк это делается
41
+ по памяти и с ошибками.
42
+ - **Номер пишется всюду одинаково: ключ задач, дефис, номер.** Заголовок задачи и PR начинается с
43
+ этой пары в квадратных скобках, имя ветки — с неё же. Одна форма, а не три похожих, потому что
44
+ номер читают не только глазами: из имени ветки его достаёт гард, из заголовка — сверка очереди.
45
+ Формы, выведенные порознь, расходятся молча и не отказывают, а перестают узнавать номер:
46
+ проверка, которая должна была найти работу без задачи, пропускает всё подряд.
47
+ - **Ключ задач дерево называет само, но назвать обязано.** В форме имени это единственное, что у
48
+ каждого дерева своё, и единственное, что настраивается. Не названный ключ не даёт ни поблажки,
49
+ ни умолчания: работа с очередью отказывает и говорит, где он задаётся. Пустой ключ хуже
50
+ отсутствующей проверки от имени остаётся огрызок, которому ничто не отвечает, и правильно
51
+ названной не выглядит ни одна задача.
52
+ - **У задачи есть исполнитель с момента её заведения.** Задача без исполнителя выглядит ничьей: по
53
+ очереди работ не видно, кто её взял, и заведённая по ходу правка теряется среди чужих.
54
+ - **Состояние задачи в очереди работ отвечает тому, что с ней происходит.** Взятая в работу видна
55
+ взятой, ждущая разбора ждущей. Иначе нетронутое, делаемое и сделанное выглядят одинаково:
56
+ работа берётся второй раз, а PR стоит неразобранным. Состояние переставляется в тот момент,
57
+ когда работа переходит на следующий шаг: очередь читают между этими моментами, а не после них.
58
+ - **Попадание правки в главную ветку означает выкатку.** Всё, от чего правка зависит снаружи кода
59
+ переменные окружения, секреты, записи имён, ставится до этого момента, а не после.
60
+ - **Признак режима исполнения объявлен в самом артефакте развёртывания, а не только в составе его
61
+ запуска.** Артефакт поднимают и мимо состава руками, при разборе, на чужой машине, — и без
62
+ объявления он в этот момент считает себя отладочным, не сказав об этом ничего.
71
63
  - **Выкатывается образ того коммита, который выкатывают.** Умолчание «последний» отстаёт от
72
64
  главной ветки, и приложение молча возвращается к прежней версии, продолжая отвечать.
73
65
  - **Из одного и того же коммита всегда ставятся одни и те же зависимости.** Если версия задана
74
- диапазоном, установка сегодня и установка через неделю дадут разный код: сборка сломается
75
- сама собой, и откатывать будет нечего. Обновление зависимости — обычная правка: у неё есть
76
- автор, описание и откат.
66
+ диапазоном, установка сегодня и установка через неделю дадут разный код: сборка сломается сама
67
+ собой, и откатывать будет нечего. Обновление зависимости — обычная правка: у неё есть автор,
68
+ описание и откат.
77
69
  - **Порядок изменений хранилища проверяется с пустого места.** На уже работающем хранилище
78
70
  неверный порядок незаметен: он проявляется только при развёртывании с нуля.
79
- - **Проверка перед отправкой смотрит на содержимое репозитория, а не на состояние машины, где
80
- она запущена.** На машине законно лежат недоделки, личные настройки и файлы вне истории.
81
- Проверка, которая их читает, отбивает правку из-за того, чего в репозитории нет, и молчит о
82
- том, что в нём есть. Проверка перед отправкой и конвейер выкатки судят по одному и тому же —
83
- иначе «сошлось» значит в этих двух местах разное.
84
- - **Документ едет вместе с правкой, которую он описывает.** Ни сборка, ни проверки текстов
85
- не читают, поэтому расхождение копится молча и потом выглядит действующей справкой.
71
+ - **Проверка перед отправкой смотрит на содержимое репозитория, а не на состояние машины, где она
72
+ запущена.** На машине законно лежат недоделки, личные настройки и файлы вне истории. Проверка,
73
+ которая их читает, отбивает правку из-за того, чего в репозитории нет, и молчит о том, что в нём
74
+ есть. Проверка перед отправкой и конвейер выкатки судят по одному и тому же — иначе «сошлось»
75
+ значит в этих двух местах разное.
76
+ - **Документ едет вместе с правкой, которую он описывает.** Ни сборка, ни проверки текстов не
77
+ читают, поэтому расхождение копится молча и потом выглядит действующей справкой.
86
78
  - **Работа, меняющая код, кончается открытым PR.** PR — единственное место, где человек видит
87
- правку целиком, отвечает на неё и вливает её; коммит в ветке и запушенная ветка этого места
88
- не заменяют. Пока PR не открыт, работа сделанной не считается, сколько бы её ни было в
89
- истории ветки: разбор по ней невозможен, а человек о ней не знает. Открывается PR тем же
90
- ходом, которым исполнитель говорит, что работу отдаёт, — а не следующим заходом и не по
91
- напоминанию.
92
- - **PR, не готовый к слиянию, помечается черновиком.** Открытый PR читается как приглашение
93
- влить, и человек нажимает слияние, не спрашивая, кончилась ли работа. Черновик разводит два
94
- состояния, которые иначе выглядят одинаково: правка выложена на обозрение и правка готова
95
- поехать в главную ветку. Помечается им всё, что ждёт прогона, доработки или ответа на
96
- вопрос; вопрос при этом задаётся в самом PR, а не остаётся в голове исполнителя.
79
+ правку целиком, отвечает на неё и вливает; коммит и запушенная ветка его не заменяют. Пока PR не
80
+ открыт, работа сделанной не считается: разбора по ней нет, а человек о ней не знает. Открывается
81
+ PR тем же ходом, которым исполнитель говорит, что работу отдаёт, — а не следующим заходом и не
82
+ по напоминанию.
83
+ - **PR, не готовый к слиянию, помечается черновиком.** Открытый PR читается как приглашение влить,
84
+ и человек нажимает слияние, не спрашивая, кончилась ли работа. Черновик разводит два состояния,
85
+ которые иначе выглядят одинаково: правка выложена на обозрение и правка готова поехать в
86
+ главную ветку. Помечается им всё, что ждёт прогона, доработки или ответа на вопрос; вопрос при
87
+ этом задаётся в самом PR, а не остаётся в голове исполнителя.
97
88
  - **Снятие черновика — отдельный ход, и им исполнитель отвечает за готовность.** Черновик
98
- снимается тогда, когда проверки пройдены, доработок не осталось и работа сходится с тем,
99
- ради чего заводилась задача. Пока он стоит, молчание исполнителя значит «ещё не готово», и
100
- человек ничего не должен переспрашивать; после снятия оно значит «можно вливать», и цена
101
- ошибки здесь — правка в главной ветке.
102
- - **PR о сделанном остаётся верным до самого слияния.** Он описывает дерево на день, когда
103
- его написали, а разбора ждёт днями: за это время главная ветка вливается в ветку, и
104
- утверждение PR о соседних файлах становится неправдой молча — тел PR не читает ни
105
- одна проверка. Всё, что вливается в ветку после публикации PR, — повод перечитать его.
106
- - **PR в главную ветку вливает человек.** Слияние — последний момент, когда разбор ещё
107
- возможен: после него правка стоит в главной ветке, работа ушла к следующей задаче, и
108
- вернуться к ней уже некому. Исполнитель работы вливает свой PR только по прямому слову
109
- человека и только про названный PR; молчание разрешением не бывает, а слово, сказанное об
110
- одном PR, на следующий не переносится. Иначе разбор проходит тот, кого разбирают, и
111
- очередь PR выглядит разобранной, не будучи ею.
89
+ снимается тогда, когда проверки пройдены, доработок не осталось и работа сходится с тем, ради
90
+ чего заводилась задача. Пока он стоит, молчание исполнителя значит «ещё не готово», и человек
91
+ ничего не должен переспрашивать; после снятия оно значит «можно вливать», и цена ошибки здесь —
92
+ правка в главной ветке.
93
+ - **PR о сделанном остаётся верным до самого слияния.** Он описывает дерево на день, когда его
94
+ написали, а разбора ждёт днями: за это время главная ветка вливается в ветку, и утверждение PR о
95
+ соседних файлах становится неправдой молча — тел PR не читает ни одна проверка. Всё, что
96
+ вливается в ветку после публикации PR, — повод перечитать его.
97
+ - **PR в главную ветку вливает человек.** Слияние — последний момент, когда разбор ещё возможен:
98
+ после него правка стоит в главной ветке, работа ушла к следующей задаче, и вернуться к ней уже
99
+ некому. Исполнитель работы вливает свой PR только по прямому слову человека и только про
100
+ названный PR; молчание разрешением не бывает, а слово, сказанное об одном PR, на следующий не
101
+ переносится. Иначе разбор проходит тот, кого разбирают, и очередь PR выглядит разобранной, не
102
+ будучи ею.
112
103
  - **Требование, стоящее перед необратимым шагом, стоит там, где этот шаг совершают.** Гард на
113
- машине исполнителя судит его команды и молчит о том же действии, совершённом кнопкой у
114
- хостинга: обход выходит не намеренным, а незамеченным — нажавший не знает, что чего-то не
115
- хватало. Требование либо переносится туда, где нажимают, либо объявляется тому, кто нажимает,
116
- до нажатия. Иначе оно держится не собой, а тем, что необратимый шаг каждый раз делает тот же
117
- человек: папка закрытой задачи так и уехала в главную ветку вместе со своей договорённостью, и
118
- вынимать её пришлось отдельной задачей.
104
+ машине исполнителя судит его команды и молчит о том же действии, совершённом кнопкой у хостинга:
105
+ обход выходит не намеренным, а незамеченным — нажавший не знает, что чего-то не хватало.
106
+ Требование либо переносится туда, где нажимают, либо объявляется тому, кто нажимает, до нажатия.
107
+ Иначе оно держится не собой, а тем, что необратимый шаг каждый раз делает тот же человек.
119
108
  - **Слияние в главную ветку ещё не означает, что правка доехала.** Отказ выкатки не трогает ни
120
109
  задачу, ни очередь работ, поэтому расхождение главной ветки с тем, что работает, обязано быть
121
110
  видно там, где очередь читают. Иначе следующие работы вливаются поверх поломки, которую не
122
111
  приносили, и каждая выглядит доехавшей.
123
- - **Состоявшаяся поломка разбирается записью, которая переживает задачу.** Починка уезжает
124
- веткой, задача закрывается — и причина, по которой приложение встало, остаётся знанием одного
125
- исполнителя. Запись называет, что сломалось, чем это стало видно и почему починка чинит
126
- причину, а не признак; живёт она среди описаний состоявшегося, а не там, что умирает вместе с
127
- задачей.
112
+ - **Состоявшаяся поломка разбирается записью, которая переживает задачу.** Починка уезжает веткой,
113
+ задача закрывается — и причина, по которой приложение встало, остаётся знанием одного
114
+ исполнителя. Запись называет, что сломалось, чем это стало видно и почему починка чинит причину,
115
+ а не признак; живёт она среди описаний состоявшегося, а не там, что умирает вместе с задачей.
@@ -5,6 +5,10 @@
5
5
 
6
6
  ## Статьи
7
7
 
8
+ - **У действия человека есть путь в интерфейсе.** Команда на сервере — путь того, кто держит
9
+ сервер: у неё свой доступ, своя оболочка и свой узел. Приложение, у которого обычное действие
10
+ делается заходом на сервер, интерфейса этому действию не дало, и наличие такой команды его
11
+ наличия не заменяет.
8
12
  - **Состояние экрана пересчитывается само, а не по команде.** Пересчёт вручную рано или
9
13
  поздно пропускают, и экран показывает прежнее значение рядом с новым.
10
14
  - **Шаблон показывает готовое, а не вычисляет.** Вычисление в шаблоне повторяется на каждой