@rt-tools/agent-kit 0.10.0 → 0.12.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 (139) hide show
  1. package/assets/checks/check-file-size.mjs +19 -4
  2. package/assets/checks/check-state-next.mjs +202 -0
  3. package/assets/checks/check-states.mjs +142 -0
  4. package/assets/checks/check-turn-map.mjs +146 -0
  5. package/assets/checks/rt-kit-checks.config.mjs +16 -2
  6. package/assets/defaults/project.sh +67 -7
  7. package/assets/defaults/turn-map.md +48 -0
  8. package/assets/hooks/browser-device-id.sh +2 -0
  9. package/assets/hooks/browser-guard-device-id.sh +5 -1
  10. package/assets/hooks/browser-guard-no-asking.sh +5 -1
  11. package/assets/hooks/browser-guard-no-listing.sh +2 -0
  12. package/assets/hooks/browser-guard-no-other-drivers.sh +7 -3
  13. package/assets/hooks/browser-guard-require-select.sh +6 -2
  14. package/assets/hooks/claim-guard.sh +5 -1
  15. package/assets/hooks/commit-msg.sh +2 -0
  16. package/assets/hooks/conscience-guard.sh +5 -1
  17. package/assets/hooks/constitution-index.sh +2 -0
  18. package/assets/hooks/dev-server-guard.sh +7 -3
  19. package/assets/hooks/dispatch.sh +69 -0
  20. package/assets/hooks/docs-guard.sh +8 -4
  21. package/assets/hooks/exam-guard.sh +7 -3
  22. package/assets/hooks/git-guard-delivery-signature.sh +2 -0
  23. package/assets/hooks/git-guard-delivery.sh +39 -5
  24. package/assets/hooks/git-guard-main.sh +8 -4
  25. package/assets/hooks/git-guard-push-tests.sh +8 -4
  26. package/assets/hooks/glossary-load.sh +2 -0
  27. package/assets/hooks/grill-gate.sh +6 -2
  28. package/assets/hooks/handoff-entry-guard.sh +6 -2
  29. package/assets/hooks/handoff-write.sh +124 -0
  30. package/assets/hooks/hook-input.sh +54 -0
  31. package/assets/hooks/lint-after-edit.sh +7 -3
  32. package/assets/hooks/observe.sh +2 -0
  33. package/assets/hooks/postmortem-guard.sh +5 -1
  34. package/assets/hooks/proposal-guard.sh +5 -1
  35. package/assets/hooks/prose-style-guard.sh +7 -3
  36. package/assets/hooks/qa-dataid-guard.sh +6 -2
  37. package/assets/hooks/rerun-guard.sh +7 -3
  38. package/assets/hooks/reuse-first-guard.sh +7 -3
  39. package/assets/hooks/roles.sh +2 -0
  40. package/assets/hooks/rule-article.sh +99 -0
  41. package/assets/hooks/skill-gate-layers.sh +2 -0
  42. package/assets/hooks/skill-gate-rearm.sh +5 -1
  43. package/assets/hooks/skill-gate.sh +25 -2
  44. package/assets/hooks/skill-loaded.sh +5 -1
  45. package/assets/hooks/sql-guard-parse.sh +2 -0
  46. package/assets/hooks/sql-guard-request.sh +4 -1
  47. package/assets/hooks/sql-guard-target.sh +2 -0
  48. package/assets/hooks/sql-guard-write.sh +2 -0
  49. package/assets/hooks/sql-guard.sh +6 -2
  50. package/assets/hooks/task-context-load.sh +2 -0
  51. package/assets/hooks/task-flow-guard.sh +8 -4
  52. package/assets/hooks/turn-entry-load.sh +62 -0
  53. package/assets/hooks/turn-exit-guard.sh +44 -17
  54. package/assets/hooks/utf8.sh +35 -0
  55. package/assets/hooks/waiting-turn-guard.sh +5 -1
  56. package/assets/hooks/window-fill-guard.sh +37 -5
  57. package/assets/laws/work-conduct.md +34 -9
  58. package/assets/patterns/dependencies-upgrade.md +1 -1
  59. package/assets/patterns/doc-style-write.md +3 -3
  60. package/assets/patterns/git-workflow-commit.azure.md +2 -202
  61. package/assets/patterns/git-workflow-commit.github.md +2 -258
  62. package/assets/patterns/git-workflow-commit.gitlab.md +1 -217
  63. package/assets/patterns/git-workflow-docker.md +3 -3
  64. package/assets/patterns/git-workflow-merge.md +3 -2
  65. package/assets/patterns/git-workflow-migration.md +3 -3
  66. package/assets/patterns/git-workflow-pr.azure.md +224 -0
  67. package/assets/patterns/git-workflow-pr.github.md +280 -0
  68. package/assets/patterns/git-workflow-pr.gitlab.md +240 -0
  69. package/assets/patterns/git-workflow-restart.md +3 -3
  70. package/assets/patterns/git-workflow-secrets.md +3 -3
  71. package/assets/patterns/task-flow-archive.md +193 -0
  72. package/assets/patterns/task-flow-close.md +11 -160
  73. package/assets/patterns/task-flow-handoff.md +22 -4
  74. package/assets/patterns/task-flow-resume.md +6 -0
  75. package/assets/patterns/task-flow-start.md +41 -0
  76. package/assets/patterns/turn-entry-map.md +81 -0
  77. package/assets/pitfalls/doc-style.md +80 -0
  78. package/assets/pitfalls/git-workflow.azure.md +50 -0
  79. package/assets/pitfalls/git-workflow.github.md +78 -0
  80. package/assets/pitfalls/git-workflow.gitlab.md +49 -0
  81. package/assets/pitfalls/spec-driven.md +36 -0
  82. package/assets/pitfalls/styling-bem.md +45 -0
  83. package/assets/pitfalls/task-flow.md +62 -0
  84. package/assets/pitfalls/testing.md +70 -0
  85. package/assets/rules/deploy-flow.azure.md +106 -0
  86. package/assets/rules/deploy-flow.github.md +113 -0
  87. package/assets/rules/deploy-flow.gitlab.md +108 -0
  88. package/assets/rules/doc-style.md +25 -76
  89. package/assets/rules/git-workflow.azure.md +6 -92
  90. package/assets/rules/git-workflow.github.md +14 -127
  91. package/assets/rules/git-workflow.gitlab.md +6 -93
  92. package/assets/rules/spec-driven.md +39 -30
  93. package/assets/rules/styling-bem.md +20 -39
  94. package/assets/rules/task-flow.md +17 -186
  95. package/assets/rules/testing.md +3 -64
  96. package/assets/rules/turn-conduct.md +206 -0
  97. package/assets/rules/turn-entry.md +93 -0
  98. package/assets/rules/typescript-conventions.md +15 -0
  99. package/assets/skills/agent-kit.md +35 -12
  100. package/assets/templates/pitfalls.md +10 -0
  101. package/assets/templates/rule.md +5 -3
  102. package/lib/assets.d.ts.map +1 -1
  103. package/lib/assets.js +6 -1
  104. package/lib/assets.js.map +1 -1
  105. package/lib/cargo.d.ts +42 -0
  106. package/lib/cargo.d.ts.map +1 -1
  107. package/lib/cargo.js +2 -0
  108. package/lib/cargo.js.map +1 -1
  109. package/lib/cascade.d.ts.map +1 -1
  110. package/lib/cascade.js +19 -1
  111. package/lib/cascade.js.map +1 -1
  112. package/lib/commands.d.ts.map +1 -1
  113. package/lib/commands.js +65 -4
  114. package/lib/commands.js.map +1 -1
  115. package/lib/config.d.ts +16 -1
  116. package/lib/config.d.ts.map +1 -1
  117. package/lib/config.js +8 -0
  118. package/lib/config.js.map +1 -1
  119. package/lib/hooks-map.d.ts +13 -0
  120. package/lib/hooks-map.d.ts.map +1 -1
  121. package/lib/hooks-map.js +33 -1
  122. package/lib/hooks-map.js.map +1 -1
  123. package/lib/observations.d.ts +35 -1
  124. package/lib/observations.d.ts.map +1 -1
  125. package/lib/observations.js +14 -2
  126. package/lib/observations.js.map +1 -1
  127. package/lib/ship.d.ts +2 -0
  128. package/lib/ship.d.ts.map +1 -1
  129. package/lib/ship.js +2 -0
  130. package/lib/ship.js.map +1 -1
  131. package/lib/thresholds.d.ts +49 -0
  132. package/lib/thresholds.d.ts.map +1 -0
  133. package/lib/thresholds.js +151 -0
  134. package/lib/thresholds.js.map +1 -0
  135. package/package.json +1 -1
  136. package/rt-tools-agent-kit-0.12.0.tgz +0 -0
  137. package/assets/commands/agent-kit-digest.md +0 -88
  138. package/assets/commands/rules-review.md +0 -98
  139. package/rt-tools-agent-kit-0.10.0.tgz +0 -0
