@rt-tools/agent-kit 0.3.0 → 0.5.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 (202) hide show
  1. package/README.md +194 -30
  2. package/assets/agents/business-analyst.md +74 -0
  3. package/assets/agents/project-manager.md +70 -0
  4. package/assets/agents/qa-engineer.md +72 -0
  5. package/assets/agents/skill-curator.md +110 -0
  6. package/assets/agents/spec-critic.md +44 -0
  7. package/assets/agents/spec-writer.md +50 -0
  8. package/assets/checks/board.github.mjs +329 -0
  9. package/assets/checks/check-board.github.mjs +181 -0
  10. package/assets/checks/check-doc-paths.mjs +163 -0
  11. package/assets/checks/check-dupes.mjs +277 -0
  12. package/assets/checks/check-lib-layers.mjs +573 -0
  13. package/assets/checks/check-reuse.mjs +208 -0
  14. package/assets/checks/check-schema-drift.mjs +186 -0
  15. package/assets/checks/check-specs.mjs +1086 -0
  16. package/assets/checks/check-styles.mjs +109 -0
  17. package/assets/checks/rt-kit-checks.config.mjs +134 -0
  18. package/assets/checks/task-new.github.mjs +198 -0
  19. package/assets/commands/skill-curator.md +70 -0
  20. package/assets/defaults/gate-map.sh +106 -0
  21. package/assets/defaults/project.sh +204 -0
  22. package/assets/hooks/browser-device-id.sh +0 -0
  23. package/assets/hooks/browser-guard-device-id.sh +2 -1
  24. package/assets/hooks/browser-guard-no-asking.sh +27 -0
  25. package/assets/hooks/browser-guard-no-listing.sh +2 -1
  26. package/assets/hooks/browser-guard-no-other-drivers.sh +2 -1
  27. package/assets/hooks/browser-guard-require-select.sh +2 -1
  28. package/assets/hooks/commit-msg.sh +1 -1
  29. package/assets/hooks/constitution-index.sh +5 -4
  30. package/assets/hooks/dev-server-guard.sh +8 -6
  31. package/assets/hooks/docs-guard.sh +223 -37
  32. package/assets/hooks/git-guard-delivery.sh +171 -31
  33. package/assets/hooks/git-guard-main.sh +1 -0
  34. package/assets/hooks/git-guard-push-tests.sh +34 -13
  35. package/assets/hooks/glossary-load.sh +23 -0
  36. package/assets/hooks/grill-gate.sh +96 -0
  37. package/assets/hooks/lint-after-edit.sh +155 -30
  38. package/assets/hooks/qa-dataid-guard.sh +72 -32
  39. package/assets/hooks/reuse-first-guard.sh +105 -34
  40. package/assets/hooks/skill-gate-rearm.sh +1 -0
  41. package/assets/hooks/skill-gate.sh +75 -15
  42. package/assets/hooks/skill-loaded.sh +1 -0
  43. package/assets/hooks/sql-guard.sh +606 -56
  44. package/assets/hooks/task-context-load.sh +100 -0
  45. package/assets/hooks/task-flow-guard.sh +118 -0
  46. package/assets/laws/{access.md → application/access.md} +1 -4
  47. package/assets/laws/{locales.md → application/locales.md} +1 -3
  48. package/assets/laws/application/money.md +41 -0
  49. package/assets/laws/application/ownership.md +32 -0
  50. package/assets/laws/{search-visibility.md → application/search-visibility.md} +1 -1
  51. package/assets/laws/code-structure.md +7 -6
  52. package/assets/laws/delivery.md +53 -3
  53. package/assets/laws/entity-editing.md +49 -55
  54. package/assets/laws/entity-models.md +4 -14
  55. package/assets/laws/frontend-application.md +5 -5
  56. package/assets/laws/lib-imports.md +14 -1
  57. package/assets/laws/lists.md +33 -0
  58. package/assets/laws/navigation.md +40 -0
  59. package/assets/laws/project-documentation.md +27 -8
  60. package/assets/laws/reuse-first.md +26 -21
  61. package/assets/laws/shared-code.md +13 -1
  62. package/assets/laws/verifiability.md +30 -1
  63. package/assets/laws/work-conduct.md +59 -0
  64. package/assets/patterns/admin-lists-screen.md +131 -0
  65. package/assets/patterns/admin-nav-item.md +71 -0
  66. package/assets/patterns/angular-patterns-state.md +29 -22
  67. package/assets/patterns/api-layer-pair.md +40 -30
  68. package/assets/patterns/browser-verification-measure.md +41 -38
  69. package/assets/patterns/browser-verification-stand.md +106 -42
  70. package/assets/patterns/component-structure-new.md +33 -32
  71. package/assets/patterns/dependencies-upgrade.md +65 -0
  72. package/assets/patterns/doc-style-sweep.md +65 -28
  73. package/assets/patterns/doc-style-write.md +36 -33
  74. package/assets/patterns/entity-aside.md +136 -0
  75. package/assets/patterns/entity-models-new.md +124 -0
  76. package/assets/patterns/entity-store.md +91 -0
  77. package/assets/patterns/git-workflow-commit.azure.md +259 -0
  78. package/assets/patterns/git-workflow-commit.github.md +337 -0
  79. package/assets/patterns/git-workflow-commit.gitlab.md +283 -0
  80. package/assets/patterns/git-workflow-merge.md +42 -25
  81. package/assets/patterns/git-workflow-migration.md +61 -31
  82. package/assets/patterns/git-workflow-restart.md +20 -20
  83. package/assets/patterns/lib-layers-move.md +50 -32
  84. package/assets/patterns/lib-layers-new.md +41 -29
  85. package/assets/patterns/ownership-scope-resolve.md +69 -0
  86. package/assets/patterns/permissions-procedure.md +35 -33
  87. package/assets/patterns/platform-access-di.md +39 -25
  88. package/assets/patterns/pricing-quote.md +71 -0
  89. package/assets/patterns/reuse-first-extend.md +22 -22
  90. package/assets/patterns/seo-page.md +52 -40
  91. package/assets/patterns/seo-verify.md +48 -29
  92. package/assets/patterns/shared-code-new.md +37 -31
  93. package/assets/patterns/spec-driven-domain.md +60 -37
  94. package/assets/patterns/spec-driven-rule.md +55 -40
  95. package/assets/patterns/styling-bem-component.md +43 -32
  96. package/assets/patterns/styling-bem-layout.md +30 -24
  97. package/assets/patterns/task-flow-close.md +154 -0
  98. package/assets/patterns/task-flow-resume.md +94 -0
  99. package/assets/patterns/task-flow-start.md +129 -0
  100. package/assets/patterns/testing-e2e.md +53 -51
  101. package/assets/patterns/testing-unit.md +70 -46
  102. package/assets/patterns/translations-key.md +32 -19
  103. package/assets/patterns/ts-procedure.md +24 -25
  104. package/assets/rules/angular-patterns.md +50 -27
  105. package/assets/rules/api-layer.md +46 -28
  106. package/assets/rules/browser-verification.md +67 -48
  107. package/assets/rules/component-structure.md +43 -27
  108. package/assets/rules/dependencies.md +66 -0
  109. package/assets/rules/doc-style.md +95 -39
  110. package/assets/rules/entity-conventions.md +78 -0
  111. package/assets/rules/entity-models.md +70 -0
  112. package/assets/rules/git-workflow.azure.md +116 -0
  113. package/assets/rules/git-workflow.github.md +123 -0
  114. package/assets/rules/git-workflow.gitlab.md +113 -0
  115. package/assets/rules/lib-layers.md +56 -30
  116. package/assets/rules/lists.md +73 -0
  117. package/assets/rules/navigation.md +78 -0
  118. package/assets/rules/ownership-scope.md +63 -0
  119. package/assets/rules/permissions.md +43 -25
  120. package/assets/rules/platform-access.md +57 -29
  121. package/assets/rules/pricing.md +64 -0
  122. package/assets/rules/reuse-first.md +57 -43
  123. package/assets/rules/seo.md +51 -30
  124. package/assets/rules/shared-code.md +51 -26
  125. package/assets/rules/spec-driven.md +107 -51
  126. package/assets/rules/styling-bem.md +54 -39
  127. package/assets/rules/task-flow.md +150 -0
  128. package/assets/rules/testing.md +78 -47
  129. package/assets/rules/translations.md +48 -31
  130. package/assets/rules/typescript-conventions.md +57 -27
  131. package/assets/skills/agent-kit.md +85 -0
  132. package/assets/skills/write-a-skill.md +108 -0
  133. package/assets/templates/gate-map.sh +23 -15
  134. package/assets/templates/implementation.md +14 -8
  135. package/assets/templates/pattern.md +1 -1
  136. package/assets/templates/project.sh +32 -19
  137. package/assets/templates/rule.md +2 -2
  138. package/assets/variants.json +20 -0
  139. package/assets/workflows/feature.js +134 -0
  140. package/assets/workflows/plan.js +150 -0
  141. package/bin/agent-kit.d.ts.map +1 -1
  142. package/bin/agent-kit.js +78 -5
  143. package/bin/agent-kit.js.map +1 -1
  144. package/bin/prompt.d.ts +5 -0
  145. package/bin/prompt.d.ts.map +1 -1
  146. package/bin/prompt.js +19 -7
  147. package/bin/prompt.js.map +1 -1
  148. package/index.d.ts +1 -0
  149. package/index.d.ts.map +1 -1
  150. package/index.js +1 -0
  151. package/index.js.map +1 -1
  152. package/lib/assets.d.ts +8 -3
  153. package/lib/assets.d.ts.map +1 -1
  154. package/lib/assets.js +13 -3
  155. package/lib/assets.js.map +1 -1
  156. package/lib/catalog.d.ts +52 -5
  157. package/lib/catalog.d.ts.map +1 -1
  158. package/lib/catalog.js +104 -16
  159. package/lib/catalog.js.map +1 -1
  160. package/lib/commands.d.ts +22 -1
  161. package/lib/commands.d.ts.map +1 -1
  162. package/lib/commands.js +202 -14
  163. package/lib/commands.js.map +1 -1
  164. package/lib/companion.d.ts +5 -1
  165. package/lib/companion.d.ts.map +1 -1
  166. package/lib/companion.js +29 -2
  167. package/lib/companion.js.map +1 -1
  168. package/lib/config.d.ts +26 -9
  169. package/lib/config.d.ts.map +1 -1
  170. package/lib/config.js +41 -15
  171. package/lib/config.js.map +1 -1
  172. package/lib/freshness.d.ts +14 -0
  173. package/lib/freshness.d.ts.map +1 -0
  174. package/lib/freshness.js +116 -0
  175. package/lib/freshness.js.map +1 -0
  176. package/lib/hooks-map.d.ts +27 -0
  177. package/lib/hooks-map.d.ts.map +1 -0
  178. package/lib/hooks-map.js +77 -0
  179. package/lib/hooks-map.js.map +1 -0
  180. package/lib/integrity.d.ts +36 -0
  181. package/lib/integrity.d.ts.map +1 -0
  182. package/lib/integrity.js +44 -0
  183. package/lib/integrity.js.map +1 -0
  184. package/lib/picker.d.ts +11 -1
  185. package/lib/picker.d.ts.map +1 -1
  186. package/lib/picker.js +44 -6
  187. package/lib/picker.js.map +1 -1
  188. package/lib/sync.d.ts +26 -0
  189. package/lib/sync.d.ts.map +1 -1
  190. package/lib/sync.js +59 -4
  191. package/lib/sync.js.map +1 -1
  192. package/lib/variants.d.ts +44 -0
  193. package/lib/variants.d.ts.map +1 -0
  194. package/lib/variants.js +82 -0
  195. package/lib/variants.js.map +1 -0
  196. package/package.json +1 -1
  197. package/rt-tools-agent-kit-0.5.0.tgz +0 -0
  198. package/assets/laws/admin-lists.md +0 -35
  199. package/assets/laws/admin-navigation.md +0 -38
  200. package/assets/patterns/git-workflow-commit.md +0 -175
  201. package/assets/rules/git-workflow.md +0 -106
  202. package/rt-tools-agent-kit-0.3.0.tgz +0 -0
