@rt-tools/agent-kit 0.11.0 → 0.13.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 (138) hide show
  1. package/assets/checks/board-runs.github.mjs +87 -0
  2. package/assets/checks/board.github.mjs +0 -40
  3. package/assets/checks/check-board.github.mjs +39 -8
  4. package/assets/checks/check-file-size.mjs +19 -4
  5. package/assets/checks/check-schema-drift.mjs +28 -5
  6. package/assets/checks/check-state-next.mjs +10 -2
  7. package/assets/checks/rt-kit-checks.config.mjs +28 -2
  8. package/assets/commands/feedback.md +8 -0
  9. package/assets/defaults/project.sh +59 -6
  10. package/assets/defaults/turn-map.md +8 -6
  11. package/assets/hooks/browser-guard-device-id.sh +3 -1
  12. package/assets/hooks/browser-guard-no-asking.sh +3 -1
  13. package/assets/hooks/browser-guard-no-other-drivers.sh +6 -4
  14. package/assets/hooks/browser-guard-require-select.sh +4 -2
  15. package/assets/hooks/claim-guard.sh +3 -1
  16. package/assets/hooks/conscience-guard.sh +3 -1
  17. package/assets/hooks/dev-server-guard.sh +6 -4
  18. package/assets/hooks/dispatch.sh +69 -0
  19. package/assets/hooks/docs-guard.sh +6 -4
  20. package/assets/hooks/exam-guard.sh +5 -3
  21. package/assets/hooks/git-guard-delivery-folder.sh +99 -0
  22. package/assets/hooks/git-guard-delivery-signature.sh +10 -4
  23. package/assets/hooks/git-guard-delivery.sh +58 -74
  24. package/assets/hooks/git-guard-main.sh +6 -4
  25. package/assets/hooks/git-guard-push-tests.sh +8 -6
  26. package/assets/hooks/grill-gate.sh +4 -2
  27. package/assets/hooks/handoff-entry-guard.sh +4 -2
  28. package/assets/hooks/handoff-write.sh +27 -6
  29. package/assets/hooks/hook-input.sh +77 -0
  30. package/assets/hooks/lint-after-edit.sh +5 -3
  31. package/assets/hooks/override-write-guard.sh +107 -0
  32. package/assets/hooks/postmortem-guard.sh +3 -1
  33. package/assets/hooks/proposal-guard.sh +3 -1
  34. package/assets/hooks/prose-style-guard.sh +5 -3
  35. package/assets/hooks/qa-dataid-guard.sh +4 -2
  36. package/assets/hooks/rerun-guard.sh +6 -4
  37. package/assets/hooks/reuse-first-guard.sh +5 -3
  38. package/assets/hooks/rule-article.sh +99 -0
  39. package/assets/hooks/rule-source-guard.sh +126 -0
  40. package/assets/hooks/skill-gate-rearm.sh +3 -1
  41. package/assets/hooks/skill-gate.sh +23 -2
  42. package/assets/hooks/skill-loaded.sh +3 -1
  43. package/assets/hooks/sql-guard-request.sh +2 -1
  44. package/assets/hooks/sql-guard.sh +4 -2
  45. package/assets/hooks/task-flow-guard.sh +24 -4
  46. package/assets/hooks/turn-exit-guard.sh +59 -17
  47. package/assets/hooks/waiting-turn-guard.sh +3 -1
  48. package/assets/hooks/window-fill-guard.sh +6 -4
  49. package/assets/laws/work-conduct.md +5 -9
  50. package/assets/patterns/dependencies-upgrade.md +1 -1
  51. package/assets/patterns/doc-style-write.md +3 -3
  52. package/assets/patterns/git-workflow-commit.azure.md +2 -202
  53. package/assets/patterns/git-workflow-commit.github.md +2 -258
  54. package/assets/patterns/git-workflow-commit.gitlab.md +1 -217
  55. package/assets/patterns/git-workflow-docker.md +3 -3
  56. package/assets/patterns/git-workflow-merge.md +3 -2
  57. package/assets/patterns/git-workflow-migration.md +3 -3
  58. package/assets/patterns/git-workflow-pr.azure.md +224 -0
  59. package/assets/patterns/git-workflow-pr.github.md +280 -0
  60. package/assets/patterns/git-workflow-pr.gitlab.md +240 -0
  61. package/assets/patterns/git-workflow-restart.md +3 -3
  62. package/assets/patterns/git-workflow-secrets.md +3 -3
  63. package/assets/patterns/task-flow-archive.md +195 -0
  64. package/assets/patterns/task-flow-close.md +72 -240
  65. package/assets/patterns/task-flow-handoff.md +4 -4
  66. package/assets/patterns/task-flow-resume.md +5 -3
  67. package/assets/pitfalls/agent-kit.md +80 -0
  68. package/assets/pitfalls/doc-style.md +80 -0
  69. package/assets/pitfalls/git-workflow.azure.md +50 -0
  70. package/assets/pitfalls/git-workflow.github.md +78 -0
  71. package/assets/pitfalls/git-workflow.gitlab.md +49 -0
  72. package/assets/pitfalls/spec-driven.md +36 -0
  73. package/assets/pitfalls/styling-bem.md +45 -0
  74. package/assets/pitfalls/task-flow.md +62 -0
  75. package/assets/pitfalls/testing.md +70 -0
  76. package/assets/rules/deploy-flow.azure.md +114 -0
  77. package/assets/rules/deploy-flow.github.md +122 -0
  78. package/assets/rules/deploy-flow.gitlab.md +116 -0
  79. package/assets/rules/doc-style.md +25 -76
  80. package/assets/rules/git-workflow.azure.md +6 -92
  81. package/assets/rules/git-workflow.github.md +20 -128
  82. package/assets/rules/git-workflow.gitlab.md +6 -93
  83. package/assets/rules/spec-driven.md +39 -30
  84. package/assets/rules/styling-bem.md +20 -39
  85. package/assets/rules/task-flow.md +56 -222
  86. package/assets/rules/testing.md +3 -64
  87. package/assets/rules/turn-conduct.md +210 -0
  88. package/assets/rules/typescript-conventions.md +15 -0
  89. package/assets/skills/agent-kit.md +67 -89
  90. package/assets/templates/pitfalls.md +10 -0
  91. package/assets/templates/proposal.md +16 -1
  92. package/assets/templates/rule.md +5 -3
  93. package/bin/agent-kit.d.ts.map +1 -1
  94. package/bin/agent-kit.js +1 -42
  95. package/bin/agent-kit.js.map +1 -1
  96. package/lib/assets.d.ts.map +1 -1
  97. package/lib/assets.js +6 -1
  98. package/lib/assets.js.map +1 -1
  99. package/lib/cascade.d.ts.map +1 -1
  100. package/lib/cascade.js +19 -1
  101. package/lib/cascade.js.map +1 -1
  102. package/lib/commands.d.ts.map +1 -1
  103. package/lib/commands.js +1 -0
  104. package/lib/commands.js.map +1 -1
  105. package/lib/config.d.ts +16 -1
  106. package/lib/config.d.ts.map +1 -1
  107. package/lib/config.js +8 -0
  108. package/lib/config.js.map +1 -1
  109. package/lib/hooks-map.d.ts +13 -0
  110. package/lib/hooks-map.d.ts.map +1 -1
  111. package/lib/hooks-map.js +33 -1
  112. package/lib/hooks-map.js.map +1 -1
  113. package/lib/proposals.d.ts +5 -1
  114. package/lib/proposals.d.ts.map +1 -1
  115. package/lib/proposals.js +74 -5
  116. package/lib/proposals.js.map +1 -1
  117. package/lib/ship.d.ts +1 -2
  118. package/lib/ship.d.ts.map +1 -1
  119. package/lib/ship.js +0 -54
  120. package/lib/ship.js.map +1 -1
  121. package/lib/shipment.d.ts.map +1 -1
  122. package/lib/shipment.fixture.d.ts +39 -0
  123. package/lib/shipment.fixture.d.ts.map +1 -0
  124. package/lib/shipment.fixture.js +99 -0
  125. package/lib/shipment.fixture.js.map +1 -0
  126. package/lib/shipment.js +59 -7
  127. package/lib/shipment.js.map +1 -1
  128. package/package.json +1 -1
  129. package/rt-tools-agent-kit-0.13.0.tgz +0 -0
  130. package/assets/commands/agent-kit-digest.md +0 -89
  131. package/assets/commands/rules-review.md +0 -98
  132. package/assets/patterns/cargo-triage-mark.md +0 -119
  133. package/assets/rules/cargo-triage.md +0 -126
  134. package/lib/cargo-state.d.ts +0 -62
  135. package/lib/cargo-state.d.ts.map +0 -1
  136. package/lib/cargo-state.js +0 -118
  137. package/lib/cargo-state.js.map +0 -1
  138. package/rt-tools-agent-kit-0.11.0.tgz +0 -0