@@ -13,6 +13,8 @@
13
13
  # команда становилась одним сегментом, доказательство чтения из первой строки покрывало
14
14
  # неразобранный вызов из второй, и запись на боевую базу проходила молча — тот же обход,
15
15
  # что чинили для `&&`, только набранный с новой строки.
16
+ . "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/utf8.sh" 2>/dev/null || true
17
+
16
18
  normalize() {
17
19
  perl -0ne '
18
20
  # Продолжение длинной команды обратным слешем — это одна строка, а не две: без
@@ -10,6 +10,9 @@
10
10
 
11
11
  # Три вида ввода: запрос через подключение редактора, команда оболочки и та же команда,
12
12
  # завёрнутая в универсальный исполнитель. Инструмент, не названный здесь, гарда не касается.
13
+ . "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/utf8.sh" 2>/dev/null || true
14
+ . "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/hook-input.sh" 2>/dev/null || true
15
+
13
16
  sql_read_request() {
14
17
  sql=""
15
18
  context=""
@@ -23,7 +26,7 @@ sql_read_request() {
23
26
  # Терминал IDE исполняет ту же командную строку и кладёт её в то же поле, что и Bash:
24
27
  # без этой ветки весь гард обходился сменой инструмента.
25
28
  Bash | mcp__webstorm__execute_terminal_command | mcp__webstorm__execute_tool)
26
- cmd="$(printf '%s' "$input" | jq -r '.tool_input.command // empty' 2>/dev/null)"
29
+ cmd="$(rt_hook_cmd)"
27
30
  [ -z "$cmd" ] && exit 0
28
31
 
29
32
  # Универсальный исполнитель зовёт ЛЮБОЙ инструмент редактора по имени, в том числе
@@ -18,6 +18,8 @@
18
18
  # подключения, и по одному SQL отличить прод от локальной копии невозможно. Поэтому
19
19
  # подключения опознаются в лицо. Список сверяется вызовом list_database_connections;
20
20
  # добавили новое — допишите сюда, иначе оно попадёт в «неизвестные» ниже.
21
+ . "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/utf8.sh" 2>/dev/null || true
22
+
21
23
  sql_resolve_target() {
22
24
  is_prod=""
23
25
  PROD_CONNECTIONS="${RT_PROD_CONNECTIONS:-}"
@@ -7,6 +7,8 @@
7
7
 
8
8
  # Глаголы ищутся в сегментах ВЫЗОВА, а не по всей строке: иначе слово из шаблона поиска в
9
9
  # соседнем звене цепочки объявляло записью читающую команду.
10
+ . "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/utf8.sh" 2>/dev/null || true
11
+
10
12
  sql_detect_write() {
11
13
  is_write=""
12
14
  write_scope="$segments"
@@ -40,11 +40,15 @@
40
40
  # пропускаются целиком — иначе слова «drop» и «update» в описании коммита читались бы как
41
41
  # запрос. Доставки запроса внутри такой команды не бывает, так что цена послабления нулевая.
42
42
 
43
- input="$(cat 2>/dev/null)"
43
+ . "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/utf8.sh" 2>/dev/null || true
44
+ . "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/hook-input.sh" 2>/dev/null || true
45
+
46
+ rt_hook_read
47
+ input="$RT_HOOK_INPUT"
44
48
  [ -z "$input" ] && exit 0
45
49
  command -v jq >/dev/null 2>&1 || exit 0
46
50
 
47
- tool="$(printf '%s' "$input" | jq -r '.tool_name // empty' 2>/dev/null)"
51
+ tool="$(rt_hook_tool)"
48
52
 
49
53
  # Рабочий каталог нужен одному правилу — разрешению адреса миграции: `DATABASE_URL`
50
54
  # обычно не назван в команде и лежит в `.env` рядом с проектом.
@@ -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
@@ -25,7 +25,11 @@
25
25
  # FAIL-OPEN: нет jq, не git-репозиторий, битый ввод, чужой инструмент → пропуск. Сломанный
26
26
  # гард не должен мешать работать.
27
27
 
28
- input="$(cat 2>/dev/null)"
28
+ . "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/utf8.sh" 2>/dev/null || true
29
+ . "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/hook-input.sh" 2>/dev/null || true
30
+
31
+ rt_hook_read
32
+ input="$RT_HOOK_INPUT"
29
33
  [ -z "$input" ] && exit 0
30
34
  command -v jq >/dev/null 2>&1 || exit 0
31
35
 
@@ -43,7 +47,7 @@ done
43
47
  [ -f "$rt_hooks_dir/profile-check.sh" ] && . "$rt_hooks_dir/profile-check.sh"
44
48
  command -v rt_needs >/dev/null 2>&1 || rt_needs() { command -v "$1" >/dev/null 2>&1; }
45
49
 
46
- tool="$(printf '%s' "$input" | jq -r '.tool_name // empty' 2>/dev/null)"
50
+ tool="$(rt_hook_tool)"
47
51
  candidates=""
48
52
  case "$tool" in
49
53
  # Инструмент редактора заводит файл теми же двумя данными, только называет их иначе —
@@ -59,7 +63,7 @@ case "$tool" in
59
63
  # имён гард стоял бы объявленным на них и молча пропускал — состояние хуже необъявленного,
60
64
  # потому что снаружи выглядит закрытым.
61
65
  Bash | mcp__webstorm__execute_terminal_command | mcp__webstorm__execute_tool)
62
- cmd="$(printf '%s' "$input" | jq -r '.tool_input.command // empty' 2>/dev/null)"
66
+ cmd="$(rt_hook_cmd)"
63
67
  [ -z "$cmd" ] && exit 0
64
68
  # Универсальный исполнитель прячет настоящую команду во вложенной строке: без её разбора
65
69
  # путь стоит за кавычкой, и до него не дотягивается ни один образец.
@@ -125,7 +129,7 @@ deny() {
125
129
  }
126
130
 
127
131
  # Ветку смотрим там же, где пойдёт правка: у worktree она своя.
128
- workdir="$(printf '%s' "$input" | jq -r '.cwd // empty' 2>/dev/null)"
132
+ workdir="$(rt_hook_cwd)"
129
133
  [ -z "$workdir" ] && workdir="${CLAUDE_PROJECT_DIR:-.}"
130
134
  cd "$workdir" 2>/dev/null || exit 0
131
135
  git rev-parse --is-inside-work-tree >/dev/null 2>&1 || exit 0
@@ -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
@@ -22,14 +22,21 @@
22
22
  # 5. Передача захода написана — окно кончилось.
23
23
  # 6. Владелец сказал остановиться.
24
24
  #
25
- # Чего страж не судит. Заход вне ветки задачи и работу без папки: состояние там объявлять
26
- # негде, и отбивать было бы не за что. Это его известная граница.
25
+ # Работа без ветки и без папки задачи судится вторым признаком. Состояния у неё нет, и первый
26
+ # признак взять неоткуда, но ход, в котором не было ни одной правки дерева, не кончается и
27
+ # здесь: просьба владельца «разложи», «обнови», «посмотри» живёт без задачи и без ветки, и
28
+ # защищена она была меньше всего. Отпускают такой ход те же четыре вещи: вопрос, отказ гарда,
29
+ # написанная передача и слово владельца об остановке.
27
30
  #
28
31
  # ОТКАЗ В ПОЛЬЗУ РАБОТЫ: при любой ошибке, нехватке `jq`, отсутствии записи хода, папки задачи
29
32
  # или строки состояния ход РАЗРЕШАЕТСЯ (exit 0). Сломанный страж не имеет права заклинить
30
33
  # разговор.
31
34
 
32
- input="$(cat 2>/dev/null)"
35
+ . "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/utf8.sh" 2>/dev/null || true
36
+ . "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/hook-input.sh" 2>/dev/null || true
37
+
38
+ rt_hook_read
39
+ input="$RT_HOOK_INPUT"
33
40
  [ -z "$input" ] && exit 0
34
41
  command -v jq >/dev/null 2>&1 || exit 0
35
42
 
@@ -41,22 +48,25 @@ transcript="$(printf '%s' "$input" | jq -r '.transcript_path // empty' 2>/dev/nu
41
48
  [ -z "$transcript" ] && exit 0
42
49
  [ -f "$transcript" ] || exit 0
43
50
 
44
- workdir="$(printf '%s' "$input" | jq -r '.cwd // empty' 2>/dev/null)"
51
+ workdir="$(rt_hook_cwd)"
45
52
  [ -z "$workdir" ] && workdir="${CLAUDE_PROJECT_DIR:-.}"
46
53
  cd "$workdir" 2>/dev/null || exit 0
47
54
  git rev-parse --is-inside-work-tree >/dev/null 2>&1 || exit 0
48
55
 
49
- branch="$(git branch --show-current 2>/dev/null)"
50
- [ -z "$branch" ] && exit 0
51
-
52
56
  root="$(git rev-parse --show-toplevel 2>/dev/null)"
53
57
  [ -z "$root" ] && exit 0
54
- tasks_dir="${RT_TASKS_DIR:-docs/tasks}"
55
- progress="$root/$tasks_dir/$branch/progress.md"
56
- [ -f "$progress" ] || exit 0
57
58
 
58
- state="$(sed -n 's/^[[:space:]]*[-*][[:space:]]*\*\*Состояние:\*\*[[:space:]]*`\([^`]*\)`.*/\1/p' "$progress" 2>/dev/null | head -1)"
59
- [ -z "$state" ] && exit 0
59
+ # Ветка, папка задачи и строка состояния берутся, пока они есть. Пусто — ход судится вторым
60
+ # признаком, а не отпускается: отсюда раньше уходили нулём, и работа по слову владельца
61
+ # кончалась объявлением намерения молча.
62
+ branch="$(git branch --show-current 2>/dev/null)"
63
+ tasks_dir="${RT_TASKS_DIR:-docs/tasks}"
64
+ progress=""
65
+ state=""
66
+ if [ -n "$branch" ] && [ -f "$root/$tasks_dir/$branch/progress.md" ]; then
67
+ progress="$root/$tasks_dir/$branch/progress.md"
68
+ state="$(sed -n 's/^[[:space:]]*[-*][[:space:]]*\*\*Состояние:\*\*[[:space:]]*`\([^`]*\)`.*/\1/p' "$progress" 2>/dev/null | head -1)"
69
+ fi
60
70
 
61
71
  # Работа, дошедшая до этих двух состояний, чужого шага уже дождалась: дальше её двигает
62
72
  # владелец, и ход, закрытый здесь, ничего не роняет.
@@ -66,7 +76,8 @@ esac
66
76
 
67
77
  # Следующий шаг из хода работы — его страж и называет в отказе: исполнитель, которому сказано
68
78
  # только «работа не кончена», перечитывает ту же строку сам.
69
- next_step="$(sed -n 's/^[[:space:]]*[-*][[:space:]]*\*\*Следующий шаг:\*\*[[:space:]]*\(.*\)/\1/p' "$progress" 2>/dev/null | head -1)"
79
+ next_step=""
80
+ [ -n "$progress" ] && next_step="$(sed -n 's/^[[:space:]]*[-*][[:space:]]*\*\*Следующий шаг:\*\*[[:space:]]*\(.*\)/\1/p' "$progress" 2>/dev/null | head -1)"
70
81
  [ -z "$next_step" ] && next_step="что стоит в разделе «Где стоим» хода работы"
71
82
 
72
83
  # Команда, меняющая дерево или его состояние. Чтение и поиск сюда не входят намеренно: ими и
@@ -117,10 +128,14 @@ commands="$(printf '%s' "$verdict" | jq -r '.ran // ""' 2>/dev/null)"
117
128
  # Страж сравнивает номер этапа с тем, что лежит в истории ветки, и на выросшем номере требует
118
129
  # команды из строки «Чем проверяется» — она стоит в замысле обратными кавычками. Приём, записанный
119
130
  # прозой, страж не читает: подтвердить его выводом нечем, и это его известная граница.
120
- stage_now="$(sed -n 's/^[[:space:]]*[-*][[:space:]]*\*\*Этап:\*\*[[:space:]]*\([0-9][0-9]*\).*/\1/p' "$progress" 2>/dev/null | head -1)"
121
- 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)"
131
+ stage_now=""
132
+ stage_was=""
133
+ if [ -n "$progress" ]; then
134
+ stage_now="$(sed -n 's/^[[:space:]]*[-*][[:space:]]*\*\*Этап:\*\*[[:space:]]*\([0-9][0-9]*\).*/\1/p' "$progress" 2>/dev/null | head -1)"
135
+ 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)"
136
+ fi
122
137
 
123
- if [ -n "$stage_now" ] && [ -n "$stage_was" ] && [ "$stage_now" -gt "$stage_was" ] 2>/dev/null; then
138
+ if [ -n "$progress" ] && [ -n "$stage_now" ] && [ -n "$stage_was" ] && [ "$stage_now" -gt "$stage_was" ] 2>/dev/null; then
124
139
  plan="$root/$tasks_dir/$branch/plan.md"
125
140
  # Контракт закрытого этапа, а не начатого: подтверждается то, что объявлено сделанным.
126
141
  contract="$(awk -v n="$stage_was" '
@@ -162,7 +177,18 @@ fi
162
177
 
163
178
  [ "$worked" = "true" ] && exit 0
164
179
 
165
- reason="BLOCKED by turn-exit-guard: работа в состоянии '${state}', а за этот ход по ней не сделано ничего — ни правки, ни команды, меняющей дерево.
180
+ if [ -z "$state" ]; then
181
+ reason="BLOCKED by turn-exit-guard: за этот ход не сделано ничего — ни правки, ни команды, меняющей дерево. Папки задачи у этой работы нет, и состояние взять неоткуда, но ход это не кончает.
182
+
183
+ Ход кончается четырьмя способами, и других нет: вопрос владельцу, ответа на который в правилах нет; отказ гарда; заполненное окно захода; отданная работа с начатой следующей. Названная и не запущенная команда выходом не является: строка «сейчас запущу» — объявление намерения, а оно прямо названо ложным концом хода.
184
+
185
+ Работа по слову владельца — «разложи», «обнови», «посмотри» — идёт без задачи и без ветки, и остановить её нечем, кроме этого признака.
186
+
187
+ Запусти названное этим же ходом. Владелец сказал остановиться — так и напиши: страж читает его слово, а не пересказ.
188
+
189
+ Страж судит один ход: следующий заход не отбивается."
190
+ else
191
+ reason="BLOCKED by turn-exit-guard: работа в состоянии '${state}', а за этот ход по ней не сделано ничего — ни правки, ни команды, меняющей дерево.
166
192
 
167
193
  Ход кончается четырьмя способами, и других нет: вопрос владельцу, ответа на который в правилах нет; отказ гарда; заполненное окно захода; отданная работа с начатой следующей. Отчёт о сделанном выходом не является — он выглядит работой лучше всякой другой, и пустоты за ним не видно.
168
194
 
@@ -171,6 +197,7 @@ reason="BLOCKED by turn-exit-guard: работа в состоянии '${state}
171
197
  Сделай его этим же ходом. Владелец сказал остановиться — так и напиши: страж читает его слово, а не пересказ.
172
198
 
173
199
  Страж судит один ход: следующий заход не отбивается."
200
+ fi
174
201
 
175
202
  # Общий хвост отказа: два законных хода. Файл может быть не разложен — тогда хвоста нет,
176
203
  # а причина отказа остаётся прежней.
@@ -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
@@ -30,7 +30,11 @@
30
30
  # ОТКАЗ В ПОЛЬЗУ РАБОТЫ: при любой ошибке, нехватке `jq`, отсутствии записи хода и повторном
31
31
  # заходе ход РАЗРЕШАЕТСЯ (exit 0). Сломанный гард не имеет права заклинить разговор.
32
32
 
33
- input="$(cat 2>/dev/null)"
33
+ . "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/utf8.sh" 2>/dev/null || true
34
+ . "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/hook-input.sh" 2>/dev/null || true
35
+
36
+ rt_hook_read
37
+ input="$RT_HOOK_INPUT"
34
38
  [ -z "$input" ] && exit 0
35
39
 
36
40
  command -v jq >/dev/null 2>&1 || exit 0
@@ -16,13 +16,24 @@
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
 
25
- input="$(cat 2>/dev/null)"
32
+ . "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/utf8.sh" 2>/dev/null || true
33
+ . "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/hook-input.sh" 2>/dev/null || true
34
+
35
+ rt_hook_read
36
+ input="$RT_HOOK_INPUT"
26
37
  [ -z "$input" ] && exit 0
27
38
 
28
39
  command -v jq >/dev/null 2>&1 || exit 0
@@ -49,6 +60,21 @@ esac
49
60
 
50
61
  warn_pct="${RT_WINDOW_WARN_PCT:-40}"
51
62
  stop_pct="${RT_WINDOW_STOP_PCT:-50}"
63
+
64
+ # Доля, на которой контекст сжимает сам инструмент. Объявлена деревом — заход через порог
65
+ # проходит сам: сжатие приходит первым, передачу к тому моменту уже написал свой хук, и работа
66
+ # идёт дальше тем же заходом. Не объявлена — прежний порядок: заход кончается передачей.
67
+ #
68
+ # От этого зависит текст напоминания, а не отказ. Отбой на пороге остановки остаётся в обоих
69
+ # случаях: он и есть страховка на случай, когда сжатие не пришло — настройка снята, версия
70
+ # другая, сжатие отказало. Отобрав отбой у дерева, объявившего сжатие, страж пустил бы такой
71
+ # заход до предела окна, где работа теряется целиком.
72
+ compact_pct="${CLAUDE_AUTOCOMPACT_PCT_OVERRIDE:-}"
73
+ case "$compact_pct" in
74
+ '' | *[!0-9]*) compact_pct='' ;;
75
+ esac
76
+ [ -n "$compact_pct" ] && [ "$compact_pct" -ge "$stop_pct" ] 2>/dev/null && compact_pct=''
77
+
52
78
  tasks_dir="${RT_TASKS_DIR:-docs/tasks}"
