@rt-tools/agent-kit 0.9.1 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (119) hide show
  1. package/README.md +5 -0
  2. package/assets/agents/conscience.md +58 -0
  3. package/assets/agents/prose-editor.md +44 -0
  4. package/assets/agents/strict-teacher.md +59 -0
  5. package/assets/checks/board.github.mjs +56 -1
  6. package/assets/checks/check-board.github.mjs +24 -0
  7. package/assets/checks/check-doc-paths.mjs +4 -15
  8. package/assets/checks/check-dupes.mjs +5 -6
  9. package/assets/checks/check-file-size.mjs +6 -20
  10. package/assets/checks/check-prose-style.mjs +137 -0
  11. package/assets/checks/check-reuse.mjs +5 -5
  12. package/assets/checks/check-state-next.mjs +194 -0
  13. package/assets/checks/check-states.mjs +142 -0
  14. package/assets/checks/check-styles.mjs +5 -5
  15. package/assets/checks/check-turn-map.mjs +146 -0
  16. package/assets/checks/lib-common.mjs +3 -3
  17. package/assets/checks/rt-kit-checks.config.mjs +85 -1
  18. package/assets/commands/agent-kit-digest.md +6 -5
  19. package/assets/defaults/project.sh +108 -6
  20. package/assets/defaults/turn-map.md +46 -0
  21. package/assets/docs/GLOSSARY.md +17 -15
  22. package/assets/hooks/browser-device-id.sh +2 -0
  23. package/assets/hooks/browser-guard-device-id.sh +11 -1
  24. package/assets/hooks/browser-guard-no-asking.sh +11 -1
  25. package/assets/hooks/browser-guard-no-listing.sh +11 -1
  26. package/assets/hooks/browser-guard-no-other-drivers.sh +9 -1
  27. package/assets/hooks/browser-guard-require-select.sh +13 -2
  28. package/assets/hooks/claim-guard.sh +115 -0
  29. package/assets/hooks/commit-msg.sh +2 -0
  30. package/assets/hooks/conscience-guard.sh +100 -0
  31. package/assets/hooks/constitution-index.sh +2 -0
  32. package/assets/hooks/deny-tail.sh +32 -0
  33. package/assets/hooks/dev-server-guard.sh +10 -2
  34. package/assets/hooks/docs-guard.sh +16 -2
  35. package/assets/hooks/exam-guard.sh +123 -0
  36. package/assets/hooks/git-guard-delivery-signature.sh +77 -0
  37. package/assets/hooks/git-guard-delivery.sh +156 -68
  38. package/assets/hooks/git-guard-main.sh +14 -0
  39. package/assets/hooks/git-guard-push-tests.sh +51 -2
  40. package/assets/hooks/glossary-load.sh +2 -0
  41. package/assets/hooks/grill-gate.sh +14 -0
  42. package/assets/hooks/handoff-entry-guard.sh +73 -0
  43. package/assets/hooks/handoff-write.sh +103 -0
  44. package/assets/hooks/lint-after-edit.sh +2 -0
  45. package/assets/hooks/observe.sh +2 -0
  46. package/assets/hooks/postmortem-guard.sh +14 -0
  47. package/assets/hooks/proposal-guard.sh +14 -0
  48. package/assets/hooks/prose-style-guard.sh +75 -0
  49. package/assets/hooks/qa-dataid-guard.sh +14 -1
  50. package/assets/hooks/rerun-guard.sh +88 -0
  51. package/assets/hooks/reuse-first-guard.sh +14 -1
  52. package/assets/hooks/roles.sh +34 -0
  53. package/assets/hooks/skill-gate-layers.sh +2 -0
  54. package/assets/hooks/skill-gate-rearm.sh +2 -0
  55. package/assets/hooks/skill-gate.sh +14 -0
  56. package/assets/hooks/skill-loaded.sh +2 -0
  57. package/assets/hooks/sql-guard-parse.sh +2 -0
  58. package/assets/hooks/sql-guard-request.sh +2 -0
  59. package/assets/hooks/sql-guard-target.sh +2 -0
  60. package/assets/hooks/sql-guard-write.sh +4 -1
  61. package/assets/hooks/sql-guard.sh +14 -1
  62. package/assets/hooks/task-context-load.sh +2 -0
  63. package/assets/hooks/task-flow-guard.sh +73 -7
  64. package/assets/hooks/turn-entry-load.sh +62 -0
  65. package/assets/hooks/turn-exit-guard.sh +191 -0
  66. package/assets/hooks/utf8.sh +35 -0
  67. package/assets/hooks/waiting-turn-guard.sh +14 -0
  68. package/assets/hooks/window-fill-guard.sh +43 -2
  69. package/assets/laws/work-conduct.md +29 -0
  70. package/assets/patterns/cargo-triage-mark.md +119 -0
  71. package/assets/patterns/task-flow-close.md +29 -8
  72. package/assets/patterns/task-flow-handoff.md +19 -1
  73. package/assets/patterns/task-flow-resume.md +36 -4
  74. package/assets/patterns/task-flow-start.md +56 -6
  75. package/assets/patterns/turn-entry-map.md +81 -0
  76. package/assets/rules/cargo-triage.md +126 -0
  77. package/assets/rules/git-workflow.azure.md +36 -9
  78. package/assets/rules/git-workflow.github.md +74 -9
  79. package/assets/rules/git-workflow.gitlab.md +40 -12
  80. package/assets/rules/task-flow.md +136 -71
  81. package/assets/rules/testing.md +15 -1
  82. package/assets/rules/turn-entry.md +93 -0
  83. package/assets/samples/tasks/_template/plan.md +4 -1
  84. package/assets/samples/tasks/_template/progress.md +1 -0
  85. package/assets/skills/agent-kit.md +33 -0
  86. package/assets/templates/project.sh +16 -0
  87. package/bin/agent-kit.d.ts.map +1 -1
  88. package/bin/agent-kit.js +42 -1
  89. package/bin/agent-kit.js.map +1 -1
  90. package/lib/cargo-state.d.ts +62 -0
  91. package/lib/cargo-state.d.ts.map +1 -0
  92. package/lib/cargo-state.js +118 -0
  93. package/lib/cargo-state.js.map +1 -0
  94. package/lib/cargo.d.ts +42 -0
  95. package/lib/cargo.d.ts.map +1 -1
  96. package/lib/cargo.js +2 -0
  97. package/lib/cargo.js.map +1 -1
  98. package/lib/commands.d.ts.map +1 -1
  99. package/lib/commands.js +64 -4
  100. package/lib/commands.js.map +1 -1
  101. package/lib/config.d.ts +8 -0
  102. package/lib/config.d.ts.map +1 -1
  103. package/lib/config.js +1 -0
  104. package/lib/config.js.map +1 -1
  105. package/lib/observations.d.ts +35 -1
  106. package/lib/observations.d.ts.map +1 -1
  107. package/lib/observations.js +14 -2
  108. package/lib/observations.js.map +1 -1
  109. package/lib/ship.d.ts +3 -0
  110. package/lib/ship.d.ts.map +1 -1
  111. package/lib/ship.js +56 -0
  112. package/lib/ship.js.map +1 -1
  113. package/lib/thresholds.d.ts +49 -0
  114. package/lib/thresholds.d.ts.map +1 -0
  115. package/lib/thresholds.js +151 -0
  116. package/lib/thresholds.js.map +1 -0
  117. package/package.json +1 -1
  118. package/rt-tools-agent-kit-0.11.0.tgz +0 -0
  119. package/rt-tools-agent-kit-0.9.1.tgz +0 -0