@@ -0,0 +1,126 @@
1
+ #!/usr/bin/env bash
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, hooks/deny-tail.sh
4
+ # Гард места правки: слой правил чинится там, где сломано, а не там, где виден.
5
+ #
6
+ # Слой правил правит тот же исполнитель, которым слой правил управляет, и разницы между
7
+ # «исполняю правило» и «правлю правило» в дереве не видно ничем: разложенная копия лежит рядом
8
+ # с обычными файлами и правится так же. Правка доходит до диска и не доходит до места, где
9
+ # промах чинится: в дереве она видна, в пакете её нет, а через месяц раскладка отказывает по
10
+ # правленому файлу целиком — и цену платит тот, кто в этот день правил соседний ресурс.
11
+ #
12
+ # Знал об этом только `sync --check`, и говорил он на следующей раскладке, то есть в чужой
13
+ # ветке и чужим ходом. Гард говорит в минуту правки и называет адрес: источник пакета, если
14
+ # дерево его держит, иначе — надстройку.
15
+ #
16
+ # ЧТО СУДИТСЯ. Файл, который правят, несёт шапку раскладки: `rt-kit v<версия> · <ресурс> ·
17
+ # <дайджест>`. Ресурс из неё и есть адрес: `<источники>/<ресурс>` в дереве пакета,
18
+ # `.claude/rt-kit/overrides/<ресурс>` у потребителя.
19
+ #
20
+ # ОТКАЗ В ПОЛЬЗУ РАБОТЫ: нет `jq`, битый ввод, чужой инструмент, файла нет, шапки в нём нет,
21
+ # не git-репозиторий — правка РАЗРЕШАЕТСЯ. Сломанный гард не имеет права заклинить работу.
22
+
23
+ . "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/utf8.sh" 2>/dev/null || true
24
+ . "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/hook-input.sh" 2>/dev/null || true
25
+
26
+ rt_hook_read
27
+ input="$RT_HOOK_INPUT"
28
+ [ -z "$input" ] && exit 0
29
+ command -v jq >/dev/null 2>&1 || exit 0
30
+
31
+ rt_hooks_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
32
+ for profile in "$rt_hooks_dir/../rt-kit/defaults/project.sh" "$rt_hooks_dir/../defaults/project.sh" "${CLAUDE_PROJECT_DIR:-.}/.claude/rt-kit/defaults/project.sh" "${CLAUDE_PROJECT_DIR:-.}/.claude/rt-kit/project.sh"; do
33
+ # shellcheck disable=SC1090
34
+ [ -f "$profile" ] && . "$profile" 2>/dev/null
35
+ done
36
+
37
+ # shellcheck disable=SC1090
38
+ [ -f "$rt_hooks_dir/profile-check.sh" ] && . "$rt_hooks_dir/profile-check.sh"
39
+ command -v rt_needs >/dev/null 2>&1 || rt_needs() { command -v "$1" >/dev/null 2>&1; }
40
+
41
+ # shellcheck disable=SC1090
42
+ [ -f "$rt_hooks_dir/deny-tail.sh" ] && . "$rt_hooks_dir/deny-tail.sh" 2>/dev/null
43
+ command -v rt_deny_tail >/dev/null 2>&1 || rt_deny_tail() { printf ''; }
44
+
45
+ deny() {
46
+ # shellcheck disable=SC1090
47
+ [ -f "$rt_hooks_dir/observe.sh" ] && . "$rt_hooks_dir/observe.sh" 2>/dev/null
48
+ command -v rt_note >/dev/null 2>&1 && rt_note guard-deny res=rule-source-guard
49
+
50
+ reason="$1 $(rt_deny_tail "$2")"
51
+ jq -n --arg r "$reason" '{hookSpecificOutput:{hookEventName:"PreToolUse",permissionDecision:"deny",permissionDecisionReason:$r}}' 2>/dev/null \
52
+ || printf '{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"deny","permissionDecisionReason":"Гард места правки."}}\n'
53
+ exit 0
54
+ }
55
+
56
+ # Цели записи, названные командой прямо. Печатает по одной в строке.
57
+ rt_write_targets() {
58
+ tr "\"'\`" ' ' \
59
+ | sed -E 's/>>?/\n>/g' \
60
+ | sed -nE '
61
+ s/^>[[:space:]]*([^[:space:]|&;]+).*/\1/p
62
+ s/(^|.*[[:space:]])tee[[:space:]]+(-a[[:space:]]+)?([^[:space:]|&;]+).*/\3/p
63
+ s/(^|.*[[:space:]])sed[[:space:]]+-i[[:space:]]+([^[:space:]]+[[:space:]]+)*([^[:space:]|&;]+)$/\3/p
64
+ s/(^|.*[[:space:]])(cp|mv|install)[[:space:]]+([^[:space:]]+[[:space:]]+)+([^[:space:]|&;]+).*/\4/p
65
+ ' \
66
+ | sort -u
67
+ }
68
+
69
+ tool="$(rt_hook_tool)"
70
+ candidates=""
71
+ case "$tool" in
72
+ Edit | Write | MultiEdit | mcp__webstorm__create_new_file)
73
+ candidates="$(printf '%s' "$input" | jq -r '.tool_input.file_path // .tool_input.pathInProject // empty' 2>/dev/null)"
74
+ ;;
75
+ Bash | mcp__webstorm__execute_terminal_command | mcp__webstorm__execute_tool)
76
+ cmd="$(rt_hook_cmd)"
77
+ [ -z "$cmd" ] && exit 0
78
+ # Здесь берётся не общий признак записи, а цель, названная в команде прямо:
79
+ # перенаправление, `tee`, правка на месте, копирование поверх. Общий признак широк
80
+ # намеренно — в нём и имя интерпретатора, — и запуск разложенной проверки читался бы
81
+ # как правка её самой. Отбитие на чтении стоит дороже пропуска: гард, мешающий читать,
82
+ # выключают в первый же день.
83
+ #
84
+ # Снятие копии сюда не входит намеренно: снятый файл раскладка кладёт заново, и так
85
+ # чинят копию, которую переписал форматтер.
86
+ candidates="$(printf '%s' "$cmd" | rt_write_targets)"
87
+ ;;
88
+ *) exit 0 ;;
89
+ esac
90
+ [ -z "$candidates" ] && exit 0
91
+
92
+ root="${CLAUDE_PROJECT_DIR:-.}"
93
+
94
+ # Каталог источников пакета в этом дереве. Есть он только у дерева, которое пакет и везёт; у
95
+ # потребителя источника нет вовсе, и адрес правки у него один — надстройка.
96
+ sources=""
97
+ if rt_needs rt_kit_sources_dir rule-source-guard; then
98
+ sources="$(rt_kit_sources_dir 2>/dev/null)"
99
+ fi
100
+
101
+ while IFS= read -r candidate; do
102
+ [ -z "$candidate" ] && continue
103
+ case "$candidate" in
104
+ /*) ;;
105
+ *) candidate="$root/$candidate" ;;
106
+ esac
107
+ [ -f "$candidate" ] || continue
108
+
109
+ # Шапка стоит в начале файла, но не первой строкой: у сценария её отодвигает `#!`, у
110
+ # правила — заголовок с именем и родом ресурса. Десяти строк хватает обоим, а читать файл
111
+ # целиком нельзя: гард стоит на каждой правке.
112
+ resource="$(head -12 "$candidate" 2>/dev/null | sed -nE 's/.*rt-kit v[^ ]+ · ([^ ]+) · [0-9a-f]+.*/\1/p' | head -1)"
113
+ [ -z "$resource" ] && continue
114
+
115
+ if [ -n "$sources" ] && [ -f "$root/$sources/$resource" ]; then
116
+ deny "BLOCKED by rule-source-guard: «${candidate#"$root"/}» разложен пакетом, и правка на его месте теряется на следующей раскладке — а до тех пор раскладка отказывает по этому файлу целиком, и цену платит тот, кто в этот день правит соседний ресурс. Ресурс — «${resource}». Правь источник: ${sources}/${resource} — потом собери пакет и разложи." \
117
+ "правка, верная только этому дереву, идёт в надстройку .claude/rt-kit/overrides/${resource} — она сливается по разделу «## » и раскладку переживает"
118
+ fi
119
+
120
+ deny "BLOCKED by rule-source-guard: «${candidate#"$root"/}» разложен пакетом @rt-tools/agent-kit, и правка на его месте теряется на следующей раскладке. Ресурс — «${resource}». Правь надстройку: .claude/rt-kit/overrides/${resource} — она сливается по разделу «## » и раскладку переживает." \
121
+ "то, что верно любому дереву, правится в самом пакете и приезжает сюда новой редакцией"
122
+ done <<EOF
123
+ $candidates
124
+ EOF
125
+
126
+ exit 0
@@ -13,8 +13,10 @@
13
13
  # отбивает.