@@ -1,35 +1,51 @@
1
1
  #!/usr/bin/env bash
2
+ # rt-hook: PreToolUse Edit|Write|MultiEdit|mcp__webstorm__create_new_file
2
3
  # Гард «ничего не пишется с нуля». PreToolUse на правке кода и разметки.
3
4
  #
4
- # Линтеры знают правила, но не знают ИНВЕНТАРЬ: линтер стилей поймает сырой цвет, линтер кода
5
- # нетипизированное значение, но ни один из них не знает, что готовое сообщение уже написано,
6
- # и молча пропустит свою область оповещения с текстом, кнопкой и стилями. Гард закрывает ровно
5
+ # Линтеры знают правила, но не знают ИНВЕНТАРЬ: линтер стилей поймает сырой цвет, линтер кода
6
+ # нетипизированное значение, но ни один из них не знает, что готовое сообщение уже написано, и
7
+ # молча пропустит свою область оповещения с текстом, кнопкой и стилями. Гард закрывает ровно
7
8
  # эту дыру: сверяет то, что пишется, с тем, что в дереве уже есть.
8
9
  #
9
10
  # Правило целиком — `reuse-first`.
10
11
  #
11
- # Что считается переизобретением, знает профиль проекта: {{projectProfile}}, функция
12
- # `rt_reinvented_in <файл>` — печатает по строке «образец<таб>чем заменить». Нет профиля или
13
- # нет функции — гард пропускает: инвентарь дерева пакет знать не может.
12
+ # Что считается переизобретением, знает профиль дерева: функция `rt_reinvented_in <файл>`
13
+ # печатает по строке на правило, четырьмя полями через табуляцию:
14
14
  #