53
79
  handoff_dir="${RT_HANDOFF_DIR:-.claude/handoff}"
54
80
 
@@ -99,7 +125,13 @@ if [ "$event" = "PostToolUse" ]; then
99
125
  printf '%s' "$step" > "$mark" 2>/dev/null
100
126
 
101
127
  if [ "$pct" -ge "$stop_pct" ]; then
102
- text="ЗАПОЛНЕНИЕ ОКНА ${pct}% (${fill_k}k из ${window_k}k) — заход закрывается сейчас. Всё, кроме записи хода работы, передачи и команд поставки, уже отбивается."
128
+ if [ -n "$compact_pct" ]; then
129
+ text="ЗАПОЛНЕНИЕ ОКНА ${pct}% (${fill_k}k из ${window_k}k) — заход закрывается сейчас. Сжатие объявлено на ${compact_pct}% и не пришло: порог остановки ${stop_pct}% пройден, а контекст прежний. Всё, кроме записи хода работы, передачи и команд поставки, уже отбивается — закрывай заход и скажи владельцу, что сжатие не сработало."
130
+ else
131
+ text="ЗАПОЛНЕНИЕ ОКНА ${pct}% (${fill_k}k из ${window_k}k) — заход закрывается сейчас. Всё, кроме записи хода работы, передачи и команд поставки, уже отбивается."
132
+ fi
133
+ elif [ -n "$compact_pct" ]; then
134
+ text="ЗАПОЛНЕНИЕ ОКНА ${pct}% (${fill_k}k из ${window_k}k). Точку остановки выбирать не надо: на ${compact_pct}% инструмент сожмёт контекст сам, передачу к тому времени напишет хук, и работа пойдёт дальше этим же заходом. Порог остановки ${stop_pct}% — страховка на случай, если сжатие не придёт. Работай дальше."
103
135
  else