14
14
 
15
15
  . "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/utf8.sh" 2>/dev/null || true
16
+ . "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/hook-input.sh" 2>/dev/null || true
16
17
 
17
- input="$(cat 2>/dev/null)"
18
+ rt_hook_read
19
+ input="$RT_HOOK_INPUT"
18
20
  [ -z "$input" ] && exit 0
19
21
 
20
22
  sid="$(printf '%s' "$input" | jq -r '.session_id // empty' 2>/dev/null)"
@@ -24,12 +24,14 @@
24
24
  # Сломанный гейт не имеет права остановить работу совсем.
25
25
 
26
26
  . "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/utf8.sh" 2>/dev/null || true
27
+ . "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/hook-input.sh" 2>/dev/null || true
27
28
 
28
- input="$(cat 2>/dev/null)"
29
+ rt_hook_read
30
+ input="$RT_HOOK_INPUT"
29
31
  [ -z "$input" ] && exit 0
30
32
 
31
33
  sid="$(printf '%s' "$input" | jq -r '.session_id // "nosession"' 2>/dev/null)"
32
- tool="$(printf '%s' "$input" | jq -r '.tool_name // empty' 2>/dev/null)"
34
+ tool="$(rt_hook_tool)"
33
35
 
34
36
  # Умолчание карты ищется и рядом с самим хуком: уезжают они вместе.