@@ -14,6 +14,8 @@
14
14
  # FAIL-OPEN: нет `jq`, не git-репозиторий, нет папки задачи — выходим молча. Сессия важнее
15
15
  # контекста.
16
16
 
17
+ . "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/utf8.sh" 2>/dev/null || true
18
+
17
19
  ROOT="${CLAUDE_PROJECT_DIR:-.}"
18
20
  command -v jq >/dev/null 2>&1 || exit 0
19
21
  cd "$ROOT" 2>/dev/null || exit 0
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env bash
2
2
  # rt-hook: PreToolUse Edit|Write|MultiEdit|Bash|mcp__webstorm__create_new_file|mcp__webstorm__execute_terminal_command|mcp__webstorm__execute_tool
3
- # Требует: hooks/profile-check.sh
3
+ # Требует: hooks/profile-check.sh, hooks/deny-tail.sh
4
4
  # PreToolUse guard for Edit|Write|MultiEdit: код не пишется раньше замысла.
5
5
  #
6
6
  # Работа идёт много заходов, и между ними исполнитель не помнит ничего. Замысел, лежащий на
@@ -8,9 +8,14 @@
8
8
  # закоммичены, PR не открыт, а очередь работ показывает задачу начатой и молчит о том, что
9
9
  # внутри неё сделано.
10
10
  #
11
- # Гард требует три вещи и ровно их: папку задачи по имени ветки, замысел в ней и названную в
12
- # замысле договорённость о продукте. Полноту написанного он не судитэто за владельцем
13
- # (решения в законе `docs/constitution/work-conduct.md`).
11
+ # Гард требует четыре вещи и ровно их: папку задачи по имени ветки, замысел в ней, объявленное
12
+ # в ходе работы состояние из тех, в которых код правится, и названную в замысле
13
+ # договорённость о продукте. Полноту написанного он не судит — это за владельцем (решения в
14
+ # законе `docs/constitution/work-conduct.md`).
15
+ #
16
+ # Состояние судится раньше договорённости и её обхода: обход снимает требование договорённости,
17
+ # а не требование дойти до правки кода. Судится объявленный переход, а не наличие файлов —
18
+ # артефакт, положенный ради снятия отказа, лежит так же, как написанный.
14
19
  #
15
20
  # Правило целиком — скил `task-flow`.
16
21
  #
@@ -20,6 +25,8 @@
20
25
  # FAIL-OPEN: нет jq, не git-репозиторий, битый ввод, чужой инструмент → пропуск. Сломанный
21
26
  # гард не должен мешать работать.
22
27
 
28
+ . "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/utf8.sh" 2>/dev/null || true
29
+
23
30
  input="$(cat 2>/dev/null)"
24
31
  [ -z "$input" ] && exit 0
25
32
  command -v jq >/dev/null 2>&1 || exit 0
@@ -102,9 +109,20 @@ EOF
102
109
  # Каталог папок задач: у дерева он свой, но имя обычно общее.
103
110
  tasks_dir="${RT_TASKS_DIR:-docs/tasks}"
104
111
 
112
+ # Общий хвост отказа: два законных хода и законная форма обхода, если она у отказа есть. Файл
113
+ # может быть не разложен — тогда хвоста нет, а причина отказа остаётся прежней.
114
+ # shellcheck disable=SC1090
115
+ [ -f "$rt_hooks_dir/deny-tail.sh" ] && . "$rt_hooks_dir/deny-tail.sh"
116
+ command -v rt_deny_tail >/dev/null 2>&1 || rt_deny_tail() { :; }
117
+
118
+ # Отказ: причина первым параметром, законная форма обхода — вторым. Хвост дописывается здесь, а
119
+ # не в каждом тексте: пропущенный в одном месте, он читается как «у этого отказа ходов нет».
105
120
  deny() {
106
- jq -n --arg r "$1" '{hookSpecificOutput:{hookEventName:"PreToolUse",permissionDecision:"deny",permissionDecisionReason:$r}}' 2>/dev/null \
107
- || printf '{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"deny","permissionDecisionReason":"%s"}}\n' "$1"
121
+ reason="$1"
122
+ tail_text="$(rt_deny_tail "$2")"
123
+ [ -n "$tail_text" ] && reason="$1 ${tail_text}"
124
+ jq -n --arg r "$reason" '{hookSpecificOutput:{hookEventName:"PreToolUse",permissionDecision:"deny",permissionDecisionReason:$r}}' 2>/dev/null \
125
+ || printf '{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"deny","permissionDecisionReason":"%s"}}\n' "$reason"
108
126
  exit 0
109
127
  }
110
128
 
@@ -130,6 +148,53 @@ if [ ! -f "$plan" ]; then
130
148
  deny "BLOCKED by task-flow: нет замысла — '${tasks_dir}/${branch}/plan.md'. Собери папку задачи с образца (cp -r ${tasks_dir}/_template ${tasks_dir}/${branch}) и заполни шапку, след задачи и этапы, затем повтори. Правило — скил task-flow."
131
149
  fi
132
150
 
