@rt-tools/agent-kit 0.16.1 → 0.18.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.
- package/assets/checks/board-epics.github.mjs +123 -0
- package/assets/checks/board-gh.github.mjs +121 -0
- package/assets/checks/board-paths.github.mjs +88 -0
- package/assets/checks/board-runs.github.mjs +8 -0
- package/assets/checks/board-titles.github.mjs +66 -0
- package/assets/checks/board.github.mjs +77 -73
- package/assets/checks/check-board.github.mjs +56 -7
- package/assets/checks/check-file-size.mjs +10 -2
- package/assets/checks/check-glossary.mjs +170 -0
- package/assets/checks/check-hook-scope.mjs +126 -0
- package/assets/checks/check-profile-drift.mjs +195 -0
- package/assets/checks/check-push-gate.mjs +59 -1
- package/assets/checks/check-schema-drift.mjs +12 -5
- package/assets/checks/check-specs.mjs +1 -1
- package/assets/checks/rt-kit-checks.config.mjs +13 -0
- package/assets/checks/spec-anchors.mjs +2 -2
- package/assets/checks/spec-contract.mjs +9 -0
- package/assets/defaults/gate-map.sh +13 -4
- package/assets/defaults/project.sh +31 -108
- package/assets/defaults/shell.sh +139 -0
- package/assets/docs/GLOSSARY.md +1 -1
- package/assets/hooks/browser-device-id.sh +20 -4
- package/assets/hooks/browser-guard-device-id.sh +5 -2
- package/assets/hooks/browser-guard-no-other-drivers.sh +41 -2
- package/assets/hooks/claim-guard.sh +22 -1
- package/assets/hooks/docs-guard.sh +1 -1
- package/assets/hooks/exam-guard.sh +85 -12
- package/assets/hooks/git-guard-delivery-conflict.sh +85 -0
- package/assets/hooks/git-guard-delivery-folder.sh +19 -0
- package/assets/hooks/git-guard-delivery-signature.sh +14 -5
- package/assets/hooks/git-guard-delivery.sh +54 -2
- package/assets/hooks/git-guard-push-tests.sh +21 -1
- package/assets/hooks/grill-gate-ask.sh +25 -0
- package/assets/hooks/grill-gate.sh +75 -11
- package/assets/hooks/lint-after-edit.sh +44 -16
- package/assets/hooks/proposal-guard.sh +11 -4
- package/assets/hooks/rule-source-guard.sh +8 -13
- package/assets/hooks/skill-gate-layers.sh +7 -0
- package/assets/hooks/skill-gate.sh +29 -0
- package/assets/hooks/task-context-load.sh +32 -0
- package/assets/hooks/task-flow-context.sh +194 -0
- package/assets/hooks/task-flow-draft-guard.sh +109 -0
- package/assets/hooks/task-flow-guard.sh +27 -159
- package/assets/hooks/turn-exit-guard.sh +53 -88
- package/assets/hooks/waiting-turn-guard.sh +65 -2
- package/assets/hooks/window-fill-guard.sh +5 -1
- package/assets/hooks/work-start-guard.sh +166 -0
- package/assets/hooks/write-targets.sh +24 -0
- package/assets/laws/delivery.md +19 -0
- package/assets/laws/frontend-application.md +4 -0
- package/assets/laws/verifiability.md +44 -0
- package/assets/laws/work-conduct.md +15 -0
- package/assets/patterns/browser-verification-measure.md +3 -1
- package/assets/patterns/browser-verification-stand.md +17 -4
- package/assets/patterns/doc-style-write.md +62 -2
- package/assets/patterns/git-workflow-commit.github.md +33 -5
- package/assets/patterns/git-workflow-docker.md +22 -0
- package/assets/patterns/git-workflow-freshness.md +87 -0
- package/assets/patterns/git-workflow-merge.md +41 -0
- package/assets/patterns/git-workflow-migration.md +11 -0
- package/assets/patterns/git-workflow-pr.github.md +12 -1
- package/assets/patterns/git-workflow-restart.md +18 -0
- package/assets/patterns/git-workflow-secrets.md +14 -0
- package/assets/patterns/git-workflow-stack.md +93 -1
- package/assets/patterns/lib-layers-move.md +4 -0
- package/assets/patterns/spec-driven-domain.md +47 -2
- package/assets/patterns/spec-driven-rule.md +16 -4
- package/assets/patterns/spec-driven-sweep.md +57 -0
- package/assets/patterns/status-report-table.github.md +1 -1
- package/assets/patterns/task-flow-archive.md +58 -16
- package/assets/patterns/task-flow-close.md +106 -23
- package/assets/patterns/task-flow-handoff.md +14 -2
- package/assets/patterns/task-flow-resume.md +35 -8
- package/assets/patterns/task-flow-start.md +60 -22
- package/assets/patterns/testing-e2e.md +23 -0
- package/assets/patterns/turn-entry-map.md +1 -1
- package/assets/pitfalls/agent-kit.md +71 -3
- package/assets/pitfalls/doc-style.md +15 -0
- package/assets/pitfalls/git-workflow.github.md +88 -0
- package/assets/pitfalls/spec-driven.md +22 -0
- package/assets/pitfalls/task-flow.md +113 -0
- package/assets/pitfalls/testing.md +6 -0
- package/assets/pitfalls/turn-conduct.md +49 -0
- package/assets/rules/browser-verification.md +58 -0
- package/assets/rules/deploy-flow.azure.md +8 -0
- package/assets/rules/deploy-flow.github.md +27 -0
- package/assets/rules/deploy-flow.gitlab.md +8 -0
- package/assets/rules/doc-style.md +20 -0
- package/assets/rules/git-workflow.azure.md +22 -2
- package/assets/rules/git-workflow.github.md +127 -118
- package/assets/rules/git-workflow.gitlab.md +17 -4
- package/assets/rules/lists.md +5 -0
- package/assets/rules/observability.needs-app.md +4 -0
- package/assets/rules/reuse-first.md +14 -0
- package/assets/rules/shared-code.md +5 -0
- package/assets/rules/spec-driven.md +33 -15
- package/assets/rules/task-flow.md +132 -126
- package/assets/rules/testing.md +41 -2
- package/assets/rules/turn-conduct.md +73 -57
- package/assets/rules/turn-entry.md +6 -0
- package/assets/samples/tasks/_template/grill.md +5 -0
- package/assets/samples/tasks/_template/plan.md +3 -0
- package/assets/skills/agent-kit.md +108 -76
- package/assets/templates/postmortem.md +5 -1
- package/bin/agent-kit.d.ts.map +1 -1
- package/bin/agent-kit.js +30 -6
- package/bin/agent-kit.js.map +1 -1
- package/lib/catalog.d.ts.map +1 -1
- package/lib/catalog.js +2 -1
- package/lib/catalog.js.map +1 -1
- package/lib/commands.d.ts.map +1 -1
- package/lib/commands.js +96 -7
- package/lib/commands.js.map +1 -1
- package/lib/hooks-map.d.ts +26 -0
- package/lib/hooks-map.d.ts.map +1 -1
- package/lib/hooks-map.js +58 -2
- package/lib/hooks-map.js.map +1 -1
- package/lib/sections.d.ts +6 -0
- package/lib/sections.d.ts.map +1 -1
- package/lib/sections.js +19 -0
- package/lib/sections.js.map +1 -1
- package/lib/shipment.d.ts +2 -0
- package/lib/shipment.d.ts.map +1 -1
- package/lib/shipment.fixture.d.ts +5 -0
- package/lib/shipment.fixture.d.ts.map +1 -1
- package/lib/shipment.fixture.js +7 -0
- package/lib/shipment.fixture.js.map +1 -1
- package/lib/shipment.js +13 -1
- package/lib/shipment.js.map +1 -1
- package/lib/sync.d.ts +35 -3
- package/lib/sync.d.ts.map +1 -1
- package/lib/sync.js +59 -8
- package/lib/sync.js.map +1 -1
- package/package.json +1 -1
- package/rt-tools-agent-kit-0.18.0.tgz +0 -0
- package/rt-tools-agent-kit-0.16.1.tgz +0 -0
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
|
-
# rt-hook: PreToolUse AskUserQuestion
|
|
3
2
|
# Требует: hooks/deny-tail.sh
|
|
4
3
|
# rt-hook: Stop
|
|
5
4
|
# Гард разговора: вопрос владельцу не задаётся, пока за этот же ход не читались законы и
|
|
6
|
-
# правила.
|
|
5
|
+
# правила. Судит два события, и это не дублирование; второе объявлено соседним ресурсом
|
|
6
|
+
# `hooks/grill-gate-ask.sh`, который отдаёт вызов сюда.
|
|
7
7
|
#
|
|
8
8
|
# Зачем именно так. Требование «правила читаются до разговора» исполнимо ровно до отправки
|
|
9
9
|
# вопроса. Проверка на завершении хода отбивает задним числом: к моменту отказа вопрос уже у
|
|
@@ -13,12 +13,23 @@
|
|
|
13
13
|
# Одним этим перехватом дыра не закрывается: вопрос чаще задаётся прозой, и ровно так был задан
|
|
14
14
|
# тот, из-за которого гард заведён. Прозаический вопрос инструментом не является, и поймать его
|
|
15
15
|
# можно только на завершении хода — событие получает путь к записи хода и видит его целиком.
|
|
16
|
-
# Отсюда два события: меню ловится до отправки, проза — после.
|
|
16
|
+
# Отсюда два события: меню ловится до отправки, проза — после. Объявлены они разными ресурсами
|
|
17
|
+
# затем, чтобы дерево, у которого инструмент вопроса занят своим гардом, могло взять половину, а
|
|
18
|
+
# не отказаться от требования целиком.
|
|
17
19
|
#
|
|
18
20
|
# Чтением правил считается любой из трёх путей: загрузка правила, чтение файла законов или
|
|
19
21
|
# правил, поиск по ним. Требовать именно загрузку значило бы гнать на неё там, где хватило
|
|
20
22
|
# одного поиска, — гард мешал бы работе вместо того, чтобы её выправлять.
|
|
21
23
|
#
|
|
24
|
+
# Но чтение чего угодно из слоя правил вопроса не закрывает: прочитанный разбор чужого промаха
|
|
25
|
+
# и поиск по каталогу засчитывались наравне с правилом, которое этой работе и требуется, — а
|
|
26
|
+
# ответ на заданный владельцу вопрос лежал ровно в нём. Поэтому, когда область работы этого хода
|
|
27
|
+
# известна, чтением считается только правило этой области. Область берётся оттуда же, откуда её
|
|
28
|
+
# берёт гейт правил: по путям правок хода.
|
|
29
|
+
#
|
|
30
|
+
# ОТКАЗ В ПОЛЬЗУ РАБОТЫ и здесь: правок в ходу нет, карты гейта нет, область не определилась —
|
|
31
|
+
# засчитывается любое чтение, как прежде.
|
|
32
|
+
#
|
|
22
33
|
# ОТКАЗ В ПОЛЬЗУ РАБОТЫ: при любой ошибке, отсутствии записи хода и повторном заходе ход
|
|
23
34
|
# РАЗРЕШАЕТСЯ (exit 0). Сломанный гард не имеет права заклинить разговор.
|
|
24
35
|
|
|
@@ -57,15 +68,56 @@ done
|
|
|
57
68
|
laws_dir="${RT_LAWS_DIR-docs/constitution}"
|
|
58
69
|
rules_dir="${RT_RULES_DIR-.claude/skills}"
|
|
59
70
|
specs_dir="${RT_SPECS_DIR-docs/specs}"
|
|
71
|
+
# Замысел эпика и описание прошлого читаются наравне с законами: решение, связывающее задачи
|
|
72
|
+
# эпика, лежит именно там. Прочитавший замысел получал отказ наравне с не читавшим ничего, и
|
|
73
|
+
# снимался тот отказ поиском по трём каталогам, среди которых нужного не было.
|
|
74
|
+
plans_dir="${RT_PLANS_DIR-docs/plans}"
|
|
75
|
+
archive_dir="${RT_ARCHIVE_DIR-docs/archive}"
|
|
60
76
|
|
|
61
77
|
# Дерево, у которого нет ни законов, ни правил, требования не получает: читать нечего.
|
|
62
78
|
[ -z "$laws_dir" ] && [ -z "$rules_dir" ] && exit 0
|
|
63
79
|
|
|
80
|
+
# Карта гейта: по ней имя правила достаётся из пути правки. Нет её — область не определяется, и
|
|
81
|
+
# всё остаётся прежним.
|
|
82
|
+
rt_hooks_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
83
|
+
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
|
|
84
|
+
# shellcheck disable=SC1090
|
|
85
|
+
[ -f "$map" ] && . "$map" 2>/dev/null
|
|
86
|
+
done
|
|
87
|
+
|
|
64
88
|
# Образец, по которому вызов инструмента считается чтением правил. Каталоги идут в него как
|
|
65
89
|
# есть: точка в `.claude` совпадает с любым знаком и лишнего сюда не приводит.
|
|
66
|
-
read_re="$(printf '%s' "$laws_dir|$rules_dir|$specs_dir" | sed 's/^|*//; s/|*$//; s/||*/|/g')"
|
|
90
|
+
read_re="$(printf '%s' "$laws_dir|$rules_dir|$specs_dir|$plans_dir|$archive_dir" | sed 's/^|*//; s/|*$//; s/||*/|/g')"
|
|
67
91
|
[ -z "$read_re" ] && exit 0
|
|
68
92
|
|
|
93
|
+
# Область работы этого хода: правила, которых требует гейт от путей, правленных в ходу. Пути
|
|
94
|
+
# достаются тем же разбором хода, что и ниже, — своим вызовом, чтобы образец чтения был готов
|
|
95
|
+
# к главному разбору.
|
|
96
|
+
need_re=''
|
|
97
|
+
if command -v skill_for >/dev/null 2>&1 && [ -n "$rules_dir" ]; then
|
|
98
|
+
edited="$(tail -n 400 "$transcript" 2>/dev/null | jq -s -r '
|
|
99
|
+
def is_input:
|
|
100
|
+
.type == "user"
|
|
101
|
+
and (((.message.content // []) | if type == "array"
|
|
102
|
+
then ([.[] | select(.type == "tool_result")] | length)
|
|
103
|
+
else 0 end) == 0);
|
|
104
|
+
(map(is_input) | rindex(true)) as $i
|
|
105
|
+
| (if $i == null then . else .[$i + 1:] end)
|
|
106
|
+
| [.[] | select(.type == "assistant") | (.message.content // [])[]
|
|
107
|
+
| select(.type == "tool_use") | select(.name == "Edit" or .name == "Write" or .name == "NotebookEdit")
|
|
108
|
+
| (.input.file_path // .input.notebook_path // "")]
|
|
109
|
+
| map(select(. != "")) | unique | .[]
|
|
110
|
+
' 2>/dev/null)"
|
|
111
|
+
for path in $edited; do
|
|
112
|
+
for rule in $(skill_for edit "$path" '' 2>/dev/null); do
|
|
113
|
+
case "|$need_re|" in
|
|
114
|
+
*"|$rule|"*) ;;
|
|
115
|
+
*) need_re="${need_re}${need_re:+|}${rule}" ;;
|
|
116
|
+
esac
|
|
117
|
+
done
|
|
118
|
+
done
|
|
119
|
+
fi
|
|
120
|
+
|
|
69
121
|
# Ход — это всё, что записано после последнего настоящего ввода владельца. Ответ инструмента
|
|
70
122
|
# приходит той же ролью `user`, поэтому строки с `tool_result` вводом не считаются: иначе ходом
|
|
71
123
|
# оказался бы кусок после последнего вызова инструмента, и чтение правил в его начале потерялось
|
|
@@ -75,7 +127,7 @@ read_re="$(printf '%s' "$laws_dir|$rules_dir|$specs_dir" | sed 's/^|*//; s/|*$//
|
|
|
75
127
|
# На событии вызова инструмента вопрос уже известен — он и есть вызов; судится только то,
|
|
76
128
|
# читались ли за этот ход правила. На завершении хода вопрос ищется в тексте реплик: меню к
|
|
77
129
|
# этому моменту уже отбито раньше.
|
|
78
|
-
verdict="$(tail -n 400 "$transcript" 2>/dev/null | jq -s -r --arg re "$read_re" --arg tool "$tool" '
|
|
130
|
+
verdict="$(tail -n 400 "$transcript" 2>/dev/null | jq -s -r --arg re "$read_re" --arg tool "$tool" --arg need "$need_re" --arg rules "$rules_dir" '
|
|
79
131
|
def is_input:
|
|
80
132
|
.type == "user"
|
|
81
133
|
and (((.message.content // []) | if type == "array"
|
|
@@ -86,11 +138,23 @@ verdict="$(tail -n 400 "$transcript" 2>/dev/null | jq -s -r --arg re "$read_re"
|
|
|
86
138
|
| (if $i == null then . else .[$i + 1:] end) as $turn
|
|
87
139
|
| [$turn[] | select(.type == "assistant") | (.message.content // [])[] | select(.type == "text") | .text] as $texts
|
|
88
140
|
| [$turn[] | select(.type == "assistant") | (.message.content // [])[] | select(.type == "tool_use")] as $uses
|
|
89
|
-
| ($
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
141
|
+
| (if $need == "" then
|
|
142
|
+
$uses | map(
|
|
143
|
+
(.name == "Skill")
|
|
144
|
+
or ((.name // "") | test("^(Read|Grep|Glob)$")) and ((.input | tostring) | test($re))
|
|
145
|
+
or ((.name == "Bash") and ((.input.command // "") | test($re)))
|
|
146
|
+
) | any
|
|
147
|
+
else
|
|
148
|
+
# Область работы известна — засчитывается только правило этой области: прочитанный
|
|
149
|
+
# разбор чужого промаха на заданный вопрос не отвечает.
|
|
150
|
+
($need | split("|")) as $rules_needed
|
|
151
|
+
| $uses | map(
|
|
152
|
+
((.name == "Skill") and (((.input.skill // "") | tostring) as $s | $rules_needed | index($s) != null))
|
|
153
|
+
or (((.name // "") | test("^(Read|Grep|Glob|Bash)$"))
|
|
154
|
+
and ((.input | tostring) as $text
|
|
155
|
+
| $rules_needed | map(. as $rule | $text | test($rules + "/" + $rule + "(/|\\b)")) | any))
|
|
156
|
+
) | any
|
|
157
|
+
end) as $read
|
|
94
158
|
| (($texts | join("\n")) | test("\\?[[:space:]]*$"; "m")) as $asked_prose
|
|
95
159
|
| ($tool != "") as $asking_now
|
|
96
160
|
| if ($asked_prose or $asking_now) and ($read | not) then "ask" else "pass" end
|
|
@@ -106,7 +170,7 @@ fi
|
|
|
106
170
|
|
|
107
171
|
reason="$head Вопрос, ответ на который уже записан, владельцу не задаётся — правило ведения работы. Прогони поиск по словам темы и ответь по найденному; спрашивай только то, что документацией не покрыто:
|
|
108
172
|
|
|
109
|
-
grep -rn -i \"<слово темы>\" $laws_dir $rules_dir $specs_dir
|
|
173
|
+
grep -rn -i \"<слово темы>\" $laws_dir $rules_dir $specs_dir $plans_dir $archive_dir
|
|
110
174
|
|
|
111
175
|
Гард судит один ход: следующий заход не отбивается."
|
|
112
176
|
|
|
@@ -3,11 +3,15 @@
|
|
|
3
3
|
# Требует: hooks/profile-check.sh
|
|
4
4
|
# Линтер по следам правки. PostToolUse.
|
|
5
5
|
#
|
|
6
|
-
# Два входа. Правка файла — линтуется один изменённый файл.
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
#
|
|
10
|
-
#
|
|
6
|
+
# Два входа. Правка файла — линтуется один изменённый файл. Команда оболочки — линтуется то,
|
|
7
|
+
# что она записала: перенос меняет либу, а вместе с ней и границы, и импорт, законный на прежнем
|
|
8
|
+
# месте, на новом уже запрещён; запись перенаправлением, дозаписью или интерпретатором меняет
|
|
9
|
+
# сам файл, и линтера он прежде не получал вовсе. Ровно так запрещённый импорт уезжает в общую
|
|
10
|
+
# либу молча, а правленая проверка выходит из-под форматтера: правило сработало бы — но его
|
|
11
|
+
# никто не запустил.
|
|
12
|
+
#
|
|
13
|
+
# Что команда пишет и куда, знает профиль дерева, и знает он это одним способом для всех: тот
|
|
14
|
+
# же признак читает гейт правил.
|
|
11
15
|
#
|
|
12
16
|
# Полный набор гоняет гард на пуше, но это конец работы: к моменту, когда правило срабатывает,
|
|
13
17
|
# поверх нарушения лежит десяток правок, и разбор превращается в археологию. Здесь тот же
|
|
@@ -19,6 +23,8 @@
|
|
|
19
23
|
#
|
|
20
24
|
# Что здесь чем зовётся, знает профиль дерева:
|
|
21
25
|
# rt_lint_for — чем линтуется этот файл;
|
|
26
|
+
# rt_shell_writes — пишет ли эта команда;
|
|
27
|
+
# rt_shell_paths — какие пути она записала;
|
|
22
28
|
# rt_is_app_code — где лежит код, к которому линтеры вообще относятся;
|
|
23
29
|
# RT_LINT_SKIP_RE — что из него исключено;
|
|
24
30
|
# rt_push_checks — что гоняет гейт пуша. По нему же решается, догонит ли замечание позже:
|
|
@@ -46,14 +52,12 @@ case "$tool" in
|
|
|
46
52
|
*) exit 0 ;;
|
|
47
53
|
esac
|
|
48
54
|
|
|
49
|
-
# Отсев до всякой работы: хук висит на каждой команде оболочки, а
|
|
55
|
+
# Отсев до всякой работы: хук висит на каждой команде оболочки, а пишет из них меньшинство.
|
|
56
|
+
# Пустая команда отсеивается здесь же — признак записи у неё спрашивать не о чем.
|
|
50
57
|
command_text=""
|
|
51
58
|
if [ "$mode" = "move" ]; then
|
|
52
59
|
command_text="$(printf '%s' "$input" | jq -r '.tool_input.command // empty' 2>/dev/null)"
|
|
53
|
-
|
|
54
|
-
*"git mv "*) ;;
|
|
55
|
-
*) exit 0 ;;
|
|
56
|
-
esac
|
|
60
|
+
[ -z "$command_text" ] && exit 0
|
|
57
61
|
fi
|
|
58
62
|
|
|
59
63
|
workdir="$(rt_hook_cwd)"
|
|
@@ -75,6 +79,15 @@ done
|
|
|
75
79
|
command -v rt_needs >/dev/null 2>&1 || rt_needs() { command -v "$1" >/dev/null 2>&1; }
|
|
76
80
|
rt_needs rt_lint_for lint-after-edit || exit 0
|
|
77
81
|
|
|
82
|
+
# Признак записи спрашивается после профиля: до него функций ещё нет. Дерево, которое признака
|
|
83
|
+
# не объявило, остаётся при прежнем поведении — разбирается один перенос.
|
|
84
|
+
if [ "$mode" = "move" ] && rt_needs rt_shell_writes lint-after-edit; then
|
|
85
|
+
case "$command_text" in
|
|
86
|
+
*"git mv "*) ;;
|
|
87
|
+
*) rt_shell_writes "$command_text" || exit 0 ;;
|
|
88
|
+
esac
|
|
89
|
+
fi
|
|
90
|
+
|
|
78
91
|
# --- какие файлы проверяем -------------------------------------------------------------
|
|
79
92
|
|
|
80
93
|
# Пути назначения всех переносов в команде. Команда бывает составной, поэтому режется по
|
|
@@ -136,7 +149,17 @@ if [ "$mode" = "edit" ]; then
|
|
|
136
149
|
esac
|
|
137
150
|
candidates="$(expand "$path")"
|
|
138
151
|
else
|
|
139
|
-
|
|
152
|
+
# Перенос разворачивает каталог: он двигает целые слои. Запись — нет: пишут всегда в файл,
|
|
153
|
+
# а каталог в команде записи — это рабочий каталог, и развёрнутый он отдаёт линтеру половину
|
|
154
|
+
# дерева. Поэтому у записанных путей берутся только существующие файлы.
|
|
155
|
+
written=""
|
|
156
|
+
if rt_needs rt_shell_paths lint-after-edit; then
|
|
157
|
+
written="$(rt_shell_paths "$command_text" 2>/dev/null | while IFS= read -r path; do
|
|
158
|
+
[ -f "$path" ] && printf '%s\n' "$path"
|
|
159
|
+
done)"
|
|
160
|
+
fi
|
|
161
|
+
candidates="$( { collect_moved "$command_text" | while IFS= read -r moved; do expand "$moved"; done
|
|
162
|
+
printf '%s\n' "$written"; } | sort -u)"
|
|
140
163
|
fi
|
|
141
164
|
|
|
142
165
|
[ -z "$candidates" ] && exit 0
|
|
@@ -214,11 +237,16 @@ else
|
|
|
214
237
|
tail_line="Почини их сейчас: этот линтер в гейт пуша не входит, и отложенное замечание уедет в главную ветку молча."
|
|
215
238
|
fi
|
|
216
239
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
240
|
+
case "$mode:$command_text" in
|
|
241
|
+
# Перенос назван отдельно: замечание после него объясняется не правкой файла, а сменой его
|
|
242
|
+
# места, и без этой строки читатель ищет промах в тексте, которого никто не менял.
|
|
243
|
+
move:*"git mv "*)
|
|
244
|
+
head_line="ЛИНТЕР (${linters}) НАШЁЛ ЗАМЕЧАНИЯ ПОСЛЕ ПЕРЕНОСА. Перенос меняет либу, а вместе с ней границы: импорт, законный на прежнем месте, на новом может быть запрещён." ;;
|
|
245
|
+
move:*)
|
|
246
|
+
head_line="ЛИНТЕР (${linters}) НАШЁЛ ЗАМЕЧАНИЯ ПОСЛЕ ЗАПИСИ КОМАНДОЙ:" ;;
|
|
247
|
+
*)
|
|
248
|
+
head_line="ЛИНТЕР (${linters}) НАШЁЛ ЗАМЕЧАНИЯ:" ;;
|
|
249
|
+
esac
|
|
222
250
|
|
|
223
251
|
ctx="${head_line}
|
|
224
252
|
${report}
|
|
@@ -38,16 +38,24 @@ transcript="$(printf '%s' "$input" | jq -r '.transcript_path // empty' 2>/dev/nu
|
|
|
38
38
|
[ -f "$transcript" ] || exit 0
|
|
39
39
|
|
|
40
40
|
# Каталог предложений у дерева свой. Заданный пустым — отказ дерева от требования: дереву, не
|
|
41
|
-
# берущему переносимый слой правил, гард не навязывается.
|
|
41
|
+
# берущему переносимый слой правил, гард не навязывается. Каталога нет на диске — то же самое:
|
|
42
|
+
# дерево механизмом не пользуется, и стража ему не навязывают. Судить по одной переменной значило
|
|
43
|
+
# бы требовать отправки там, где отправлять нечем и некуда.
|
|
44
|
+
root="${CLAUDE_PROJECT_DIR:-.}"
|
|
42
45
|
proposals_dir="${RT_PROPOSALS_DIR-.claude/rt-kit/proposals}"
|
|
43
46
|
[ -z "$proposals_dir" ] && exit 0
|
|
47
|
+
[ -d "$root/$proposals_dir" ] || exit 0
|
|
44
48
|
|
|
45
49
|
# Просьба владельца. Набор открыт и пополняется правкой: полнота его — открытый вопрос
|
|
46
50
|
# договорённости, а не обещание.
|
|
47
|
-
|
|
51
|
+
#
|
|
52
|
+
# Латиница читается наравне: заход, который владелец ведёт по-английски, отличается от русского
|
|
53
|
+
# словами, а требование в нём то же. Своей ветки ей не заводится — обе пары стоят в одном
|
|
54
|
+
# образце, иначе одну из них правили бы, забывая про вторую.
|
|
55
|
+
asked_re='(завед|напиш|отправ|пошл|зашл|отошл|выгруз|send|file|open|submit|raise|report)[а-яёa-z]*[^.!?]{0,40}(пропозал|предложени|proposal)|(пропозал|предложени|proposal)[а-яёa-z]*[^.!?]{0,40}(завед|напиш|отправ|пошл|зашл|отошл|выгруз|send|file|open|submit|raise|report)'
|
|
48
56
|
|
|
49
57
|
# Сосед, без которого слово «предложение» не считается просьбой о слое правил.
|
|
50
|
-
context_re='пропозал|слою правил|слоя правил|слой правил|пакет|agent-kit
|
|
58
|
+
context_re='пропозал|слою правил|слоя правил|слой правил|пакет|agent-kit|наверх|proposal|rule layer|upstream|package'
|
|
51
59
|
|
|
52
60
|
# Ход — это всё, что записано после последнего настоящего ввода владельца. Ответ инструмента
|
|
53
61
|
# приходит той же ролью, поэтому строки с `tool_result` вводом не считаются.
|
|
@@ -86,7 +94,6 @@ verdict="$(tail -n 400 "$transcript" 2>/dev/null | jq -s -r --arg asked "$asked_
|
|
|
86
94
|
# зависимостью, зовёт бинарь из зависимостей; дерево, где пакет живёт исходниками, бинаря не
|
|
87
95
|
# имеет вовсе — там зовут собранный bin. Названная наугад команда стоит исполнителю хода: отказ
|
|
88
96
|
# читается как указание, и вызов `npx agent-kit` отвечает в таком дереве отказом установки.
|
|
89
|
-
root="${CLAUDE_PROJECT_DIR:-.}"
|
|
90
97
|
if [ -x "$root/node_modules/.bin/agent-kit" ]; then
|
|
91
98
|
propose_cmd="npx agent-kit propose"
|
|
92
99
|
else
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
# rt-hook: PreToolUse Edit|Write|MultiEdit|Bash|mcp__webstorm__create_new_file|mcp__webstorm__execute_terminal_command|mcp__webstorm__execute_tool
|
|
3
|
-
# Требует: hooks/profile-check.sh, hooks/deny-tail.sh
|
|
3
|
+
# Требует: hooks/profile-check.sh, hooks/deny-tail.sh, hooks/write-targets.sh
|
|
4
4
|
# Гард места правки: слой правил чинится там, где сломано, а не там, где виден.
|
|
5
5
|
#
|
|
6
6
|
# Слой правил правит тот же исполнитель, которым слой правил управляет, и разницы между
|
|
@@ -53,18 +53,13 @@ deny() {
|
|
|
53
53
|
exit 0
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
-
# Цели
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
s/(^|.*[[:space:]])sed[[:space:]]+-i[[:space:]]+([^[:space:]]+[[:space:]]+)*([^[:space:]|&;]+)$/\3/p
|
|
64
|
-
s/(^|.*[[:space:]])(cp|mv|install)[[:space:]]+([^[:space:]]+[[:space:]]+)+([^[:space:]|&;]+).*/\4/p
|
|
65
|
-
' \
|
|
66
|
-
| sort -u
|
|
67
|
-
}
|
|
56
|
+
# Цели записи разбирает общий помощник: тот же признак нужен гарду экзамена, и разойдясь, две
|
|
57
|
+
# копии пропустили бы разные формы записи. Файла нет — остаётся молчаливое умолчание, чтобы гард
|
|
58
|
+
# не сломался на неполной раскладке.
|
|
59
|
+
# shellcheck disable=SC1090
|
|
60
|
+
[ -f "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/write-targets.sh" ] \
|
|
61
|
+
&& . "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/write-targets.sh" 2>/dev/null
|
|
62
|
+
command -v rt_write_targets >/dev/null 2>&1 || rt_write_targets() { cat >/dev/null; }
|
|
68
63
|
|
|
69
64
|
tool="$(rt_hook_tool)"
|
|
70
65
|
candidates=""
|
|
@@ -22,6 +22,13 @@
|
|
|
22
22
|
# как два разных требования.
|
|
23
23
|
. "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/utf8.sh" 2>/dev/null || true
|
|
24
24
|
|
|
25
|
+
# Собранное дерево кодом не бывает: путь к артефакту приходит из команды, которая его запускает.
|
|
26
|
+
# Карта гейта такой путь уже пропускает, и слои обязаны молчать вместе с ней — иначе выход из
|
|
27
|
+
# карты снимает одно требование и оставляет полтора десятка других.
|
|
28
|
+
case "$target" in
|
|
29
|
+
*/node_modules/* | */dist/* | */build/* | */.nx/* | */coverage/*) return 0 ;;
|
|
30
|
+
esac
|
|
31
|
+
|
|
25
32
|
rt_layer_add() {
|
|
26
33
|
case " $req " in
|
|
27
34
|
*" $1 "*) return 0 ;;
|
|
@@ -151,6 +151,20 @@ for name in $req; do
|
|
|
151
151
|
break
|
|
152
152
|
done
|
|
153
153
|
[ -z "$want" ] && exit 0
|
|
154
|
+
|
|
155
|
+
# Что потребуется дальше по этой же команде. Требуется по-прежнему одно правило за раз, но
|
|
156
|
+
# длину пути заход видит с первого отказа: на сквозной правке отказы идут по одному на вызов и
|
|
157
|
+
# читаются как разные требования — тринадцать подряд за одну задачу, и каждый стоил хода.
|
|
158
|
+
ahead=""
|
|
159
|
+
for name in $req; do
|
|
160
|
+
[ "$name" = "$want" ] && continue
|
|
161
|
+
[ -f "$root/$rules_dir/${name}/SKILL.md" ] || continue
|
|
162
|
+
if [ -f "$loaded" ] && grep -qE "^([^:]*:)?$(printf '%s' "$name" | sed 's/[][\.*^$/]/\\&/g')$" "$loaded" 2>/dev/null; then
|
|
163
|
+
continue
|
|
164
|
+
fi
|
|
165
|
+
ahead="${ahead}${ahead:+, }${name}"
|
|
166
|
+
done
|
|
167
|
+
|
|
154
168
|
req="$want"
|
|
155
169
|
|
|
156
170
|
# Отбитие — наблюдение: правило, которое приходится требовать чаще прочего, и род правки, на
|
|
@@ -197,6 +211,21 @@ ${article}
|
|
|
197
211
|
fi
|
|
198
212
|
fi
|
|
199
213
|
|
|
214
|
+
# Что потребуется дальше по этой же команде. Строка стоит после всех веток текста: их три, и
|
|
215
|
+
# каждая переписывает причину целиком.
|
|
216
|
+
[ -n "$ahead" ] && reason="${reason} Дальше по этой команде потребуются: ${ahead}."
|
|
217
|
+
|
|
218
|
+
# Спутник зовётся отдельным предложением и ко всем видам отказа разом. Прежде он был назван
|
|
219
|
+
# только запасным ходом — «если инструмент такого имени не знает», — и читатель, у которого имя
|
|
220
|
+
# известно, до него не доходил вовсе. Цена этого: правило называет способ, а спутник рядом
|
|
221
|
+
# объявляет его здесь невозможным; работа, сделанная способом из правила, кончилась результатом,
|
|
222
|
+
# которого для её заказчика не существовало, а разделы спутника за тот заход не открывались ни
|
|
223
|
+
# разу.
|
|
224
|
+
companion="$rules_dir/${req}/implementation.md"
|
|
225
|
+
[ -f "$root/$companion" ] && reason="${reason}
|
|
226
|
+
|
|
227
|
+
Спутник правила — ${companion} — читается вместе с ним: правило говорит, что должно быть верно, а спутник — чем это верно здесь и что здесь названо невозможным. Инструментом он не грузится, его читают файлом."
|
|
228
|
+
|
|
200
229
|
# Общий хвост отказа: два законных хода и законная форма обхода, если она у отказа есть.
|
|
201
230
|
# Файл может быть не разложен — тогда хвоста нет, а причина отказа остаётся прежней.
|
|
202
231
|
# shellcheck disable=SC1090
|
|
@@ -52,12 +52,44 @@ emit() {
|
|
|
52
52
|
# Ветка под задачу без папки — работа идёт мимо. Сессию не рвём: SessionStart, отбивающий
|
|
53
53
|
# запуск, оставляет владельца без агента вовсе, а правку кода поймает `task-flow-guard`.
|
|
54
54
|
if [ ! -d "$DIR" ]; then
|
|
55
|
+
# Папки нет по двум разным причинам, и говорить о них надо разное. Первая — работа шла мимо
|
|
56
|
+
# правила. Вторая — папку разобрала сама ветка последним коммитом перед заявкой: это законный
|
|
57
|
+
# исход, и указание собрать её заново уводит заход с хвоста работы обратно в её начало.
|
|
58
|
+
#
|
|
59
|
+
# Различает их история ветки: снос папки её же коммитом после общего предка с главной. Ход
|
|
60
|
+
# работы к этой минуте уехал вместе с папкой, и состояние держат заявка и передача захода.
|
|
61
|
+
main_branch="${RT_MAIN_BRANCH:-main}"
|
|
62
|
+
base="$(git merge-base "origin/${main_branch}" HEAD 2>/dev/null || git merge-base "$main_branch" HEAD 2>/dev/null)"
|
|
63
|
+
dropped=''
|
|
64
|
+
[ -n "$base" ] && dropped="$(git log "${base}..HEAD" --diff-filter=D --name-only --pretty=format: -- "$DIR" 2>/dev/null | head -1)"
|
|
65
|
+
|
|
66
|
+
if [ -n "$dropped" ]; then
|
|
67
|
+
{
|
|
68
|
+
printf 'РАБОТА ЗАКРЫВАЕТСЯ — папка задачи разобрана этой веткой.\n\n'
|
|
69
|
+
printf 'Ход работы удалён вместе с папкой: состояние держат заявка и передача захода.\n'
|
|
70
|
+
printf 'Папка заново не собирается. Правка кода после разбора требует восстановить её\n'
|
|
71
|
+
printf 'на время правки и повторить разбор тем же коммитом. Порядок — скил `task-flow`,\n'
|
|
72
|
+
printf 'паттерны `task-flow-close` и `task-flow-archive`.\n'
|
|
73
|
+
} | emit
|
|
74
|
+
exit 0
|
|
75
|
+
fi
|
|
76
|
+
|
|
55
77
|
if rt_needs rt_task_branch_ok task-context-load && rt_task_branch_ok "$branch"; then
|
|
56
78
|
{
|
|
57
79
|
printf 'РАБОТА БЕЗ ПАПКИ ЗАДАЧИ.\n\n'
|
|
58
80
|
printf 'Ветка `%s` названа задачей, а `%s/` нет: ход работы записывать некуда,\n' "$branch" "$DIR"
|
|
59
81
|
printf 'и следующий заход начнёт с расспросов владельца.\n\n'
|
|
60
82
|
printf 'Собрать с образца:\n\n cp -r %s/_template %s\n\n' "$TASKS_DIR" "$DIR"
|
|
83
|
+
# Папка бывает названа не именем ветки: этапы одной большой задачи идут отдельными
|
|
84
|
+
# ветками при одной общей папке. Названная поимённо папка — единственное, по чему
|
|
85
|
+
# заход её найдёт; иначе он читает отказ как «записей нет» и отвечает владельцу из
|
|
86
|
+
# кода, минуя всё, что в этих записях решено.
|
|
87
|
+
others="$(find "$TASKS_DIR" -mindepth 1 -maxdepth 1 -type d ! -name '_template' 2>/dev/null | sort)"
|
|
88
|
+
if [ -n "$others" ]; then
|
|
89
|
+
printf 'В каталоге задач при этом лежит:\n\n%s\n\n' "$others"
|
|
90
|
+
printf 'Этапы одной задачи идут отдельными ветками при общей папке — прежде чем\n'
|
|
91
|
+
printf 'считать, что записей нет, смотрят в названные.\n\n'
|
|
92
|
+
fi
|
|
61
93
|
# О соседнем ресурсе — условно и по имени: пакет не знает, разложен ли он здесь,
|
|
62
94
|
# а сказанное безусловно приходит в контекст каждой сессии и врёт про дерево тем
|
|
63
95
|
# увереннее, что печатает это сам инструмент.
|