35
37
  rt_hooks_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
@@ -175,6 +177,25 @@ if [ -n "$law" ]; then
175
177
  && reason="Отбито гейтом правил: загрузи правило «${req}» инструментом Skill — оно применяет закон ${law_path} к этому дереву — и повтори действие. ${fallback} Для этой области это происходит один раз за сессию."
176
178
  fi
177
179
 
180
+ # Статья, под которую подпадает эта правка. Правило весит от двадцати до шестидесяти килобайт,
181
+ # и отказ, зовущий его целиком, платит за решение полной ценой правила. Статья говорит о своей
182
+ # применимости сама — разбирает её помощник рядом; нет помощника или нет размеченных статей,
183
+ # отказ остаётся прежним, и это тот же отказ в пользу работы, что и везде.
184
+ #
185
+ # Загрузку правила статья не отменяет: она снимает чтение правила целиком, а не сам отказ.
186
+ # Правило целиком остаётся вторым ходом — для того, кому статьи мало.
187
+ # shellcheck disable=SC1090
188
+ [ -f "$rt_hooks_dir/rule-article.sh" ] && . "$rt_hooks_dir/rule-article.sh" 2>/dev/null
189
+ if [ "$kind" != "command" ] && [ "$kind" != "browser" ] && command -v rt_rule_articles >/dev/null 2>&1; then
190
+ article="$(rt_rule_articles "$root/$rules_dir/${req}/SKILL.md" "$target" 2>/dev/null)"
191
+ if [ -n "$article" ]; then
192
+ reason="Отбито гейтом правил. Под эту правку подпадает статья правила «${req}»:
193
+
194
+ ${article}
195
+ Статья снимает чтение правила целиком, а не отказ: загрузи правило «${req}» инструментом Skill и повтори действие. ${fallback} Для этой области это происходит один раз за сессию."
196
+ fi
197
+ fi
198
+
178
199
  # Общий хвост отказа: два законных хода и законная форма обхода, если она у отказа есть.