104
136
  text="ЗАПОЛНЕНИЕ ОКНА ${pct}% (${fill_k}k из ${window_k}k). Пора выбирать точку остановки: с ${stop_pct}% останется только закрыть заход. Доведи текущий шаг до состояния, с которого следующий заход продолжит, перепиши «Где стоим» в ходе работы, напиши передачу и отдай владельцу путь к ней — паттерн task-flow-handoff."
105
137
  fi
@@ -114,9 +146,9 @@ fi
114
146
  [ "$event" = "PreToolUse" ] || exit 0
115
147
  [ "$pct" -ge "$stop_pct" ] || exit 0
116
148
 
117
- tool="$(printf '%s' "$input" | jq -r '.tool_name // empty' 2>/dev/null)"
118
- path="$(printf '%s' "$input" | jq -r '.tool_input.file_path // empty' 2>/dev/null)"
119
- cmd="$(printf '%s' "$input" | jq -r '.tool_input.command // empty' 2>/dev/null)"
149
+ tool="$(rt_hook_tool)"
150
+ path="$(rt_hook_file)"
151
+ cmd="$(rt_hook_cmd)"
120
152
 
121
153
  allowed=0
122
154
  case "$tool" in
@@ -1,9 +1,7 @@
1
1
  # Закон о ведении работы