15
- # Осознанный выход: строка с маркером отступления пропускается — для случаев, которых в
16
- # готовом действительно нет. Маркер объясняет, чего именно не хватает; «эти строки были здесь
17
- # раньше» причиной не считается, и оттого проверяется только НОВЫЙ текст.
15
+ # <над чем> <образец> <образец отмены> <чем заменить>
16
+ #
17
+ # «Над чем» `added` (только новый текст правки) или `whole` (правка вместе с содержимым
18
+ # файла). Второе нужно там, где признак виден только целиком: наследование основы и метка
19
+ # процедуры в точечную правку не попадают. «Образец отмены» гасит правило: маппер, который уже
20
+ # наследует общую основу, переизобретением не является; пустое поле не гасит ничего.
21
+ #
22
+ # Функция получает путь и вправе решать по нему сама — заглянуть в инвентарь кита, различить
23
+ # слой дерева, промолчать на файле, который уже существует. Нет профиля или нет функции — гард
24
+ # пропускает: инвентарь дерева пакет знать не может.
25
+ #
26
+ # Образцы разбирает `perl`: конъюнкция и отрицание пишутся заглядыванием вперёд, а без них
27
+ # самодельный оверлей не отличить от липкой шапки, а нативную кнопку — от кнопки кита.
28
+ #
29
+ # Осознанный выход: маркер отступления в тексте правки. Он объясняет, чего именно не хватает;
30
+ # «эти строки были здесь раньше» причиной не считается, и оттого проверяется только НОВЫЙ текст.
18
31
  #