179
200
  # Файл может быть не разложен — тогда хвоста нет, а причина отказа остаётся прежней.
180
201
  # shellcheck disable=SC1090
@@ -8,8 +8,10 @@
8
8
  # Хук только наблюдает: он всегда пропускает и ничего не отбивает.
9
9
 
10
10
  . "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/utf8.sh" 2>/dev/null || true
11
+ . "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/hook-input.sh" 2>/dev/null || true
11
12
 
12
- input="$(cat 2>/dev/null)"
13
+ rt_hook_read
14
+ input="$RT_HOOK_INPUT"
13
15
  [ -z "$input" ] && exit 0
14
16
 
15
17
  sid="$(printf '%s' "$input" | jq -r '.session_id // "nosession"' 2>/dev/null)"
@@ -11,6 +11,7 @@
11
11
  # Три вида ввода: запрос через подключение редактора, команда оболочки и та же команда,
12
12
  # завёрнутая в универсальный исполнитель. Инструмент, не названный здесь, гарда не касается.
13
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
14
15
 
15
16
  sql_read_request() {
16
17
  sql=""
@@ -25,7 +26,7 @@ sql_read_request() {
25
26
  # Терминал IDE исполняет ту же командную строку и кладёт её в то же поле, что и Bash:
26
27
  # без этой ветки весь гард обходился сменой инструмента.
27
28
  Bash | mcp__webstorm__execute_terminal_command | mcp__webstorm__execute_tool)
28
- cmd="$(printf '%s' "$input" | jq -r '.tool_input.command // empty' 2>/dev/null)"
29
+ cmd="$(rt_hook_cmd)"
29
30
  [ -z "$cmd" ] && exit 0
30
31
 
31
32
  # Универсальный исполнитель зовёт ЛЮБОЙ инструмент редактора по имени, в том числе
@@ -41,12 +41,14 @@
41
41
  # запрос. Доставки запроса внутри такой команды не бывает, так что цена послабления нулевая.
42
42
 
43
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
44
45
 
45
- input="$(cat 2>/dev/null)"
46
+ rt_hook_read
47
+ input="$RT_HOOK_INPUT"
46
48
  [ -z "$input" ] && exit 0
47
49
  command -v jq >/dev/null 2>&1 || exit 0
48
50
 
49
- tool="$(printf '%s' "$input" | jq -r '.tool_name // empty' 2>/dev/null)"
51
+ tool="$(rt_hook_tool)"
50
52
 
51
53
  # Рабочий каталог нужен одному правилу — разрешению адреса миграции: `DATABASE_URL`
52
54
  # обычно не назван в команде и лежит в `.env` рядом с проектом.
@@ -26,8 +26,10 @@
26
26
  # гард не должен мешать работать.
27
27
 
28
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
29
30
 
30
- input="$(cat 2>/dev/null)"
31
+ rt_hook_read
32
+ input="$RT_HOOK_INPUT"
31
33
  [ -z "$input" ] && exit 0
32
34
  command -v jq >/dev/null 2>&1 || exit 0
33
35
 
@@ -45,7 +47,7 @@ done
45
47
  [ -f "$rt_hooks_dir/profile-check.sh" ] && . "$rt_hooks_dir/profile-check.sh"
46
48
  command -v rt_needs >/dev/null 2>&1 || rt_needs() { command -v "$1" >/dev/null 2>&1; }
47
49
 
48
- tool="$(printf '%s' "$input" | jq -r '.tool_name // empty' 2>/dev/null)"
50
+ tool="$(rt_hook_tool)"
49
51
  candidates=""
50
52
  case "$tool" in
51
53
  # Инструмент редактора заводит файл теми же двумя данными, только называет их иначе —
@@ -61,7 +63,7 @@ case "$tool" in
61
63
  # имён гард стоял бы объявленным на них и молча пропускал — состояние хуже необъявленного,
62
64
  # потому что снаружи выглядит закрытым.
63
65
  Bash | mcp__webstorm__execute_terminal_command | mcp__webstorm__execute_tool)