2
2
 
3
- Что должно быть верно про то, как работа идёт от просьбы владельца до её закрытия. Работа
4
- длится дольше одного захода и переживает перерывы: между заходами исполнитель не помнит
5
- ничего, а владелец помнит и вынужден пересказывать. Пересказ каждый раз выходит короче
6
- предыдущего, и работа доделывается по обрывку исходной просьбы.
3
+ Закон устанавливает как ведётся работа от момента постановки задачи до её закрытия. Работа
4
+ длится дольше одной сессии и должна сохранять весь контекст и состояние при переходах из одной сессии в другую.
7
5
 
8
6
  ## Статьи
9
7
 
@@ -13,15 +11,13 @@
13
11
  - **Пробел закрывается вопросом владельцу, а не догадкой.** Догадка неотличима от знания:
14
12
  она попадает в работу молча и обнаруживается только при приёмке, когда переделывать дороже
15
13
  всего.
16
- - **Работа, объявленная сборкой по образцу, начинается с чтения самого образца.** Пересказ
14
+ - **Работа, которая требует изменений по образцу, начинается с чтения самого образца.** Пересказ
17
15
  образца образцом не является: по нему собирается понимание того, кто пересказывал, и
18
16
  расхождение всплывает на приёмке целой работой, а не строкой. Читается та часть образца,