19
32
  # ОТКАЗ В ПОЛЬЗУ РАБОТЫ: нет разборщика, битый ввод, чужой инструмент — пропуск.
20
33
 
21
34
  input="$(cat 2>/dev/null)"
22
35
  [ -z "$input" ] && exit 0
23
36
  command -v jq >/dev/null 2>&1 || exit 0
37
+ command -v perl >/dev/null 2>&1 || exit 0
24
38
 
25
39
  tool="$(printf '%s' "$input" | jq -r '.tool_name // empty' 2>/dev/null)"
26
40
  case "$tool" in
41
+ # Инструмент среды заводит файл теми же двумя данными, только называет их иначе — без этой
42
+ # ветки файл заводился мимо всех проверок.
27
43
  Edit | Write | MultiEdit | mcp__webstorm__create_new_file) ;;
28
44
  *) exit 0 ;;
29
45
  esac
30
46
 
31
47
  path="$(printf '%s' "$input" | jq -r '.tool_input.file_path // .tool_input.pathInProject // empty' 2>/dev/null)"
32
- # Путь от корня проекта приводится к абсолютному один раз, чтобы образцы не двоились.
48
+ # Путь от корня дерева приводится к абсолютному один раз, чтобы образцы не двоились.
33
49
  case "$path" in