64
- cmd="$(printf '%s' "$input" | jq -r '.tool_input.command // empty' 2>/dev/null)"
66
+ cmd="$(rt_hook_cmd)"
65
67
  [ -z "$cmd" ] && exit 0
66
68
  # Универсальный исполнитель прячет настоящую команду во вложенной строке: без её разбора
67
69
  # путь стоит за кавычкой, и до него не дотягивается ни один образец.
@@ -108,6 +110,7 @@ EOF
108
110
 
109
111
  # Каталог папок задач: у дерева он свой, но имя обычно общее.
110
112
  tasks_dir="${RT_TASKS_DIR:-docs/tasks}"
113
+ main_branch="${RT_MAIN_BRANCH:-main}"
111
114
 
112
115
  # Общий хвост отказа: два законных хода и законная форма обхода, если она у отказа есть. Файл
113
116
  # может быть не разложен — тогда хвоста нет, а причина отказа остаётся прежней.
@@ -127,7 +130,7 @@ deny() {
127
130
  }
128
131
 
129
132
  # Ветку смотрим там же, где пойдёт правка: у worktree она своя.
130
- workdir="$(printf '%s' "$input" | jq -r '.cwd // empty' 2>/dev/null)"
133
+ workdir="$(rt_hook_cwd)"
131
134
  [ -z "$workdir" ] && workdir="${CLAUDE_PROJECT_DIR:-.}"
132
135
  cd "$workdir" 2>/dev/null || exit 0
133
136
  git rev-parse --is-inside-work-tree >/dev/null 2>&1 || exit 0
@@ -144,6 +147,23 @@ root="$(git rev-parse --show-toplevel 2>/dev/null)"
144
147
  dir="$root/$tasks_dir/$branch"
145
148
  plan="$dir/plan.md"
146
149
 
150
+ # Папка, разобранная коммитом этой ветки, — признак того, что работа отдана. Замысел с диска к
151
+ # этой минуте снят намеренно: уборка стоит до открытия заявки, потому что кнопку слияния
152
+ # нажимает человек на хостинге и закрывающему коммиту места после одобрения не остаётся.
153
+ # Правка после уборки — это правка по замечаниям разбора, и требовать под неё замысла значило
154
+ # бы запирать ветку собственным порядком. Признак берётся из истории ветки, а не с диска:
155
+ # снесённая, но не закоммиченная папка отданной работы не означает.
156
+ folder_archived() {
157
+ [ -n "$(git ls-tree -d --name-only HEAD -- "$tasks_dir/$branch" 2>/dev/null | head -1)" ] && return 1
158
+ base="$(git merge-base "$main_branch" HEAD 2>/dev/null)"
159
+ [ -z "$base" ] && return 1
160
+ had="$(git ls-tree -d --name-only "$base" -- "$tasks_dir/$branch" 2>/dev/null | head -1)"
161
+ [ -z "$had" ] && had="$(git log "$base..HEAD" --diff-filter=A --name-only --pretty=format: -- "$tasks_dir/$branch" 2>/dev/null | head -1)"
162
+ [ -n "$had" ]
163
+ }
164
+
165
+ folder_archived && exit 0
166
+
147
167
  if [ ! -f "$plan" ]; then
148
168
  deny "BLOCKED by task-flow: нет замысла — '${tasks_dir}/${branch}/plan.md'. Собери папку задачи с образца (cp -r ${tasks_dir}/_template ${tasks_dir}/${branch}) и заполни шапку, след задачи и этапы, затем повтори. Правило — скил task-flow."