19
17
  которую работа повторяет, и читается целиком — об устройстве чужого дерева по одному его
20
18
  файлу не судят.
21
- - **Образец, названный однажды, доступен каждому заходу работы.** Где он лежит, записано
22
- там, где заход найдёт это без владельца. Иначе второй заход собирает по памяти первого,
23
- третий — по пересказу второго, и к четвёртому от образца не остаётся ничего, кроме слова
24
- «образец».
19
+ - **Образец, указанный в ходе работы должен быть доступен каждой сессии во время работы.**
20
+ Ссылка на образец нужно указывать непосредственно в файле с описанием прогресса работы.
25
21
  - **Вопрос, у которого есть очевидный ответ, работу не останавливает.** Исполнитель называет
26
22
  допущение, идёт дальше и записывает его туда же, где идёт работа. Останавливает только тот
27
23
  пробел, при котором любая догадка делает работу опасной или бесполезной. Вопрос, заданный
@@ -71,6 +67,15 @@
71
67
  владельца.** Владелец знает, что работа не кончена, но не знает, где именно она стоит;
72
68
  пересказ он даёт по своей памяти, а не по ходу работы, и следующий заход начинает с чужой
73
69
  картины.
70
+ - **Переданное прошлым заходом приходит в новый заход само, а не кладётся рукой.** Передача,
71
+ написанная и не прочитанная, равна ненаписанной: следующий заход о ней не знает и начинает с
72
+ пустого места — то есть с того же, ради чего её и писали. Кладёт её человек ровно до тех пор,
73
+ пока это не поручено машине; поручённое машине не забывается ночью.
74
+ - **Порядок ведения работы приходит в заход вместе с работой, а не разыскивается им.** Заход,
75
+ знающий задачу и не знающий, что с ней делают дальше, первым движением идёт читать правило
76
+ целиком — и тратит на это ту часть места, ради которой заход и начали заново. Приходит
77
+ короткая выжимка: состояния, обязательное действие каждого и то, чем ход кончается. Правило
78
+ она не заменяет и заменить не может — она отвечает на вопрос «что делать», а не «почему».
74
79
  - **Замысел и ход работы — разные записи.** Замысел — то, с чем сверяют результат при