151
+ # Состояние работы. Артефакт на диске не говорит, дошла ли работа до правки кода: пустой
152
+ # `plan.md`, положенный ради снятия отказа, лежит точно так же, как написанный замысел, и
153
+ # требование снимает сам собой. Единица работы — состояние, а не файл: исполнитель объявляет
154
+ # его строкой в ходе работы, гард судит объявленный переход, а не наличие файлов.
155
+ #
156
+ # Состояние стоит в разделе «Где стоим» и перезаписывается вместе с ним. Имя берётся из
157
+ # перечня — своё имя состоянием не является: перечень называет вход, выход и обязательное
158
+ # действие каждого, и слово вне перечня не говорит ни о чём из трёх.
159
+ progress="$dir/progress.md"
160
+
161
+ if [ ! -f "$progress" ]; then
162
+ deny "BLOCKED by task-flow: нет хода работы — '${tasks_dir}/${branch}/progress.md'. В нём объявляется состояние работы, и без него не видно, дошла ли она до правки кода. Собери папку задачи с образца (cp -r ${tasks_dir}/_template ${tasks_dir}/${branch}), затем повтори. Правило — скил task-flow."
163
+ fi
164
+
165
+ state="$(sed -n 's/^[[:space:]]*[-*][[:space:]]*\*\*Состояние:\*\*[[:space:]]*`\([^`]*\)`.*/\1/p' "$progress" 2>/dev/null | head -1)"
166
+
167
+ # Обязательное действие состояния. Отказ называет его целиком: исполнитель, которому сказано
168
+ # только «не в том состоянии», переписывает строку состояния вместо того, чтобы сделать шаг.
169
+ state_action() {
170
+ case "$1" in
171
+ просьба-не-разобрана) printf '%s' 'разведка по дереву, затем вопросы владельцу' ;;
172
+ разбор-закрыт) printf '%s' 'договорённость о продукте либо названная причина её отсутствия' ;;
173
+ договорённость-записана) printf '%s' 'завести задачу, ветку и папку задачи' ;;
174
+ задача-взята) printf '%s' 'написать замысел' ;;
175
+ замысел-записан) printf '%s' 'делать первый этап' ;;
176
+ папка-разобрана) printf '%s' 'снять черновик и попросить владельца влить' ;;
177
+ влито) printf '%s' 'разбор работы правилами и сверка очереди работ' ;;
178
+ *) printf '%s' '' ;;
179
+ esac
180
+ }
181
+
182
+ if [ -z "$state" ]; then
183
+ deny "BLOCKED by task-flow: в '${tasks_dir}/${branch}/progress.md' не объявлено состояние работы. Впиши в раздел «Где стоим» строку '- **Состояние:** \`<имя>\`' — имя из перечня состояний правила, — затем повтори. Код правится в состояниях 'этап-идёт', 'этапы-кончились', 'работа-отдана' и 'разбор-кончился'. Правило — скил task-flow."
184
+ fi
185
+
186
+ case "$state" in
187
+ # Состояния, в которых код приложения правится. Три последних — не про первый заход:
188
+ # прогон бывает красным, а разбор — с замечаниями, и починка идёт в ту же ветку.
189
+ этап-идёт | этапы-кончились | работа-отдана | разбор-кончился) ;;
190
+ просьба-не-разобрана | разбор-закрыт | договорённость-записана | задача-взята | замысел-записан | папка-разобрана | влито)
191
+ deny "BLOCKED by task-flow: в ходе работы объявлено состояние '${state}', а код в нём не правится. Обязательное действие этого состояния — $(state_action "$state"). Дошла работа до правки кода — перепиши строку состояния в '${tasks_dir}/${branch}/progress.md' на '- **Состояние:** \`этап-идёт\`'. Правило — скил task-flow."
192
+ ;;
193
+ *)
194
+ deny "BLOCKED by task-flow: в '${tasks_dir}/${branch}/progress.md' объявлено состояние '${state}', а такого в перечне нет. Имя берётся из перечня состояний правила — своё слово не говорит ни о входе, ни о выходе, ни об обязательном действии. Правило — скил task-flow."
195
+ ;;
196
+ esac
197
+
133
198
  # Строка обхода: поведение не меняется, договорённость о продукте не нужна. Причина обязана
134
199
  # стоять — без неё обход становится умолчанием.
135
200
  if grep -qE '^\*\*Поведение:\*\*[[:space:]]*не меняется[[:space:]]*—[[:space:]]*\S' "$plan" 2>/dev/null; then
@@ -139,7 +204,8 @@ fi
139
204
  draft="$(sed -n 's/^\*\*Драфт:\*\*[[:space:]]*`\([^`]*\)`.*/\1/p' "$plan" 2>/dev/null | head -1)"
140
205
 
141
206
  if [ -z "$draft" ]; then
142
- deny "BLOCKED by task-flow: в '${tasks_dir}/${branch}/plan.md' не названа договорённость о продукте. Заведи её в docs/specs/<домен>/proposed/<фича>/ и укажи строкой '**Драфт:** \`путь\`'. Если правка поведения не меняет — поставь '**Поведение:** не меняется — <причина владельца>'. Правило — скил task-flow."
207
+ deny "BLOCKED by task-flow: в '${tasks_dir}/${branch}/plan.md' не названа договорённость о продукте. Заведи её в docs/specs/<домен>/proposed/<фича>/ и укажи строкой '**Драфт:** \`путь\`'. Правило — скил task-flow." \
208
+ "строка '**Поведение:** не меняется — <причина владельца>' в замысле; пустая причина не принимается"
143
209
  fi
144
210
 
145
211
  case "$draft" in