34
50
  /*) ;;
35
51
  ?*) path="${CLAUDE_PROJECT_DIR:-.}/$path" ;;
@@ -39,43 +55,98 @@ case "$path" in
39
55
  *) exit 0 ;;
40
56
  esac
41
57
 
42
- # Правила этого дерева действуют на файлы этого дерева: без проверки корня гард требовал бы
43
- # верстать готовым и в черновике за пределами репозитория.
44
- case "$path" in
45
- "${CLAUDE_PROJECT_DIR:-.}"/*) ;;
46
- *) exit 0 ;;
47
- esac
48
-
49
- profile="${CLAUDE_PROJECT_DIR:-.}/{{projectProfile}}"
50
- [ -f "$profile" ] || exit 0
51
- # shellcheck disable=SC1090
52
- . "$profile" 2>/dev/null || exit 0
58
+ # Профиль дерева: сперва умолчание пакета, поверх него надстройка проекта, если она есть.
59
+ rt_hooks_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
60
+ 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
61
+ # shellcheck disable=SC1090
62
+ [ -f "$profile" ] && . "$profile" 2>/dev/null
63
+ done
53
64
  command -v rt_reinvented_in >/dev/null 2>&1 || exit 0
54
65
 
66
+ # Правила этого дерева действуют на код этого дерева: без положительной проверки гард требовал
67
+ # бы собирать готовым и в черновике за пределами дерева.
68
+ if command -v rt_is_app_code >/dev/null 2>&1; then
69
+ rt_is_app_code "$path" || exit 0
70
+ fi
71
+
72
+ # Готовое собирается там, где ему и место: внутри кита нативные контролы и примитивы уместны,
73
+ # витрина и спеки — тоже не забота этого гарда.
74
+ if [ -n "${RT_REUSE_SKIP_RE:-}" ] && printf '%s' "$path" | grep -qE "$RT_REUSE_SKIP_RE"; then
75
+ exit 0
76
+ fi
77
+
55
78
  # Только новый текст: строка, уже лежавшая в файле, этой правкой не заводилась.
56
79
  added="$(printf '%s' "$input" | jq -r '
57
- [.tool_input.content? // empty,
58
- .tool_input.text? // empty,
59
- .tool_input.new_string? // empty,
60
- (.tool_input.edits? // [] | map(.new_string) | join("\n"))] | join("\n")' 2>/dev/null)"
80
+ [ .tool_input.content?, .tool_input.text?, .tool_input.new_string?, (.tool_input.edits[]?.new_string) ]
81
+ | map(select(. != null)) | join("\n")
82
+ ' 2>/dev/null)"
61
83
  [ -z "$added" ] && exit 0
62
84
 
63
- found=""
64
- while IFS="$(printf '\t')" read -r pattern replacement; do
85
+ # Явный отказ от правила: готового такого нет, автор это осознал и пометил. Считается по
86
+ # исходному тексту правки, до вычёркивания ниже: маркер обычно и стоит на строке, которая уже
87
+ # лежит в файле, и вычеркнутый маркер отбивал бы правку соседней строки.
88
+ case "$added" in
89
+ *native-ok*) exit 0 ;;
90
+ esac
91
+
92
+ # Перенос — не написание заново. Строку, которая уже лежит в файле, гард из проверяемого текста
93
+ # вычёркивает: блок, переехавший вместе с экраном из одного места файла в другое, отбивался
94
+ # наравне с новым, и маркер отступления приходилось ставить вслепую.
95
+ #
96
+ # Сверка идёт без отступов: при переезде блок меняет отступ, оставаясь тем же кодом.
97
+ if [ -f "$path" ]; then
98
+ added="$(
99
+ printf '%s' "$added" | awk '
100
+ NR == FNR {
101
+ line = $0
102
+ gsub(/^[ \t]+|[ \t]+$/, "", line)
103
+ if (line != "") { existing[line] = 1 }
104
+ next
105
+ }
106
+ {
107
+ line = $0
108
+ gsub(/^[ \t]+|[ \t]+$/, "", line)
109
+ if (line == "" || !(line in existing)) { print }
110
+ }
111
+ ' "$path" - 2>/dev/null
112
+ )"
113
+ [ -z "${added//[[:space:]]/}" ] && exit 0
114
+ fi
115
+
116
+ # Признак, который виден только целиком по файлу (наследование основы, метка процедуры),
117
+ # считается по итоговому содержимому: точечная правка приносит кусок без объявления класса.
118
+ whole="$added"
119
+ if [ -f "$path" ]; then
120
+ whole="$added
121
+ $(cat "$path" 2>/dev/null)"
122
+ fi
123
+
124
+ has_re() {
125
+ printf '%s' "$1" | RT_RE="$2" perl -0777 -ne 'exit(/$ENV{RT_RE}/s ? 0 : 1)' 2>/dev/null
126
+ }
127
+
128
+ found=''
129
+ while IFS="$(printf '\t')" read -r scope pattern cancel replacement; do
65
130
  [ -z "$pattern" ] && continue
66
- # Строка с маркером отступления снимается до сверки — вместе с ней снимается и повод.
67
- printf '%s\n' "$added" | grep -v 'native-ok' | grep -qE "$pattern" || continue
68
- found="${found} ${pattern} — есть готовое: ${replacement}"$'\n'
131
+ case "$scope" in
132
+ whole) text="$whole" ;;
133
+ *) text="$added" ;;
134
+ esac
135
+ has_re "$text" "$pattern" || continue
136
+ [ -n "$cancel" ] && has_re "$text" "$cancel" && continue
137
+ found="${found}
138
+ - ${replacement}"
69
139
  done <<EOF
70
- $(rt_reinvented_in "$path")
140
+ $(rt_reinvented_in "$path" 2>/dev/null)
71
141
  EOF
72
142
 
73
143
  [ -z "$found" ] && exit 0
74
144
 
75
- reason="Написано своё там, где готовое уже есть:
76
-
145
+ reason="BLOCKED: это уже написано. Файл: ${path##*/}
77
146
  ${found}