75
80
  приёмке; правленный по ходу, он перестаёт отличаться от PR, и приёмке сверять нечего.
76
81
  - **Сделанное отмечается в одном месте.** Две записи об одном разъезжаются молча, и после
@@ -142,6 +147,26 @@
142
147
  остановиться позволяет только предел заполнения окна. Заход, закрытый на готовой задаче,
143
148
  оставляет владельцу пустое место и стоит целого захода на возвращение к тому, что и так было
144
149
  под рукой.
150
+ - **Предел заполнения окна останавливает заход только там, где инструмент не сжимает контекст
151
+ сам.** Где сжатие объявлено и приходит раньше предела, окно — не конец захода, а его
152
+ продолжение: контекст сжимается, записанное состояние работы возвращается в него, и работа
153
+ идёт дальше тем же заходом. Останавливаться там незачем, а остановка стоит того же, что и
154
+ всякая другая: владелец возвращает исполнителя в работу руками.
155
+ - **Порог, на котором заход останавливают, стоит позже порога, на котором его продолжают.**
156
+ Два порога на одном числе — это не согласие, а гонка, и выигрывает её тот, кто ближе к
157
+ действию: остановка приходит на вызове, а сжатие — между ходами. Расстояние между ними
158
+ объявляется, а не выводится разницей: сжатие идёт не мгновенно, и порог, отстоящий на волос,
159
+ требование «раньше» удовлетворяет, а работу не спасает.
160
+ - **Переход из состояния в состояние исполнителя не останавливает.** Обязательное
161
+ действие сделано — следующее начинается тем же движением, без отчёта владельцу и без его
162
+ слова. Граница между состояниями выглядит законченным куском лучше всякой другой вехи:
163
+ сделанное названо, отчитаться есть чем, — и отчёт встаёт ровно на то место, где должно было
164
+ стоять следующее действие. Владелец читает такой отчёт как сделанную работу, а работа стоит.
165
+ - **Текст, ведущий состояние работы, называет, что делается сразу за ним.** Дочитанный до
166
+ последнего приёма, он кончается ничем: следующего движения в нём нет, и исполнитель выводит
167
+ его из пустоты — то есть останавливается. Названное движение стоит там же, где приёмы, и
168
+ своими словами: одинаковая на все состояния строка перестаёт замечаться раньше, чем
169
+ понадобится.
145
170
  - **Работа, отданная на разбор, освобождает исполнителя, а не останавливает его.** Отданное на