149
169
  fi
@@ -22,16 +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
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
33
37
 
34
- input="$(cat 2>/dev/null)"
38
+ rt_hook_read
39
+ input="$RT_HOOK_INPUT"
35
40
  [ -z "$input" ] && exit 0
36
41
  command -v jq >/dev/null 2>&1 || exit 0
37
42
 
@@ -43,22 +48,25 @@ transcript="$(printf '%s' "$input" | jq -r '.transcript_path // empty' 2>/dev/nu
43
48
  [ -z "$transcript" ] && exit 0
44
49
  [ -f "$transcript" ] || exit 0
45
50
 
46
- workdir="$(printf '%s' "$input" | jq -r '.cwd // empty' 2>/dev/null)"
51
+ workdir="$(rt_hook_cwd)"
47
52
  [ -z "$workdir" ] && workdir="${CLAUDE_PROJECT_DIR:-.}"
48
53
  cd "$workdir" 2>/dev/null || exit 0
49
54
  git rev-parse --is-inside-work-tree >/dev/null 2>&1 || exit 0
50
55
 
51
- branch="$(git branch --show-current 2>/dev/null)"
52
- [ -z "$branch" ] && exit 0
53
-
54
56
  root="$(git rev-parse --show-toplevel 2>/dev/null)"
55
57
  [ -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
58
 
60
- state="$(sed -n 's/^[[:space:]]*[-*][[:space:]]*\*\*Состояние:\*\*[[:space:]]*`\([^`]*\)`.*/\1/p' "$progress" 2>/dev/null | head -1)"
61
- [ -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
62
70
 
63
71
  # Работа, дошедшая до этих двух состояний, чужого шага уже дождалась: дальше её двигает
64
72
  # владелец, и ход, закрытый здесь, ничего не роняет.
@@ -66,9 +74,27 @@ case "$state" in
66
74
  работа-отдана | влито) exit 0 ;;
67
75
  esac
68
76
 
77
+ # То же самое, но объявить это на диске уже нечем: папка задачи разбирается до открытия заявки,
78
+ # и ход работы уезжает вместе с ней. Признак берётся из истории ветки — папка, снятая её
79
+ # коммитом. Без этой ветки хвост работы судился бы вторым признаком, то есть отбивался бы за
80
+ # ход, в котором исполнитель ждёт чужого прогона и ничего в дереве не двигает.
81
+ folder_archived() {
82
+ [ -n "$branch" ] || return 1
83
+ [ -n "$(git ls-tree -d --name-only HEAD -- "$tasks_dir/$branch" 2>/dev/null | head -1)" ] && return 1
84
+ main_branch="${RT_MAIN_BRANCH:-main}"
85
+ base="$(git merge-base "$main_branch" HEAD 2>/dev/null)"
86
+ [ -z "$base" ] && return 1
87
+ had="$(git ls-tree -d --name-only "$base" -- "$tasks_dir/$branch" 2>/dev/null | head -1)"
88
+ [ -z "$had" ] && had="$(git log "$base..HEAD" --diff-filter=A --name-only --pretty=format: -- "$tasks_dir/$branch" 2>/dev/null | head -1)"
89
+ [ -n "$had" ]
90
+ }
91
+
92
+ [ -z "$progress" ] && folder_archived && exit 0
93
+
69
94
  # Следующий шаг из хода работы — его страж и называет в отказе: исполнитель, которому сказано
70
95
  # только «работа не кончена», перечитывает ту же строку сам.
71
- next_step="$(sed -n 's/^[[:space:]]*[-*][[:space:]]*\*\*Следующий шаг:\*\*[[:space:]]*\(.*\)/\1/p' "$progress" 2>/dev/null | head -1)"
96
+ next_step=""
97
+ [ -n "$progress" ] && next_step="$(sed -n 's/^[[:space:]]*[-*][[:space:]]*\*\*Следующий шаг:\*\*[[:space:]]*\(.*\)/\1/p' "$progress" 2>/dev/null | head -1)"
72
98
  [ -z "$next_step" ] && next_step="что стоит в разделе «Где стоим» хода работы"
73
99
 
74
100
  # Команда, меняющая дерево или его состояние. Чтение и поиск сюда не входят намеренно: ими и
@@ -119,10 +145,14 @@ commands="$(printf '%s' "$verdict" | jq -r '.ran // ""' 2>/dev/null)"
119
145
  # Страж сравнивает номер этапа с тем, что лежит в истории ветки, и на выросшем номере требует
120
146
  # команды из строки «Чем проверяется» — она стоит в замысле обратными кавычками. Приём, записанный
121
147
  # прозой, страж не читает: подтвердить его выводом нечем, и это его известная граница.
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)"
148
+ stage_now=""
149
+ stage_was=""
150
+ if [ -n "$progress" ]; then
151
+ stage_now="$(sed -n 's/^[[:space:]]*[-*][[:space:]]*\*\*Этап:\*\*[[:space:]]*\([0-9][0-9]*\).*/\1/p' "$progress" 2>/dev/null | head -1)"
152
+ 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)"
153
+ fi
124
154
 