78
- Работа начинается с чтения готового, а не с чистого файла — правило reuse-first. Недостающий вариант заводится у готового, а не клонируется рядом: клон забирает правки на себя и расходится с оригиналом с первой же. Если готового здесь правда не хватает, поставь маркер отступления в той же строке и объясни, чего именно нет."
147
+
148
+ Порядок действий: 1) открой готовое — барель кита или базовый класс — и используй его; 2) найди в дереве экран, где этот случай уже собран, и повтори сборку; 3) если готового правда не хватает — расширяй его на месте, у готового, а не клонируй рядом: клон забирает правки на себя и расходится с оригиналом с первой же.
149
+ Свой примитив, своя основа и свои инлайновые стили заводятся только с явного одобрения владельца, и спрашивается это до первого написанного файла. Разовое исключение помечается маркером отступления в той же строке, с объяснением, чего именно нет в готовом. Переименованием файла это не обходится."
79
150
 
80
151
  jq -n --arg r "$reason" '{hookSpecificOutput:{hookEventName:"PreToolUse",permissionDecision:"deny",permissionDecisionReason:$r}}' 2>/dev/null \
81
152
  || printf '{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"deny","permissionDecisionReason":"Написано своё там, где готовое уже есть."}}\n'
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env bash
2
+ # rt-hook: SessionStart compact|clear
2
3
  # Взвод гейта заново. SessionStart(compact|clear).
3
4
  #
4
5
  # Гейт помнит загруженное правило по идентификатору сессии и дальше пропускает эту область
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env bash
2
+ # rt-hook: PreToolUse Edit|Write|MultiEdit|Bash
2
3
  # Гейт правил: не даёт править файл, пока не загружено правило, под которое он подпадает.
3
4
  #
4
5
  # Закон и правило, которых никто не открывает, не действуют. Напоминание в подсказке помогает
@@ -6,9 +7,17 @@
6
7
  # для каждой области, а после загрузки правила та же область проходит молча — повторов нет, и
7
8
  # стоит это почти ничего.
8
9
  #
9
- # Карта «файл — правило» живёт не здесь: она знает имена этого дерева, а хук их знать не может.
10
- # Её пишет проект в {{gateMap}} функцией `skill_for <род вызова> <цель>`, печатающей имя
11
- # правила. Нет карты гейт пропускает всё: пустой гейт лучше гейта, отбивающего наугад.
10
+ # Карта «файл — правило» живёт не здесь, а в двух файлах рядом, и это не дублирование.
11
+ # Умолчание `.claude/rt-kit/defaults/gate-map.sh`везёт пакет: деревья этой мастерской
12
+ # устроены одинаково, и переписывать одну и ту же карту в каждом заново значило бы заводить
13
+ # столько её редакций, сколько репозиториев. Надстройку — `.claude/rt-kit/gate-map.sh` — пишет
14
+ # проект, и она необязательна: витрина, свой род файлов, чужая раскладка есть не у всех.
15
+ # Обе объявляют `skill_for <род вызова> <цель> <текст правки>`, печатающую имена правил через
16
+ # пробел; надстройка грузится второй и вправе позвать умолчание обратно — `skill_for_default`.
17
+ # Нет ни одной — гейт пропускает всё: пустой гейт лучше гейта, отбивающего наугад.
18
+ #
19
+ # Род вызова — `edit`, `bash` или `browser`. Браузерная проверка — единственная область, где
20
+ # правило нужно не под правку файла, а под инструмент: врут там не файлы, а стенд и координаты.
12
21
  #
