@rt-tools/agent-kit 0.5.1 → 0.5.2

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 (72) hide show
  1. package/README.md +68 -0
  2. package/assets/commands/agent-kit-digest.md +83 -0
  3. package/assets/commands/skill-curator.md +33 -1
  4. package/assets/defaults/gate-map.sh +2 -0
  5. package/assets/defaults/project.sh +12 -1
  6. package/assets/docs/GLOSSARY.md +3 -0
  7. package/assets/hooks/docs-guard.sh +18 -1
  8. package/assets/hooks/git-guard-delivery.sh +30 -3
  9. package/assets/hooks/git-guard-main.sh +8 -0
  10. package/assets/hooks/git-guard-push-tests.sh +8 -1
  11. package/assets/hooks/grill-gate.sh +38 -16
  12. package/assets/hooks/lint-after-edit.sh +10 -3
  13. package/assets/hooks/observe.sh +90 -0
  14. package/assets/hooks/postmortem-guard.sh +92 -0
  15. package/assets/hooks/profile-check.sh +43 -0
  16. package/assets/hooks/qa-dataid-guard.sh +9 -2
  17. package/assets/hooks/reuse-first-guard.sh +9 -2
  18. package/assets/hooks/skill-gate.sh +15 -0
  19. package/assets/hooks/skill-loaded.sh +7 -0
  20. package/assets/hooks/task-context-load.sh +16 -2
  21. package/assets/hooks/task-flow-guard.sh +9 -2
  22. package/assets/hooks/window-fill-guard.sh +8 -1
  23. package/assets/laws/project-documentation.md +5 -0
  24. package/assets/laws/verifiability.md +9 -0
  25. package/assets/laws/work-conduct.md +39 -0
  26. package/assets/patterns/git-workflow-commit.azure.md +16 -12
  27. package/assets/patterns/git-workflow-commit.github.md +16 -12
  28. package/assets/patterns/git-workflow-commit.gitlab.md +16 -12
  29. package/assets/patterns/task-flow-resume.md +12 -0
  30. package/assets/patterns/task-flow-start.md +36 -2
  31. package/assets/rules/git-workflow.azure.md +15 -0
  32. package/assets/rules/git-workflow.github.md +15 -0
  33. package/assets/rules/git-workflow.gitlab.md +15 -0
  34. package/assets/rules/spec-driven.md +6 -0
  35. package/assets/rules/task-flow.md +24 -3
  36. package/assets/skills/agent-kit.md +32 -0
  37. package/assets/templates/postmortem.md +32 -0
  38. package/assets/templates/proposal.md +39 -0
  39. package/bin/agent-kit.d.ts.map +1 -1
  40. package/bin/agent-kit.js +50 -1
  41. package/bin/agent-kit.js.map +1 -1
  42. package/lib/catalog.d.ts +33 -0
  43. package/lib/catalog.d.ts.map +1 -1
  44. package/lib/catalog.js +55 -1
  45. package/lib/catalog.js.map +1 -1
  46. package/lib/commands.d.ts +41 -0
  47. package/lib/commands.d.ts.map +1 -1
  48. package/lib/commands.js +314 -3
  49. package/lib/commands.js.map +1 -1
  50. package/lib/config.d.ts +8 -0
  51. package/lib/config.d.ts.map +1 -1
  52. package/lib/config.js +4 -0
  53. package/lib/config.js.map +1 -1
  54. package/lib/observations.d.ts +72 -0
  55. package/lib/observations.d.ts.map +1 -0
  56. package/lib/observations.js +126 -0
  57. package/lib/observations.js.map +1 -0
  58. package/lib/proposals.d.ts +48 -0
  59. package/lib/proposals.d.ts.map +1 -0
  60. package/lib/proposals.js +111 -0
  61. package/lib/proposals.js.map +1 -0
  62. package/lib/submit.d.ts +24 -0
  63. package/lib/submit.d.ts.map +1 -0
  64. package/lib/submit.js +26 -0
  65. package/lib/submit.js.map +1 -0
  66. package/lib/sync.d.ts +9 -1
  67. package/lib/sync.d.ts.map +1 -1
  68. package/lib/sync.js +2 -1
  69. package/lib/sync.js.map +1 -1
  70. package/package.json +1 -1
  71. package/rt-tools-agent-kit-0.5.2.tgz +0 -0
  72. package/rt-tools-agent-kit-0.5.1.tgz +0 -0