125
- if [ -n "$stage_now" ] && [ -n "$stage_was" ] && [ "$stage_now" -gt "$stage_was" ] 2>/dev/null; then
155
+ if [ -n "$progress" ] && [ -n "$stage_now" ] && [ -n "$stage_was" ] && [ "$stage_now" -gt "$stage_was" ] 2>/dev/null; then
126
156
  plan="$root/$tasks_dir/$branch/plan.md"
127
157
  # Контракт закрытого этапа, а не начатого: подтверждается то, что объявлено сделанным.
128
158
  contract="$(awk -v n="$stage_was" '
@@ -164,7 +194,18 @@ fi
164
194
 
165
195
  [ "$worked" = "true" ] && exit 0
166
196
 
167
- reason="BLOCKED by turn-exit-guard: работа в состоянии '${state}', а за этот ход по ней не сделано ничего — ни правки, ни команды, меняющей дерево.
197
+ if [ -z "$state" ]; then
198
+ reason="BLOCKED by turn-exit-guard: за этот ход не сделано ничего — ни правки, ни команды, меняющей дерево. Папки задачи у этой работы нет, и состояние взять неоткуда, но ход это не кончает.
199
+
200
+ Ход кончается четырьмя способами, и других нет: вопрос владельцу, ответа на который в правилах нет; отказ гарда; заполненное окно захода; отданная работа с начатой следующей. Названная и не запущенная команда выходом не является: строка «сейчас запущу» — объявление намерения, а оно прямо названо ложным концом хода.
201
+
202
+ Работа по слову владельца — «разложи», «обнови», «посмотри» — идёт без задачи и без ветки, и остановить её нечем, кроме этого признака.
203
+
204
+ Запусти названное этим же ходом. Владелец сказал остановиться — так и напиши: страж читает его слово, а не пересказ.
205
+
206
+ Страж судит один ход: следующий заход не отбивается."
207
+ else
208
+ reason="BLOCKED by turn-exit-guard: работа в состоянии '${state}', а за этот ход по ней не сделано ничего — ни правки, ни команды, меняющей дерево.
168
209
 
169
210
  Ход кончается четырьмя способами, и других нет: вопрос владельцу, ответа на который в правилах нет; отказ гарда; заполненное окно захода; отданная работа с начатой следующей. Отчёт о сделанном выходом не является — он выглядит работой лучше всякой другой, и пустоты за ним не видно.
170
211
 
@@ -173,6 +214,7 @@ reason="BLOCKED by turn-exit-guard: работа в состоянии '${state}
173
214
  Сделай его этим же ходом. Владелец сказал остановиться — так и напиши: страж читает его слово, а не пересказ.
174
215
 
175
216
  Страж судит один ход: следующий заход не отбивается."
217
+ fi
176
218
 
177
219
  # Общий хвост отказа: два законных хода. Файл может быть не разложен — тогда хвоста нет,
178
220
  # а причина отказа остаётся прежней.
@@ -31,8 +31,10 @@
31
31
  # заходе ход РАЗРЕШАЕТСЯ (exit 0). Сломанный гард не имеет права заклинить разговор.
32
32
 
33
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
34
35
 
35
- input="$(cat 2>/dev/null)"
36
+ rt_hook_read
37
+ input="$RT_HOOK_INPUT"
36
38
  [ -z "$input" ] && exit 0
37
39
 
38
40
  command -v jq >/dev/null 2>&1 || exit 0
@@ -30,8 +30,10 @@
30
30
  # работа РАЗРЕШАЕТСЯ (exit 0). Сломанный гард не имеет права заклинить работу.
31
31
 
32
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
33
34
 
34
- input="$(cat 2>/dev/null)"
35
+ rt_hook_read
36
+ input="$RT_HOOK_INPUT"
35
37
  [ -z "$input" ] && exit 0
36
38
 
37
39
  command -v jq >/dev/null 2>&1 || exit 0
@@ -144,9 +146,9 @@ fi
144
146
  [ "$event" = "PreToolUse" ] || exit 0
145
147
  [ "$pct" -ge "$stop_pct" ] || exit 0
146
148
 
147
- tool="$(printf '%s' "$input" | jq -r '.tool_name // empty' 2>/dev/null)"
148
- path="$(printf '%s' "$input" | jq -r '.tool_input.file_path // empty' 2>/dev/null)"
149
- 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)"
150
152
 
151
153
  allowed=0
152
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
  пробел, при котором любая догадка делает работу опасной или бесполезной. Вопрос, заданный
@@ -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