@rt-tools/agent-kit 0.2.0 → 0.4.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 (203) hide show
  1. package/README.md +235 -18
  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 +286 -0
  9. package/assets/checks/check-board.github.mjs +188 -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 +1007 -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 +100 -0
  21. package/assets/defaults/project.sh +179 -0
  22. package/assets/hooks/browser-device-id.sh +20 -0
  23. package/assets/hooks/browser-guard-device-id.sh +28 -0
  24. package/assets/hooks/browser-guard-no-asking.sh +27 -0
  25. package/assets/hooks/browser-guard-no-listing.sh +18 -0
  26. package/assets/hooks/browser-guard-no-other-drivers.sh +79 -0
  27. package/assets/hooks/browser-guard-require-select.sh +54 -0
  28. package/assets/hooks/commit-msg.sh +26 -0
  29. package/assets/hooks/constitution-index.sh +43 -0
  30. package/assets/hooks/dev-server-guard.sh +115 -0
  31. package/assets/hooks/docs-guard.sh +282 -0
  32. package/assets/hooks/git-guard-delivery.sh +167 -0
  33. package/assets/hooks/git-guard-main.sh +73 -0
  34. package/assets/hooks/git-guard-push-tests.sh +94 -0
  35. package/assets/hooks/glossary-load.sh +23 -0
  36. package/assets/hooks/lint-after-edit.sh +219 -0
  37. package/assets/hooks/qa-dataid-guard.sh +121 -0
  38. package/assets/hooks/reuse-first-guard.sh +154 -0
  39. package/assets/hooks/skill-gate-rearm.sh +23 -0
  40. package/assets/hooks/skill-gate.sh +128 -0
  41. package/assets/hooks/skill-loaded.sh +21 -0
  42. package/assets/hooks/sql-guard.sh +679 -0
  43. package/assets/hooks/task-context-load.sh +100 -0
  44. package/assets/hooks/task-flow-guard.sh +107 -0
  45. package/assets/laws/{access.md → application/access.md} +1 -4
  46. package/assets/laws/{locales.md → application/locales.md} +1 -3
  47. package/assets/laws/application/money.md +41 -0
  48. package/assets/laws/application/ownership.md +32 -0
  49. package/assets/laws/{search-visibility.md → application/search-visibility.md} +1 -1
  50. package/assets/laws/code-structure.md +7 -6
  51. package/assets/laws/delivery.md +53 -3
  52. package/assets/laws/entity-editing.md +49 -55
  53. package/assets/laws/entity-models.md +4 -14
  54. package/assets/laws/frontend-application.md +5 -5
  55. package/assets/laws/lib-imports.md +14 -1
  56. package/assets/laws/lists.md +33 -0
  57. package/assets/laws/navigation.md +40 -0
  58. package/assets/laws/project-documentation.md +17 -8
  59. package/assets/laws/reuse-first.md +26 -21
  60. package/assets/laws/shared-code.md +13 -1
  61. package/assets/laws/verifiability.md +17 -1
  62. package/assets/laws/work-conduct.md +48 -0
  63. package/assets/patterns/admin-lists-screen.md +131 -0
  64. package/assets/patterns/admin-nav-item.md +71 -0
  65. package/assets/patterns/angular-patterns-state.md +101 -0
  66. package/assets/patterns/api-layer-pair.md +88 -0
  67. package/assets/patterns/browser-verification-measure.md +86 -0
  68. package/assets/patterns/browser-verification-stand.md +143 -0
  69. package/assets/patterns/component-structure-new.md +99 -0
  70. package/assets/patterns/dependencies-upgrade.md +65 -0
  71. package/assets/patterns/doc-style-sweep.md +137 -0
  72. package/assets/patterns/doc-style-write.md +109 -0
  73. package/assets/patterns/entity-aside.md +136 -0
  74. package/assets/patterns/entity-models-new.md +124 -0
  75. package/assets/patterns/entity-store.md +91 -0
  76. package/assets/patterns/git-workflow-commit.azure.md +259 -0
  77. package/assets/patterns/git-workflow-commit.github.md +333 -0
  78. package/assets/patterns/git-workflow-commit.gitlab.md +283 -0
  79. package/assets/patterns/git-workflow-merge.md +99 -0
  80. package/assets/patterns/git-workflow-migration.md +88 -0
  81. package/assets/patterns/git-workflow-restart.md +49 -0
  82. package/assets/patterns/lib-layers-move.md +95 -0
  83. package/assets/patterns/lib-layers-new.md +82 -0
  84. package/assets/patterns/ownership-scope-resolve.md +69 -0
  85. package/assets/patterns/permissions-procedure.md +71 -0
  86. package/assets/patterns/platform-access-di.md +84 -0
  87. package/assets/patterns/pricing-quote.md +71 -0
  88. package/assets/patterns/reuse-first-extend.md +73 -0
  89. package/assets/patterns/seo-page.md +104 -0
  90. package/assets/patterns/seo-verify.md +83 -0
  91. package/assets/patterns/shared-code-new.md +86 -0
  92. package/assets/patterns/spec-driven-domain.md +107 -0
  93. package/assets/patterns/spec-driven-rule.md +127 -0
  94. package/assets/patterns/styling-bem-component.md +88 -0
  95. package/assets/patterns/styling-bem-layout.md +73 -0
  96. package/assets/patterns/task-flow-close.md +90 -0
  97. package/assets/patterns/task-flow-resume.md +94 -0
  98. package/assets/patterns/task-flow-start.md +117 -0
  99. package/assets/patterns/testing-e2e.md +92 -0
  100. package/assets/patterns/testing-unit.md +117 -0
  101. package/assets/patterns/translations-key.md +64 -0
  102. package/assets/patterns/ts-procedure.md +65 -0
  103. package/assets/rules/angular-patterns.md +71 -0
  104. package/assets/rules/api-layer.md +71 -0
  105. package/assets/rules/browser-verification.md +87 -0
  106. package/assets/rules/component-structure.md +64 -0
  107. package/assets/rules/dependencies.md +66 -0
  108. package/assets/rules/doc-style.md +103 -0
  109. package/assets/rules/entity-conventions.md +78 -0
  110. package/assets/rules/entity-models.md +70 -0
  111. package/assets/rules/git-workflow.azure.md +116 -0
  112. package/assets/rules/git-workflow.github.md +123 -0
  113. package/assets/rules/git-workflow.gitlab.md +113 -0
  114. package/assets/rules/lib-layers.md +80 -0
  115. package/assets/rules/lists.md +73 -0
  116. package/assets/rules/navigation.md +78 -0
  117. package/assets/rules/ownership-scope.md +63 -0
  118. package/assets/rules/permissions.md +70 -0
  119. package/assets/rules/platform-access.md +77 -0
  120. package/assets/rules/pricing.md +64 -0
  121. package/assets/rules/reuse-first.md +83 -0
  122. package/assets/rules/seo.md +71 -0
  123. package/assets/rules/shared-code.md +70 -0
  124. package/assets/rules/spec-driven.md +135 -0
  125. package/assets/rules/styling-bem.md +74 -0
  126. package/assets/rules/task-flow.md +110 -0
  127. package/assets/rules/testing.md +100 -0
  128. package/assets/rules/translations.md +69 -0
  129. package/assets/rules/typescript-conventions.md +76 -0
  130. package/assets/skills/agent-kit.md +81 -0
  131. package/assets/skills/write-a-skill.md +108 -0
  132. package/assets/templates/gate-map.sh +45 -0
  133. package/assets/templates/implementation.md +44 -0
  134. package/assets/templates/pattern.md +5 -1
  135. package/assets/templates/project.sh +54 -0
  136. package/assets/templates/rule.md +12 -23
  137. package/assets/variants.json +20 -0
  138. package/assets/workflows/feature.js +134 -0
  139. package/assets/workflows/plan.js +150 -0
  140. package/bin/agent-kit.d.ts.map +1 -1
  141. package/bin/agent-kit.js +78 -5
  142. package/bin/agent-kit.js.map +1 -1
  143. package/bin/prompt.d.ts +5 -0
  144. package/bin/prompt.d.ts.map +1 -1
  145. package/bin/prompt.js +19 -7
  146. package/bin/prompt.js.map +1 -1
  147. package/index.d.ts +1 -0
  148. package/index.d.ts.map +1 -1
  149. package/index.js +1 -0
  150. package/index.js.map +1 -1
  151. package/lib/assets.d.ts +14 -1
  152. package/lib/assets.d.ts.map +1 -1
  153. package/lib/assets.js +23 -2
  154. package/lib/assets.js.map +1 -1
  155. package/lib/catalog.d.ts +52 -5
  156. package/lib/catalog.d.ts.map +1 -1
  157. package/lib/catalog.js +104 -16
  158. package/lib/catalog.js.map +1 -1
  159. package/lib/commands.d.ts +22 -1
  160. package/lib/commands.d.ts.map +1 -1
  161. package/lib/commands.js +218 -11
  162. package/lib/commands.js.map +1 -1
  163. package/lib/companion.d.ts +57 -0
  164. package/lib/companion.d.ts.map +1 -0
  165. package/lib/companion.js +60 -0
  166. package/lib/companion.js.map +1 -0
  167. package/lib/config.d.ts +42 -2
  168. package/lib/config.d.ts.map +1 -1
  169. package/lib/config.js +60 -2
  170. package/lib/config.js.map +1 -1
  171. package/lib/freshness.d.ts +14 -0
  172. package/lib/freshness.d.ts.map +1 -0
  173. package/lib/freshness.js +116 -0
  174. package/lib/freshness.js.map +1 -0
  175. package/lib/hooks-map.d.ts +24 -0
  176. package/lib/hooks-map.d.ts.map +1 -0
  177. package/lib/hooks-map.js +72 -0
  178. package/lib/hooks-map.js.map +1 -0
  179. package/lib/integrity.d.ts +36 -0
  180. package/lib/integrity.d.ts.map +1 -0
  181. package/lib/integrity.js +44 -0
  182. package/lib/integrity.js.map +1 -0
  183. package/lib/picker.d.ts +11 -1
  184. package/lib/picker.d.ts.map +1 -1
  185. package/lib/picker.js +44 -6
  186. package/lib/picker.js.map +1 -1
  187. package/lib/stamp.d.ts +2 -5
  188. package/lib/stamp.d.ts.map +1 -1
  189. package/lib/stamp.js +25 -10
  190. package/lib/stamp.js.map +1 -1
  191. package/lib/sync.d.ts +29 -0
  192. package/lib/sync.d.ts.map +1 -1
  193. package/lib/sync.js +78 -4
  194. package/lib/sync.js.map +1 -1
  195. package/lib/variants.d.ts +44 -0
  196. package/lib/variants.d.ts.map +1 -0
  197. package/lib/variants.js +82 -0
  198. package/lib/variants.js.map +1 -0
  199. package/package.json +1 -1
  200. package/rt-tools-agent-kit-0.4.0.tgz +0 -0
  201. package/assets/laws/admin-lists.md +0 -35
  202. package/assets/laws/admin-navigation.md +0 -38
  203. package/rt-tools-agent-kit-0.2.0.tgz +0 -0
