@rt-tools/agent-kit 0.16.1 → 0.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/assets/checks/board-epics.github.mjs +123 -0
- package/assets/checks/board-gh.github.mjs +121 -0
- package/assets/checks/board-paths.github.mjs +88 -0
- package/assets/checks/board-runs.github.mjs +8 -0
- package/assets/checks/board-titles.github.mjs +66 -0
- package/assets/checks/board.github.mjs +77 -73
- package/assets/checks/check-board.github.mjs +56 -7
- package/assets/checks/check-file-size.mjs +10 -2
- package/assets/checks/check-glossary.mjs +170 -0
- package/assets/checks/check-hook-scope.mjs +126 -0
- package/assets/checks/check-profile-drift.mjs +195 -0
- package/assets/checks/check-push-gate.mjs +59 -1
- package/assets/checks/check-schema-drift.mjs +12 -5
- package/assets/checks/check-specs.mjs +1 -1
- package/assets/checks/rt-kit-checks.config.mjs +13 -0
- package/assets/checks/spec-anchors.mjs +2 -2
- package/assets/checks/spec-contract.mjs +9 -0
- package/assets/defaults/gate-map.sh +13 -4
- package/assets/defaults/project.sh +31 -108
- package/assets/defaults/shell.sh +139 -0
- package/assets/docs/GLOSSARY.md +1 -1
- package/assets/hooks/browser-device-id.sh +20 -4
- package/assets/hooks/browser-guard-device-id.sh +5 -2
- package/assets/hooks/browser-guard-no-other-drivers.sh +41 -2
- package/assets/hooks/claim-guard.sh +22 -1
- package/assets/hooks/docs-guard.sh +1 -1
- package/assets/hooks/exam-guard.sh +85 -12
- package/assets/hooks/git-guard-delivery-conflict.sh +85 -0
- package/assets/hooks/git-guard-delivery-folder.sh +19 -0
- package/assets/hooks/git-guard-delivery-signature.sh +14 -5
- package/assets/hooks/git-guard-delivery.sh +54 -2
- package/assets/hooks/git-guard-push-tests.sh +21 -1
- package/assets/hooks/grill-gate-ask.sh +25 -0
- package/assets/hooks/grill-gate.sh +75 -11
- package/assets/hooks/lint-after-edit.sh +44 -16
- package/assets/hooks/proposal-guard.sh +11 -4
- package/assets/hooks/rule-source-guard.sh +8 -13
- package/assets/hooks/skill-gate-layers.sh +7 -0
- package/assets/hooks/skill-gate.sh +29 -0
- package/assets/hooks/task-context-load.sh +32 -0
- package/assets/hooks/task-flow-context.sh +194 -0
- package/assets/hooks/task-flow-draft-guard.sh +109 -0
- package/assets/hooks/task-flow-guard.sh +27 -159
- package/assets/hooks/turn-exit-guard.sh +53 -88
- package/assets/hooks/waiting-turn-guard.sh +65 -2
- package/assets/hooks/window-fill-guard.sh +5 -1
- package/assets/hooks/work-start-guard.sh +166 -0
- package/assets/hooks/write-targets.sh +24 -0
- package/assets/laws/delivery.md +19 -0
- package/assets/laws/frontend-application.md +4 -0
- package/assets/laws/verifiability.md +44 -0
- package/assets/laws/work-conduct.md +15 -0
- package/assets/patterns/browser-verification-measure.md +3 -1
- package/assets/patterns/browser-verification-stand.md +17 -4
- package/assets/patterns/doc-style-write.md +62 -2
- package/assets/patterns/git-workflow-commit.github.md +33 -5
- package/assets/patterns/git-workflow-docker.md +22 -0
- package/assets/patterns/git-workflow-freshness.md +87 -0
- package/assets/patterns/git-workflow-merge.md +41 -0
- package/assets/patterns/git-workflow-migration.md +11 -0
- package/assets/patterns/git-workflow-pr.github.md +12 -1
- package/assets/patterns/git-workflow-restart.md +18 -0
- package/assets/patterns/git-workflow-secrets.md +14 -0
- package/assets/patterns/git-workflow-stack.md +93 -1
- package/assets/patterns/lib-layers-move.md +4 -0
- package/assets/patterns/spec-driven-domain.md +47 -2
- package/assets/patterns/spec-driven-rule.md +16 -4
- package/assets/patterns/spec-driven-sweep.md +57 -0
- package/assets/patterns/status-report-table.github.md +1 -1
- package/assets/patterns/task-flow-archive.md +58 -16
- package/assets/patterns/task-flow-close.md +106 -23
- package/assets/patterns/task-flow-handoff.md +14 -2
- package/assets/patterns/task-flow-resume.md +35 -8
- package/assets/patterns/task-flow-start.md +60 -22
- package/assets/patterns/testing-e2e.md +23 -0
- package/assets/patterns/turn-entry-map.md +1 -1
- package/assets/pitfalls/agent-kit.md +71 -3
- package/assets/pitfalls/doc-style.md +15 -0
- package/assets/pitfalls/git-workflow.github.md +88 -0
- package/assets/pitfalls/spec-driven.md +22 -0
- package/assets/pitfalls/task-flow.md +113 -0
- package/assets/pitfalls/testing.md +6 -0
- package/assets/pitfalls/turn-conduct.md +49 -0
- package/assets/rules/browser-verification.md +58 -0
- package/assets/rules/deploy-flow.azure.md +8 -0
- package/assets/rules/deploy-flow.github.md +27 -0
- package/assets/rules/deploy-flow.gitlab.md +8 -0
- package/assets/rules/doc-style.md +20 -0
- package/assets/rules/git-workflow.azure.md +22 -2
- package/assets/rules/git-workflow.github.md +127 -118
- package/assets/rules/git-workflow.gitlab.md +17 -4
- package/assets/rules/lists.md +5 -0
- package/assets/rules/observability.needs-app.md +4 -0
- package/assets/rules/reuse-first.md +14 -0
- package/assets/rules/shared-code.md +5 -0
- package/assets/rules/spec-driven.md +33 -15
- package/assets/rules/task-flow.md +132 -126
- package/assets/rules/testing.md +41 -2
- package/assets/rules/turn-conduct.md +73 -57
- package/assets/rules/turn-entry.md +6 -0
- package/assets/samples/tasks/_template/grill.md +5 -0
- package/assets/samples/tasks/_template/plan.md +3 -0
- package/assets/skills/agent-kit.md +108 -76
- package/assets/templates/postmortem.md +5 -1
- package/bin/agent-kit.d.ts.map +1 -1
- package/bin/agent-kit.js +30 -6
- package/bin/agent-kit.js.map +1 -1
- package/lib/catalog.d.ts.map +1 -1
- package/lib/catalog.js +2 -1
- package/lib/catalog.js.map +1 -1
- package/lib/commands.d.ts.map +1 -1
- package/lib/commands.js +96 -7
- package/lib/commands.js.map +1 -1
- package/lib/hooks-map.d.ts +26 -0
- package/lib/hooks-map.d.ts.map +1 -1
- package/lib/hooks-map.js +58 -2
- package/lib/hooks-map.js.map +1 -1
- package/lib/sections.d.ts +6 -0
- package/lib/sections.d.ts.map +1 -1
- package/lib/sections.js +19 -0
- package/lib/sections.js.map +1 -1
- package/lib/shipment.d.ts +2 -0
- package/lib/shipment.d.ts.map +1 -1
- package/lib/shipment.fixture.d.ts +5 -0
- package/lib/shipment.fixture.d.ts.map +1 -1
- package/lib/shipment.fixture.js +7 -0
- package/lib/shipment.fixture.js.map +1 -1
- package/lib/shipment.js +13 -1
- package/lib/shipment.js.map +1 -1
- package/lib/sync.d.ts +35 -3
- package/lib/sync.d.ts.map +1 -1
- package/lib/sync.js +59 -8
- package/lib/sync.js.map +1 -1
- package/package.json +1 -1
- package/rt-tools-agent-kit-0.18.0.tgz +0 -0
- package/rt-tools-agent-kit-0.16.1.tgz +0 -0
|
@@ -38,6 +38,27 @@
|
|
|
38
38
|
. "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/hook-input.sh" 2>/dev/null || true
|
|
39
39
|
|
|
40
40
|
rt_hook_read
|
|
41
|
+
|
|
42
|
+
# Отказ печатается одной формой: причина, общий хвост о двух законных ходах и запасная строка
|
|
43
|
+
# на случай, если разборщика нет. Прежде эти двенадцать строк стояли восемью копиями — файл рос
|
|
44
|
+
# на каждом новом ярусе быстрее, чем на самом требовании.
|
|
45
|
+
rt_te_deny() {
|
|
46
|
+
rt_te_reason="$1"
|
|
47
|
+
rt_te_short="$2"
|
|
48
|
+
# shellcheck disable=SC1090
|
|
49
|
+
[ -f "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/deny-tail.sh" ] \
|
|
50
|
+
&& . "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/deny-tail.sh" 2>/dev/null
|
|
51
|
+
command -v rt_deny_tail >/dev/null 2>&1 || rt_deny_tail() { :; }
|
|
52
|
+
rt_te_tail="$(rt_deny_tail "")"
|
|
53
|
+
[ -n "$rt_te_tail" ] && rt_te_reason="${rt_te_reason}
|
|
54
|
+
|
|
55
|
+
${rt_te_tail}"
|
|
56
|
+
|
|
57
|
+
jq -n --arg r "$rt_te_reason" '{decision:"block",reason:$r}' 2>/dev/null \
|
|
58
|
+
|| printf '{"decision":"block","reason":"turn-exit-guard: %s"}\n' "$rt_te_short"
|
|
59
|
+
exit 0
|
|
60
|
+
}
|
|
61
|
+
|
|
41
62
|
input="$RT_HOOK_INPUT"
|
|
42
63
|
[ -z "$input" ] && exit 0
|
|
43
64
|
command -v jq >/dev/null 2>&1 || exit 0
|
|
@@ -97,20 +118,7 @@ if [ "$state" = "замысел-записан" ]; then
|
|
|
97
118
|
|
|
98
119
|
Страж судит один ход: следующий заход не отбивается."
|
|
99
120
|
|
|
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
|
|
121
|
+
rt_te_deny "$reason" "замысел записан, а первый этап не начат."
|
|
114
122
|
fi
|
|
115
123
|
|
|
116
124
|
# То же самое, но объявить это на диске уже нечем: папка задачи разбирается до открытия заявки,
|
|
@@ -145,7 +153,14 @@ next_step=""
|
|
|
145
153
|
|
|
146
154
|
# Команда, меняющая дерево или его состояние. Чтение и поиск сюда не входят намеренно: ими и
|
|
147
155
|
# заполняется ход, который встал.
|
|
148
|
-
|
|
156
|
+
#
|
|
157
|
+
# Перенаправления вывода здесь нет, и снято оно по разбору: страж отбил два хода из семи,
|
|
158
|
+
# простоявших без работы, а пять засчитал работой ровно по нему. Тело будущей заявки писалось во
|
|
159
|
+
# временный каталог захода — вне рабочего дерева, — а одно из ожиданий несло только отвод потока
|
|
160
|
+
# ошибок в устройство пустоты. Ни то ни другое дерева не меняет, а образцу они были неотличимы
|
|
161
|
+
# от записи в рабочий файл: он видел саму стрелку и не спрашивал, куда она ведёт. Признаком
|
|
162
|
+
# остаются правка файла инструментом правки и перечисленные здесь команды.
|
|
163
|
+
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 '
|
|
149
164
|
|
|
150
165
|
# Разведка. Те же слова, что и в образце работы, но подкоманда читающая: переключение ветки,
|
|
151
166
|
# подтягивание, просмотр истории, чтение заявок и прогонов. Образец работы называет их работой,
|
|
@@ -270,18 +285,7 @@ if [ "$archived" != "true" ] && [ -z "$progress" ] && [ -n "$branch" ] && [ ! -d
|
|
|
270
285
|
|
|
271
286
|
Страж судит один ход: следующий заход не отбивается."
|
|
272
287
|
|
|
273
|
-
|
|
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
|
|
288
|
+
rt_te_deny "$reason" "работа взята и не начата — напиши замысел."
|
|
285
289
|
fi
|
|
286
290
|
fi
|
|
287
291
|
|
|
@@ -320,20 +324,7 @@ EOF
|
|
|
320
324
|
Запусти их этим же ходом либо верни прежний номер этапа в ход работы.
|
|
321
325
|
|
|
322
326
|
Страж судит один ход: следующий заход не отбивается."
|
|
323
|
-
|
|
324
|
-
# а причина отказа остаётся прежней.
|
|
325
|
-
# shellcheck disable=SC1090
|
|
326
|
-
[ -f "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/deny-tail.sh" ] \
|
|
327
|
-
&& . "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/deny-tail.sh" 2>/dev/null
|
|
328
|
-
command -v rt_deny_tail >/dev/null 2>&1 || rt_deny_tail() { :; }
|
|
329
|
-
deny_tail_text="$(rt_deny_tail "")"
|
|
330
|
-
[ -n "$deny_tail_text" ] && reason="${reason}
|
|
331
|
-
|
|
332
|
-
${deny_tail_text}"
|
|
333
|
-
|
|
334
|
-
jq -n --arg r "$reason" '{decision:"block",reason:$r}' 2>/dev/null \
|
|
335
|
-
|| printf '{"decision":"block","reason":"turn-exit-guard: закрытый этап не подтверждён выводом команды."}\n'
|
|
336
|
-
exit 0
|
|
327
|
+
rt_te_deny "$reason" "закрытый этап не подтверждён выводом команды."
|
|
337
328
|
fi
|
|
338
329
|
fi
|
|
339
330
|
|
|
@@ -356,18 +347,7 @@ if [ "$handed_over" = "true" ] && [ "$started_next" != "true" ]; then
|
|
|
356
347
|
|
|
357
348
|
Страж судит один ход: следующий заход не отбивается."
|
|
358
349
|
|
|
359
|
-
|
|
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
|
|
350
|
+
rt_te_deny "$reason" "работа отдана, а следующая не начата."
|
|
371
351
|
fi
|
|
372
352
|
|
|
373
353
|
# Ход кончился ожиданием чужого шага. Работа в нём была — тем он и обманчив: полон, и пустоты за
|
|
@@ -383,18 +363,7 @@ if [ "$waited" = "true" ]; then
|
|
|
383
363
|
|
|
384
364
|
Страж судит один ход: следующий заход не отбивается."
|
|
385
365
|
|
|
386
|
-
|
|
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
|
|
366
|
+
rt_te_deny "$reason" "ход кончился ожиданием чужого шага."
|
|
398
367
|
fi
|
|
399
368
|
|
|
400
369
|
# Общий рубеж. Работа за ход была — но последним действием стал не она, а текст владельцу.
|
|
@@ -410,18 +379,26 @@ if [ "$worked" = "true" ] && [ "$ended_working" != "true" ]; then
|
|
|
410
379
|
|
|
411
380
|
Страж судит один ход: следующий заход не отбивается."
|
|
412
381
|
|
|
413
|
-
|
|
414
|
-
|
|
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}
|
|
382
|
+
rt_te_deny "$reason" "последним действием хода стала не работа."
|
|
383
|
+
fi
|
|
419
384
|
|
|
420
|
-
|
|
385
|
+
# Работа, оставшаяся в рабочем дереве. Ветка ушла вперёд своей удалённой ссылки, а заявки при
|
|
386
|
+
# ней в этом ходе не открывали: сделанное лежит там, где его не видит никто, кроме сделавшего.
|
|
387
|
+
# Признак читается без сети и молчит там, где ссылки нет вовсе — под пробу заводят и такие ветки.
|
|
388
|
+
# Разбор — `docs/postmortems/2026-08-25-fifteen-branches-over-one-index.md`.
|
|
389
|
+
if [ "$worked" = "true" ] && [ "$handed_over" != "true" ]; then
|
|
390
|
+
unpushed="$(git -C "$root" rev-list --count '@{u}..HEAD' 2>/dev/null)"
|
|
391
|
+
if [ -n "$unpushed" ] && [ "$unpushed" -gt 0 ] 2>/dev/null; then
|
|
392
|
+
reason="BLOCKED by turn-exit-guard: за ход была работа, но она осталась в рабочем дереве — неотданных коммитов ${unpushed}.
|
|
421
393
|
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
394
|
+
Владелец видит прежнее состояние и читает его как «не сделано ничего». Ходов отсюда два: довести работу до хостинга — запушить ветку и открыть заявку — либо снять сделанное, если оно не нужно.
|
|
395
|
+
|
|
396
|
+
Оставшееся в дереве называется причиной — словом владельца, а не списком остатков.
|
|
397
|
+
|
|
398
|
+
Страж судит один ход: следующий заход не отбивается."
|
|
399
|
+
|
|
400
|
+
rt_te_deny "$reason" "работа осталась в рабочем дереве."
|
|
401
|
+
fi
|
|
425
402
|
fi
|
|
426
403
|
|
|
427
404
|
[ "$worked" = "true" ] && exit 0
|
|
@@ -456,18 +433,6 @@ else
|
|
|
456
433
|
Страж судит один ход: следующий заход не отбивается."
|
|
457
434
|
fi
|
|
458
435
|
|
|
459
|
-
|
|
460
|
-
# а причина отказа остаётся прежней.
|
|
461
|
-
# shellcheck disable=SC1090
|
|
462
|
-
[ -f "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/deny-tail.sh" ] \
|
|
463
|
-
&& . "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/deny-tail.sh" 2>/dev/null
|
|
464
|
-
command -v rt_deny_tail >/dev/null 2>&1 || rt_deny_tail() { :; }
|
|
465
|
-
deny_tail_text="$(rt_deny_tail "")"
|
|
466
|
-
[ -n "$deny_tail_text" ] && reason="${reason}
|
|
467
|
-
|
|
468
|
-
${deny_tail_text}"
|
|
469
|
-
|
|
470
|
-
jq -n --arg r "$reason" '{decision:"block",reason:$r}' 2>/dev/null \
|
|
471
|
-
|| printf '{"decision":"block","reason":"turn-exit-guard: работа не кончена — следующий шаг стоит в ходе работы."}\n'
|
|
436
|
+
rt_te_deny "$reason" "работа не кончена — следующий шаг стоит в ходе работы."
|
|
472
437
|
|
|
473
438
|
exit 0
|
|
@@ -69,13 +69,20 @@ moved_re='task:new|task:move|checkout[[:space:]]+-b|docs/tasks/'
|
|
|
69
69
|
# следующая задача была взята вместо этого, а не сверх этого.
|
|
70
70
|
ready_re='pr[[:space:]]+ready|run[[:space:]]+(list|view|watch)|pr[[:space:]]+checks|check-runs|check:board|board\.mjs'
|
|
71
71
|
|
|
72
|
+
# Своё названное действие. Пустой ход, объявивший, что сделает дальше, ничем себя не выдаёт:
|
|
73
|
+
# заявку он не открывал, прогона не читал, и оба прежних признака молчат. Ловится он формой —
|
|
74
|
+
# набором образцов будущего времени о собственном шаге, — а не пониманием смысла; за словами
|
|
75
|
+
# при этом не должно стоять ни одного вызова, иначе объявление сказано по ходу работы, а не
|
|
76
|
+
# вместо неё.
|
|
77
|
+
vow_re='дальше беру|дальше возьму|дальше иду|следующим шагом|следующий шаг:|затем сделаю|затем возьму|потом сделаю|после этого сделаю|далее беру'
|
|
78
|
+
|
|
72
79
|
# Ход — это всё, что записано после последнего настоящего ввода владельца. Ответ инструмента
|
|
73
80
|
# приходит той же ролью, поэтому строки с `tool_result` вводом не считаются.
|
|
74
81
|
#
|
|
75
82
|
# Хвост в 400 строк: запись хода растёт всю сессию, а судится только последний ход.
|
|
76
83
|
verdict="$(tail -n 400 "$transcript" 2>/dev/null | jq -s -r \
|
|
77
84
|
--arg opened "$opened_re" --arg moved "$moved_re" --arg read "$read_re" --arg red "$red_re" \
|
|
78
|
-
--arg ready "$ready_re" '
|
|
85
|
+
--arg ready "$ready_re" --arg vow "$vow_re" '
|
|
79
86
|
def is_input:
|
|
80
87
|
.type == "user"
|
|
81
88
|
and (((.message.content // []) | if type == "array"
|
|
@@ -98,13 +105,69 @@ verdict="$(tail -n 400 "$transcript" 2>/dev/null | jq -s -r \
|
|
|
98
105
|
| (($ran | test($read; "i")) and ($out | test($red; "i"))) as $red_run
|
|
99
106
|
| ($ran | test($moved; "i")) as $went_on
|
|
100
107
|
| ($ran | test($ready; "i")) as $checked
|
|
101
|
-
|
|
|
108
|
+
| ([$turn[] | select(.type == "assistant") | (.message.content // [])[]
|
|
109
|
+
| select(.type == "text") | .text] | join("\n")) as $said
|
|
110
|
+
| ($said | test($vow; "i")) as $announced
|
|
111
|
+
| ($uses | length) as $tools
|
|
112
|
+
| if $opened_pr and ($went_on | not) and ($checked | not) then "owe:both"
|
|
113
|
+
elif $opened_pr and ($went_on | not) then "owe:pr"
|
|
102
114
|
elif $opened_pr and ($checked | not) then "owe:draft"
|
|
103
115
|
elif $went_on then "pass"
|
|
104
116
|
elif $red_run then "owe:run"
|
|
117
|
+
elif $announced and $tools == 0 then "owe:vow"
|
|
105
118
|
else "pass" end
|
|
106
119
|
' 2>/dev/null)"
|
|
107
120
|
|
|
121
|
+
# Ни одного из двух действий: отказ называет оба разом. Прежде он называл только первое, и
|
|
122
|
+
# снимался тоже первым — исполнитель брал следующую задачу, признак открытой заявки уходил
|
|
123
|
+
# вместе с ходом, и второе требование испарялось, ни разу не прозвучав.
|
|
124
|
+
if [ "$verdict" = "owe:both" ]; then
|
|
125
|
+
reason="BLOCKED by waiting-turn-guard: в этом ходе открыт PR, а по отданной работе не сделано ни одного из двух действий — ни состояние её не спрошено, ни следующая задача не взята.
|
|
126
|
+
|
|
127
|
+
Действий именно два, и снять отказ одним нельзя: отданное доводится до снятого черновика тем, кто его отдал, а следующая берётся сверх этого, а не вместо. Взятая следующая уносит признак открытой заявки с собой — второе требование после неё не прозвучит уже никогда.
|
|
128
|
+
|
|
129
|
+
gh run list # состояние отданного
|
|
130
|
+
npm run task:new -- <заголовок> # следующая работа
|
|
131
|
+
|
|
132
|
+
Гард судит один ход: следующий заход не отбивается."
|
|
133
|
+
|
|
134
|
+
# shellcheck disable=SC1090
|
|
135
|
+
[ -f "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/deny-tail.sh" ] \
|
|
136
|
+
&& . "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/deny-tail.sh" 2>/dev/null
|
|
137
|
+
command -v rt_deny_tail >/dev/null 2>&1 || rt_deny_tail() { :; }
|
|
138
|
+
deny_tail_text="$(rt_deny_tail "")"
|
|
139
|
+
[ -n "$deny_tail_text" ] && reason="${reason}
|
|
140
|
+
|
|
141
|
+
${deny_tail_text}"
|
|
142
|
+
|
|
143
|
+
jq -n --arg r "$reason" '{decision:"block",reason:$r}' 2>/dev/null \
|
|
144
|
+
|| printf '{"decision":"block","reason":"waiting-turn-guard: по отданной работе не сделано ни одного из двух действий."}\n'
|
|
145
|
+
exit 0
|
|
146
|
+
fi
|
|
147
|
+
|
|
148
|
+
# Ход, объявивший своё следующее действие и не сделавший по нему ничего. Отказ называет само
|
|
149
|
+
# объявление: исполнитель, которому сказано «ход пуст», перепишет слова, а не сделает шаг.
|
|
150
|
+
if [ "$verdict" = "owe:vow" ]; then
|
|
151
|
+
reason="BLOCKED by waiting-turn-guard: за ход не сделано ни одной правки и не позвана ни одна команда, а следующее действие названо словами — «дальше беру», «следующим шагом», «затем сделаю».
|
|
152
|
+
|
|
153
|
+
Объявление своего же шага работой не бывает: оно точнее всякого обещания и пустоты за ним не видно никому. Сделай названное этим же ходом — либо скажи владельцу, что работу останавливает, и назови, чем именно.
|
|
154
|
+
|
|
155
|
+
Гард судит один ход: следующий заход не отбивается."
|
|
156
|
+
|
|
157
|
+
# shellcheck disable=SC1090
|
|
158
|
+
[ -f "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/deny-tail.sh" ] \
|
|
159
|
+
&& . "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/deny-tail.sh" 2>/dev/null
|
|
160
|
+
command -v rt_deny_tail >/dev/null 2>&1 || rt_deny_tail() { :; }
|
|
161
|
+
deny_tail_text="$(rt_deny_tail "")"
|
|
162
|
+
[ -n "$deny_tail_text" ] && reason="${reason}
|
|
163
|
+
|
|
164
|
+
${deny_tail_text}"
|
|
165
|
+
|
|
166
|
+
jq -n --arg r "$reason" '{decision:"block",reason:$r}' 2>/dev/null \
|
|
167
|
+
|| printf '{"decision":"block","reason":"waiting-turn-guard: следующее действие названо словами, а за ход не сделано ничего."}\n'
|
|
168
|
+
exit 0
|
|
169
|
+
fi
|
|
170
|
+
|
|
108
171
|
# Черновик, оставленный при взятой следующей задаче, — отдельный отказ: там требование не про
|
|
109
172
|
# следующую задачу, а про доведение отданной.
|
|
110
173
|
if [ "$verdict" = "owe:draft" ]; then
|
|
@@ -180,7 +180,11 @@ reason="BLOCKED by window-fill-guard: заполнение окна ${pct}% (${f
|
|
|
180
180
|
2. Закоммить проверенное: незакоммиченное не переживёт перерыв.
|
|
181
181
|
3. Напиши передачу в ${handoff_dir}/ и отдай владельцу путь к ней — что в неё входит, говорит паттерн task-flow-handoff.
|
|
182
182
|
|
|
183
|
-
|
|
183
|
+
Что после порога проходит:
|
|
184
|
+
- правка ${tasks_dir}/** и ${handoff_dir}/**, чтение любого файла, вопрос владельцу;
|
|
185
|
+
- команда, начинающаяся со слова поставки или сверки: git, клиент хостинга, перевод колонки, npm run check:*.
|
|
186
|
+
|
|
187
|
+
Команда судится по началу строки: вход в каталог перед ней снимает совпадение, и отбит будет тот же коммит, который прошёл бы без него. Начинай команду с самого слова поставки."
|
|
184
188
|
|
|
185
189
|
# Общий хвост отказа: два законных хода и законная форма обхода, если она у отказа есть.
|
|
186
190
|
# Файл может быть не разложен — тогда хвоста нет, а причина отказа остаётся прежней.
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# rt-hook: Stop
|
|
3
|
+
# Требует: hooks/deny-tail.sh, hooks/profile-check.sh
|
|
4
|
+
# Гард начала работы: ход, правивший код приложения, не заканчивается, пока владелец в этом же
|
|
5
|
+
# ходе о работе не просил. Stop.
|
|
6
|
+
#
|
|
7
|
+
# Зачем именно так. Статья «заход работу не начинает сам» держится памятью исполнителя, и
|
|
8
|
+
# держится плохо: заход, открывшийся после чистки контекста, получает от хука запуска состояние
|
|
9
|
+
# незаконченной работы и замысел с этапами — и оба говорят, что делать, если работать. О том,
|
|
10
|
+
# работать ли, не говорит ни один. Строка с путём к файлу, присланная владельцем, прочитана как
|
|
11
|
+
# поручение, и заход правит полсотни файлов, которых у него никто не просил.
|
|
12
|
+
#
|
|
13
|
+
# Соседние гарды этого не ловят, и не по недосмотру: каждый судит своё. Гард замысла требует
|
|
14
|
+
# замысел на диске — он лежит; гард эпика отбивает чужую задачу — задача своя; гард разговора
|
|
15
|
+
# судит ход, в котором задан вопрос, — а вопроса не задали ровно потому, что решили не
|
|
16
|
+
# спрашивать. Все три судят предмет правки и её порядок, и ни один не спрашивает, кто эту
|
|
17
|
+
# правку заказал.
|
|
18
|
+
#
|
|
19
|
+
# Просьба ловится формой, а не смыслом. Смысл машине не виден, и гард, взявшийся его понимать,
|
|
20
|
+
# отбивал бы работу по настроению; поэтому судится обратное — то, что просьбой не бывает ни при
|
|
21
|
+
# каком прочтении: пустая реплика, одно слово и путь к файлу. Всё остальное считается просьбой,
|
|
22
|
+
# и это выбрано намеренно: ложный отказ здесь стоит дороже пропуска — он останавливает работу,
|
|
23
|
+
# которую владелец заказал.
|
|
24
|
+
#
|
|
25
|
+
# Код приложения узнаётся признаком дерева `rt_is_app_code` — тем же, которым его узнаёт гард
|
|
26
|
+
# замысла. Дерево, признака не объявившее, этого гарда не получает: судить ему нечем.
|
|
27
|
+
#
|
|
28
|
+
# ОТКАЗ В ПОЛЬЗУ РАБОТЫ: нет `jq`, нет записи хода, нет признака дерева, повторный заход, любая
|
|
29
|
+
# своя ошибка — ход РАЗРЕШАЕТСЯ (exit 0). Сломанный гард не имеет права заклинить работу.
|
|
30
|
+
|
|
31
|
+
. "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/utf8.sh" 2>/dev/null || true
|
|
32
|
+
. "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/hook-input.sh" 2>/dev/null || true
|
|
33
|
+
|
|
34
|
+
rt_hook_read
|
|
35
|
+
input="$RT_HOOK_INPUT"
|
|
36
|
+
[ -z "$input" ] && exit 0
|
|
37
|
+
|
|
38
|
+
command -v jq >/dev/null 2>&1 || exit 0
|
|
39
|
+
|
|
40
|
+
# Повторный заход по тому же ходу не судится: гард сказал своё один раз и отпускает.
|
|
41
|
+
active="$(printf '%s' "$input" | jq -r '.stop_hook_active // false' 2>/dev/null)"
|
|
42
|
+
[ "$active" = "true" ] && exit 0
|
|
43
|
+
|
|
44
|
+
transcript="$(printf '%s' "$input" | jq -r '.transcript_path // empty' 2>/dev/null)"
|
|
45
|
+
[ -z "$transcript" ] && exit 0
|
|
46
|
+
[ -f "$transcript" ] || exit 0
|
|
47
|
+
|
|
48
|
+
rt_hooks_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
49
|
+
|
|
50
|
+
# Профиль дерева: сперва умолчание пакета, поверх него — надстройка проекта, если она есть.
|
|
51
|
+
for profile in "$rt_hooks_dir/../rt-kit/defaults/project.sh" "$rt_hooks_dir/../defaults/project.sh" \
|
|
52
|
+
"${CLAUDE_PROJECT_DIR:-.}/.claude/rt-kit/defaults/project.sh" "${CLAUDE_PROJECT_DIR:-.}/.claude/rt-kit/project.sh"; do
|
|
53
|
+
# shellcheck disable=SC1090
|
|
54
|
+
[ -f "$profile" ] && . "$profile" 2>/dev/null
|
|
55
|
+
done
|
|
56
|
+
|
|
57
|
+
# shellcheck disable=SC1090
|
|
58
|
+
[ -f "$rt_hooks_dir/profile-check.sh" ] && . "$rt_hooks_dir/profile-check.sh"
|
|
59
|
+
command -v rt_needs >/dev/null 2>&1 || rt_needs() { command -v "$1" >/dev/null 2>&1; }
|
|
60
|
+
rt_needs rt_is_app_code work-start-guard || exit 0
|
|
61
|
+
|
|
62
|
+
# Ход — это всё, что записано после последнего настоящего ввода владельца. Ответ инструмента
|
|
63
|
+
# приходит той же ролью, поэтому строки с `tool_result` вводом не считаются.
|
|
64
|
+
#
|
|
65
|
+
# Хвост в 400 строк: запись хода растёт всю сессию, а судится только последний ход.
|
|
66
|
+
asked="$(tail -n 400 "$transcript" 2>/dev/null | jq -s -r '
|
|
67
|
+
def is_input:
|
|
68
|
+
.type == "user"
|
|
69
|
+
and (((.message.content // []) | if type == "array"
|
|
70
|
+
then ([.[] | select(.type == "tool_result")] | length)
|
|
71
|
+
else 0 end) == 0);
|
|
72
|
+
|
|
73
|
+
(map(is_input) | rindex(true)) as $i
|
|
74
|
+
| if $i == null then "" else
|
|
75
|
+
(.[$i].message.content
|
|
76
|
+
| if type == "string" then .
|
|
77
|
+
elif type == "array" then (map(if type == "object" then (.text // "") else tostring end) | join("\n"))
|
|
78
|
+
else "" end)
|
|
79
|
+
end
|
|
80
|
+
' 2>/dev/null)"
|
|
81
|
+
|
|
82
|
+
# Ввода в записи нет вовсе — судить нечего: ход разрешается.
|
|
83
|
+
[ -z "$asked" ] && exit 0
|
|
84
|
+
|
|
85
|
+
# Правленные за ход файлы: инструменты правки называют путь полем, а команда оболочки — своим
|
|
86
|
+
# разбором, и его здесь нет намеренно. Гард судит явную правку файла: команда, пишущая в код
|
|
87
|
+
# мимо инструмента правки, остаётся его известной границей.
|
|
88
|
+
edited="$(tail -n 400 "$transcript" 2>/dev/null | jq -s -r '
|
|
89
|
+
def is_input:
|
|
90
|
+
.type == "user"
|
|
91
|
+
and (((.message.content // []) | if type == "array"
|
|
92
|
+
then ([.[] | select(.type == "tool_result")] | length)
|
|
93
|
+
else 0 end) == 0);
|
|
94
|
+
|
|
95
|
+
(map(is_input) | rindex(true)) as $i
|
|
96
|
+
| (if $i == null then [] else .[$i:] end)
|
|
97
|
+
| [.[] | select(.type == "assistant") | (.message.content // [])[]
|
|
98
|
+
| select(.type == "tool_use")
|
|
99
|
+
| select(.name == "Edit" or .name == "Write" or .name == "MultiEdit" or .name == "NotebookEdit")
|
|
100
|
+
| (.input.file_path // .input.notebook_path // empty)]
|
|
101
|
+
| unique | .[]
|
|
102
|
+
' 2>/dev/null)"
|
|
103
|
+
|
|
104
|
+
[ -z "$edited" ] && exit 0
|
|
105
|
+
|
|
106
|
+
touched=""
|
|
107
|
+
while IFS= read -r path; do
|
|
108
|
+
[ -z "$path" ] && continue
|
|
109
|
+
if rt_is_app_code "$path" 2>/dev/null; then
|
|
110
|
+
touched="$path"
|
|
111
|
+
break
|
|
112
|
+
fi
|
|
113
|
+
done <<EOF
|
|
114
|
+
$edited
|
|
115
|
+
EOF
|
|
116
|
+
|
|
117
|
+
# Код приложения за ход не правился: гард молчит. Разбор, тексты и обвязка идут своим ходом —
|
|
118
|
+
# требовать слова владельца на них значило бы запретить разведку до просьбы.
|
|
119
|
+
[ -z "$touched" ] && exit 0
|
|
120
|
+
|
|
121
|
+
# Просьбой не бывает ни при каком прочтении: пустая реплика, одно слово, путь к файлу. Судится
|
|
122
|
+
# первая непустая строка ввода: развёрнутая просьба своей первой строкой уже просьба, а путь,
|
|
123
|
+
# присланный один, ею не станет и дальше.
|
|
124
|
+
first="$(printf '%s' "$asked" | tr -d '\r' | sed -n '/[^[:space:]]/{p;q;}')"
|
|
125
|
+
words="$(printf '%s' "$asked" | tr -s '[:space:]' '\n' | grep -c '[^[:space:]]' 2>/dev/null || echo 0)"
|
|
126
|
+
|
|
127
|
+
case "$first" in
|
|
128
|
+
# Служебная отметка о прерывании: своей просьбы в ней нет.
|
|
129
|
+
'[Request interrupted'*) kind="прерывание" ;;
|
|
130
|
+
*)
|
|
131
|
+
if [ "$words" -le 1 ] 2>/dev/null; then
|
|
132
|
+
case "$first" in
|
|
133
|
+
*/*) kind="путь к файлу" ;;
|
|
134
|
+
*) kind="одно слово" ;;
|
|
135
|
+
esac
|
|
136
|
+
else
|
|
137
|
+
kind=""
|
|
138
|
+
fi
|
|
139
|
+
;;
|
|
140
|
+
esac
|
|
141
|
+
|
|
142
|
+
# Просьба в ходе есть: работа заказана, и гард отпускает.
|
|
143
|
+
[ -z "$kind" ] && exit 0
|
|
144
|
+
|
|
145
|
+
reason="BLOCKED by work-start-guard: за ход правился код приложения — «${touched}», — а последняя реплика владельца просьбой не была: ${kind}.
|
|
146
|
+
|
|
147
|
+
Заход работу не начинает сам. Передача прошлого захода, состояние задачи из хука запуска и назначенный эпик говорят, что делать, если работать, и молчат о том, работать ли. Строка с адресом называет файл, а не действие: прочитанная как поручение, она даёт заходу задание, которого владелец не давал.
|
|
148
|
+
|
|
149
|
+
Ход отсюда один: назови владельцу состояние работы и спроси, продолжать ли, — и дождись ответа. Сделанное этим ходом не откатывается само: скажи, что уже правлено.
|
|
150
|
+
|
|
151
|
+
Гард судит один ход: следующий заход не отбивается."
|
|
152
|
+
|
|
153
|
+
# Общий хвост отказа: два законных хода. Файл может быть не разложен — тогда хвоста нет,
|
|
154
|
+
# а причина отказа остаётся прежней.
|
|
155
|
+
# shellcheck disable=SC1090
|
|
156
|
+
[ -f "$rt_hooks_dir/deny-tail.sh" ] && . "$rt_hooks_dir/deny-tail.sh" 2>/dev/null
|
|
157
|
+
command -v rt_deny_tail >/dev/null 2>&1 || rt_deny_tail() { :; }
|
|
158
|
+
deny_tail_text="$(rt_deny_tail "")"
|
|
159
|
+
[ -n "$deny_tail_text" ] && reason="${reason}
|
|
160
|
+
|
|
161
|
+
${deny_tail_text}"
|
|
162
|
+
|
|
163
|
+
jq -n --arg r "$reason" '{decision:"block",reason:$r}' 2>/dev/null \
|
|
164
|
+
|| printf '{"decision":"block","reason":"work-start-guard: код правился, а просьбы владельца в этом ходе не было."}\n'
|
|
165
|
+
|
|
166
|
+
exit 0
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Цели записи, названные командой оболочки прямо: перенаправление, `tee`, правка на месте,
|
|
3
|
+
# копирование поверх. Печатает по одной в строке.
|
|
4
|
+
#
|
|
5
|
+
# Строки `# rt-hook:` здесь нет намеренно: это помощник, а не хук. Он ничего не решает сам и
|
|
6
|
+
# зовётся оттуда, где судят правку файла, — гардом места правки и гардом экзамена. Общий он
|
|
7
|
+
# потому, что признак записи у обоих один и тот же: разойдясь, две копии пропустили бы разные
|
|
8
|
+
# формы записи, и заметить это было бы нечем.
|
|
9
|
+
#
|
|
10
|
+
# Общий признак записи здесь не берётся намеренно — он широк, в нём и имя интерпретатора, и
|
|
11
|
+
# запуск разложенной проверки читался бы как правка её самой. Снятие копии сюда тоже не входит:
|
|
12
|
+
# снятый файл раскладка кладёт заново, и так чинят копию, которую переписал форматтер.
|
|
13
|
+
|
|
14
|
+
rt_write_targets() {
|
|
15
|
+
tr "\"'\`" ' ' \
|
|
16
|
+
| sed -E 's/>>?/\n>/g' \
|
|
17
|
+
| sed -nE '
|
|
18
|
+
s/^>[[:space:]]*([^[:space:]|&;]+).*/\1/p
|
|
19
|
+
s/(^|.*[[:space:]])tee[[:space:]]+(-a[[:space:]]+)?([^[:space:]|&;]+).*/\3/p
|
|
20
|
+
s/(^|.*[[:space:]])sed[[:space:]]+-i[[:space:]]+([^[:space:]]+[[:space:]]+)*([^[:space:]|&;]+)$/\3/p
|
|
21
|
+
s/(^|.*[[:space:]])(cp|mv|install)[[:space:]]+([^[:space:]]+[[:space:]]+)+([^[:space:]|&;]+).*/\4/p
|
|
22
|
+
' \
|
|
23
|
+
| sort -u
|
|
24
|
+
}
|
package/assets/laws/delivery.md
CHANGED
|
@@ -27,6 +27,20 @@
|
|
|
27
27
|
Правка одного рода остаётся одной задачей, сколько бы файлов она ни задела; объём захода
|
|
28
28
|
говорит, какого размера задачу заводить среди тех, что делятся законно, и делить неделимое не
|
|
29
29
|
даёт.
|
|
30
|
+
- **Работы, идущие одна за другой, ветвятся одна от другой.** Ветка следующей отводится от
|
|
31
|
+
предыдущей, а не от главной: тогда правка предыдущей лежит в общем предке, и сводить два разных
|
|
32
|
+
изменения одного файла уже не нужно. Ветки, заведённые от главной подряд, друг о друге не знают,
|
|
33
|
+
и первое же слияние делает остальные расходящимися — тем вернее, чем ближе работы по предмету:
|
|
34
|
+
расходятся не сами правки, а строки, куда обе дописали. Цена не исчезает, а переносится: платит
|
|
35
|
+
её тот, кто ветвится, один раз — вместо того, кто вливает, столько раз, сколько заявок открыто.
|
|
36
|
+
От главной ветвится первая работа череды и всякая, не связанная с предыдущей.
|
|
37
|
+
- **Череда веток отдаётся снизу вверх, и порядок называется владельцу.** Заявка каждой стоит на
|
|
38
|
+
предыдущей, а не на главной, поэтому влитая не по порядку тащит за собой всё, что под ней.
|
|
39
|
+
Порядок известен тому, кто ветвился, и не виден тому, кто вливает: несказанный, он читается как
|
|
40
|
+
его отсутствие.
|
|
41
|
+
- **Ветка, стоящая под другой, историю не переписывает.** Силовая отправка нижней делает вершину
|
|
42
|
+
верхней достижимой из её основания, и хостинг закрывает заявку верхней как слитую — при том что
|
|
43
|
+
в главной ветке её правок нет. Расхождение чинится вливанием, а не переносом истории.
|
|
30
44
|
- **Правка самой поставки проверяется её прогоном, а не рассуждением.** Проверить её иначе нечем:
|
|
31
45
|
она исполняется только там, где выкатывает, и в среде, которой на месте работы нет — с чужими
|
|
32
46
|
правами, чужим хранилищем ключей и чужой сетью. «Проверю после слияния» решением исполнителя не
|
|
@@ -60,6 +74,11 @@
|
|
|
60
74
|
- **Признак режима исполнения объявлен в самом артефакте развёртывания, а не только в составе его
|
|
61
75
|
запуска.** Артефакт поднимают и мимо состава — руками, при разборе, на чужой машине, — и без
|
|
62
76
|
объявления он в этот момент считает себя отладочным, не сказав об этом ничего.
|
|
77
|
+
- **Артефакт развёртывания несёт всё, чем настраивается запускаемое им.** Сборка проверяет, что
|
|
78
|
+
артефакт собрался, а не что его собственные шаги запуска исполнимы: недостающий файл настройки
|
|
79
|
+
не виден ни линтеру, ни сборке, ни шагу сборки образа в конвейере — он отказывает на первом
|
|
80
|
+
подъёме, то есть уже там, куда выкатили. Файл, лежащий рядом в репозитории, внутрь артефакта
|
|
81
|
+
сам не попадает.
|
|
63
82
|
- **Выкатывается образ того коммита, который выкатывают.** Умолчание «последний» отстаёт от
|
|
64
83
|
главной ветки, и приложение молча возвращается к прежней версии, продолжая отвечать.
|
|
65
84
|
- **Из одного и того же коммита всегда ставятся одни и те же зависимости.** Если версия задана
|
|
@@ -18,6 +18,10 @@
|
|
|
18
18
|
- **Оформление берётся из общего набора значений, а не пишется числом на месте.** Написанное
|
|
19
19
|
числом расходится с остальным при первой же смене оформления, и найти все такие места
|
|
20
20
|
нечем.
|
|
21
|
+
- **Управляющий элемент не меняет размер, пока идёт начатая им работа.** Готовые наборы
|
|
22
|
+
элементов на время работы подменяют подпись и значок индикатором ожидания, и элемент
|
|
23
|
+
сжимается до собственных отступов: он прыгает, сдвигает соседей, а нажатие, которое человек
|
|
24
|
+
уже начал, уходит мимо.
|
|
21
25
|
- **У каждого класса в разметке есть своё правило стилей.** Класс без правила выглядит
|
|
22
26
|
рабочим и молча ничего не делает.
|
|
23
27
|
- **Набор записей приходит с сервера страницей, а не целиком.** Список, растущий вместе с
|
|
@@ -16,6 +16,17 @@
|
|
|
16
16
|
человек его видит, лежит всё, чего проверка не касалась.
|
|
17
17
|
- **Тест, выключенный признаком окружения, покрытием не считается.** В обычном прогоне он не
|
|
18
18
|
исполняется ни разу, а в сводке выглядит так же, как исполненный.
|
|
19
|
+
- **Тест, снимающий себя по тому, что застал на экране, покрытием не считается.** Набор, идущий
|
|
20
|
+
одной сессией, несёт состояние из теста в тест, и проверка, начинающаяся с «нужного на экране
|
|
21
|
+
нет — пропускаю», снимает себя от чужой правки, а не от своего окружения. В сводке это одна
|
|
22
|
+
строка про пропуск, а стоит за ней целая возможность.
|
|
23
|
+
- **Тест приводит экран в нужное ему состояние сам и возвращает общее состояние таким, каким
|
|
24
|
+
застал.** Иначе снявшийся тест держится ровно до следующей правки соседа, а прогон называет
|
|
25
|
+
ноль падений и в тот день, когда проверка не исполнялась ни разу.
|
|
26
|
+
- **Проговорённый список проверок проверкой не бывает.** Он называет замысел, а не то, что
|
|
27
|
+
ушло в команду, и оба текста пишутся одним ходом, не сверяясь друг с другом: список, названный
|
|
28
|
+
пункт за пунктом, от короткого «я проверил» отличается только длиной. Проверка — чтение
|
|
29
|
+
полученного: вывода команды, отданного текста, состояния после вызова.
|
|
19
30
|
- **Упоминание в тесте сценария, которого нет, — отказ.** Так ловится переименованный или
|
|
20
31
|
выкинутый сценарий: без этого он пропадает молча.
|
|
21
32
|
- **Работающее приложение проверяется там, где его видит пользователь.** Отладочный режим ведёт
|
|
@@ -37,6 +48,16 @@
|
|
|
37
48
|
Сообщение о готовности говорит лишь, что служба себя объявила: та, которой не досталось ни
|
|
38
49
|
одного задания, выглядит в нём точно так же, как работающая. Проверяются обе стороны связи — что
|
|
39
50
|
заказчик выбирает именно её и что задание через неё прошло.
|
|
51
|
+
- **У обмена спрашивают обе стороны, и сторона, которой нет, называется прямо.** Односторонний
|
|
52
|
+
обмен выглядит рабочим с обеих сторон: отправляющая получает успех на каждый вызов, а того, что
|
|
53
|
+
прочитать результат нечем, не видно ниоткуда — молчание об отсутствующей стороне неотличимо от
|
|
54
|
+
работающего обмена. Поэтому у обмена спрашивают не «прошёл ли вызов», а «читает ли кто-нибудь
|
|
55
|
+
вторую сторону», и ответ «нечем» записывается словом, а не остаётся пробелом.
|
|
56
|
+
- **Значение, объявленное одной стороной обмена, второй не пересчитывается, а берётся у первой.**
|
|
57
|
+
Две копии одного счёта расходятся молча, и обе стороны при этом отвечают успехом: одна шлёт под
|
|
58
|
+
одним значением, другая ищет под другим, и не находит ничего ни разу. Что именно объявлено —
|
|
59
|
+
признак, форма записи, способ счёта ключа — берётся у объявившей стороны целиком, а не
|
|
60
|
+
повторяется по её описанию.
|
|
40
61
|
- **Причина отказа, на которой строится решение, подтверждается измерением, а не правдоподобием.**
|
|
41
62
|
Объяснение, пришедшее первым, объясняет наблюдаемое не хуже верного: свойство среды и
|
|
42
63
|
собственный промах выглядят в отказе одинаково, и разводит их только замер, поставленный так,
|
|
@@ -65,3 +86,26 @@
|
|
|
65
86
|
- **Решение, зависящее от текущего момента, получает момент снаружи.** Иначе проверить его можно
|
|
66
87
|
только подкруткой часов, а подкрученные часы действуют и на всё, что оказалось рядом: проверка
|
|
67
88
|
начинает зависеть от того, что к ней отношения не имеет.
|
|
89
|
+
- **Признак проверки выбирается по вопросу, на который отвечают, а не по тому, что задела
|
|
90
|
+
правка.** Признак, снятый с предмета правки, отвечает про неё же: правка собирала адрес — и
|
|
91
|
+
наличие адреса в отданной странице подтверждает, что приложение его собрало, а не что по нему
|
|
92
|
+
что-то приедет. Спрашивают при этом «показывается ли», и разницы между двумя ответами не видно
|
|
93
|
+
ни в одном выводе: оба выглядят как «проверено». Признак поэтому называется вместе с вопросом,
|
|
94
|
+
и первым проверяется, что он на этот вопрос отвечает.
|
|
95
|
+
- **Чужой отказ называет своё состояние, а не причину на своей стороне.** Ответ внешней службы
|
|
96
|
+
говорит, чем кончился запрос у неё; что лежит в настройке, которую она читала, он не говорит
|
|
97
|
+
никогда — и достроенное из него утверждение выглядит выводом из наблюдения, которого не было.
|
|
98
|
+
Значение, о котором идёт речь, читается там, где оно лежит, а решение, выведенное из
|
|
99
|
+
непроверенной причины, наружу не выносится вовсе: оно просит чинить не то.
|
|
100
|
+
- **Про прошлое поведение отвечает код той версии, о которой спрашивают.** Тело правки говорит,
|
|
101
|
+
что она сменила, и молчит о том, что было верно до неё: условие, в которое дописано ещё одно
|
|
102
|
+
обязательное значение, гасит показанное раньше — и видно это только сравнением с версией до
|
|
103
|
+
правки, а не чтением самой правки. Слово того, кто видел прежнее поведение своими глазами, —
|
|
104
|
+
показание: расхождение между ним и текстом в истории разбирается чтением кода на его стороне
|
|
105
|
+
расхождения.
|
|
106
|
+
- **Проверка, снятая с набора ради того, чтобы прогон прошёл, работу не подтверждает.** Набор
|
|
107
|
+
собирается умолчанием и надстройкой дерева, и выкушенная надстройкой проверка ничем не отличима
|
|
108
|
+
от той, которой в дереве нет вовсе: прогон зелен потому, что её никто не звал. Соседняя статья
|
|
109
|
+
судит место, выведенное из-под проверки, — здесь выведено не место, а вся она, и следа в коде
|
|
110
|
+
не остаётся никакого. Расхождение закрывается тем, на что проверка указала, а несогласие с ней
|
|
111
|
+
самой — вопрос к владельцу.
|