@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.
Files changed (136) hide show
  1. package/assets/checks/board-epics.github.mjs +123 -0
  2. package/assets/checks/board-gh.github.mjs +121 -0
  3. package/assets/checks/board-paths.github.mjs +88 -0
  4. package/assets/checks/board-runs.github.mjs +8 -0
  5. package/assets/checks/board-titles.github.mjs +66 -0
  6. package/assets/checks/board.github.mjs +77 -73
  7. package/assets/checks/check-board.github.mjs +56 -7
  8. package/assets/checks/check-file-size.mjs +10 -2
  9. package/assets/checks/check-glossary.mjs +170 -0
  10. package/assets/checks/check-hook-scope.mjs +126 -0
  11. package/assets/checks/check-profile-drift.mjs +195 -0
  12. package/assets/checks/check-push-gate.mjs +59 -1
  13. package/assets/checks/check-schema-drift.mjs +12 -5
  14. package/assets/checks/check-specs.mjs +1 -1
  15. package/assets/checks/rt-kit-checks.config.mjs +13 -0
  16. package/assets/checks/spec-anchors.mjs +2 -2
  17. package/assets/checks/spec-contract.mjs +9 -0
  18. package/assets/defaults/gate-map.sh +13 -4
  19. package/assets/defaults/project.sh +31 -108
  20. package/assets/defaults/shell.sh +139 -0
  21. package/assets/docs/GLOSSARY.md +1 -1
  22. package/assets/hooks/browser-device-id.sh +20 -4
  23. package/assets/hooks/browser-guard-device-id.sh +5 -2
  24. package/assets/hooks/browser-guard-no-other-drivers.sh +41 -2
  25. package/assets/hooks/claim-guard.sh +22 -1
  26. package/assets/hooks/docs-guard.sh +1 -1
  27. package/assets/hooks/exam-guard.sh +85 -12
  28. package/assets/hooks/git-guard-delivery-conflict.sh +85 -0
  29. package/assets/hooks/git-guard-delivery-folder.sh +19 -0
  30. package/assets/hooks/git-guard-delivery-signature.sh +14 -5
  31. package/assets/hooks/git-guard-delivery.sh +54 -2
  32. package/assets/hooks/git-guard-push-tests.sh +21 -1
  33. package/assets/hooks/grill-gate-ask.sh +25 -0
  34. package/assets/hooks/grill-gate.sh +75 -11
  35. package/assets/hooks/lint-after-edit.sh +44 -16
  36. package/assets/hooks/proposal-guard.sh +11 -4
  37. package/assets/hooks/rule-source-guard.sh +8 -13
  38. package/assets/hooks/skill-gate-layers.sh +7 -0
  39. package/assets/hooks/skill-gate.sh +29 -0
  40. package/assets/hooks/task-context-load.sh +32 -0
  41. package/assets/hooks/task-flow-context.sh +194 -0
  42. package/assets/hooks/task-flow-draft-guard.sh +109 -0
  43. package/assets/hooks/task-flow-guard.sh +27 -159
  44. package/assets/hooks/turn-exit-guard.sh +53 -88
  45. package/assets/hooks/waiting-turn-guard.sh +65 -2
  46. package/assets/hooks/window-fill-guard.sh +5 -1
  47. package/assets/hooks/work-start-guard.sh +166 -0
  48. package/assets/hooks/write-targets.sh +24 -0
  49. package/assets/laws/delivery.md +19 -0
  50. package/assets/laws/frontend-application.md +4 -0
  51. package/assets/laws/verifiability.md +44 -0
  52. package/assets/laws/work-conduct.md +15 -0
  53. package/assets/patterns/browser-verification-measure.md +3 -1
  54. package/assets/patterns/browser-verification-stand.md +17 -4
  55. package/assets/patterns/doc-style-write.md +62 -2
  56. package/assets/patterns/git-workflow-commit.github.md +33 -5
  57. package/assets/patterns/git-workflow-docker.md +22 -0
  58. package/assets/patterns/git-workflow-freshness.md +87 -0
  59. package/assets/patterns/git-workflow-merge.md +41 -0
  60. package/assets/patterns/git-workflow-migration.md +11 -0
  61. package/assets/patterns/git-workflow-pr.github.md +12 -1
  62. package/assets/patterns/git-workflow-restart.md +18 -0
  63. package/assets/patterns/git-workflow-secrets.md +14 -0
  64. package/assets/patterns/git-workflow-stack.md +93 -1
  65. package/assets/patterns/lib-layers-move.md +4 -0
  66. package/assets/patterns/spec-driven-domain.md +47 -2
  67. package/assets/patterns/spec-driven-rule.md +16 -4
  68. package/assets/patterns/spec-driven-sweep.md +57 -0
  69. package/assets/patterns/status-report-table.github.md +1 -1
  70. package/assets/patterns/task-flow-archive.md +58 -16
  71. package/assets/patterns/task-flow-close.md +106 -23
  72. package/assets/patterns/task-flow-handoff.md +14 -2
  73. package/assets/patterns/task-flow-resume.md +35 -8
  74. package/assets/patterns/task-flow-start.md +60 -22
  75. package/assets/patterns/testing-e2e.md +23 -0
  76. package/assets/patterns/turn-entry-map.md +1 -1
  77. package/assets/pitfalls/agent-kit.md +71 -3
  78. package/assets/pitfalls/doc-style.md +15 -0
  79. package/assets/pitfalls/git-workflow.github.md +88 -0
  80. package/assets/pitfalls/spec-driven.md +22 -0
  81. package/assets/pitfalls/task-flow.md +113 -0
  82. package/assets/pitfalls/testing.md +6 -0
  83. package/assets/pitfalls/turn-conduct.md +49 -0
  84. package/assets/rules/browser-verification.md +58 -0
  85. package/assets/rules/deploy-flow.azure.md +8 -0
  86. package/assets/rules/deploy-flow.github.md +27 -0
  87. package/assets/rules/deploy-flow.gitlab.md +8 -0
  88. package/assets/rules/doc-style.md +20 -0
  89. package/assets/rules/git-workflow.azure.md +22 -2
  90. package/assets/rules/git-workflow.github.md +127 -118
  91. package/assets/rules/git-workflow.gitlab.md +17 -4
  92. package/assets/rules/lists.md +5 -0
  93. package/assets/rules/observability.needs-app.md +4 -0
  94. package/assets/rules/reuse-first.md +14 -0
  95. package/assets/rules/shared-code.md +5 -0
  96. package/assets/rules/spec-driven.md +33 -15
  97. package/assets/rules/task-flow.md +132 -126
  98. package/assets/rules/testing.md +41 -2
  99. package/assets/rules/turn-conduct.md +73 -57
  100. package/assets/rules/turn-entry.md +6 -0
  101. package/assets/samples/tasks/_template/grill.md +5 -0
  102. package/assets/samples/tasks/_template/plan.md +3 -0
  103. package/assets/skills/agent-kit.md +108 -76
  104. package/assets/templates/postmortem.md +5 -1
  105. package/bin/agent-kit.d.ts.map +1 -1
  106. package/bin/agent-kit.js +30 -6
  107. package/bin/agent-kit.js.map +1 -1
  108. package/lib/catalog.d.ts.map +1 -1
  109. package/lib/catalog.js +2 -1
  110. package/lib/catalog.js.map +1 -1
  111. package/lib/commands.d.ts.map +1 -1
  112. package/lib/commands.js +96 -7
  113. package/lib/commands.js.map +1 -1
  114. package/lib/hooks-map.d.ts +26 -0
  115. package/lib/hooks-map.d.ts.map +1 -1
  116. package/lib/hooks-map.js +58 -2
  117. package/lib/hooks-map.js.map +1 -1
  118. package/lib/sections.d.ts +6 -0
  119. package/lib/sections.d.ts.map +1 -1
  120. package/lib/sections.js +19 -0
  121. package/lib/sections.js.map +1 -1
  122. package/lib/shipment.d.ts +2 -0
  123. package/lib/shipment.d.ts.map +1 -1
  124. package/lib/shipment.fixture.d.ts +5 -0
  125. package/lib/shipment.fixture.d.ts.map +1 -1
  126. package/lib/shipment.fixture.js +7 -0
  127. package/lib/shipment.fixture.js.map +1 -1
  128. package/lib/shipment.js +13 -1
  129. package/lib/shipment.js.map +1 -1
  130. package/lib/sync.d.ts +35 -3
  131. package/lib/sync.d.ts.map +1 -1
  132. package/lib/sync.js +59 -8
  133. package/lib/sync.js.map +1 -1
  134. package/package.json +1 -1
  135. package/rt-tools-agent-kit-0.18.0.tgz +0 -0
  136. 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
- 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:]]*[^|&]'
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
- # 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
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
- # 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
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
- # 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
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
- # 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}
382
+ rt_te_deny "$reason" "последним действием хода стала не работа."
383
+ fi
419
384
 
420
- ${deny_tail_text}"
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
- jq -n --arg r "$reason" '{decision:"block",reason:$r}' 2>/dev/null \
423
- || printf '{"decision":"block","reason":"turn-exit-guard: последним действием хода стала не работа."}\n'
424
- exit 0
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
- | if $opened_pr and ($went_on | not) then "owe:pr"
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
- Пропускаются при этом: правка ${tasks_dir}/**, запись передачи, команды поставки и сверки, чтение файлов и вопрос владельцу."
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
+ }
@@ -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
+ самой — вопрос к владельцу.