@@ -0,0 +1,23 @@
1
+ #!/usr/bin/env bash
2
+ # rt-hook: SessionStart startup|resume|compact|clear
3
+ # SessionStart: словарь проекта уезжает в контекст целиком, на каждом запуске сессии.
4
+ #
5
+ # Памятью это не держится: словарь читают перед тем, как написать текст, а разговор с
6
+ # владельцем идёт без чтения — и слово из левой колонки «Так не пишем» всплывает в ответе,
7
+ # хотя в файлах его уже нет. Здесь словарь приходит до первой реплики и действует и на
8
+ # тексты, и на ответы.
9
+ #
10
+ # FAIL-OPEN: нет файла или нет `jq` — выходим молча. Сессия важнее словаря.
11
+
12
+ GLOSSARY="${CLAUDE_PROJECT_DIR:-.}/docs/GLOSSARY.md"
13
+ [ -f "$GLOSSARY" ] || exit 0
14
+ command -v jq >/dev/null 2>&1 || exit 0
15
+
16
+ {
17
+ printf 'СЛОВАРЬ ПРОЕКТА — `docs/GLOSSARY.md`, ниже целиком.\n\n'
18
+ printf 'Слово отсюда употребляется в том значении, что здесь: и в файлах, и в ответах владельцу.\n'
19
+ printf 'Раздел «Так не пишем» действует так же — левая колонка не пишется и не произносится нигде.\n'
20
+ printf 'Слова, которого здесь нет, у читателя нет тоже: оно либо заводится здесь же той же правкой,\n'
21
+ printf 'либо заменяется тем, что уже есть.\n\n'
22
+ cat "$GLOSSARY"
23
+ } | jq -Rs '{hookSpecificOutput:{hookEventName:"SessionStart",additionalContext:.}}'
@@ -0,0 +1,219 @@
1
+ #!/usr/bin/env bash
2
+ # rt-hook: PostToolUse Edit|Write|MultiEdit|Bash|mcp__webstorm__create_new_file
3
+ # Линтер по следам правки. PostToolUse.
4
+ #
5
+ # Два входа. Правка файла — линтуется один изменённый файл. Перенос файла командой — линтуются
6
+ # файлы по адресу назначения: перенос меняет либу, а вместе с ней и границы, и импорт, законный
7
+ # на прежнем месте, на новом уже запрещён. Ровно так запрещённый импорт уезжает в общую либу
8
+ # молча: файлы перекладываются командой, хук на неё не смотрит, и правило границ сработало бы —
9
+ # но его никто не запустил.
10
+ #
11
+ # Полный набор гоняет гард на пуше, но это конец работы: к моменту, когда правило срабатывает,
12
+ # поверх нарушения лежит десяток правок, и разбор превращается в археологию. Здесь тот же
13
+ # линтер, но по затронутым файлам — секунды, сразу после действия, пока контекст ещё свой.
14
+ #
15
+ # Замечания возвращаются добавленным контекстом, а не отказом: действие уже применено, и
16
+ # незаконченный промежуточный файл имеет право быть красным. Чинить их надо до конца задачи —
17
+ # все, включая лежавшие в файле раньше.
18
+ #
19
+ # Что здесь чем зовётся, знает профиль дерева:
20
+ # rt_lint_for — чем линтуется этот файл;
21
+ # rt_is_app_code — где лежит код, к которому линтеры вообще относятся;
22
+ # RT_LINT_SKIP_RE — что из него исключено;
23
+ # rt_push_checks — что гоняет гейт пуша. По нему же решается, догонит ли замечание позже:
24
+ # линтер, которого в гейте нет, ловит только этот хук.
25
+ # Нет профиля или нет функции — хук молчит.
26
+ #
27
+ # ОТКАЗ В ПОЛЬЗУ РАБОТЫ и молча: нет линтера, файл вне дерева, зелёный результат — пустой вывод.
28
+
29
+ # Сколько файлов линтуется за один перенос. Переезд либы трогает десятки файлов, и прогон по
30
+ # каждому превратил бы хук в минутную паузу; на нарушение границы хватает первых.
31
+ MAX_FILES=12
32
+
33
+ input="$(cat 2>/dev/null)"
34
+ [ -z "$input" ] && exit 0
35
+ command -v jq >/dev/null 2>&1 || exit 0
36
+
37
+ tool="$(printf '%s' "$input" | jq -r '.tool_name // empty' 2>/dev/null)"
38
+ case "$tool" in
39
+ Edit | Write | MultiEdit | mcp__webstorm__create_new_file) mode="edit" ;;
40
+ Bash) mode="move" ;;
41
+ *) exit 0 ;;
42
+ esac
43
+
44
+ # Отсев до всякой работы: хук висит на каждой команде оболочки, а перенос среди них — редкость.
45
+ command_text=""
46
+ if [ "$mode" = "move" ]; then
47
+ command_text="$(printf '%s' "$input" | jq -r '.tool_input.command // empty' 2>/dev/null)"
48
+ case "$command_text" in
49
+ *"git mv "*) ;;
50
+ *) exit 0 ;;
51
+ esac
52
+ fi
53
+
54
+ workdir="$(printf '%s' "$input" | jq -r '.cwd // empty' 2>/dev/null)"
55
+ [ -z "$workdir" ] && workdir="${CLAUDE_PROJECT_DIR:-.}"
56
+ cd "$workdir" 2>/dev/null || exit 0
57
+ [ -f package.json ] || exit 0
58
+
59
+ # Профиль дерева: сперва умолчание пакета, поверх него — надстройка проекта, если она есть.
60
+ rt_hooks_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
61
+ 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
62
+ # shellcheck disable=SC1090
63
+ [ -f "$profile" ] && . "$profile" 2>/dev/null
64
+ done
65
+ command -v rt_lint_for >/dev/null 2>&1 || exit 0
66
+
67
+ # --- какие файлы проверяем -------------------------------------------------------------
68
+
69
+ # Пути назначения всех переносов в команде. Команда бывает составной, поэтому режется по
70
+ # разделителям, и каждый кусок разбирается отдельно.
71
+ collect_moved() {
72
+ # Перевод строки в конце обязателен: чтение без него теряет последний кусок, а команда
73
+ # чаще всего состоит ровно из одного.
74
+ printf '%s\n' "$1" | tr ';&\n' '\n\n\n' | while IFS= read -r segment; do
75
+ case "$segment" in
76
+ *"git mv "*) ;;
77
+ *) continue ;;
78
+ esac
79
+
80
+ # shellcheck disable=SC2086 # разбиение по пробелам здесь и нужно
81
+ set -- ${segment#*git mv }
82
+ args=''
83
+ for arg in "$@"; do
84
+ case "$arg" in
85
+ -*) continue ;;
86
+ esac
87
+ args="${args}${args:+ }${arg}"
88
+ done
89
+
90
+ # shellcheck disable=SC2086
91
+ set -- $args
92
+ [ "$#" -lt 2 ] && continue
93
+
94
+ dest=''
95
+ for arg in "$@"; do dest="$arg"; done
96
+
97
+ # Назначение — каталог: имя файла остаётся прежним, меняется только путь.
98
+ if [ -d "$dest" ]; then
99
+ for arg in "$@"; do
100
+ [ "$arg" = "$dest" ] && continue
101
+ printf '%s/%s\n' "${dest%/}" "${arg##*/}"
102
+ done
103
+ else
104
+ printf '%s\n' "$dest"
105
+ fi
106
+ done
107
+ }
108
+
109
+ # Каталог разворачивается в лежащие в нём файлы: перенос умеет двигать целые слои.
110
+ expand() {
111
+ if [ -d "$1" ]; then
112
+ find "$1" -type f \( -name '*.ts' -o -name '*.html' -o -name '*.scss' \) 2>/dev/null
113
+ elif [ -f "$1" ]; then
114
+ printf '%s\n' "$1"
115
+ fi
116
+ }
117
+
118
+ if [ "$mode" = "edit" ]; then
119
+ path="$(printf '%s' "$input" | jq -r '.tool_input.file_path // .tool_input.pathInProject // empty' 2>/dev/null)"
120
+ [ -z "$path" ] && exit 0
121
+ # Путь от корня дерева приводится к абсолютному один раз, чтобы образцы не двоились.
122
+ case "$path" in
123
+ /*) ;;
124
+ *) path="${CLAUDE_PROJECT_DIR:-.}/$path" ;;
125
+ esac
126
+ candidates="$(expand "$path")"
127
+ else
128
+ candidates="$(collect_moved "$command_text" | while IFS= read -r moved; do expand "$moved"; done)"
129
+ fi
130
+
131
+ [ -z "$candidates" ] && exit 0
132
+
133
+ # --- отсев того, что линтерами дерева не покрыто ----------------------------------------
134
+
135
+ lintable() {
136
+ if command -v rt_is_app_code >/dev/null 2>&1; then
137
+ rt_is_app_code "$1" || return 1
138
+ fi
139
+ if [ -n "${RT_LINT_SKIP_RE:-}" ] && printf '%s' "$1" | grep -qE "$RT_LINT_SKIP_RE"; then
140
+ return 1
141
+ fi
142
+
143
+ return 0
144
+ }
145
+
146
+ # Имя линтера в команде — первое слово, которое не запускатель. Оно идёт в заголовок и по нему
147
+ # же ищется гейт пуша.
148
+ linter_name() {
149
+ printf '%s' "$1" | awk '{
150
+ for (i = 1; i <= NF; i++) {
151
+ if ($i ~ /^(npx|pnpm|yarn|bun|npm|exec|run|dlx|--no-install)$/) { continue }
152
+ print $i; exit
153
+ }
154
+ }'
155
+ }
156
+
157
+ # --- прогон -----------------------------------------------------------------------------
158
+
159
+ push_checks="$(command -v rt_push_checks >/dev/null 2>&1 && rt_push_checks 2>/dev/null)"
160
+
161
+ report=""
162
+ linters=""
163
+ covered=1
164
+ checked=0
165
+
166
+ while IFS= read -r file; do
167
+ [ -z "$file" ] && continue
168
+ lintable "$file" || continue
169
+ [ "$checked" -ge "$MAX_FILES" ] && break
170
+
171
+ lint="$(rt_lint_for "$file" 2>/dev/null)"
172
+ [ -z "$lint" ] && continue
173
+ checked=$((checked + 1))
174
+
175
+ out="$(eval "$lint" 2>&1)" && continue
176
+
177
+ # Линтера нет или он упал сам по себе — это не замечание к файлу, молчим.
178
+ printf '%s' "$out" | grep -qiE 'command not found|could not determine executable|Cannot find module' && continue
179
+
180
+ linter="$(linter_name "$lint")"
181
+ case " $linters " in
182
+ *" $linter "*) ;;
183
+ *) linters="${linters}${linters:+ }${linter}" ;;
184
+ esac
185
+ printf '%s' "$push_checks" | grep -qF "$linter" || covered=0
186
+
187
+ report="${report}
188
+ ${file}:
189
+ $(printf '%s' "$out" | head -c 2500 | tr -d '\000')
190
+ "
191
+ done <<EOF
192
+ $candidates
193
+ EOF
194
+
195
+ [ -z "$report" ] && exit 0
196
+
197
+ # Длинный вывод режем: важен факт и первые нарушения, остальное видно при полном прогоне.
198
+ report="$(printf '%s' "$report" | head -c 6000)"
199
+
200
+ if [ "$covered" -eq 1 ]; then
201
+ tail_line="Пуш всё равно не пройдёт, пока набор красный."
202
+ else
203
+ tail_line="Почини их сейчас: этот линтер в гейт пуша не входит, и отложенное замечание уедет в главную ветку молча."
204
+ fi
205
+
206
+ if [ "$mode" = "move" ]; then
207
+ head_line="ЛИНТЕР (${linters}) НАШЁЛ ЗАМЕЧАНИЯ ПОСЛЕ ПЕРЕНОСА. Перенос меняет либу, а вместе с ней границы: импорт, законный на прежнем месте, на новом может быть запрещён."
208
+ else
209
+ head_line="ЛИНТЕР (${linters}) НАШЁЛ ЗАМЕЧАНИЯ:"
210
+ fi
211
+
212
+ ctx="${head_line}
213
+ ${report}
214
+
215
+ Почини их до конца задачи — правятся ВСЕ замечания в затронутом файле, и новые, и лежавшие раньше: накопленные нарушения глушат сигнал о свежих. ${tail_line}"
216
+
217
+ jq -n --arg c "$ctx" '{hookSpecificOutput:{hookEventName:"PostToolUse",additionalContext:$c}}' 2>/dev/null
218
+
219
+ exit 0
@@ -0,0 +1,121 @@
1
+ #!/usr/bin/env bash
2
+ # rt-hook: PreToolUse Edit|Write|MultiEdit|mcp__webstorm__create_new_file
3
+ # Гард якоря для спек. PreToolUse на правке разметки.
4
+ #
5
+ # Спеки адресуют элементы только через этот атрибут. Классы оформления меняются вместе с
6
+ # вёрсткой, а поиск по роли и тексту ломается на переводах — приложение живёт во многих
7
+ # локалях. Оба вида селекторов делают сквозные спеки хрупкими, поэтому якорь один и ставится
8
+ # по умолчанию, а не дописывается потом: элемент без атрибута попадает в спеку только после
9
+ # того, как кто-то заметит его отсутствие.
10
+ #
11
+ # Правило инвертировано: интерактивным считается ЛЮБОЙ составной тег (компонент) и любой тег с
12
+ # привязкой события, кроме помеченных декоративными. Перечислять интерактивные поимённо не
13
+ # выходит — компонентов в ките больше сотни, и список устаревает молча: забытый в нём
14
+ # компонент проходит без якоря.
15
+ #
16
+ # Осознанный выход: атрибут пропуска НА САМОМ ТЕГЕ. Маркер, который ищется во всей правке,
17
+ # выключал бы проверку целиком — декоративная ссылка уносила бы с собой кнопку отправки из той
18
+ # же правки.
19
+ #
20
+ # Что здесь чем зовётся, знает профиль дерева:
21
+ # RT_QA_SKIP_RE — пути, где якорь не требуется: витрина, корневая разметка, спеки;
22
+ # RT_QA_COMPONENT_RE — по какому имени тег опознаётся компонентом этого дерева;
23
+ # rt_qa_decorative — имена тегов, которые спека не нажимает.
24
+ #
25
+ # ОТКАЗ В ПОЛЬЗУ РАБОТЫ: нет разборщика, битый ввод, чужой инструмент — пропуск.
26
+
27
+ input="$(cat 2>/dev/null)"
28
+ [ -z "$input" ] && exit 0
29
+ command -v jq >/dev/null 2>&1 || exit 0
30
+ command -v perl >/dev/null 2>&1 || exit 0
31
+
32
+ tool="$(printf '%s' "$input" | jq -r '.tool_name // empty' 2>/dev/null)"
33
+ case "$tool" in
34
+ # Инструмент среды заводит файл теми же двумя данными, только называет их иначе — без этой
35
+ # ветки разметка заводилась мимо всех проверок.
36
+ Edit | Write | MultiEdit | mcp__webstorm__create_new_file) ;;
37
+ *) exit 0 ;;
38
+ esac
39
+
40
+ path="$(printf '%s' "$input" | jq -r '.tool_input.file_path // .tool_input.pathInProject // empty' 2>/dev/null)"
41
+ # Путь из среды приходит относительным корню дерева, а все образцы ниже написаны от каталога
42
+ # приложения. Приводим к одному виду один раз, чтобы правила не двоились.
43
+ case "$path" in
44
+ /*) ;;
45
+ ?*) path="${CLAUDE_PROJECT_DIR:-.}/$path" ;;
46
+ esac
47
+ case "$path" in
48
+ *.html) ;;
49
+ *) exit 0 ;;
50
+ esac
51
+
52
+ # Профиль дерева: сперва умолчание пакета, поверх него — надстройка проекта, если она есть.
53
+ rt_hooks_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
54
+ 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
55
+ # shellcheck disable=SC1090
56
+ [ -f "$profile" ] && . "$profile" 2>/dev/null
57
+ done
58
+
59
+ # Разметка приложения живёт там же, где его код. Без этой положительной проверки гард
60
+ # распространялся на любой файл разметки на диске — черновик вне дерева отклонялся требованием
61
+ # проставить якоря.
62
+ if command -v rt_is_app_code >/dev/null 2>&1; then
63
+ rt_is_app_code "$path" || exit 0
64
+ fi
65
+
66
+ if [ -n "${RT_QA_SKIP_RE:-}" ] && printf '%s' "$path" | grep -qE "$RT_QA_SKIP_RE"; then
67
+ exit 0
68
+ fi
69
+
70
+ # Проверяется только НОВЫЙ текст: то, что уже лежало в файле, эта правка не заводила, и
71
+ # требовать якорь у чужой строки — значит отбивать правку соседней.
72
+ added="$(printf '%s' "$input" | jq -r '
73
+ [ .tool_input.content?, .tool_input.text?, .tool_input.new_string?, (.tool_input.edits[]?.new_string) ]
74
+ | map(select(. != null)) | join("\n")
75
+ ' 2>/dev/null)"
76
+ [ -z "$added" ] && exit 0
77
+
78
+ decorative="$(command -v rt_qa_decorative >/dev/null 2>&1 && rt_qa_decorative 2>/dev/null)"
79
+ component_re="${RT_QA_COMPONENT_RE:--}"
80
+
81
+ # Открывающие теги разбираются ЦЕЛИКОМ: тег занимает несколько строк, и якорь часто стоит не в
82
+ # первой из них — построчная проверка врала бы.
83
+ missing="$(printf '%s' "$added" | RT_QA_DECORATIVE="$decorative" RT_QA_COMPONENT_RE="$component_re" perl -0777 -ne '
84
+ # Комментарии вырезаются до разбора: в них часто лежит разметка-пример («было <button …>,
85
+ # заменено на переход»). Без этого правка отклонялась из-за элемента, которого в дереве
86
+ # документа не будет, а обойти отказ можно было только испортив текст самого комментария.
87
+ s/<!--.*?-->//gs;
88
+ # Декоративное и структурное: спека такое не нажимает и по нему не проверяет состояние.
89
+ # Всё остальное считается интерактивным по умолчанию — кит растёт, и умолчание должно быть
90
+ # в сторону якоря, а не в сторону тишины. Попадание в список НЕ выдаёт индульгенцию: тег с
91
+ # привязкой события проверяется наравне с прочими.
92
+ my %decorative = map { $_ => 1 } split /\s+/, ($ENV{RT_QA_DECORATIVE} // "");
93
+ my $component = $ENV{RT_QA_COMPONENT_RE} || "-";
94
+ my %seen;
95
+ while (/<([a-zA-Z][\w-]*)((?:[^<>"\x27]|"[^"]*"|\x27[^\x27]*\x27)*)>/gs) {
96
+ my ($tag, $attrs) = ($1, $2);
97
+ # Структурные теги каркаса узла документа не порождают: поставленный на них якорь не
98
+ # найдёт ни один селектор спеки. Требовать его — значит требовать неработающего.
99
+ next if $tag =~ /^(ng-container|ng-template|ng-content)$/;
100
+ next if $attrs =~ /\bqa-dataid\b/;
101
+ # Осознанный отказ помечается на самом теге, а не где-то в той же правке.
102
+ next if $attrs =~ /\bqa-skip\b/;
103
+ my $interactive =
104
+ $attrs =~ /\(\s*[a-zA-Z][\w.:-]*\s*\)\s*=/
105
+ || $attrs =~ /\b(routerLink|rtButton|href)\b/
106
+ || $tag =~ /^(button|a|input|select|textarea|form|dialog)$/
107
+ || ($tag =~ /$component/ && !$decorative{$tag});
108
+ next unless $interactive;
109
+ next if $seen{$tag}++;
110
+ print "<$tag> ";
111
+ }
112
+ ' 2>/dev/null)"
113
+
114
+ [ -z "$missing" ] && exit 0
115
+
116
+ reason="BLOCKED: интерактивные элементы без якоря для спек в ${path##*/}: ${missing}. Спеки адресуют элементы только через этот атрибут: классы оформления меняются вместе с вёрсткой, а поиск по роли и тексту ломается на переводах — оба вида селекторов делают сквозные спеки хрупкими. Проставь якорь через дефис по смыслу элемента; повторяющиеся элементы списка носят ОДИН якорь и различаются атрибутами данных. Если элемент чисто декоративный и спека его никогда не тронет — поставь атрибут пропуска НА САМ ЭТОТ ТЕГ, соседние элементы правки при этом продолжают проверяться."
117
+
118
+ jq -n --arg r "$reason" '{hookSpecificOutput:{hookEventName:"PreToolUse",permissionDecision:"deny",permissionDecisionReason:$r}}' 2>/dev/null \
119
+ || printf '{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"deny","permissionDecisionReason":"Интерактивный элемент без якоря для спек."}}\n'
120
+
121
+ exit 0
@@ -0,0 +1,154 @@
1
+ #!/usr/bin/env bash
2
+ # rt-hook: PreToolUse Edit|Write|MultiEdit|mcp__webstorm__create_new_file
3
+ # Гард «ничего не пишется с нуля». PreToolUse на правке кода и разметки.
4
+ #
5
+ # Линтеры знают правила, но не знают ИНВЕНТАРЬ: линтер стилей поймает сырой цвет, линтер кода —
6
+ # нетипизированное значение, но ни один из них не знает, что готовое сообщение уже написано, и
7
+ # молча пропустит свою область оповещения с текстом, кнопкой и стилями. Гард закрывает ровно
8
+ # эту дыру: сверяет то, что пишется, с тем, что в дереве уже есть.
9
+ #
10
+ # Правило целиком — `reuse-first`.
11
+ #
12
+ # Что считается переизобретением, знает профиль дерева: функция `rt_reinvented_in <файл>`
13
+ # печатает по строке на правило, четырьмя полями через табуляцию:
14
+ #
15
+ # <над чем> <образец> <образец отмены> <чем заменить>
16
+ #
17
+ # «Над чем» — `added` (только новый текст правки) или `whole` (правка вместе с содержимым
18
+ # файла). Второе нужно там, где признак виден только целиком: наследование основы и метка
19
+ # процедуры в точечную правку не попадают. «Образец отмены» гасит правило: маппер, который уже
20
+ # наследует общую основу, переизобретением не является; пустое поле не гасит ничего.
21
+ #
22
+ # Функция получает путь и вправе решать по нему сама — заглянуть в инвентарь кита, различить
23
+ # слой дерева, промолчать на файле, который уже существует. Нет профиля или нет функции — гард
24
+ # пропускает: инвентарь дерева пакет знать не может.
25
+ #
26
+ # Образцы разбирает `perl`: конъюнкция и отрицание пишутся заглядыванием вперёд, а без них
27
+ # самодельный оверлей не отличить от липкой шапки, а нативную кнопку — от кнопки кита.
28
+ #
29
+ # Осознанный выход: маркер отступления в тексте правки. Он объясняет, чего именно не хватает;
30
+ # «эти строки были здесь раньше» причиной не считается, и оттого проверяется только НОВЫЙ текст.
31
+ #
32
+ # ОТКАЗ В ПОЛЬЗУ РАБОТЫ: нет разборщика, битый ввод, чужой инструмент — пропуск.
33
+
34
+ input="$(cat 2>/dev/null)"
35
+ [ -z "$input" ] && exit 0
36
+ command -v jq >/dev/null 2>&1 || exit 0
37
+ command -v perl >/dev/null 2>&1 || exit 0
38
+
39
+ tool="$(printf '%s' "$input" | jq -r '.tool_name // empty' 2>/dev/null)"
40
+ case "$tool" in
41
+ # Инструмент среды заводит файл теми же двумя данными, только называет их иначе — без этой
42
+ # ветки файл заводился мимо всех проверок.
43
+ Edit | Write | MultiEdit | mcp__webstorm__create_new_file) ;;
44
+ *) exit 0 ;;
45
+ esac
46
+
47
+ path="$(printf '%s' "$input" | jq -r '.tool_input.file_path // .tool_input.pathInProject // empty' 2>/dev/null)"
48
+ # Путь от корня дерева приводится к абсолютному один раз, чтобы образцы не двоились.
49
+ case "$path" in
50
+ /*) ;;
51
+ ?*) path="${CLAUDE_PROJECT_DIR:-.}/$path" ;;
52
+ esac
53
+ case "$path" in
54
+ *.html | *.scss | *.ts) ;;
55
+ *) exit 0 ;;
56
+ esac
57
+
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
64
+ command -v rt_reinvented_in >/dev/null 2>&1 || exit 0
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
+
78
+ # Только новый текст: строка, уже лежавшая в файле, этой правкой не заводилась.
79
+ added="$(printf '%s' "$input" | jq -r '
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)"
83
+ [ -z "$added" ] && exit 0
84
+
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
130
+ [ -z "$pattern" ] && continue
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}"
139
+ done <<EOF
140
+ $(rt_reinvented_in "$path" 2>/dev/null)
141
+ EOF
142
+
143
+ [ -z "$found" ] && exit 0
144
+
145
+ reason="BLOCKED: это уже написано. Файл: ${path##*/}
146
+ ${found}
147
+
148
+ Порядок действий: 1) открой готовое — барель кита или базовый класс — и используй его; 2) найди в дереве экран, где этот случай уже собран, и повтори сборку; 3) если готового правда не хватает — расширяй его на месте, у готового, а не клонируй рядом: клон забирает правки на себя и расходится с оригиналом с первой же.
149
+ Свой примитив, своя основа и свои инлайновые стили заводятся только с явного одобрения владельца, и спрашивается это до первого написанного файла. Разовое исключение помечается маркером отступления в той же строке, с объяснением, чего именно нет в готовом. Переименованием файла это не обходится."
150
+
151
+ jq -n --arg r "$reason" '{hookSpecificOutput:{hookEventName:"PreToolUse",permissionDecision:"deny",permissionDecisionReason:$r}}' 2>/dev/null \
152
+ || printf '{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"deny","permissionDecisionReason":"Написано своё там, где готовое уже есть."}}\n'
153
+
154
+ exit 0
@@ -0,0 +1,23 @@
1
+ #!/usr/bin/env bash
2
+ # rt-hook: SessionStart compact|clear
3
+ # Взвод гейта заново. SessionStart(compact|clear).
4
+ #
5
+ # Гейт помнит загруженное правило по идентификатору сессии и дальше пропускает эту область
6
+ # молча. Сжатие контекста и очистка выносят из контекста САМ ТЕКСТ правила, но идентификатор
7
+ # сессии оставляют прежним — без этого хука гейт продолжал бы пропускать, пока агент работает
8
+ # по пересказу вместо самого правила.
9
+ #
10
+ # Снятие записи заставляет каждую область загрузить своё правило заново.
11
+ #
12
+ # ОТКАЗ В ПОЛЬЗУ РАБОТЫ: любая ошибка пропускает. Хук удаляет временный файл и ничего не
13
+ # отбивает.
14
+
15
+ input="$(cat 2>/dev/null)"
16
+ [ -z "$input" ] && exit 0
17
+
18
+ sid="$(printf '%s' "$input" | jq -r '.session_id // empty' 2>/dev/null)"
19
+ [ -z "$sid" ] && exit 0
20
+
21
+ rm -f "${TMPDIR:-/tmp}/claude-skill-gate/${sid}.loaded" 2>/dev/null
22
+
23
+ exit 0