146
171
  разбор ждёт владельца, а не машину: следующая задача эпика берётся тем же движением, которым
147
172
  предыдущая ушла на разбор.
@@ -2,7 +2,7 @@
2
2
  name: dependencies-upgrade
3
3
  kind: pattern
4
4
  rule: dependencies
5
- description: Паттерн правила dependencies. Брать при подъёме версий пакетов — выбор верхней границы по peer-диапазонам, порядок проверок, граница переформатирования после обновления форматтера, разбор новых правил линтера. Не брать для ветки, коммита и PR — это паттерн git-workflow-commit.
5
+ description: Паттерн правила dependencies. Брать при подъёме версий пакетов — выбор верхней границы по peer-диапазонам, порядок проверок, граница переформатирования после обновления форматтера, разбор новых правил линтера. Не брать для ветки и коммита — это паттерн git-workflow-commit, для PR — git-workflow-pr.
6
6
  ---
7
7
 
8
8
  # Подъём версий
@@ -19,7 +19,7 @@ description: Паттерн правила doc-style. Брать при напи
19
19
 
20
20
  Само правило укладывается в одно предложение. Следом — не больше двух предложений о том, что
21
21
  сломается иначе, и только если из самой фразы это не видно. Обоснование, у которого была
22
- альтернатива, идёт в «Ловушки» правила, а не в сам закон: разросшийся буллет читают по
22
+ альтернатива, идёт в ловушки — холодную часть правила, а не в сам закон: разросшийся буллет читают по
23
23
  диагонали, а закон говорит, что верно, — не почему когда-то выбрали так.
24
24
 
25
25
  ```
@@ -41,8 +41,8 @@ description: Паттерн правила doc-style. Брать при напи
41
41
  Место исполнения меняется при первом же рефакторинге, и документ, который его называет,
42
42
  устаревает молча. Для него заведён отдельный файл — `implementation.md` рядом.
43
43
 
44
- Исключение — «Ловушки» правила: там устройство кода называется прямо, потому что ловушка и
45
- есть место, где на него наступают.
44
+ Исключение — ловушки в холодной части правила: там устройство кода называется прямо, потому
45
+ что ловушка и есть место, где на него наступают.
46
46
 
47
47
  ## Без утверждений о будущем
48
48