13
22
  # ОТКАЗ В ПОЛЬЗУ РАБОТЫ: любая ошибка и любой неопознанный путь пропускают правку (exit 0).
14
23
  # Сломанный гейт не имеет права остановить работу совсем.
@@ -19,16 +28,20 @@ input="$(cat 2>/dev/null)"
19
28
  sid="$(printf '%s' "$input" | jq -r '.session_id // "nosession"' 2>/dev/null)"
20
29
  tool="$(printf '%s' "$input" | jq -r '.tool_name // empty' 2>/dev/null)"
21
30
 
22
- map="${CLAUDE_PROJECT_DIR:-.}/{{gateMap}}"
23
- [ -f "$map" ] || exit 0
24
- # shellcheck disable=SC1090
25
- . "$map" 2>/dev/null || exit 0
31
+ # Умолчание карты ищется и рядом с самим хуком: уезжают они вместе.
32
+ rt_hooks_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
33
+ for map in "$rt_hooks_dir/../rt-kit/defaults/gate-map.sh" "$rt_hooks_dir/../defaults/gate-map.sh" "${CLAUDE_PROJECT_DIR:-.}/.claude/rt-kit/defaults/gate-map.sh" "${CLAUDE_PROJECT_DIR:-.}/.claude/rt-kit/gate-map.sh"; do
34
+ # shellcheck disable=SC1090
35
+ [ -f "$map" ] && . "$map" 2>/dev/null
36
+ done
26
37
  command -v skill_for >/dev/null 2>&1 || exit 0
27
38
 
28
39
  req=""
29
40
  target=""
30
41
  case "$tool" in
31
- Edit|Write|MultiEdit)
42
+ # Инструмент среды заводит файл теми же двумя данными, только называет их иначе — без этой
43
+ # ветки файл заводился мимо гейта.
44
+ Edit|Write|MultiEdit|mcp__webstorm__create_new_file)
32
45
  target="$(printf '%s' "$input" | jq -r '.tool_input.file_path // .tool_input.pathInProject // empty' 2>/dev/null)"
33
46
  [ -z "$target" ] && exit 0
34
47
  # Путь от корня проекта приводится к абсолютному один раз: иначе карту пришлось бы