@@ -0,0 +1,62 @@
1
+ #!/usr/bin/env bash
2
+ # rt-hook: SessionStart startup|resume|compact|clear
3
+ # Требует: hooks/handoff-write.sh
4
+ # SessionStart: передача прошлого захода и карта хода уезжают в контекст на каждом запуске.
5
+ #
6
+ # Передачу пишет хук перед сжатием — и на этом её путь кончался: читать её не умел никто, и
7
+ # в новый заход её клал человек. Написанная и не прочитанная, она равна ненаписанной.
8
+ #
9
+ # Второе, чего заход после сжатия не знает, — как здесь ведут работу. Перечень состояний с
10
+ # обязательными действиями лежит в правиле, а правило после сжатия не загружено: заход первым
11
+ # движением идёт читать его целиком, то есть тратит на восстановление порядка ту часть окна,
12
+ # ради которой сжатие и случилось. Карта отвечает на этот вопрос до того, как он задан.
13
+ #
14
+ # Подаётся это на всех четырёх запусках, а не только после сжатия: заход после обрыва и заход
15
+ # после очистки начинают с того же пустого места, и разница между ними исполнителю не видна.
16
+ #
17
+ # FAIL-OPEN: не git-репозиторий, нет ветки, нет каталога передач, нет карты, нечитаемый файл —
18
+ # хук выходит нулём и молчит о той части, которой нет. Запуск он не отбивает никогда: сессия
19
+ # важнее контекста.
20
+
21
+ . "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/utf8.sh" 2>/dev/null || true
22
+
23
+ ROOT="${CLAUDE_PROJECT_DIR:-.}"
24
+ cd "$ROOT" 2>/dev/null || exit 0
25
+ git rev-parse --is-inside-work-tree >/dev/null 2>&1 || exit 0
26
+
27
+ branch="$(git branch --show-current 2>/dev/null)"
28
+
29
+ # Профиль дерева: сперва умолчание пакета, поверх него — надстройка проекта, если она есть.
30
+ rt_hooks_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
31
+ for profile in "$rt_hooks_dir/../rt-kit/defaults/project.sh" "$rt_hooks_dir/../defaults/project.sh" \
32
+ "$ROOT/.claude/rt-kit/defaults/project.sh" "$ROOT/.claude/rt-kit/project.sh"; do
33
+ # shellcheck disable=SC1090
34
+ [ -f "$profile" ] && . "$profile" 2>/dev/null
35
+ done
36
+
37
+ handoff_dir="${RT_HANDOFF_DIR:-.claude/handoff}"
38
+ map="$rt_hooks_dir/../rt-kit/defaults/turn-map.md"
39
+ [ -f "$map" ] || map="$ROOT/.claude/rt-kit/defaults/turn-map.md"
40
+
41
+ # Передача берётся по имени текущей ветки. Чужая, поданная как своя, описывает работу, которой
42
+ # в этом дереве нет, — поэтому имя файла собирается из ветки, а не выбирается из каталога.
43
+ handoff=''
44
+ [ -n "$branch" ] && handoff="$ROOT/$handoff_dir/$branch.md"
45
+
46
+ if [ -n "$handoff" ] && [ -r "$handoff" ]; then
47
+ printf 'ПЕРЕДАЧА ПРОШЛОГО ЗАХОДА — `%s/%s.md`, ниже целиком.\n\n' "$handoff_dir" "$branch"
48
+ printf 'Она написана прошлым заходом и описывает минуту, когда её собрали. Всё, что в ней\n'
49
+ printf 'стоит о дереве, проверяется деревом: команда этого хода главнее записанной вчера.\n\n'
50
+ cat "$handoff" 2>/dev/null
51
+ printf '\n\n'
52
+ fi
53
+
54
+ if [ -r "$map" ]; then
55
+ printf 'КАРТА ХОДА — состояния работы и то, чем ход кончается. Ниже целиком.\n\n'
56
+ printf 'Это выжимка правила ведения работы, а не оно само: правило объясняет, карта\n'
57
+ printf 'называет. Своё состояние заход читает в разделе «Где стоим» хода работы.\n\n'
58
+ cat "$map" 2>/dev/null
59
+ printf '\n'
60
+ fi
61
+
62
+ exit 0
@@ -0,0 +1,191 @@
1
+ #!/usr/bin/env bash
2
+ # rt-hook: Stop
3
+ # Требует: hooks/deny-tail.sh
4
+ # Страж выходов хода: ход, в котором по работе не сделано ничего, не заканчивается, пока работа
5
+ # не отдана. Stop.
6
+ #
7
+ # Зачем именно так. Правило перечисляет четыре законных выхода хода — вопрос без ответа в
8
+ # правилах, отказ гарда, заполненное окно, отданная работа с начатой следующей, — и держится
9
+ # это памятью исполнителя. Держится плохо: ход, кончившийся отчётом о сделанном, выглядит
10
+ # работой лучше всякой другой — он полон, в нём названы номера и состояния, и пустоты за ним не
11
+ # видно ни владельцу, ни самому заходу. Владелец назвал это прямо: прерываться посреди работы
12
+ # нельзя, и запрет должна держать машина.
13
+ #
14
+ # Что считается работой: правка файла и команда, меняющая дерево или его состояние. Чтение,
15
+ # поиск и разговор работой не считаются — именно ими и заполняется ход, который встал.
16
+ #
17
+ # Что отпускает ход:
18
+ # 1. Работа отдана либо влита — состояние работы говорит об этом само.
19
+ # 2. За ход была работа: правка файла или команда, меняющая дерево.
20
+ # 3. Вопрос владельцу инструментом опроса.
21
+ # 4. Отказ гарда — он кончает ход по правилу.
22
+ # 5. Передача захода написана — окно кончилось.
23
+ # 6. Владелец сказал остановиться.
24
+ #
25
+ # Чего страж не судит. Заход вне ветки задачи и работу без папки: состояние там объявлять
26
+ # негде, и отбивать было бы не за что. Это его известная граница.
27
+ #
28
+ # ОТКАЗ В ПОЛЬЗУ РАБОТЫ: при любой ошибке, нехватке `jq`, отсутствии записи хода, папки задачи
29
+ # или строки состояния ход РАЗРЕШАЕТСЯ (exit 0). Сломанный страж не имеет права заклинить
30
+ # разговор.
31
+
32
+ . "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/utf8.sh" 2>/dev/null || true
33
+
34
+ input="$(cat 2>/dev/null)"
35
+ [ -z "$input" ] && exit 0
36
+ command -v jq >/dev/null 2>&1 || exit 0
37
+
38
+ # Повторный заход по тому же ходу не судится: страж сказал своё один раз и отпускает.
39
+ active="$(printf '%s' "$input" | jq -r '.stop_hook_active // false' 2>/dev/null)"
40
+ [ "$active" = "true" ] && exit 0
41
+
42
+ transcript="$(printf '%s' "$input" | jq -r '.transcript_path // empty' 2>/dev/null)"
43
+ [ -z "$transcript" ] && exit 0
44
+ [ -f "$transcript" ] || exit 0
45
+
46
+ workdir="$(printf '%s' "$input" | jq -r '.cwd // empty' 2>/dev/null)"
47
+ [ -z "$workdir" ] && workdir="${CLAUDE_PROJECT_DIR:-.}"
48
+ cd "$workdir" 2>/dev/null || exit 0
49
+ git rev-parse --is-inside-work-tree >/dev/null 2>&1 || exit 0
50
+
51
+ branch="$(git branch --show-current 2>/dev/null)"
52
+ [ -z "$branch" ] && exit 0
53
+
54
+ root="$(git rev-parse --show-toplevel 2>/dev/null)"
55
+ [ -z "$root" ] && exit 0
56
+ tasks_dir="${RT_TASKS_DIR:-docs/tasks}"
57
+ progress="$root/$tasks_dir/$branch/progress.md"
58
+ [ -f "$progress" ] || exit 0
59
+
60
+ state="$(sed -n 's/^[[:space:]]*[-*][[:space:]]*\*\*Состояние:\*\*[[:space:]]*`\([^`]*\)`.*/\1/p' "$progress" 2>/dev/null | head -1)"
61
+ [ -z "$state" ] && exit 0
62
+
63
+ # Работа, дошедшая до этих двух состояний, чужого шага уже дождалась: дальше её двигает
64
+ # владелец, и ход, закрытый здесь, ничего не роняет.
65
+ case "$state" in
66
+ работа-отдана | влито) exit 0 ;;
67
+ esac
68
+
69
+ # Следующий шаг из хода работы — его страж и называет в отказе: исполнитель, которому сказано
70
+ # только «работа не кончена», перечитывает ту же строку сам.
71
+ next_step="$(sed -n 's/^[[:space:]]*[-*][[:space:]]*\*\*Следующий шаг:\*\*[[:space:]]*\(.*\)/\1/p' "$progress" 2>/dev/null | head -1)"
72
+ [ -z "$next_step" ] && next_step="что стоит в разделе «Где стоим» хода работы"
73
+
74
+ # Команда, меняющая дерево или его состояние. Чтение и поиск сюда не входят намеренно: ими и
75
+ # заполняется ход, который встал.
76
+ 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:]]*[^|&]'
77
+
78
+ verdict="$(tail -n 400 "$transcript" 2>/dev/null | jq -s -r --arg work "$work_re" '
79
+ def is_input:
80
+ .type == "user"
81
+ and (((.message.content // []) | if type == "array"
82
+ then ([.[] | select(.type == "tool_result")] | length)
83
+ else 0 end) == 0);
84
+
85
+ (map(is_input) | rindex(true)) as $i
86
+ | (if $i == null then [] else .[$i:] end) as $turn
87
+ | [$turn[] | select(.type == "assistant") | (.message.content // [])[] | select(.type == "tool_use")] as $uses
88
+ # Правка файла — работа по определению, каким бы инструментом она ни шла.
89
+ | ($uses | map(.name // "") | any(test("^(Edit|Write|MultiEdit|NotebookEdit)$"))) as $edited
90
+ | ($uses | map(.name // "") | any(test("AskUserQuestion"))) as $asked
91
+ | ($uses | map((.input.command // "")) | join("\n")) as $ran
92
+ | ($ran | test($work)) as $ran_work
93
+ # Отказ гарда и передача захода — оба кончают ход по правилу.
94
+ | ([$turn[] | select(.type == "user") | .message.content // [] | select(type == "array") | .[]
95
+ | select(.type == "tool_result") | .content
96
+ | if type == "string" then .
97
+ elif type == "array" then (map(if type == "object" then (.text // "") else tostring end) | join("\n"))
98
+ else tostring end] | join("\n")) as $out
99
+ | (($out | test("BLOCKED by|Отбито гейтом")) or ($ran | test("BLOCKED by"))) as $denied
100
+ | ($ran | test("handoff")) as $handed
101
+ # Слово владельца об остановке: судится его собственная реплика, а не пересказ исполнителя.
102
+ | ([$turn[] | select(.type == "user") | .message.content
103
+ | if type == "string" then . elif type == "array"
104
+ then (map(if type == "object" then (.text // "") else "" end) | join("\n")) else "" end] | join("\n")) as $said
105
+ | ($said | test("останов|стоп|хватит|подожди|не надо|прерв|отложи")) as $told_stop
106
+ | { worked: ($edited or $ran_work), released: ($asked or $denied or $handed or $told_stop), ran: $ran }
107
+ ' 2>/dev/null)"
108
+
109
+ [ -z "$verdict" ] && exit 0
110
+
111
+ worked="$(printf '%s' "$verdict" | jq -r '.worked // false' 2>/dev/null)"
112
+ released="$(printf '%s' "$verdict" | jq -r '.released // false' 2>/dev/null)"
113
+ commands="$(printf '%s' "$verdict" | jq -r '.ran // ""' 2>/dev/null)"
114
+
115
+ [ "$released" = "true" ] && exit 0
116
+
117
+ # Контракт этапа. Отметка «этап сделан» — утверждение о дереве, и подтверждается оно выводом
118
+ # команды, а не словами: этап, отмеченный по памяти, через заход неотличим от проверенного.
119
+ # Страж сравнивает номер этапа с тем, что лежит в истории ветки, и на выросшем номере требует
120
+ # команды из строки «Чем проверяется» — она стоит в замысле обратными кавычками. Приём, записанный
121
+ # прозой, страж не читает: подтвердить его выводом нечем, и это его известная граница.
122
+ stage_now="$(sed -n 's/^[[:space:]]*[-*][[:space:]]*\*\*Этап:\*\*[[:space:]]*\([0-9][0-9]*\).*/\1/p' "$progress" 2>/dev/null | head -1)"
123
+ stage_was="$(git -C "$root" show "HEAD:$tasks_dir/$branch/progress.md" 2>/dev/null | sed -n 's/^[[:space:]]*[-*][[:space:]]*\*\*Этап:\*\*[[:space:]]*\([0-9][0-9]*\).*/\1/p' | head -1)"
124
+
125
+ if [ -n "$stage_now" ] && [ -n "$stage_was" ] && [ "$stage_now" -gt "$stage_was" ] 2>/dev/null; then
126
+ plan="$root/$tasks_dir/$branch/plan.md"
127
+ # Контракт закрытого этапа, а не начатого: подтверждается то, что объявлено сделанным.
128
+ contract="$(awk -v n="$stage_was" '
129
+ $0 ~ "^### " n "\\." { inside = 1; next }
130
+ /^### / { inside = 0 }
131
+ inside && /\*\*Чем проверяется:\*\*/ { print }
132
+ ' "$plan" 2>/dev/null)"
133
+ missing=""
134
+ while IFS= read -r cmd; do
135
+ [ -z "$cmd" ] && continue
136
+ printf '%s' "$commands" | grep -qF -- "$cmd" || missing="$missing\n $cmd"
137
+ done <<EOF
138
+ $(printf '%s' "$contract" | grep -o '`[^`]*`' | tr -d '`')
139
+ EOF
140
+ if [ -n "$missing" ]; then
141
+ reason="BLOCKED by turn-exit-guard: этап ${stage_was} объявлен закрытым, а команды, которыми он проверяется, за этот ход не запускались:$(printf '%b' "$missing")
142
+
143
+ Отметка «этап сделан» — утверждение о дереве, и подтверждается оно выводом команды, а не словами: через заход отмеченное по памяти неотличимо от проверенного.
144
+
145
+ Запусти их этим же ходом либо верни прежний номер этапа в ход работы.
146
+
147
+ Страж судит один ход: следующий заход не отбивается."
148
+ # Общий хвост отказа: два законных хода. Файл может быть не разложен — тогда хвоста нет,
149
+ # а причина отказа остаётся прежней.
150
+ # shellcheck disable=SC1090
151
+ [ -f "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/deny-tail.sh" ] \
152
+ && . "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/deny-tail.sh" 2>/dev/null
153
+ command -v rt_deny_tail >/dev/null 2>&1 || rt_deny_tail() { :; }
154
+ deny_tail_text="$(rt_deny_tail "")"
155
+ [ -n "$deny_tail_text" ] && reason="${reason}
156
+
157
+ ${deny_tail_text}"
158
+
159
+ jq -n --arg r "$reason" '{decision:"block",reason:$r}' 2>/dev/null \
160
+ || printf '{"decision":"block","reason":"turn-exit-guard: закрытый этап не подтверждён выводом команды."}\n'
161
+ exit 0
162
+ fi
163
+ fi
164
+
165
+ [ "$worked" = "true" ] && exit 0
166
+
167
+ reason="BLOCKED by turn-exit-guard: работа в состоянии '${state}', а за этот ход по ней не сделано ничего — ни правки, ни команды, меняющей дерево.
168
+
169
+ Ход кончается четырьмя способами, и других нет: вопрос владельцу, ответа на который в правилах нет; отказ гарда; заполненное окно захода; отданная работа с начатой следующей. Отчёт о сделанном выходом не является — он выглядит работой лучше всякой другой, и пустоты за ним не видно.
170
+
171
+ Следующий шаг записан в ходе работы: ${next_step}
172
+
173
+ Сделай его этим же ходом. Владелец сказал остановиться — так и напиши: страж читает его слово, а не пересказ.
174
+
175
+ Страж судит один ход: следующий заход не отбивается."
176
+
177
+ # Общий хвост отказа: два законных хода. Файл может быть не разложен — тогда хвоста нет,
178
+ # а причина отказа остаётся прежней.
179
+ # shellcheck disable=SC1090
180
+ [ -f "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/deny-tail.sh" ] \
181
+ && . "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/deny-tail.sh" 2>/dev/null
182
+ command -v rt_deny_tail >/dev/null 2>&1 || rt_deny_tail() { :; }
183
+ deny_tail_text="$(rt_deny_tail "")"
184
+ [ -n "$deny_tail_text" ] && reason="${reason}
185
+
186
+ ${deny_tail_text}"
187
+
188
+ jq -n --arg r "$reason" '{decision:"block",reason:$r}' 2>/dev/null \
189
+ || printf '{"decision":"block","reason":"turn-exit-guard: работа не кончена — следующий шаг стоит в ходе работы."}\n'
190
+
191
+ exit 0
@@ -0,0 +1,35 @@
1
+ #!/usr/bin/env bash
2
+ # Локаль исполнения гарда. Подключается первой строкой тела: `. "<каталог гардов>/utf8.sh"`.
3
+ #
4
+ # Образцы гардов написаны словами того языка, на котором говорит дерево, и совпадение с ними
5
+ # зависит от локали процесса. В локали C складывание регистра работает только для латиницы:
6
+ # «В дереве этого нет» под образцом `(в дереве|здесь)…` не находится вовсе, гард выходит нулём
7
+ # и пропускает ход, который обязан был вернуть. Счёт `{0,30}` там же считает байты, а не буквы,
8
+ # и буква вне латиницы весит два.
9
+ #
10
+ # Заметить это на своей машине нечем: терминал разработчика идёт в UTF-8, а служба, запускающая
11
+ # ту же работу, наследует пустую локаль. Три сценария так и падали в конвейере, проходя на
12
+ # машине, где их писали.
13
+ #
14
+ # FAIL-OPEN: локали UTF-8 в системе не нашлось — ничего не объявляется, и гард работает как
15
+ # прежде. Отобрать у него работу из-за отсутствия локали хуже, чем оставить прежнее поведение.
16
+
17
+ rt_use_utf8_locale() {
18
+ case "${LC_ALL:-${LC_CTYPE:-${LANG:-}}}" in
19
+ *UTF-8* | *utf-8* | *UTF8* | *utf8*) return 0 ;;
20
+ esac
21
+
22
+ local available candidate
23
+ available="$(locale -a 2>/dev/null)" || return 0
24
+
25
+ for candidate in C.UTF-8 en_US.UTF-8 ru_RU.UTF-8; do
26
+ if printf '%s\n' "$available" | grep -qx -- "$candidate"; then
27
+ export LC_ALL="$candidate"
28
+ return 0
29
+ fi
30
+ done
31
+
32
+ return 0
33
+ }
34
+
35
+ rt_use_utf8_locale
@@ -1,5 +1,6 @@
1
1
  #!/usr/bin/env bash
2
2
  # rt-hook: Stop
3
+ # Требует: hooks/deny-tail.sh
3
4
  # Гард ожидания: ход, сообщающий владельцу о чужом шаге, не заканчивается, пока в нём не было ни
4
5
  # одного действия по следующей задаче. Stop.
5
6
  #
@@ -29,6 +30,8 @@
29
30
  # ОТКАЗ В ПОЛЬЗУ РАБОТЫ: при любой ошибке, нехватке `jq`, отсутствии записи хода и повторном
30
31
  # заходе ход РАЗРЕШАЕТСЯ (exit 0). Сломанный гард не имеет права заклинить разговор.
31
32
 
33
+ . "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/utf8.sh" 2>/dev/null || true
34
+
32
35
  input="$(cat 2>/dev/null)"
33
36
  [ -z "$input" ] && exit 0
34
37
 
@@ -110,6 +113,17 @@ reason="BLOCKED by waiting-turn-guard: ${said}, а действия по сле
110
113
 
111
114
  Гард судит один ход: следующий заход не отбивается."
112
115
 
116
+ # Общий хвост отказа: два законных хода. Файл может быть не разложен — тогда хвоста нет,
117
+ # а причина отказа остаётся прежней.
118
+ # shellcheck disable=SC1090
119
+ [ -f "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/deny-tail.sh" ] \
120
+ && . "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/deny-tail.sh" 2>/dev/null
121
+ command -v rt_deny_tail >/dev/null 2>&1 || rt_deny_tail() { :; }
122
+ deny_tail_text="$(rt_deny_tail "")"
123
+ [ -n "$deny_tail_text" ] && reason="${reason}
124
+
125
+ ${deny_tail_text}"
126
+
113
127
  jq -n --arg r "$reason" '{decision:"block",reason:$r}' 2>/dev/null \
114
128
  || printf '{"decision":"block","reason":"waiting-turn-guard: PR открыт — тем же ходом берётся следующая задача."}\n'
115
129
 
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env bash
2
2
  # rt-hook: PostToolUse .*
3
- # Требует: hooks/profile-check.sh
3
+ # Требует: hooks/profile-check.sh, hooks/deny-tail.sh
4
4
  # rt-hook: PreToolUse .*
5
5
  # Заполнение окна: заход доводится до логической точки заранее, а не обрывается на середине.
6
6
  #
@@ -16,12 +16,21 @@
16
16
  # Место между порогами и есть то, на что закрывается заход: дописать ход работы, написать
17
17
  # передачу, закоммитить проверенное.
18
18
  #
19
+ # Там, где дерево объявило порог сжатия ниже порога остановки, напоминание говорит обратное:
20
+ # точку остановки выбирать не надо, потому что заход через порог пройдёт сам — сжатие придёт
21
+ # первым, передачу к тому времени напишет свой хук, и работа продолжится тем же заходом. Отбой
22
+ # при этом остаётся: он превращается из конца захода в страховку на случай, когда сжатие не
23
+ # пришло. Напоминание, зовущее закрывать заход там, где закрывать его не надо, — это остановка
24
+ # работы без причины, и стоит она ровно того же, что и отбой.
25
+ #
19
26
  # Размер окна берётся из настройки дерева. Из записи захода он не выводится: модель записана
20
27
  # там без пометки о расширенном окне, и заход на широкое окно от захода на узкое неотличим.
21
28
  #
22
29
  # ОТКАЗ В ПОЛЬЗУ РАБОТЫ: нет размера окна, нет записи захода, нет разборщика, битый разбор —
23
30
  # работа РАЗРЕШАЕТСЯ (exit 0). Сломанный гард не имеет права заклинить работу.
24
31
 
32
+ . "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/utf8.sh" 2>/dev/null || true
33
+
25
34
  input="$(cat 2>/dev/null)"
26
35
  [ -z "$input" ] && exit 0
27
36
 
@@ -49,6 +58,21 @@ esac
49
58
 
50
59
  warn_pct="${RT_WINDOW_WARN_PCT:-40}"
51
60
  stop_pct="${RT_WINDOW_STOP_PCT:-50}"
61
+
62
+ # Доля, на которой контекст сжимает сам инструмент. Объявлена деревом — заход через порог
63
+ # проходит сам: сжатие приходит первым, передачу к тому моменту уже написал свой хук, и работа
64
+ # идёт дальше тем же заходом. Не объявлена — прежний порядок: заход кончается передачей.
65
+ #
66
+ # От этого зависит текст напоминания, а не отказ. Отбой на пороге остановки остаётся в обоих
67
+ # случаях: он и есть страховка на случай, когда сжатие не пришло — настройка снята, версия
68
+ # другая, сжатие отказало. Отобрав отбой у дерева, объявившего сжатие, страж пустил бы такой
69
+ # заход до предела окна, где работа теряется целиком.
70
+ compact_pct="${CLAUDE_AUTOCOMPACT_PCT_OVERRIDE:-}"
71
+ case "$compact_pct" in
72
+ '' | *[!0-9]*) compact_pct='' ;;
73
+ esac
74
+ [ -n "$compact_pct" ] && [ "$compact_pct" -ge "$stop_pct" ] 2>/dev/null && compact_pct=''
75
+
52
76
  tasks_dir="${RT_TASKS_DIR:-docs/tasks}"
53
77
  handoff_dir="${RT_HANDOFF_DIR:-.claude/handoff}"
54
78
 
@@ -99,7 +123,13 @@ if [ "$event" = "PostToolUse" ]; then
99
123
  printf '%s' "$step" > "$mark" 2>/dev/null
100
124
 
101
125
  if [ "$pct" -ge "$stop_pct" ]; then
102
- text="ЗАПОЛНЕНИЕ ОКНА ${pct}% (${fill_k}k из ${window_k}k) — заход закрывается сейчас. Всё, кроме записи хода работы, передачи и команд поставки, уже отбивается."
126
+ if [ -n "$compact_pct" ]; then
127
+ text="ЗАПОЛНЕНИЕ ОКНА ${pct}% (${fill_k}k из ${window_k}k) — заход закрывается сейчас. Сжатие объявлено на ${compact_pct}% и не пришло: порог остановки ${stop_pct}% пройден, а контекст прежний. Всё, кроме записи хода работы, передачи и команд поставки, уже отбивается — закрывай заход и скажи владельцу, что сжатие не сработало."
128
+ else
129
+ text="ЗАПОЛНЕНИЕ ОКНА ${pct}% (${fill_k}k из ${window_k}k) — заход закрывается сейчас. Всё, кроме записи хода работы, передачи и команд поставки, уже отбивается."
130
+ fi
131
+ elif [ -n "$compact_pct" ]; then
132
+ text="ЗАПОЛНЕНИЕ ОКНА ${pct}% (${fill_k}k из ${window_k}k). Точку остановки выбирать не надо: на ${compact_pct}% инструмент сожмёт контекст сам, передачу к тому времени напишет хук, и работа пойдёт дальше этим же заходом. Порог остановки ${stop_pct}% — страховка на случай, если сжатие не придёт. Работай дальше."
103
133
  else
104
134
  text="ЗАПОЛНЕНИЕ ОКНА ${pct}% (${fill_k}k из ${window_k}k). Пора выбирать точку остановки: с ${stop_pct}% останется только закрыть заход. Доведи текущий шаг до состояния, с которого следующий заход продолжит, перепиши «Где стоим» в ходе работы, напиши передачу и отдай владельцу путь к ней — паттерн task-flow-handoff."
105
135
  fi
@@ -150,6 +180,17 @@ reason="BLOCKED by window-fill-guard: заполнение окна ${pct}% (${f
150
180
 
151
181
  Пропускаются при этом: правка ${tasks_dir}/**, запись передачи, команды поставки и сверки, чтение файлов и вопрос владельцу."
152
182
 
183
+ # Общий хвост отказа: два законных хода и законная форма обхода, если она у отказа есть.
184
+ # Файл может быть не разложен — тогда хвоста нет, а причина отказа остаётся прежней.
185
+ # shellcheck disable=SC1090
186
+ [ -f "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/deny-tail.sh" ] \
187
+ && . "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/deny-tail.sh" 2>/dev/null
188
+ command -v rt_deny_tail >/dev/null 2>&1 || rt_deny_tail() { :; }
189
+ deny_tail_text="$(rt_deny_tail "")"
190
+ [ -n "$deny_tail_text" ] && reason="${reason}
191
+
192
+ ${deny_tail_text}"
193
+
153
194
  jq -n --arg r "$reason" \
154
195
  '{hookSpecificOutput:{hookEventName:"PreToolUse",permissionDecision:"deny",permissionDecisionReason:$r}}' 2>/dev/null \
155
196
  || printf '{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"deny","permissionDecisionReason":"window-fill-guard: окно заполнено, заход закрывается передачей."}}\n'
@@ -71,6 +71,15 @@
71
71
  владельца.** Владелец знает, что работа не кончена, но не знает, где именно она стоит;
72
72
  пересказ он даёт по своей памяти, а не по ходу работы, и следующий заход начинает с чужой
73
73
  картины.
74
+ - **Переданное прошлым заходом приходит в новый заход само, а не кладётся рукой.** Передача,
75
+ написанная и не прочитанная, равна ненаписанной: следующий заход о ней не знает и начинает с
76
+ пустого места — то есть с того же, ради чего её и писали. Кладёт её человек ровно до тех пор,
77
+ пока это не поручено машине; поручённое машине не забывается ночью.
78
+ - **Порядок ведения работы приходит в заход вместе с работой, а не разыскивается им.** Заход,
79
+ знающий задачу и не знающий, что с ней делают дальше, первым движением идёт читать правило
80
+ целиком — и тратит на это ту часть места, ради которой заход и начали заново. Приходит
81
+ короткая выжимка: состояния, обязательное действие каждого и то, чем ход кончается. Правило
82
+ она не заменяет и заменить не может — она отвечает на вопрос «что делать», а не «почему».
74
83
  - **Замысел и ход работы — разные записи.** Замысел — то, с чем сверяют результат при
75
84
  приёмке; правленный по ходу, он перестаёт отличаться от PR, и приёмке сверять нечего.
76
85
  - **Сделанное отмечается в одном месте.** Две записи об одном разъезжаются молча, и после
@@ -142,6 +151,26 @@
142
151
  остановиться позволяет только предел заполнения окна. Заход, закрытый на готовой задаче,
143
152
  оставляет владельцу пустое место и стоит целого захода на возвращение к тому, что и так было
144
153
  под рукой.
154
+ - **Предел заполнения окна останавливает заход только там, где инструмент не сжимает контекст
155
+ сам.** Где сжатие объявлено и приходит раньше предела, окно — не конец захода, а его
156
+ продолжение: контекст сжимается, записанное состояние работы возвращается в него, и работа
157
+ идёт дальше тем же заходом. Останавливаться там незачем, а остановка стоит того же, что и
158
+ всякая другая: владелец возвращает исполнителя в работу руками.
159
+ - **Порог, на котором заход останавливают, стоит позже порога, на котором его продолжают.**
160
+ Два порога на одном числе — это не согласие, а гонка, и выигрывает её тот, кто ближе к
161
+ действию: остановка приходит на вызове, а сжатие — между ходами. Расстояние между ними
162
+ объявляется, а не выводится разницей: сжатие идёт не мгновенно, и порог, отстоящий на волос,
163
+ требование «раньше» удовлетворяет, а работу не спасает.
164
+ - **Переход из состояния в состояние исполнителя не останавливает.** Обязательное
165
+ действие сделано — следующее начинается тем же движением, без отчёта владельцу и без его
166
+ слова. Граница между состояниями выглядит законченным куском лучше всякой другой вехи:
167
+ сделанное названо, отчитаться есть чем, — и отчёт встаёт ровно на то место, где должно было
168
+ стоять следующее действие. Владелец читает такой отчёт как сделанную работу, а работа стоит.
169
+ - **Текст, ведущий состояние работы, называет, что делается сразу за ним.** Дочитанный до
170
+ последнего приёма, он кончается ничем: следующего движения в нём нет, и исполнитель выводит
171
+ его из пустоты — то есть останавливается. Названное движение стоит там же, где приёмы, и
172
+ своими словами: одинаковая на все состояния строка перестаёт замечаться раньше, чем
173
+ понадобится.
145
174
  - **Работа, отданная на разбор, освобождает исполнителя, а не останавливает его.** Отданное на
146
175
  разбор ждёт владельца, а не машину: следующая задача эпика берётся тем же движением, которым
147
176
  предыдущая ушла на разбор.