@@ -0,0 +1,90 @@
1
+ #!/usr/bin/env bash
2
+ # Запись наблюдений о слое правил. НЕ гард: объявления `rt-hook:` у него нет, к событиям агента
3
+ # он не подключается. Его источают гарды — тем же приёмом, каким гейт источает карту.
4
+ #
5
+ # Зачем он есть. Правки в тексты пакета шли из головы того, кто их пишет: чем из разложенного
6
+ # пользуются каждый день, чем не пользовались ни разу и обо что спотыкаются по второму кругу,
7
+ # пакету было неизвестно. Часть данных при этом уже собиралась и выбрасывалась — запись загрузок
8
+ # жила во временном каталоге и гибла со сжатием контекста.
9
+ #
10
+ # ЧТО ЗАПИСЫВАЕТСЯ. Только имена ресурсов пакета и счётчики: имя правила или гарда, род события,
11
+ # род файла, версия пакета и признак сессии. Ни путей дерева, ни имён его доменов, ни имени
12
+ # самого дерева — и держится это не памятью зовущего, а `rt_observe_clean`: значение со слэшем
13
+ # не пишется вовсе. Наблюдение уезжает потом в чужой репозиторий, и запрет называть чужое дерево
14
+ # обязан держаться конструкцией.
15
+ #
16
+ # ОТКАЗ В ПОЛЬЗУ РАБОТЫ. Наблюдение — побочная работа гарда, и любая её поломка молча пропускает
17
+ # действие: недоступный каталог, отсутствие `date`, полный диск. Гард, упавший на записи
18
+ # наблюдения, останавливал бы работу ради статистики.
19
+
20
+ # Значение, годное к записи. Путь не выносится ни при каких обстоятельствах, остальное чистится
21
+ # до имени и обрезается: длинное значение в наблюдении не значит ничего, кроме утечки.
22
+ rt_observe_clean() {
23
+ case "$1" in
24
+ */*) return 0 ;;
25
+ esac
26
+ printf '%s' "$1" | LC_ALL=C tr -cd 'A-Za-z0-9._:-' | cut -c1-48
27
+ }
28
+
29
+ # Признак сессии: считается по её имени и обратно не восстанавливается. Нужен, чтобы сводка
30
+ # знала число заходов, а не только число событий.
31
+ rt_observe_session() {
32
+ printf '%s' "$1" | cksum 2>/dev/null | cut -d' ' -f1
33
+ }
34
+
35
+ # Версия пакета, разложившего этот файл. Стоит в шапке, которую ставит раскладка; в исходниках
36
+ # пакета шапки нет, и там версия — `dev`.
37
+ rt_observe_version() {
38
+ local found
39
+ found="$(sed -n 's/^# rt-kit v\([^ ]*\) .*/\1/p' "${BASH_SOURCE[0]}" 2>/dev/null | head -1)"
40
+ printf '%s' "${found:-dev}"
41
+ }
42
+
43
+ # Каталог наблюдений этого дерева. Пустая строка — записи не будет.
44
+ rt_observe_dir() {
45
+ local root="${CLAUDE_PROJECT_DIR:-.}" config
46
+ config="$root/.claude/rt-kit.json"
47
+ # Выключатель дерева гасит запись целиком, а не частями: пакет стоит и у тех, о ком мы не
48
+ # знаем. Нечитаемая настройка выключателем не считается — иначе сломанный JSON тихо
49
+ # выключал бы наблюдения, и понять это было бы нечем.
50
+ if [ -f "$config" ] && command -v jq >/dev/null 2>&1; then
51
+ [ "$(jq -r 'if .observe == false then "off" else "on" end' "$config" 2>/dev/null)" = "off" ] && return 0
52
+ fi
53
+ printf '%s' "$root/.claude/rt-kit/observations"
54
+ }
55
+
56
+ # Одно наблюдение: род события и пары `ключ=значение`. Ключ `sid` хешируется, остальные
57
+ # чистятся. Пустое значение поля не заводит.
58
+ #
59
+ # rt_note gate-deny res=styling-bem kind=scss sid="$sid"
60
+ rt_note() {
61
+ local kind="$1" dir day stamp line pair key value
62
+ [ -n "$kind" ] || return 0
63
+ shift
64
+
65
+ dir="$(rt_observe_dir)"
66
+ [ -n "$dir" ] || return 0
67
+ mkdir -p "$dir" 2>/dev/null || return 0
68
+
69
+ day="$(date -u +%Y-%m-%d 2>/dev/null)" || return 0
70
+ stamp="$(date -u +%Y-%m-%dT%H:%M:%SZ 2>/dev/null)" || return 0
71
+ [ -n "$day" ] || return 0
72
+
73
+ line="{\"t\":\"$stamp\",\"ev\":\"$(rt_observe_clean "$kind")\""
74
+ for pair in "$@"; do
75
+ key="$(rt_observe_clean "${pair%%=*}")"
76
+ value="${pair#*=}"
77
+ [ -n "$key" ] || continue
78
+ if [ "$key" = "sid" ]; then
79
+ value="$(rt_observe_session "$value")"
80
+ else
81
+ value="$(rt_observe_clean "$value")"
82
+ fi
83
+ [ -n "$value" ] || continue
84
+ line="$line,\"$key\":\"$value\""
85
+ done
86
+ line="$line,\"v\":\"$(rt_observe_version)\"}"
87
+
88
+ printf '%s\n' "$line" >> "$dir/$day.jsonl" 2>/dev/null || return 0
89
+ return 0
90
+ }
@@ -0,0 +1,92 @@
1
+ #!/usr/bin/env bash
2
+ # rt-hook: Stop
3
+ # Гард происшествия: ход, в котором исполнитель признал промах, не заканчивается, пока записи о
4
+ # происшествии нет. Stop.
5
+ #
6
+ # Зачем именно так. Происшествие — заход, в котором исполнитель сделал не то, а слой правил
7
+ # этого не отбил, — исправляемого кода за собой не оставляет. Такой заход кончается извинением
8
+ # в переписке: назавтра механизм промаха пересказывается уже приглаженно, остаются выводы, а из
9
+ # выводов правило не выводится. Разбор случался только тогда, когда владелец требовал его вслух.
10
+ #
11
+ # Ловится признание образцами, а не пониманием смысла: оценку «это был промах» назначал бы тот,
12
+ # кому она мешает, и порог плыл бы. Набор образцов виден, пополняется правкой и промахивается
13
+ # заметно — заход, признавший промах словами вне набора, гард пропускает, и это сказано вслух в
14
+ # договорённости, а не считается закрытым.
15
+ #
16
+ # Ловится на завершении хода, а не на отправке реплики: к моменту признания промах уже случился,
17
+ # и ловить раньше нечего. Этим он отличается от гарда разговора, у которого есть свой инструмент
18
+ # — вопрос владельцу.
19
+ #
20
+ # ОТКАЗ В ПОЛЬЗУ РАБОТЫ: при любой ошибке, отсутствии записи хода и повторном заходе ход
21
+ # РАЗРЕШАЕТСЯ (exit 0). Сломанный гард не имеет права заклинить разговор.
22
+
23
+ input="$(cat 2>/dev/null)"
24
+ [ -z "$input" ] && exit 0
25
+
26
+ command -v jq >/dev/null 2>&1 || exit 0
27
+
28
+ # Повторный заход по тому же ходу не судится: гард сказал своё один раз и отпускает.
29
+ active="$(printf '%s' "$input" | jq -r '.stop_hook_active // false' 2>/dev/null)"
30
+ [ "$active" = "true" ] && exit 0
31
+
32
+ transcript="$(printf '%s' "$input" | jq -r '.transcript_path // empty' 2>/dev/null)"
33
+ [ -z "$transcript" ] && exit 0
34
+ [ -f "$transcript" ] || exit 0
35
+
36
+ root="${CLAUDE_PROJECT_DIR:-.}"
37
+
38
+ # Каталог записей у дерева свой. Заданный пустым — отказ дерева от требования: дереву, которое
39
+ # записей не ведёт, гард не навязывается.
40
+ notes_dir="${RT_POSTMORTEMS_DIR-docs/postmortems}"
41
+ [ -z "$notes_dir" ] && exit 0
42
+ [ -d "$root/$notes_dir" ] || exit 0
43
+
44
+ # Образцы признания промаха. Набор открыт и пополняется правкой: полнота его — открытый вопрос
45
+ # договорённости, а не обещание.
46
+ admitted_re='был неправ|был не прав|ошибс|моя ошибк|мой промах|промахнул|проглядел|не проверил|соврал|виноват|извин|прошу прощения|неверно утверждал|утверждение было ложн|принял на веру'
47
+
48
+ # Ход — это всё, что записано после последнего настоящего ввода владельца. Ответ инструмента
49
+ # приходит той же ролью, поэтому строки с `tool_result` вводом не считаются.
50
+ #
51
+ # Хвост в 400 строк: запись хода растёт всю сессию, а судится только последний ход.
52
+ verdict="$(tail -n 400 "$transcript" 2>/dev/null | jq -s -r --arg re "$admitted_re" '
53
+ def is_input:
54
+ .type == "user"
55
+ and (((.message.content // []) | if type == "array"
56
+ then ([.[] | select(.type == "tool_result")] | length)
57
+ else 0 end) == 0);
58
+
59
+ (map(is_input) | rindex(true)) as $i
60
+ | (if $i == null then . else .[$i + 1:] end) as $turn
61
+ | [$turn[] | select(.type == "assistant") | (.message.content // [])[] | select(.type == "text") | .text] as $texts
62
+ | [$turn[] | select(.type == "assistant") | (.message.content // [])[] | select(.type == "tool_use")] as $uses
63
+ # Признак нечувствителен к регистру флагом, а не приведением: приведение знает только
64
+ # латиницу, и «Был неправ» с большой буквы проходило бы мимо набора образцов молча.
65
+ | (($texts | join("\n")) | test($re; "i")) as $admitted
66
+ | ($uses | map(
67
+ ((.name // "") | test("^(Write|Edit|MultiEdit)$"))
68
+ and ((.input.file_path // "") | test("postmortem"))
69
+ ) | any) as $wrote
70
+ | if $admitted and ($wrote | not) then "admit" else "pass" end
71
+ ' 2>/dev/null)"
72
+
73
+ [ "$verdict" = "admit" ] || exit 0
74
+
75
+ # Запись, сделанная за эту сессию, снимает требование и без правки в этом же ходе: разбор мог
76
+ # лечь файлом ходом раньше — тем, в котором промах и признали.
77
+ if [ -n "$(find "$root/$notes_dir" -name '*.md' -newermt '-1 day' 2>/dev/null | head -1)" ]; then
78
+ exit 0
79
+ fi
80
+
81
+ reason="BLOCKED by postmortem-guard: в ответе признан промах, а записи о происшествии в \`$notes_dir/\` за сегодня нет. Происшествие — заход, в котором исполнитель сделал не то, а слой правил этого не отбил, — записывается в тот же заход: назавтра механизм промаха пересказывается уже приглаженно, и правило из него не выводится.
82
+
83
+ Запись называет: механизм промаха по шагам, что было доступно до него, чем ловилось и что из этого ушло в слой правил. Без последней строки это жалоба, а не разбор.
84
+
85
+ $notes_dir/<год>-<месяц>-<день>-<короткое имя>.md
86
+
87
+ Гард судит один ход: следующий заход не отбивается."
88
+
89
+ jq -n --arg r "$reason" '{decision:"block",reason:$r}' 2>/dev/null \
90
+ || printf '{"decision":"block","reason":"postmortem-guard: признан промах — запиши разбор происшествия."}\n'
91
+
92
+ exit 0
@@ -0,0 +1,43 @@
1
+ #!/usr/bin/env bash
2
+ # Нехватка функции профиля, сказанная вслух. НЕ гард: объявления `rt-hook:` у него нет, к
3
+ # событиям агента он не подключается. Его источают сами гарды — тем же приёмом, каким гейт
4
+ # источает карту, а гарды — запись наблюдений.
5
+ #
6
+ # Зачем он есть. Девять хуков пакета зовут функции профиля дерева, и у пяти проверка стоит до
7
+ # всякого поведения: нет функции — выход с нулём, не сделав ничего. Такой гард хуже
8
+ # отсутствующего: он лежит в дереве, стоит в настройке, виден в списке хуков и читается как
9
+ # работающий. Тишина при этом означает разом три разных вещи — «нечего проверять», «проверять
10
+ # нечем» и «всё в порядке», — и отличить их нечем.
11
+ #
12
+ # ЧТО ГОВОРИТСЯ. Имя функции и файл, в котором её определяют. Один раз за сессию: на каждый
13
+ # вызов та же строка повторялась бы десятки раз за заход и перестала бы читаться.
14
+ #
15
+ # ДЕЙСТВИЕ ВСЁ РАВНО ПРОПУСКАЕТСЯ. Хук сообщает о своей неполноте, а не судит правку: судить её
16
+ # нечем, и отбивать работу из-за ненастроенного дерева он не вправе.
17
+
18
+ # Куда кладётся отметка «об этом уже сказано». Каталог временных файлов, а не дерево: отметка
19
+ # живёт один заход и в историю не едет.
20
+ rt_needs_mark_dir() {
21
+ printf '%s' "${TMPDIR:-/tmp}"
22
+ }
23
+
24
+ # Есть ли функция профиля. Успех — есть, и хук работает дальше. Отказ — нет, и об этом сказано.
25
+ #
26
+ # rt_needs rt_is_app_code task-flow-guard "$sid"
27
+ #
28
+ # Третий параметр — признак сессии, если хук его знает. Не знает — отметка кладётся на день:
29
+ # без признака «один раз за сессию» превратилось бы в «один раз за всю жизнь машины».
30
+ rt_needs() {
31
+ command -v "$1" >/dev/null 2>&1 && return 0
32
+
33
+ rt_needs_key="${3:-$(date +%Y%m%d 2>/dev/null || printf 'nosession')}"
34
+ rt_needs_mark="$(rt_needs_mark_dir)/rt-kit-needs-$1-$rt_needs_key"
35
+ if [ ! -f "$rt_needs_mark" ]; then
36
+ printf '%s: нет функции %s — её определяют в .claude/rt-kit/project.sh, а умолчание везёт пакет в .claude/rt-kit/defaults/project.sh\n' \
37
+ "${2:-хук}" "$1" >&2
38
+ printf 'проверка не работает, действие пропущено\n' >&2
39
+ : >"$rt_needs_mark" 2>/dev/null || true
40
+ fi
41
+
42
+ return 1
43
+ }
@@ -1,5 +1,6 @@
1
1
  #!/usr/bin/env bash
2
2
  # rt-hook: PreToolUse Edit|Write|MultiEdit|mcp__webstorm__create_new_file
3
+ # Требует: hooks/profile-check.sh
3
4
  # Гард якоря для спек. PreToolUse на правке разметки.
4
5
  #
5
6
  # Спеки адресуют элементы только через этот атрибут. Классы оформления меняются вместе с
@@ -56,10 +57,16 @@ for profile in "$rt_hooks_dir/../rt-kit/defaults/project.sh" "$rt_hooks_dir/../d
56
57
  [ -f "$profile" ] && . "$profile" 2>/dev/null
57
58
  done
58
59
 
60
+ # Слово о нехватке функции профиля: хук, вышедший молча, неотличим от работающего. Файл может
61
+ # быть не разложен — тогда остаётся прежнее поведение, молчаливое.
62
+ # shellcheck disable=SC1090
63
+ [ -f "$rt_hooks_dir/profile-check.sh" ] && . "$rt_hooks_dir/profile-check.sh"
64
+ command -v rt_needs >/dev/null 2>&1 || rt_needs() { command -v "$1" >/dev/null 2>&1; }
65
+
59
66
  # Разметка приложения живёт там же, где его код. Без этой положительной проверки гард
60
67
  # распространялся на любой файл разметки на диске — черновик вне дерева отклонялся требованием
61
68
  # проставить якоря.
62
- if command -v rt_is_app_code >/dev/null 2>&1; then
69
+ if rt_needs rt_is_app_code qa-dataid-guard; then
63
70
  rt_is_app_code "$path" || exit 0
64
71
  fi
65
72
 
@@ -75,7 +82,7 @@ added="$(printf '%s' "$input" | jq -r '
75
82
  ' 2>/dev/null)"
76
83
  [ -z "$added" ] && exit 0
77
84
 
78
- decorative="$(command -v rt_qa_decorative >/dev/null 2>&1 && rt_qa_decorative 2>/dev/null)"
85
+ decorative="$(rt_needs rt_qa_decorative qa-dataid-guard && rt_qa_decorative 2>/dev/null)"
79
86
  component_re="${RT_QA_COMPONENT_RE:--}"
80
87
 
81
88
  # Открывающие теги разбираются ЦЕЛИКОМ: тег занимает несколько строк, и якорь часто стоит не в
@@ -1,5 +1,6 @@
1
1
  #!/usr/bin/env bash
2
2
  # rt-hook: PreToolUse Edit|Write|MultiEdit|mcp__webstorm__create_new_file
3
+ # Требует: hooks/profile-check.sh
3
4
  # Гард «ничего не пишется с нуля». PreToolUse на правке кода и разметки.
4
5
  #
5
6
  # Линтеры знают правила, но не знают ИНВЕНТАРЬ: линтер стилей поймает сырой цвет, линтер кода —
@@ -61,11 +62,17 @@ for profile in "$rt_hooks_dir/../rt-kit/defaults/project.sh" "$rt_hooks_dir/../d
61
62
  # shellcheck disable=SC1090
62
63
  [ -f "$profile" ] && . "$profile" 2>/dev/null
63
64
  done
64
- command -v rt_reinvented_in >/dev/null 2>&1 || exit 0
65
+
66
+ # Слово о нехватке функции профиля: хук, вышедший молча, неотличим от работающего. Файл может
67
+ # быть не разложен — тогда остаётся прежнее поведение, молчаливое.
68
+ # shellcheck disable=SC1090
69
+ [ -f "$rt_hooks_dir/profile-check.sh" ] && . "$rt_hooks_dir/profile-check.sh"
70
+ command -v rt_needs >/dev/null 2>&1 || rt_needs() { command -v "$1" >/dev/null 2>&1; }
71
+ rt_needs rt_reinvented_in reuse-first-guard || exit 0
65
72
 
66
73
  # Правила этого дерева действуют на код этого дерева: без положительной проверки гард требовал
67
74
  # бы собирать готовым и в черновике за пределами дерева.
68
- if command -v rt_is_app_code >/dev/null 2>&1; then
75
+ if rt_needs rt_is_app_code reuse-first-guard; then
69
76
  rt_is_app_code "$path" || exit 0
70
77
  fi
71
78
 
@@ -38,6 +38,7 @@ command -v skill_for >/dev/null 2>&1 || exit 0
38
38
 
39
39
  req=""
40
40
  target=""
41
+ kind=""
41
42
  case "$tool" in
42
43
  # Инструмент среды заводит файл теми же двумя данными, только называет их иначе — без этой
43
44
  # ветки файл заводился мимо гейта.
@@ -61,6 +62,12 @@ case "$tool" in
61
62
  # приходит в обычный сервис, а число-настройка в обычный класс.
62
63
  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
64
  req="$(skill_for edit "$target" "$written" 2>/dev/null)"
65
+ # Род правки для наблюдения. Одно расширение, без пути и без имени файла: наблюдение
66
+ # уезжает наружу, и всё, кроме рода, там было бы адресом этого дерева.
67
+ case "${target##*/}" in
68
+ *.*) kind="${target##*.}" ;;
69
+ *) kind="none" ;;
70
+ esac
64
71
  ;;
65
72
  # Терминал среды исполняет ту же командную строку и кладёт её в то же поле: без этой ветки
66
73
  # коммит из него не требовал правила, тогда как тот же коммит из оболочки требовал.
@@ -77,9 +84,11 @@ case "$tool" in
77
84
  [ -n "$inner" ] && target="$inner"
78
85
  fi
79
86
  req="$(skill_for bash "$target" "" 2>/dev/null)"
87
+ kind="command"
80
88
  ;;
81
89
  mcp__claude-in-chrome__*)
82
90
  req="$(skill_for browser "$tool" "" 2>/dev/null)"
91
+ kind="browser"
83
92
  ;;
84
93
  *) exit 0 ;;
85
94
  esac
@@ -108,6 +117,12 @@ done
108
117
  [ -z "$want" ] && exit 0
109
118
  req="$want"
110
119
 
120
+ # Отбитие — наблюдение: правило, которое приходится требовать чаще прочего, и род правки, на
121
+ # котором это происходит, говорят о слое правил больше, чем список загруженного.
122
+ # shellcheck disable=SC1090
123
+ [ -f "$rt_hooks_dir/observe.sh" ] && . "$rt_hooks_dir/observe.sh" 2>/dev/null
124
+ command -v rt_note >/dev/null 2>&1 && rt_note gate-deny "res=$req" "kind=$kind" "sid=$sid"
125
+
111
126
  reason="Отбито гейтом правил: загрузи правило «${req}» инструментом Skill и повтори действие. Для этой области это происходит один раз за сессию."
112
127
 
113
128
  # Правило называет свой закон одним словом, а слоёв законов два: общий лежит в корне, закон
@@ -18,4 +18,11 @@ dir="${TMPDIR:-/tmp}/claude-skill-gate"
18
18
  mkdir -p "$dir" 2>/dev/null || exit 0
19
19
  printf '%s\n' "$skill" >> "$dir/${sid}.loaded" 2>/dev/null
20
20
 
21
+ # Та же загрузка вторым адресом — в наблюдения дерева. Запись выше живёт до сжатия контекста и
22
+ # гибнет вместе с ним: она отвечает гейту на вопрос «загружено ли», и больше ни на что.
23
+ rt_hooks_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
24
+ # shellcheck disable=SC1090
25
+ [ -f "$rt_hooks_dir/observe.sh" ] && . "$rt_hooks_dir/observe.sh" 2>/dev/null
26
+ command -v rt_note >/dev/null 2>&1 && rt_note skill-load "res=$skill" "sid=$sid"
27
+
21
28
  exit 0
@@ -1,5 +1,6 @@
1
1
  #!/usr/bin/env bash
2
2
  # rt-hook: SessionStart startup|resume|compact|clear
3
+ # Требует: hooks/profile-check.sh
3
4
  # SessionStart: состояние незаконченной работы уезжает в контекст на каждом запуске сессии.
4
5
  #
5
6
  # Памятью это не держится по той же причине, что и словарь: замысел читают перед правкой
@@ -28,6 +29,12 @@ for profile in "$rt_hooks_dir/../rt-kit/defaults/project.sh" "$rt_hooks_dir/../d
28
29
  [ -f "$profile" ] && . "$profile" 2>/dev/null
29
30
  done
30
31
 
32
+ # Слово о нехватке функции профиля: хук, вышедший молча, неотличим от работающего. Файл может
33
+ # быть не разложен — тогда остаётся прежнее поведение, молчаливое.
34
+ # shellcheck disable=SC1090
35
+ [ -f "$rt_hooks_dir/profile-check.sh" ] && . "$rt_hooks_dir/profile-check.sh"
36
+ command -v rt_needs >/dev/null 2>&1 || rt_needs() { command -v "$1" >/dev/null 2>&1; }
37
+
31
38
  TASKS_DIR="${RT_TASKS_DIR:-docs/tasks}"
32
39
  [ -z "$TASKS_DIR" ] && exit 0
33
40
 
@@ -43,13 +50,20 @@ emit() {
43
50
  # Ветка под задачу без папки — работа идёт мимо. Сессию не рвём: SessionStart, отбивающий
44
51
  # запуск, оставляет владельца без агента вовсе, а правку кода поймает `task-flow-guard`.
45
52
  if [ ! -d "$DIR" ]; then
46
- if command -v rt_task_branch_ok >/dev/null 2>&1 && rt_task_branch_ok "$branch"; then
53
+ if rt_needs rt_task_branch_ok task-context-load && rt_task_branch_ok "$branch"; then
47
54
  {
48
55
  printf 'РАБОТА БЕЗ ПАПКИ ЗАДАЧИ.\n\n'
49
56
  printf 'Ветка `%s` названа задачей, а `%s/` нет: ход работы записывать некуда,\n' "$branch" "$DIR"
50
57
  printf 'и следующий заход начнёт с расспросов владельца.\n\n'
51
58
  printf 'Собрать с образца:\n\n cp -r %s/_template %s\n\n' "$TASKS_DIR" "$DIR"
52
- printf 'Правку кода приложения до этого отбивает гард. Правило скил `task-flow`.\n'
59
+ # О соседнем ресурсе условно и по имени: пакет не знает, разложен ли он здесь,
60
+ # а сказанное безусловно приходит в контекст каждой сессии и врёт про дерево тем
61
+ # увереннее, что печатает это сам инструмент.
62
+ if [ -f "$rt_hooks_dir/task-flow-guard.sh" ]; then
63
+ printf 'Правку кода приложения до этого отбивает гард `task-flow-guard`. Правило — скил `task-flow`.\n'
64
+ else
65
+ printf 'Правило — скил `task-flow`. Гарда `task-flow-guard` в дереве нет: правку кода до этого не отбивает ничто.\n'
66
+ fi
53
67
  } | emit
54
68
  fi
55
69
  exit 0
@@ -1,5 +1,6 @@
1
1
  #!/usr/bin/env bash
2
2
  # rt-hook: PreToolUse Edit|Write|MultiEdit
3
+ # Требует: hooks/profile-check.sh
3
4
  # PreToolUse guard for Edit|Write|MultiEdit: код не пишется раньше замысла.
4
5
  #
5
6
  # Работа идёт много заходов, и между ними исполнитель не помнит ничего. Замысел, лежащий на
@@ -45,11 +46,17 @@ for profile in "$rt_hooks_dir/../rt-kit/defaults/project.sh" "$rt_hooks_dir/../d
45
46
  [ -f "$profile" ] && . "$profile" 2>/dev/null
46
47
  done
47
48
 
49
+ # Слово о нехватке функции профиля: хук, вышедший молча, неотличим от работающего. Файл может
50
+ # быть не разложен — тогда остаётся прежнее поведение, молчаливое.
51
+ # shellcheck disable=SC1090
52
+ [ -f "$rt_hooks_dir/profile-check.sh" ] && . "$rt_hooks_dir/profile-check.sh"
53
+ command -v rt_needs >/dev/null 2>&1 || rt_needs() { command -v "$1" >/dev/null 2>&1; }
54
+
48
55
  # Признак «правка меняет поведение» — путь, а не оценка на глаз: оценку назначает тот, кому
49
56
  # она мешает, и порог плывёт. Где живёт код приложения, знает профиль: правила, тексты, обвязка
50
57
  # и зависимости под требование не попадают — иначе разбор задачи нельзя было бы вести до
51
58
  # заведения ветки.
52
- command -v rt_is_app_code >/dev/null 2>&1 || exit 0
59
+ rt_needs rt_is_app_code task-flow-guard || exit 0
53
60
  rt_is_app_code "$path" || exit 0
54
61
 
55
62
  # Каталог папок задач: у дерева он свой, но имя обычно общее.
@@ -70,7 +77,7 @@ git rev-parse --is-inside-work-tree >/dev/null 2>&1 || exit 0
70
77
  branch="$(git branch --show-current 2>/dev/null)"
71
78
  [ -z "$branch" ] && exit 0 # detached HEAD — не про наш случай
72
79
 
73
- if command -v rt_task_branch_ok >/dev/null 2>&1 && ! rt_task_branch_ok "$branch"; then
80
+ if rt_needs rt_task_branch_ok task-flow-guard && ! rt_task_branch_ok "$branch"; then
74
81
  deny "BLOCKED by task-flow: правка кода идёт в ветке под задачу, а текущая ветка — '${branch}'. Заведи задачу (npm run task:new -- --title '…' --slug <slug>) и ветку под её номером, затем повтори. Правило — скил task-flow."
75
82
  fi
76
83
 
@@ -1,5 +1,6 @@
1
1
  #!/usr/bin/env bash
2
2
  # rt-hook: PostToolUse .*
3
+ # Требует: hooks/profile-check.sh
3
4
  # rt-hook: PreToolUse .*
4
5
  # Заполнение окна: заход доводится до логической точки заранее, а не обрывается на середине.
5
6
  #
@@ -34,6 +35,12 @@ for profile in "$rt_hooks_dir/../rt-kit/defaults/project.sh" "$rt_hooks_dir/../d
34
35
  [ -f "$profile" ] && . "$profile" 2>/dev/null
35
36
  done
36
37
 
38
+ # Слово о нехватке функции профиля: хук, вышедший молча, неотличим от работающего. Файл может
39
+ # быть не разложен — тогда остаётся прежнее поведение, молчаливое.
40
+ # shellcheck disable=SC1090
41
+ [ -f "$rt_hooks_dir/profile-check.sh" ] && . "$rt_hooks_dir/profile-check.sh"
42
+ command -v rt_needs >/dev/null 2>&1 || rt_needs() { command -v "$1" >/dev/null 2>&1; }
43
+
37
44
  window="${RT_WINDOW_TOKENS:-}"
38
45
  case "$window" in
39
46
  '' | *[!0-9]*) exit 0 ;;
@@ -126,7 +133,7 @@ case "$tool" in
126
133
  Bash | mcp__webstorm__execute_terminal_command)
127
134
  # Поставка и сверки: коммит, пуш, отчёт, колонка задачи, состояние дерева. Список
128
135
  # дописывается профилем дерева — клиент хостинга и имена команд у каждого свои.
129
- if command -v rt_handoff_allowed_cmd >/dev/null 2>&1 && rt_handoff_allowed_cmd "$cmd"; then
136
+ if rt_needs rt_handoff_allowed_cmd window-fill-guard && rt_handoff_allowed_cmd "$cmd"; then
130
137
  allowed=1
131
138
  fi
132
139
  ;;
@@ -47,3 +47,8 @@
47
47
  видит, проверки текстов на них не смотрят, и отказ выглядит сделанным ровно до того, как
48
48
  читатель наткнётся на снятое слово в заголовке. Читатель при этом заключает, что от слова
49
49
  не отказывались вовсе.
50
+ - **Текст, который переносится в чужое дерево, не описывает состояние этого дерева как факт.**
51
+ О соседней части он говорит условно и называет её по имени: что в дереве стоит, а чего нет,
52
+ знает само дерево, а не текст, приехавший в него. Утверждение, сказанное безусловно, врёт тем
53
+ увереннее, что печатает его сам инструмент, — и поправить его дерево не может, если правки на
54
+ месте у такого текста не предусмотрено.
@@ -47,3 +47,12 @@
47
47
  выключают в настройке инструмента, а не обходят в каждом месте.** Обход приходится повторять
48
48
  столько раз, сколько таких мест, и ни в одном из них не написано, зачем он: со стороны это
49
49
  выглядит ошибкой автора, а не решением.
50
+ - **Польза правила подтверждается наблюдением за тем, как им пользуются, а не мнением о нём.**
51
+ Правило, которого не открыли ни разу, и правило, на котором держится половина работы, в тексте
52
+ выглядят одинаково — и правится первым обычно то, о чём вспомнили, а не то, что мешает.
53
+ Наблюдение ведётся там же, где идёт работа, и переживает отдельный заход: запись, которая
54
+ умирает вместе с сессией, отвечает только на вопрос «что было минуту назад».
55
+ - **Наблюдение за работой не выносит наружу ничего, кроме того, что общее у всех.** Имя правила,
56
+ род события и версия одинаковы везде, где стоит слой правил; путь, домен и имя дерева
57
+ принадлежат одному дереву и в чужом месте не значат ничего, кроме утечки. Держится это
58
+ проверкой на выносящей стороне, а не памятью того, кто пишет.
@@ -13,8 +13,24 @@
13
13
  - **Пробел закрывается вопросом владельцу, а не догадкой.** Догадка неотличима от знания:
14
14
  она попадает в работу молча и обнаруживается только при приёмке, когда переделывать дороже
15
15
  всего.
16
+ - **Вопрос, у которого есть очевидный ответ, работу не останавливает.** Исполнитель называет
17
+ допущение, идёт дальше и записывает его туда же, где идёт работа. Останавливает только тот
18
+ пробел, при котором любая догадка делает работу опасной или бесполезной. Вопрос, заданный
19
+ вместо работы, выглядит добросовестно, а стоит владельцу захода.
20
+ - **Остановка называется первой строкой.** Сообщение, которым исполнитель останавливается,
21
+ начинается с того, чего он ждёт и что будет, если ответа не будет. Замеры, находки и разбор
22
+ к этому моменту уже записаны в ход работы — в сообщении владельцу они лишние. Чем подробнее
23
+ отчёт, тем надёжнее вопрос в нём тонет, и владелец переспрашивает, почему работа стоит.
16
24
  - **Владельцу не задаётся вопрос, ответ на который уже записан.** Записанное читают до
17
25
  разговора, а не вместо ответа: вопрос о том, что уже решено, обесценивает и остальные.
26
+ - **Вопрос владельцу задаётся после того, как ответ искали в дереве.** Что лежит в дереве,
27
+ исполнитель узнаёт сам: имя, путь, наличие файла и устройство репозитория он берёт командой,
28
+ а не у владельца. Вопрос, ответ на который дала бы одна команда, стоит владельцу захода и
29
+ обесценивает заданные рядом.
30
+ - **Утверждение о дереве стоит ровно столько, сколько команда, его показавшая.** Отрицание —
31
+ «здесь нет», «этого не заводили», «такого файла не бывает» — произносится только как вывод
32
+ команды. Не проверенное отрицание опаснее вопроса: вопрос владелец поправит, а факт от
33
+ исполнителя примет на веру, потому что тот в дерево смотрит.
18
34
  - **Решение, однажды записанное, действует, пока его не отменили, и читается до того, как
19
35
  принимается заново.** Отменённое решение следа в работе не оставляет — по результату не
20
36
  видно ни того, что его принимали, ни того, что от него отказались. Принятое заново оно
@@ -49,6 +65,11 @@
49
65
  читается как случайное и отменяется следующим заходом, а отменённое возвращается третьим.
50
66
  - **Граница работы названа до её начала.** Не названная вслух граница не существует: правка
51
67
  расползается на соседнее, и снимать её приходится вручную.
68
+ - **Действия, которые исполнитель не делает сам, названы списком.** Всё, что уходит за
69
+ пределы рабочего дерева или не откатывается — запись в общий репозиторий, публикация, отчёт,
70
+ правка общего документа, — делается по слову владельца, и слово это даётся на действие, а не
71
+ на работу целиком. Не названная списком граница выводится из общих слов: «делай, что нужно
72
+ по плану» прочитывается как разрешение на всё, что в плане подразумевалось.
52
73
  - **Признак закрытия работы назван до её начала и проверяем.** «Работает» признаком не
53
74
  является: под ним каждый заход понимает своё, и работа закрывается тогда, когда надоела.
54
75
  - **Начатая и брошенная работа видна.** Брошенное на середине выглядит так же, как
@@ -65,3 +86,21 @@
65
86
  - **Незаданный вопрос замечает только тот, кто знает, чего хотел.** Вопрос, которого не
66
87
  задали, следа не оставляет: работа выглядит понятой ровно до приёмки, и вывести отсутствие
67
88
  вопроса не из чего.
89
+ - **Происшествие названо признаком, а не оценкой заднего числа.** Происшествие — заход, в
90
+ котором исполнитель сделал не то, а слой правил этого не отбил. Дефект в коде происшествием
91
+ не является: его объясняет договорённость о продукте. Признак записан один раз, а не
92
+ выводится каждым заходом заново — иначе его назначает тот, кому он мешает.
93
+ - **Происшествие кончается записью, и запись делается в тот же заход.** Через день механизм
94
+ промаха пересказывается уже приглаженно: остаются выводы, а из выводов правило не выводится.
95
+ Запись называет механизм промаха по шагам, что было доступно до него, чем ловилось и что из
96
+ этого ушло в слой правил.
97
+ - **Запись без правки слоя правил закрытой не считается.** Разбор, из которого не вышло ни
98
+ предложения, ни правки закона, правила или паттерна, — жалоба: он объясняет случившееся и
99
+ ничего не меняет, а значит повторится.
100
+ - **Закрытая работа оставляет то, что узнала о правилах, там, откуда это возьмёт машина.**
101
+ Понимание, добытое одной работой, всего дороже соседней — и живёт оно ровно до конца захода,
102
+ если осталось в разговоре. Записанное свободным пересказом лежит не хуже, но переносить его
103
+ приходится руками, и переносится оно ровно до первой занятой недели.
104
+ - **У предложенной правки правил назван адрес: сам слой правил, имена этого дерева или его
105
+ надстройка.** Без адреса правку кладут туда, где она видна автору, — то есть в своё дерево, —
106
+ и общее оседает в одном месте, оставаясь неизвестным всем остальным.
@@ -212,26 +212,30 @@ npm run task:move -- 86 in-review
212
212
  Конвейер видит только отправленное, а отправляется оно пушем. Линтеры, юниты и сценарии хуков
213
213
  снимает гейт пуша — ниже то, чего он не знает.
214
214
 
215
- 1. **В ветке только та правка, за которой её заводили** `git diff main...HEAD --stat`. Чужой
215
+ 1. **Главная ветка влита в эту ветку** `git fetch origin && git merge origin/main`.
216
+ Всё, что проверяется ниже, проверяется от этого основания: PR с разошедшейся ветки
217
+ показывает ревьюверу правку вперемешку с чужой. Порядок и разбор конфликта — паттерн
218
+ `git-workflow-merge`.
219
+ 2. **В ветке только та правка, за которой её заводили** — `git diff main...HEAD --stat`. Чужой
216
220
  домен в списке файлов означает, что правка расползлась, и её надо вернуть в свои границы.
217
- 2. **Ни мока, ни подменённого ответа, ни отладочной строки** — `git diff main...HEAD` читается
221
+ 3. **Ни мока, ни подменённого ответа, ни отладочной строки** — `git diff main...HEAD` читается
218
222
  целиком, а не по именам файлов. На прод они уезжают молча и портят настоящие данные.
219
- 3. **Документ едет тем же коммитом.** Пару называет `docs-guard`, но спек домена и правку его
223
+ 4. **Документ едет тем же коммитом.** Пару называет `docs-guard`, но спек домена и правку его
220
224
  поведения он не знает — это остаётся за автором.
221
- 4. **Проверки текстов и раскладки зелёные** — те, что дерево завело в `tools/`. Какие именно
225
+ 5. **Проверки текстов и раскладки зелёные** — те, что дерево завело в `tools/`. Какие именно
222
226
  есть здесь — `implementation.md` правила.
223
- 5. **Все приложения дерева собираются** — `nx build` по каждому. Гейт пуша сборку не гоняет.
224
- 6. **Видимый текст заведён во всех локалях перевода** — тестом полноты словарей, если дерево
227
+ 6. **Все приложения дерева собираются** — `nx build` по каждому. Гейт пуша сборку не гоняет.
228
+ 7. **Видимый текст заведён во всех локалях перевода** — тестом полноты словарей, если дерево
225
229
  переводится.
226
- 7. **Правка вёрстки подтверждена замером**, а не взглядом, и снята при узком экране — паттерн
230
+ 8. **Правка вёрстки подтверждена замером**, а не взглядом, и снята при узком экране — паттерн
227
231
  `browser-verification-measure`.
228
- 8. **Правка разметки публичного сайта проверена на прод-сборке по всем локалям перевода** —
232
+ 9. **Правка разметки публичного сайта проверена на прод-сборке по всем локалям перевода** —
229
233
  паттерн `seo-verify`.
230
- 9. **PR привязан к рабочему элементу**, ревьювер и исполнитель стоят.
231
- 10. **Заголовок PR несёт номер элемента и называет работу сделанной:** `[<номер>] <Что
234
+ 10. **PR привязан к рабочему элементу**, ревьювер и исполнитель стоят.
235
+ 11. **Заголовок PR несёт номер элемента и называет работу сделанной:** `[<номер>] <Что
232
236
  сделано>`, тем же номером, что стоит у элемента и в имени ветки.
233
- 11. **Очередь работ сходится** — `npm run check:board`.
234
- 12. **Состояние PR прочитано, а не выведено из кодов возврата.**
237
+ 12. **Очередь работ сходится** — `npm run check:board`.
238
+ 13. **Состояние PR прочитано, а не выведено из кодов возврата.**
235
239
 
236
240
  Сразу после публикации элемент переводится в разбор, и сверка очереди прогоняется ещё раз: до
237
241
  открытия PR состояние она не судит, а после открытия расхождение видит.