@@ -43,25 +56,72 @@ case "$tool" in
43
56
  "${CLAUDE_PROJECT_DIR:-.}"/*) ;;
44
57
  *) exit 0 ;;
45
58
  esac
46
- req="$(skill_for edit "$target" 2>/dev/null)"
59
+ # Текст правки идёт карте вторым доводом: есть правила, которые вступают не от того,
60
+ # ЧТО за файл правится, а от того, ЧТО в него пишут, — обращение к среде исполнения
61
+ # приходит в обычный сервис, а число-настройка в обычный класс.
62
+ written="$(printf '%s' "$input" | jq -r '[.tool_input.content, .tool_input.text, .tool_input.new_string, (.tool_input.edits[]?.new_string)] | map(select(. != null)) | join("\n")' 2>/dev/null)"
63
+ req="$(skill_for edit "$target" "$written" 2>/dev/null)"
47
64
  ;;
48
- Bash)
65
+ # Терминал среды исполняет ту же командную строку и кладёт её в то же поле: без этой ветки
66
+ # коммит из него не требовал правила, тогда как тот же коммит из оболочки требовал.
67
+ Bash|mcp__webstorm__execute_terminal_command|mcp__webstorm__execute_tool)
49
68
  target="$(printf '%s' "$input" | jq -r '.tool_input.command // empty' 2>/dev/null)"
50
69
  [ -z "$target" ] && exit 0
51
- req="$(skill_for bash "$target" 2>/dev/null)"
70
+ # Универсальный исполнитель прячет настоящую команду во вложенной строке.
71
+ if [ "$tool" = "mcp__webstorm__execute_tool" ] && command -v perl >/dev/null 2>&1; then
72
+ inner="$(printf '%s' "$target" | perl -0ne '
73
+ if (/--command(?:=|\s+)(?:"((?:[^"\\]|\\.)*)"|\x27([^\x27]*)\x27|(.+))/s) {
74
+ print defined $1 ? $1 : (defined $2 ? $2 : $3);
75
+ }
76
+ ' 2>/dev/null)"
77
+ [ -n "$inner" ] && target="$inner"
78
+ fi
79
+ req="$(skill_for bash "$target" "" 2>/dev/null)"
80
+ ;;
81
+ mcp__claude-in-chrome__*)
82
+ req="$(skill_for browser "$tool" "" 2>/dev/null)"
52
83
  ;;
53
84
  *) exit 0 ;;
54
85
  esac
55
86
 
56
87
  [ -z "$req" ] && exit 0
57
88
 
58
- # Принимается и голое имя правила, и имя с областью каталога («<каталог>:<имя>»).
89
+ # Карта вправе назвать несколько правил: доменное правило и то, что действует вторым слоем.
90
+ # Требуется первое незагруженное, а не все сразу: отказ, перечисляющий три правила, читается
91
+ # как «загрузи три», и загружают их подряд, теряя ту самую однократность.
59
92
  loaded="${TMPDIR:-/tmp}/claude-skill-gate/${sid}.loaded"
60
- if [ -f "$loaded" ] && grep -qE "^([^:]*:)?$(printf '%s' "$req" | sed 's/[][\.*^$/]/\\&/g')$" "$loaded" 2>/dev/null; then
61
- exit 0
62
- fi
93
+ root="${CLAUDE_PROJECT_DIR:-.}"
94
+ rules_dir="${RT_RULES_DIR:-.claude/skills}"
95
+ want=""
96
+ for name in $req; do
97
+ # Правила, которого в дереве нет, гейт не требует. Карта умолчания называет и то, от чего
98
+ # дерево отказалось списком: правило предметной области, чужой род файлов. Отказ «загрузи
99
+ # правило X» на такое имя — отказ без действия: загружать нечего, и работа встаёт совсем.
100
+ [ -f "$root/$rules_dir/${name}/SKILL.md" ] || continue
101
+ # Принимается и голое имя правила, и имя с областью каталога («<каталог>:<имя>»).
102
+ if [ -f "$loaded" ] && grep -qE "^([^:]*:)?$(printf '%s' "$name" | sed 's/[][\.*^$/]/\\&/g')$" "$loaded" 2>/dev/null; then
103
+ continue
104
+ fi
105
+ want="$name"
106
+ break
107
+ done
108
+ [ -z "$want" ] && exit 0
109
+ req="$want"
63
110
 
64
111
  reason="Отбито гейтом правил: загрузи правило «${req}» инструментом Skill и повтори действие. Для этой области это происходит один раз за сессию."
112
+
113
+ # Правило называет свой закон одним словом, а слоёв законов два: общий лежит в корне, закон
114
+ # приложения — в каталоге под ним. Путь ищется, а не собирается из имени, иначе отказ ведёт в
115
+ # несуществующий файл ровно у тех правил, чей закон предметен.
116
+ laws_dir="${RT_LAWS_DIR:-docs/constitution}"
117
+ law="$(sed -n 's/^law:[[:space:]]*//p' "$root/$rules_dir/${req}/SKILL.md" 2>/dev/null | head -1)"
118
+ if [ -n "$law" ]; then
119
+ law_path="$laws_dir/${law}.md"
120
+ [ -f "$root/$law_path" ] || law_path="$laws_dir/application/${law}.md"
121
+ [ -f "$root/$law_path" ] \
122
+ && reason="Отбито гейтом правил: загрузи правило «${req}» инструментом Skill — оно применяет закон ${law_path} к этому дереву — и повтори действие. Для этой области это происходит один раз за сессию."
123
+ fi
124
+
65
125
  jq -n --arg r "$reason" '{hookSpecificOutput:{hookEventName:"PreToolUse",permissionDecision:"deny",permissionDecisionReason:$r}}' 2>/dev/null \
66
126
  || printf '{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"deny","permissionDecisionReason":"Загрузи правило %s и повтори."}}\n' "$req"
67
127
 
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env bash
2
+ # rt-hook: PostToolUse Skill
2
3
  # Запись о загруженном правиле. PostToolUse на инструменте `Skill`.
3
4
  #
4
5
  # Гейт обязан знать, загружено правило или нет, а спросить об этом ему некого: инструмент о