@rt-tools/agent-kit 0.20.0 → 0.22.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/check-reuse.mjs +11 -1
- package/assets/checks/check-specs.mjs +8 -23
- package/assets/checks/signals/core.json +12 -2
- package/assets/checks/signals.mjs +17 -0
- package/assets/checks/spec-proposed.mjs +122 -0
- package/assets/hooks/browser-guard-device-id.sh +3 -0
- package/assets/hooks/browser-guard-no-asking.sh +3 -0
- package/assets/hooks/browser-guard-no-listing.sh +3 -0
- package/assets/hooks/browser-guard-no-other-drivers.sh +3 -0
- package/assets/hooks/browser-guard-require-select.sh +3 -0
- package/assets/hooks/claim-guard.sh +4 -0
- package/assets/hooks/conscience-guard.sh +4 -0
- package/assets/hooks/deny-tail.sh +12 -0
- package/assets/hooks/dev-server-guard.sh +3 -0
- package/assets/hooks/docs-guard.sh +4 -11
- package/assets/hooks/exam-guard.sh +3 -0
- package/assets/hooks/git-guard-delivery.sh +4 -4
- package/assets/hooks/git-guard-main.sh +4 -9
- package/assets/hooks/git-guard-push-tests.sh +3 -0
- package/assets/hooks/grill-gate.sh +5 -0
- package/assets/hooks/guard-note.sh +45 -0
- package/assets/hooks/handoff-entry-guard.sh +3 -0
- package/assets/hooks/handoff-write.sh +58 -7
- package/assets/hooks/hook-input.sh +1 -0
- package/assets/hooks/override-write-guard.sh +4 -4
- package/assets/hooks/postmortem-guard.sh +4 -0
- package/assets/hooks/proposal-guard.sh +11 -0
- package/assets/hooks/prose-style-guard.sh +3 -0
- package/assets/hooks/qa-dataid-guard.sh +3 -0
- package/assets/hooks/rerun-guard.sh +3 -0
- package/assets/hooks/reuse-first-guard.sh +3 -0
- package/assets/hooks/rule-article.sh +1 -1
- package/assets/hooks/rule-source-guard.sh +4 -4
- package/assets/hooks/sql-guard.sh +3 -0
- package/assets/hooks/task-context-load.sh +1 -1
- package/assets/hooks/task-flow-draft-guard.sh +3 -0
- package/assets/hooks/task-flow-guard.sh +3 -0
- package/assets/hooks/turn-entry-load.sh +27 -2
- package/assets/hooks/turn-exit-guard.sh +4 -0
- package/assets/hooks/waiting-turn-guard.sh +32 -10
- package/assets/hooks/window-fill-guard.sh +3 -0
- package/assets/hooks/work-start-guard.sh +5 -0
- package/assets/hooks/write-targets.sh +59 -10
- package/assets/laws/delivery.md +5 -0
- package/assets/laws/frontend-application.md +10 -0
- package/assets/laws/work-conduct.md +8 -0
- package/assets/patterns/browser-verification-stand.md +24 -9
- package/assets/patterns/git-workflow-pr.github.md +29 -22
- package/assets/patterns/spec-driven-domain.md +5 -0
- package/assets/patterns/spec-driven-rule.md +41 -1
- package/assets/patterns/task-flow-archive.md +12 -0
- package/assets/patterns/task-flow-handoff.md +14 -1
- package/assets/patterns/task-flow-resume.md +11 -0
- package/assets/patterns/task-flow-start.md +40 -40
- package/assets/patterns/testing-e2e.md +15 -0
- package/assets/patterns/testing-unit.md +13 -0
- package/assets/pitfalls/git-workflow.github.md +13 -0
- package/assets/pitfalls/spec-driven.md +20 -0
- package/assets/pitfalls/task-flow.md +78 -48
- package/assets/rules/git-workflow.azure.md +5 -0
- package/assets/rules/git-workflow.github.md +26 -23
- package/assets/rules/git-workflow.gitlab.md +5 -0
- package/assets/rules/spec-driven.md +23 -24
- package/assets/rules/turn-conduct.md +13 -15
- package/assets/rules/turn-entry.md +6 -4
- package/assets/rules/typescript-conventions.md +7 -0
- package/assets/skills/agent-kit-extend.md +11 -0
- package/assets/skills/agent-kit.md +12 -2
- package/lib/commands.d.ts.map +1 -1
- package/lib/commands.js +36 -1
- package/lib/commands.js.map +1 -1
- package/lib/observations.d.ts +13 -1
- package/lib/observations.d.ts.map +1 -1
- package/lib/observations.js +7 -1
- package/lib/observations.js.map +1 -1
- package/package.json +1 -1
- package/rt-tools-agent-kit-0.22.0.tgz +0 -0
- package/rt-tools-agent-kit-0.20.0.tgz +0 -0
|
@@ -12,6 +12,16 @@
|
|
|
12
12
|
# дерево — ветку, незакоммиченное и коммиты сверх главной. Ничего от себя он не добавляет:
|
|
13
13
|
# передача пересказывает записанное, а не заменяет его.
|
|
14
14
|
#
|
|
15
|
+
# Кладётся она разделом в ход работы задачи — туда же, где лежит состояние. Свой файл вне дерева
|
|
16
|
+
# передачу теряет при переходе на другую машину: каталог закрыт от истории, и работу, оборванную
|
|
17
|
+
# заполнением окна, оттуда не подхватить — состояние есть в ходе работы, а особенности захода,
|
|
18
|
+
# следующий шаг и то, чего делать не надо, остаются на прежней машине. Второй записи об одном и
|
|
19
|
+
# том же при этом не заводится: раздел живёт в том же файле, что и «Где стоим», и уезжает в
|
|
20
|
+
# ветку тем же коммитом. Коммитит его исполнитель — хук не знает, что ещё лежит в индексе.
|
|
21
|
+
#
|
|
22
|
+
# Файл вне дерева остаётся запасным путём: у работы без папки задачи — на отсоединённой голове,
|
|
23
|
+
# на ветке без заведённой задачи — разделу лечь некуда, а молчание здесь стоит захода целиком.
|
|
24
|
+
#
|
|
15
25
|
# Написанное руками не затирается молча: файл один, и последняя запись побеждает. Исполнитель,
|
|
16
26
|
# закрывающий заход по правилу, пишет поверх — его передача полнее, потому что он знает то,
|
|
17
27
|
# чего на диске нет.
|
|
@@ -93,8 +103,15 @@ ahead="$(git log --oneline origin/main..HEAD 2>/dev/null | head -20)"
|
|
|
93
103
|
|
|
94
104
|
target="$root/$handoff_dir/$handoff_name.md"
|
|
95
105
|
|
|
106
|
+
# Заголовок раздела в ходе работы: по нему прежняя передача и находится.
|
|
107
|
+
section='## Передача захода'
|
|
108
|
+
|
|
109
|
+
# Собирается передача во временный файл: и раздел, и запасной путь пишут одно и то же, и второй
|
|
110
|
+
# сборки для второго места не заводится.
|
|
111
|
+
scratch="$(mktemp 2>/dev/null)" || exit 0
|
|
112
|
+
|
|
96
113
|
{
|
|
97
|
-
printf '
|
|
114
|
+
printf 'Собрана хуком перед сжатием контекста (%s).\n\n' "$trigger"
|
|
98
115
|
printf '**Рабочее дерево:** %s\n' "$root"
|
|
99
116
|
if [ -n "$branch" ]; then
|
|
100
117
|
printf '**Ветка:** %s\n\n' "$branch"
|
|
@@ -103,22 +120,56 @@ target="$root/$handoff_dir/$handoff_name.md"
|
|
|
103
120
|
fi
|
|
104
121
|
|
|
105
122
|
if [ -n "$state" ]; then
|
|
106
|
-
printf '
|
|
123
|
+
printf '### Где стоим на минуту сжатия\n\n'
|
|
107
124
|
printf -- '- **Состояние:** %s\n' "$state"
|
|
108
125
|
[ -n "$stage" ] && printf -- '- **Этап:** %s\n' "$stage"
|
|
109
126
|
[ -n "$next_step" ] && printf -- '- **Следующий шаг:** %s\n' "$next_step"
|
|
110
127
|
[ -n "$pull" ] && printf -- '- **PR:** %s\n' "$pull"
|
|
111
128
|
printf '\nХод работы целиком — `%s/%s/progress.md`; замысел рядом с ним.\n\n' "$tasks_dir" "$branch"
|
|
112
129
|
elif [ -z "$branch" ]; then
|
|
113
|
-
printf '
|
|
130
|
+
printf '### Где стоим на минуту сжатия\n\nИмени у головы нет, папки задачи при ней тоже: состояние работы взять неоткуда. Читающая сторона ищет передачу по имени ветки, а не найдя его — по последней записи каталога.\n\n'
|
|
114
131
|
else
|
|
115
|
-
printf '
|
|
132
|
+
printf '### Где стоим на минуту сжатия\n\nПапки задачи у этой ветки нет: состояние работы взять неоткуда.\n\n'
|
|
116
133
|
fi
|
|
117
134
|
|
|
118
|
-
printf '
|
|
119
|
-
printf '
|
|
135
|
+
printf '### Незакоммиченное\n\n```\n%s\n```\n\n' "$uncommitted"
|
|
136
|
+
printf '### Коммиты сверх главной\n\n```\n%s\n```\n\n' "$ahead"
|
|
120
137
|
printf 'Написана хуком перед сжатием контекста. Всё, что здесь стоит, проверяется деревом:\n'
|
|
121
138
|
printf 'передача пересказывает записанное и описывает минуту, когда её собрали.\n'
|
|
122
|
-
} > "$
|
|
139
|
+
} > "$scratch" 2>/dev/null
|
|
140
|
+
|
|
141
|
+
[ -s "$scratch" ] || exit 0
|
|
142
|
+
|
|
143
|
+
# Есть ход работы — передача ложится его разделом, и прежний такой раздел заменяется целиком:
|
|
144
|
+
# дописанный вторым, он оставил бы в одном файле две правды об одной работе.
|
|
145
|
+
if [ -n "$progress" ] && [ -f "$progress" ]; then
|
|
146
|
+
kept="$scratch.kept"
|
|
147
|
+
# Сравнение идёт байтами. Под локалью с национальными настройками `awk` этой системы считает
|
|
148
|
+
# разные кириллические строки равными — «## Где стоим» совпадало с «## Передача захода», — и
|
|
149
|
+
# выемка прежнего раздела сносила состояние работы вместе с ним. Виден промах только там, где
|
|
150
|
+
# локаль объявлена: у исполнителя с `C.UTF-8` набор зелёный, на прогоне конвейера — красный.
|
|
151
|
+
LC_ALL=C awk -v mark="$section" '
|
|
152
|
+
$0 == mark { skip = 1; next }
|
|
153
|
+
skip && /^## / && $0 != mark { skip = 0 }
|
|
154
|
+
!skip { print }
|
|
155
|
+
' "$progress" > "$kept" 2>/dev/null || { rm -f "$scratch" "$kept"; exit 0; }
|
|
156
|
+
|
|
157
|
+
{
|
|
158
|
+
# Хвостовые пустые строки прежнего текста снимаются: раздел приписывается к нему через
|
|
159
|
+
# одну пустую строку, а не через сколько их там осталось.
|
|
160
|
+
awk 'BEGIN { blanks = 0 }
|
|
161
|
+
{ if ($0 ~ /^[[:space:]]*$/) { blanks++; next }
|
|
162
|
+
while (blanks > 0) { print ""; blanks-- }
|
|
163
|
+
print }' "$kept"
|
|
164
|
+
printf '\n%s\n\n' "$section"
|
|
165
|
+
cat "$scratch"
|
|
166
|
+
} > "$progress.rt-handoff" 2>/dev/null \
|
|
167
|
+
&& mv "$progress.rt-handoff" "$progress" 2>/dev/null
|
|
168
|
+
|
|
169
|
+
rm -f "$scratch" "$kept"
|
|
170
|
+
exit 0
|
|
171
|
+
fi
|
|
172
|
+
|
|
173
|
+
mv "$scratch" "$target" 2>/dev/null || rm -f "$scratch"
|
|
123
174
|
|
|
124
175
|
exit 0
|
|
@@ -95,6 +95,7 @@ rt_turn_has_text() {
|
|
|
95
95
|
got="$(tail -n 400 "$transcript" 2>/dev/null | jq -s -r '
|
|
96
96
|
def is_input:
|
|
97
97
|
.type == "user"
|
|
98
|
+
and ((.isCompactSummary // false) | not)
|
|
98
99
|
and (((.message.content // []) | if type == "array"
|
|
99
100
|
then ([.[] | select(.type == "tool_result")] | length)
|
|
100
101
|
else 0 end) == 0);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
# rt-hook: PreToolUse Write|Bash|mcp__webstorm__create_new_file|mcp__webstorm__execute_terminal_command|mcp__webstorm__execute_tool
|
|
3
|
-
# Требует: hooks/deny-tail.sh
|
|
3
|
+
# Требует: hooks/deny-tail.sh, hooks/guard-note.sh
|
|
4
4
|
# Гард затирания надстройки: запись поверх — не то же самое, что правка.
|
|
5
5
|
#
|
|
6
6
|
# Надстройка дерева сливается с ресурсом пакета по разделу «## »: раздел, который дерево
|
|
@@ -20,6 +20,9 @@
|
|
|
20
20
|
# ОТКАЗ В ПОЛЬЗУ РАБОТЫ: нет `jq`, битый ввод, чужой инструмент, файла нет, файл пуст —
|
|
21
21
|
# правка РАЗРЕШАЕТСЯ. Сломанный гард не имеет права заклинить работу.
|
|
22
22
|
|
|
23
|
+
# Своё имя в наблюдениях: отбой пишет общий хвост отказа, а не сам гард.
|
|
24
|
+
RT_GUARD_NAME=override-write-guard
|
|
25
|
+
|
|
23
26
|
. "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/utf8.sh" 2>/dev/null || true
|
|
24
27
|
. "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/hook-input.sh" 2>/dev/null || true
|
|
25
28
|
|
|
@@ -35,9 +38,6 @@ rt_hooks_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
|
35
38
|
command -v rt_deny_tail >/dev/null 2>&1 || rt_deny_tail() { printf ''; }
|
|
36
39
|
|
|
37
40
|
deny() {
|
|
38
|
-
# shellcheck disable=SC1090
|
|
39
|
-
[ -f "$rt_hooks_dir/observe.sh" ] && . "$rt_hooks_dir/observe.sh" 2>/dev/null
|
|
40
|
-
command -v rt_note >/dev/null 2>&1 && rt_note guard-deny res=override-write-guard
|
|
41
41
|
|
|
42
42
|
reason="$1 $(rt_deny_tail "$2")"
|
|
43
43
|
jq -n --arg r "$reason" '{hookSpecificOutput:{hookEventName:"PreToolUse",permissionDecision:"deny",permissionDecisionReason:$r}}' 2>/dev/null \
|
|
@@ -21,6 +21,9 @@
|
|
|
21
21
|
# ОТКАЗ В ПОЛЬЗУ РАБОТЫ: при любой ошибке, отсутствии записи хода и повторном заходе ход
|
|
22
22
|
# РАЗРЕШАЕТСЯ (exit 0). Сломанный гард не имеет права заклинить разговор.
|
|
23
23
|
|
|
24
|
+
# Своё имя в наблюдениях: отбой пишет общий хвост отказа, а не сам гард.
|
|
25
|
+
RT_GUARD_NAME=postmortem-guard
|
|
26
|
+
|
|
24
27
|
. "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/utf8.sh" 2>/dev/null || true
|
|
25
28
|
. "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/hook-input.sh" 2>/dev/null || true
|
|
26
29
|
|
|
@@ -66,6 +69,7 @@ admitted_re='был неправ|был не прав|ошибс|моя ошиб
|
|
|
66
69
|
verdict="$(tail -n 400 "$transcript" 2>/dev/null | jq -s -r --arg re "$admitted_re" '
|
|
67
70
|
def is_input:
|
|
68
71
|
.type == "user"
|
|
72
|
+
and ((.isCompactSummary // false) | not)
|
|
69
73
|
and (((.message.content // []) | if type == "array"
|
|
70
74
|
then ([.[] | select(.type == "tool_result")] | length)
|
|
71
75
|
else 0 end) == 0);
|
|
@@ -20,6 +20,9 @@
|
|
|
20
20
|
# ОТКАЗ В ПОЛЬЗУ РАБОТЫ: при любой ошибке, нехватке `jq`, отсутствии записи хода и повторном
|
|
21
21
|
# заходе ход РАЗРЕШАЕТСЯ (exit 0). Сломанный гард не имеет права заклинить разговор.
|
|
22
22
|
|
|
23
|
+
# Своё имя в наблюдениях: отбой пишет общий хвост отказа, а не сам гард.
|
|
24
|
+
RT_GUARD_NAME=proposal-guard
|
|
25
|
+
|
|
23
26
|
. "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/utf8.sh" 2>/dev/null || true
|
|
24
27
|
. "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/hook-input.sh" 2>/dev/null || true
|
|
25
28
|
|
|
@@ -60,10 +63,18 @@ context_re='пропозал|слою правил|слоя правил|сло
|
|
|
60
63
|
# Ход — это всё, что записано после последнего настоящего ввода владельца. Ответ инструмента
|
|
61
64
|
# приходит той же ролью, поэтому строки с `tool_result` вводом не считаются.
|
|
62
65
|
#
|
|
66
|
+
# Сводка сжатия — тоже не ввод, хотя приходит ролью владельца и ответом инструмента не является.
|
|
67
|
+
# Она пересказывает ходы, которые уже кончились, и просьба, исполненная вчера, читается в ней как
|
|
68
|
+
# сказанная сейчас: ход, где о предложениях не было сказано ни слова, отбивался на пересказе
|
|
69
|
+
# чужой просьбы — каталог предложений при этом был пуст, и отправлять было нечего вовсе. По
|
|
70
|
+
# словам сводка от речи владельца неотличима и богаче её, потому что пересказывает всю сессию
|
|
71
|
+
# разом; узнаётся она признаком записи, а не текстом.
|
|
72
|
+
#
|
|
63
73
|
# Хвост в 400 строк: запись хода растёт всю сессию, а судится только последний ход.
|
|
64
74
|
verdict="$(tail -n 400 "$transcript" 2>/dev/null | jq -s -r --arg asked "$asked_re" --arg ctx "$context_re" '
|
|
65
75
|
def is_input:
|
|
66
76
|
.type == "user"
|
|
77
|
+
and ((.isCompactSummary // false) | not)
|
|
67
78
|
and (((.message.content // []) | if type == "array"
|
|
68
79
|
then ([.[] | select(.type == "tool_result")] | length)
|
|
69
80
|
else 0 end) == 0);
|
|
@@ -12,6 +12,9 @@
|
|
|
12
12
|
#
|
|
13
13
|
# FAIL-OPEN: нет узла, нет проверки, чужой инструмент, не `.md` → пропуск.
|
|
14
14
|
|
|
15
|
+
# Своё имя в наблюдениях: отбой пишет общий хвост отказа, а не сам гард.
|
|
16
|
+
RT_GUARD_NAME=prose-style-guard
|
|
17
|
+
|
|
15
18
|
. "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/utf8.sh" 2>/dev/null || true
|
|
16
19
|
. "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/hook-input.sh" 2>/dev/null || true
|
|
17
20
|
|
|
@@ -25,6 +25,9 @@
|
|
|
25
25
|
#
|
|
26
26
|
# ОТКАЗ В ПОЛЬЗУ РАБОТЫ: нет разборщика, битый ввод, чужой инструмент — пропуск.
|
|
27
27
|
|
|
28
|
+
# Своё имя в наблюдениях: отбой пишет общий хвост отказа, а не сам гард.
|
|
29
|
+
RT_GUARD_NAME=qa-dataid-guard
|
|
30
|
+
|
|
28
31
|
. "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/utf8.sh" 2>/dev/null || true
|
|
29
32
|
. "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/hook-input.sh" 2>/dev/null || true
|
|
30
33
|
|
|
@@ -20,6 +20,9 @@
|
|
|
20
20
|
# похож на перезапуск — пропуск. Гард без номера судить не берётся: перезапуск последнего
|
|
21
21
|
# упавшего прогона зовут и без него, а угадывать, о каком задании речь, значит отбивать наугад.
|
|
22
22
|
|
|
23
|
+
# Своё имя в наблюдениях: отбой пишет общий хвост отказа, а не сам гард.
|
|
24
|
+
RT_GUARD_NAME=rerun-guard
|
|
25
|
+
|
|
23
26
|
. "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/utf8.sh" 2>/dev/null || true
|
|
24
27
|
. "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/hook-input.sh" 2>/dev/null || true
|
|
25
28
|
|
|
@@ -32,6 +32,9 @@
|
|
|
32
32
|
#
|
|
33
33
|
# ОТКАЗ В ПОЛЬЗУ РАБОТЫ: нет разборщика, битый ввод, чужой инструмент — пропуск.
|
|
34
34
|
|
|
35
|
+
# Своё имя в наблюдениях: отбой пишет общий хвост отказа, а не сам гард.
|
|
36
|
+
RT_GUARD_NAME=reuse-first-guard
|
|
37
|
+
|
|
35
38
|
. "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/utf8.sh" 2>/dev/null || true
|
|
36
39
|
. "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/hook-input.sh" 2>/dev/null || true
|
|
37
40
|
|
|
@@ -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, hooks/write-targets.sh
|
|
3
|
+
# Требует: hooks/profile-check.sh, hooks/deny-tail.sh, hooks/write-targets.sh, hooks/guard-note.sh
|
|
4
4
|
# Гард места правки: слой правил чинится там, где сломано, а не там, где виден.
|
|
5
5
|
#
|
|
6
6
|
# Слой правил правит тот же исполнитель, которым слой правил управляет, и разницы между
|
|
@@ -20,6 +20,9 @@
|
|
|
20
20
|
# ОТКАЗ В ПОЛЬЗУ РАБОТЫ: нет `jq`, битый ввод, чужой инструмент, файла нет, шапки в нём нет,
|
|
21
21
|
# не git-репозиторий — правка РАЗРЕШАЕТСЯ. Сломанный гард не имеет права заклинить работу.
|
|
22
22
|
|
|
23
|
+
# Своё имя в наблюдениях: отбой пишет общий хвост отказа, а не сам гард.
|
|
24
|
+
RT_GUARD_NAME=rule-source-guard
|
|
25
|
+
|
|
23
26
|
. "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/utf8.sh" 2>/dev/null || true
|
|
24
27
|
. "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/hook-input.sh" 2>/dev/null || true
|
|
25
28
|
|
|
@@ -43,9 +46,6 @@ command -v rt_needs >/dev/null 2>&1 || rt_needs() { command -v "$1" >/dev/null 2
|
|
|
43
46
|
command -v rt_deny_tail >/dev/null 2>&1 || rt_deny_tail() { printf ''; }
|
|
44
47
|
|
|
45
48
|
deny() {
|
|
46
|
-
# shellcheck disable=SC1090
|
|
47
|
-
[ -f "$rt_hooks_dir/observe.sh" ] && . "$rt_hooks_dir/observe.sh" 2>/dev/null
|
|
48
|
-
command -v rt_note >/dev/null 2>&1 && rt_note guard-deny res=rule-source-guard
|
|
49
49
|
|
|
50
50
|
reason="$1 $(rt_deny_tail "$2")"
|
|
51
51
|
jq -n --arg r "$reason" '{hookSpecificOutput:{hookEventName:"PreToolUse",permissionDecision:"deny",permissionDecisionReason:$r}}' 2>/dev/null \
|
|
@@ -40,6 +40,9 @@
|
|
|
40
40
|
# пропускаются целиком — иначе слова «drop» и «update» в описании коммита читались бы как
|
|
41
41
|
# запрос. Доставки запроса внутри такой команды не бывает, так что цена послабления нулевая.
|
|
42
42
|
|
|
43
|
+
# Своё имя в наблюдениях: отбой пишет общий хвост отказа, а не сам гард.
|
|
44
|
+
RT_GUARD_NAME=sql-guard
|
|
45
|
+
|
|
43
46
|
. "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/utf8.sh" 2>/dev/null || true
|
|
44
47
|
. "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/hook-input.sh" 2>/dev/null || true
|
|
45
48
|
|
|
@@ -139,7 +139,7 @@ done
|
|
|
139
139
|
cat "$PROGRESS"
|
|
140
140
|
else
|
|
141
141
|
# Раздел «Где стоим» перезаписывается каждым заходом и переживает любой объём.
|
|
142
|
-
awk '/^## Где стоим/{f=1} f&&/^## /&&!/^## Где стоим/{exit} f' "$PROGRESS"
|
|
142
|
+
LC_ALL=C awk '/^## Где стоим/{f=1} f&&/^## /&&!/^## Где стоим/{exit} f' "$PROGRESS"
|
|
143
143
|
printf '\n<обрезано по объёму. Последние записи:>\n\n'
|
|
144
144
|
tail -40 "$PROGRESS"
|
|
145
145
|
printf '\n<читается целиком: %s>\n' "$PROGRESS"
|
|
@@ -23,6 +23,9 @@
|
|
|
23
23
|
# FAIL-OPEN: нет jq, не git-репозиторий, битый ввод, чужой инструмент → пропуск. Сломанный
|
|
24
24
|
# гард не должен мешать работать.
|
|
25
25
|
|
|
26
|
+
# Своё имя в наблюдениях: отбой пишет общий хвост отказа, а не сам гард.
|
|
27
|
+
RT_GUARD_NAME=task-flow-draft-guard
|
|
28
|
+
|
|
26
29
|
rt_hooks_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
27
30
|
|
|
28
31
|
# Разбор вызова — общий у обоих гардов хода работы. Файл может быть не разложен: тогда судить
|
|
@@ -22,6 +22,9 @@
|
|
|
22
22
|
# FAIL-OPEN: нет jq, не git-репозиторий, битый ввод, чужой инструмент → пропуск. Сломанный
|
|
23
23
|
# гард не должен мешать работать.
|
|
24
24
|
|
|
25
|
+
# Своё имя в наблюдениях: отбой пишет общий хвост отказа, а не сам гард.
|
|
26
|
+
RT_GUARD_NAME=task-flow-guard
|
|
27
|
+
|
|
25
28
|
rt_hooks_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
26
29
|
|
|
27
30
|
# Разбор вызова — общий у обоих гардов хода работы. Файл может быть не разложен: тогда судить
|
|
@@ -39,11 +39,36 @@ map="$rt_hooks_dir/../rt-kit/defaults/turn-map.md"
|
|
|
39
39
|
[ -f "$map" ] || map="$ROOT/.claude/rt-kit/defaults/turn-map.md"
|
|
40
40
|
|
|
41
41
|
# Передача берётся по имени текущей ветки. Чужая, поданная как своя, описывает работу, которой
|
|
42
|
-
# в этом дереве нет, — поэтому
|
|
42
|
+
# в этом дереве нет, — поэтому и раздел, и файл ищутся по ветке, а не выбираются из каталога.
|
|
43
|
+
#
|
|
44
|
+
# Мест два, и порядок между ними один. Передача ветки с папкой задачи лежит разделом её хода
|
|
45
|
+
# работы: он коммитится и едет в ветку, поэтому переживает переход на другую машину. Файл вне
|
|
46
|
+
# дерева — запасной путь: у работы без папки задачи разделу лечь некуда. Найдя раздел, файл не
|
|
47
|
+
# читают: это одна и та же передача, и вторая копия старше первой.
|
|
48
|
+
tasks_dir="${RT_TASKS_DIR:-docs/tasks}"
|
|
49
|
+
progress=''
|
|
50
|
+
[ -n "$branch" ] && progress="$ROOT/$tasks_dir/$branch/progress.md"
|
|
51
|
+
|
|
52
|
+
section=''
|
|
53
|
+
if [ -n "$progress" ] && [ -r "$progress" ]; then
|
|
54
|
+
# Сравнение байтами: под локалью с национальными настройками `awk` этой системы считает
|
|
55
|
+
# разные кириллические заголовки равными, и вход читал бы разделом передачи первый попавшийся.
|
|
56
|
+
section="$(LC_ALL=C awk '
|
|
57
|
+
$0 == "## Передача захода" { skip = 1; next }
|
|
58
|
+
skip && /^## / { skip = 0 }
|
|
59
|
+
skip { print }
|
|
60
|
+
' "$progress" 2>/dev/null)"
|
|
61
|
+
fi
|
|
62
|
+
|
|
43
63
|
handoff=''
|
|
44
64
|
[ -n "$branch" ] && handoff="$ROOT/$handoff_dir/$branch.md"
|
|
45
65
|
|
|
46
|
-
if [ -n "$
|
|
66
|
+
if [ -n "$section" ]; then
|
|
67
|
+
printf 'ПЕРЕДАЧА ПРОШЛОГО ЗАХОДА — раздел «Передача захода» в `%s/%s/progress.md`, ниже целиком.\n\n' "$tasks_dir" "$branch"
|
|
68
|
+
printf 'Она написана прошлым заходом и описывает минуту, когда её собрали. Всё, что в ней\n'
|
|
69
|
+
printf 'стоит о дереве, проверяется деревом: команда этого хода главнее записанной вчера.\n\n'
|
|
70
|
+
printf '%s\n\n' "$section"
|
|
71
|
+
elif [ -n "$handoff" ] && [ -r "$handoff" ]; then
|
|
47
72
|
printf 'ПЕРЕДАЧА ПРОШЛОГО ЗАХОДА — `%s/%s.md`, ниже целиком.\n\n' "$handoff_dir" "$branch"
|
|
48
73
|
printf 'Она написана прошлым заходом и описывает минуту, когда её собрали. Всё, что в ней\n'
|
|
49
74
|
printf 'стоит о дереве, проверяется деревом: команда этого хода главнее записанной вчера.\n\n'
|
|
@@ -34,6 +34,9 @@
|
|
|
34
34
|
# или строки состояния ход РАЗРЕШАЕТСЯ (exit 0). Сломанный страж не имеет права заклинить
|
|
35
35
|
# разговор.
|
|
36
36
|
|
|
37
|
+
# Своё имя в наблюдениях: отбой пишет общий хвост отказа, а не сам гард.
|
|
38
|
+
RT_GUARD_NAME=turn-exit-guard
|
|
39
|
+
|
|
37
40
|
. "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/utf8.sh" 2>/dev/null || true
|
|
38
41
|
. "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/hook-input.sh" 2>/dev/null || true
|
|
39
42
|
|
|
@@ -194,6 +197,7 @@ started_re='task:new|task:move|board\.mjs[[:space:]]+move|git[[:space:]]+checkou
|
|
|
194
197
|
verdict="$(tail -n 400 "$transcript" 2>/dev/null | jq -s -r --arg work "$work_re" --arg read "$read_re" --arg part "$part_re" --arg wait "$wait_re" --arg handover "$handover_re" --arg started "$started_re" '
|
|
195
198
|
def is_input:
|
|
196
199
|
.type == "user"
|
|
200
|
+
and ((.isCompactSummary // false) | not)
|
|
197
201
|
and (((.message.content // []) | if type == "array"
|
|
198
202
|
then ([.[] | select(.type == "tool_result")] | length)
|
|
199
203
|
else 0 end) == 0);
|
|
@@ -19,9 +19,10 @@
|
|
|
19
19
|
# Спросить хостинг было бы точнее, но сетевой вызов на завершении хода падает вместе со связью и
|
|
20
20
|
# отбивал бы работу вместо промаха.
|
|
21
21
|
#
|
|
22
|
-
# Что считается действием: заведение задачи, заведение ветки, перевод
|
|
23
|
-
# заведение папки задачи.
|
|
24
|
-
#
|
|
22
|
+
# Что считается действием: заведение задачи, заведение ветки, перевод задачи в колонку работы,
|
|
23
|
+
# заведение папки задачи. Перевод в колонку разбора и снятие папки сюда не входят — это шаги
|
|
24
|
+
# закрытия прежней работы, а не начало следующей. Набор открыт и пополняется правкой — полнота
|
|
25
|
+
# его открытый вопрос, а не обещание.
|
|
25
26
|
#
|
|
26
27
|
# Чего гард не судит. Ход, в котором о чужом шаге не сказано ничего, — здесь он молчит: пустой
|
|
27
28
|
# ход неотличим от хода, которому нечего было делать. Это известная его граница. Зелёный прогон
|
|
@@ -30,6 +31,9 @@
|
|
|
30
31
|
# ОТКАЗ В ПОЛЬЗУ РАБОТЫ: при любой ошибке, нехватке `jq`, отсутствии записи хода и повторном
|
|
31
32
|
# заходе ход РАЗРЕШАЕТСЯ (exit 0). Сломанный гард не имеет права заклинить разговор.
|
|
32
33
|
|
|
34
|
+
# Своё имя в наблюдениях: отбой пишет общий хвост отказа, а не сам гард.
|
|
35
|
+
RT_GUARD_NAME=waiting-turn-guard
|
|
36
|
+
|
|
33
37
|
. "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/utf8.sh" 2>/dev/null || true
|
|
34
38
|
. "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/hook-input.sh" 2>/dev/null || true
|
|
35
39
|
|
|
@@ -58,10 +62,23 @@ opened_re='gh[^|;&]*pr[[:space:]]+create|api[^|;&]*-X[[:space:]]+POST[^|;&]*/pul
|
|
|
58
62
|
read_re='run[[:space:]]+(list|view|watch)|pipelines[[:space:]]+runs'
|
|
59
63
|
red_re='completed[[:space:]]+failure|"conclusion"[[:space:]]*:[[:space:]]*"failure"|conclusion:[[:space:]]*failure|completed with .failure.|run[[:space:]]+failed'
|
|
60
64
|
|
|
61
|
-
# Первое действие по следующей задаче.
|
|
62
|
-
#
|
|
63
|
-
#
|
|
64
|
-
|
|
65
|
+
# Первое действие по следующей задаче. Перечисляется то, чем взятие БЫВАЕТ, а не то, что
|
|
66
|
+
# действием считается: список шагов закрытия работы открыт и растёт, а список действий по
|
|
67
|
+
# следующей задаче закрыт — у перечня «что бывает» дыры закрывающего шага нет.
|
|
68
|
+
#
|
|
69
|
+
# Перевода закрываемой задачи в колонку разбора и снятия её папки здесь нет намеренно: оба —
|
|
70
|
+
# обязательные шаги закрытия работы, и оба стоят в том же ходе, которым открыт PR. Пока признак
|
|
71
|
+
# перечислял их наравне со взятием, он совпадал всегда, и гард отпускал ход, ради которого
|
|
72
|
+
# заведён, — работа так вставала трижды за один заход.
|
|
73
|
+
#
|
|
74
|
+
# Правка кода в ветке того же PR действием по следующей задаче не считается — она чинит
|
|
75
|
+
# прежнюю, а не двигает работу дальше.
|
|
76
|
+
taken_re='task:new|checkout[[:space:]]+-b|task:move[^|;&]*in-progress|(cp|mkdir)[^|;&]*/tasks/'
|
|
77
|
+
|
|
78
|
+
# Заведение папки задачи идёт и не оболочкой: инструмент правки файла пишет её первым файлом, и
|
|
79
|
+
# командной строки за этим нет вовсе. Поэтому рядом с командами судится путь правки — у него
|
|
80
|
+
# свой образец, потому что имени команды в нём нет.
|
|
81
|
+
taken_path_re='/tasks/'
|
|
65
82
|
|
|
66
83
|
# Чем ход показывает, что отданную работу он довёл до конца, а не бросил черновиком. Снятие
|
|
67
84
|
# черновика — очевидный случай; чтение прогона — тот, где снимать ещё нечего, но исполнитель
|
|
@@ -81,10 +98,11 @@ vow_re='дальше беру|дальше возьму|дальше иду|сл
|
|
|
81
98
|
#
|
|
82
99
|
# Хвост в 400 строк: запись хода растёт всю сессию, а судится только последний ход.
|
|
83
100
|
verdict="$(tail -n 400 "$transcript" 2>/dev/null | jq -s -r \
|
|
84
|
-
--arg opened "$opened_re" --arg
|
|
85
|
-
--arg ready "$ready_re" --arg vow "$vow_re" '
|
|
101
|
+
--arg opened "$opened_re" --arg taken "$taken_re" --arg taken_path "$taken_path_re" \
|
|
102
|
+
--arg read "$read_re" --arg red "$red_re" --arg ready "$ready_re" --arg vow "$vow_re" '
|
|
86
103
|
def is_input:
|
|
87
104
|
.type == "user"
|
|
105
|
+
and ((.isCompactSummary // false) | not)
|
|
88
106
|
and (((.message.content // []) | if type == "array"
|
|
89
107
|
then ([.[] | select(.type == "tool_result")] | length)
|
|
90
108
|
else 0 end) == 0);
|
|
@@ -93,6 +111,10 @@ verdict="$(tail -n 400 "$transcript" 2>/dev/null | jq -s -r \
|
|
|
93
111
|
| (if $i == null then [] else .[$i:] end) as $turn
|
|
94
112
|
| [$turn[] | select(.type == "assistant") | (.message.content // [])[] | select(.type == "tool_use")] as $uses
|
|
95
113
|
| ($uses | map((.input.command // "")) | join("\n")) as $ran
|
|
114
|
+
# Путь правки — второй способ завести папку задачи. Судятся только инструменты письма: у
|
|
115
|
+
# чтения путь тот же, а работой оно не является.
|
|
116
|
+
| ($uses | map(select(.name == "Write" or .name == "Edit" or .name == "MultiEdit")
|
|
117
|
+
| (.input.file_path // "")) | join("\n")) as $wrote
|
|
96
118
|
# Ответ инструмента берётся текстом, а не в записи JSON: у той таб и кавычка уезжают
|
|
97
119
|
# экранированными, и образец по ним не попадает вовсе. Приходит он и строкой, и списком
|
|
98
120
|
# блоков — обе формы сводятся к одному тексту.
|
|
@@ -103,7 +125,7 @@ verdict="$(tail -n 400 "$transcript" 2>/dev/null | jq -s -r \
|
|
|
103
125
|
else tostring end] | join("\n")) as $out
|
|
104
126
|
| ($ran | test($opened; "i")) as $opened_pr
|
|
105
127
|
| (($ran | test($read; "i")) and ($out | test($red; "i"))) as $red_run
|
|
106
|
-
| ($ran | test($
|
|
128
|
+
| (($ran | test($taken; "i")) or ($wrote | test($taken_path; "i"))) as $went_on
|
|
107
129
|
| ($ran | test($ready; "i")) as $checked
|
|
108
130
|
| ([$turn[] | select(.type == "assistant") | (.message.content // [])[]
|
|
109
131
|
| select(.type == "text") | .text] | join("\n")) as $said
|
|
@@ -29,6 +29,9 @@
|
|
|
29
29
|
# ОТКАЗ В ПОЛЬЗУ РАБОТЫ: нет размера окна, нет записи захода, нет разборщика, битый разбор —
|
|
30
30
|
# работа РАЗРЕШАЕТСЯ (exit 0). Сломанный гард не имеет права заклинить работу.
|
|
31
31
|
|
|
32
|
+
# Своё имя в наблюдениях: отбой пишет общий хвост отказа, а не сам гард.
|
|
33
|
+
RT_GUARD_NAME=window-fill-guard
|
|
34
|
+
|
|
32
35
|
. "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/utf8.sh" 2>/dev/null || true
|
|
33
36
|
. "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/hook-input.sh" 2>/dev/null || true
|
|
34
37
|
|
|
@@ -28,6 +28,9 @@
|
|
|
28
28
|
# ОТКАЗ В ПОЛЬЗУ РАБОТЫ: нет `jq`, нет записи хода, нет признака дерева, повторный заход, любая
|
|
29
29
|
# своя ошибка — ход РАЗРЕШАЕТСЯ (exit 0). Сломанный гард не имеет права заклинить работу.
|
|
30
30
|
|
|
31
|
+
# Своё имя в наблюдениях: отбой пишет общий хвост отказа, а не сам гард.
|
|
32
|
+
RT_GUARD_NAME=work-start-guard
|
|
33
|
+
|
|
31
34
|
. "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/utf8.sh" 2>/dev/null || true
|
|
32
35
|
. "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/hook-input.sh" 2>/dev/null || true
|
|
33
36
|
|
|
@@ -66,6 +69,7 @@ rt_needs rt_is_app_code work-start-guard || exit 0
|
|
|
66
69
|
asked="$(tail -n 400 "$transcript" 2>/dev/null | jq -s -r '
|
|
67
70
|
def is_input:
|
|
68
71
|
.type == "user"
|
|
72
|
+
and ((.isCompactSummary // false) | not)
|
|
69
73
|
and (((.message.content // []) | if type == "array"
|
|
70
74
|
then ([.[] | select(.type == "tool_result")] | length)
|
|
71
75
|
else 0 end) == 0);
|
|
@@ -88,6 +92,7 @@ asked="$(tail -n 400 "$transcript" 2>/dev/null | jq -s -r '
|
|
|
88
92
|
edited="$(tail -n 400 "$transcript" 2>/dev/null | jq -s -r '
|
|
89
93
|
def is_input:
|
|
90
94
|
.type == "user"
|
|
95
|
+
and ((.isCompactSummary // false) | not)
|
|
91
96
|
and (((.message.content // []) | if type == "array"
|
|
92
97
|
then ([.[] | select(.type == "tool_result")] | length)
|
|
93
98
|
else 0 end) == 0);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
# Цели записи, названные командой оболочки прямо: перенаправление, `tee`, правка на месте,
|
|
3
|
-
# копирование
|
|
3
|
+
# копирование поверх, а у интерпретатора — пути из его тела. Печатает по одной в строке.
|
|
4
4
|
#
|
|
5
5
|
# Строки `# rt-hook:` здесь нет намеренно: это помощник, а не хук. Он ничего не решает сам и
|
|
6
6
|
# зовётся оттуда, где судят правку файла, — гардом места правки и гардом экзамена. Общий он
|
|
@@ -10,15 +10,64 @@
|
|
|
10
10
|
# Общий признак записи здесь не берётся намеренно — он широк, в нём и имя интерпретатора, и
|
|
11
11
|
# запуск разложенной проверки читался бы как правка её самой. Снятие копии сюда тоже не входит:
|
|
12
12
|
# снятый файл раскладка кладёт заново, и так чинят копию, которую переписал форматтер.
|
|
13
|
+
#
|
|
14
|
+
# Интерпретатор с кодом в доводе или в теле — особый случай, и без него у гардов была дыра
|
|
15
|
+
# ровно в ту сторону, которой правят чаще всего. Путь записи стоит там внутри кода — в вызове
|
|
16
|
+
# записи файла, — и ни одним из образцов выше не ловится: команда снаружи выглядит запуском
|
|
17
|
+
# интерпретатора, а не записью. Разложенную копию так правили молча, и ловила это уже раскладка
|
|
18
|
+
# — на сборке, когда правка написана целиком и переносить её приходится руками.
|
|
19
|
+
#
|
|
20
|
+
# Разбирать чужой язык нечем, и здесь не разбирают: из тела берётся всё путеподобное, а решает
|
|
21
|
+
# по нему зовущий — гард места правки отбивает лишь то, что несёт шапку раскладки. Цена названа
|
|
22
|
+
# прямо: чтение файла интерпретатором судится наравне с записью в него. Читают в оболочке
|
|
23
|
+
# другим — `cat`, `grep`, `sed -n`, — а интерпретатор, которому назвали путь разложенной копии,
|
|
24
|
+
# почти всегда её и правит.
|
|
13
25
|
|
|
14
26
|
rt_write_targets() {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
s
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
27
|
+
rt_wt_text="$(cat)"
|
|
28
|
+
|
|
29
|
+
{
|
|
30
|
+
printf '%s' "$rt_wt_text" \
|
|
31
|
+
| tr "\"'\`" ' ' \
|
|
32
|
+
| sed -E 's/>>?/\n>/g' \
|
|
33
|
+
| sed -nE '
|
|
34
|
+
s/^>[[:space:]]*([^[:space:]|&;]+).*/\1/p
|
|
35
|
+
s/(^|.*[[:space:]])tee[[:space:]]+(-a[[:space:]]+)?([^[:space:]|&;]+).*/\3/p
|
|
36
|
+
s/(^|.*[[:space:]])sed[[:space:]]+-i[[:space:]]+([^[:space:]]+[[:space:]]+)*([^[:space:]|&;]+)$/\3/p
|
|
37
|
+
s/(^|.*[[:space:]])(cp|mv|install)[[:space:]]+([^[:space:]]+[[:space:]]+)+([^[:space:]|&;]+).*/\4/p
|
|
38
|
+
'
|
|
39
|
+
|
|
40
|
+
# Тело интерпретатора: код приходит доводом `-c`, доводом `-e` либо телом heredoc, и
|
|
41
|
+
# путь записи стоит внутри него. Берётся именно тело, а не вся команда: составная
|
|
42
|
+
# строка, в которой heredoc пишет один файл, а рядом запускается проверка, отдала бы
|
|
43
|
+
# путь этой проверки как цель записи — и запуск разложенной проверки снова читался бы
|
|
44
|
+
# правкой её самой.
|
|
45
|
+
printf '%s\n' "$rt_wt_text" | awk '
|
|
46
|
+
function emit(s, n, i, parts) {
|
|
47
|
+
n = split(s, parts, /[^A-Za-z0-9_.@\/-]+/)
|
|
48
|
+
for (i = 1; i <= n; i++) {
|
|
49
|
+
if (parts[i] ~ /\// && parts[i] ~ /\.[A-Za-z0-9]+$/) { print parts[i] }
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
function trim(s) { sub(/^[ \t]+/, "", s); sub(/[ \t]+$/, "", s); return s }
|
|
53
|
+
tag != "" {
|
|
54
|
+
if (trim($0) == tag) { tag = ""; next }
|
|
55
|
+
if (keep) { emit($0) }
|
|
56
|
+
next
|
|
57
|
+
}
|
|
58
|
+
{
|
|
59
|
+
# Код доводом: всё, что стоит за `-c` или `-e` у интерпретатора.
|
|
60
|
+
if ($0 ~ /(^|[|;&(]|[ \t])(python3?|node|ruby|perl|php|deno|bun)([ \t]|$)/ \
|
|
61
|
+
&& match($0, /[ \t]-[ce][ \t]/)) {
|
|
62
|
+
emit(substr($0, RSTART + RLENGTH))
|
|
63
|
+
}
|
|
64
|
+
# Тело heredoc: путь записи интерпретатора стоит именно там.
|
|
65
|
+
if (match($0, /<<-?[ \t]*[\047\"]?[A-Za-z_][A-Za-z0-9_]*/)) {
|
|
66
|
+
t = substr($0, RSTART, RLENGTH)
|
|
67
|
+
sub(/^<<-?[ \t]*[\047\"]?/, "", t)
|
|
68
|
+
tag = t
|
|
69
|
+
keep = ($0 ~ /(^|[|;&(]|[ \t])(python3?|node|ruby|perl|php|deno|bun)([ \t]|$)/)
|
|
70
|
+
}
|
|
71
|
+
}'
|
|
72
|
+
} | sort -u
|
|
24
73
|
}
|
package/assets/laws/delivery.md
CHANGED
|
@@ -63,6 +63,11 @@
|
|
|
63
63
|
ни умолчания: работа с очередью отказывает и говорит, где он задаётся. Пустой ключ хуже
|
|
64
64
|
отсутствующей проверки — от имени остаётся огрызок, которому ничто не отвечает, и правильно
|
|
65
65
|
названной не выглядит ни одна задача.
|
|
66
|
+
- **Заведённое в очереди работ видно той стороне, для которой заводилось.** Учётная запись, чьи
|
|
67
|
+
объекты хостинг показывает не всем, оставляет за собой работу, которой для остальных не
|
|
68
|
+
существует, — и снаружи это неотличимо от сделанного: задача заведена, PR открыт, а владелец
|
|
69
|
+
видит пустую очередь. Видимость проверяется чужими глазами — чтением очереди тем, кому она
|
|
70
|
+
адресована, — а не успешным ответом команды заведения.
|
|
66
71
|
- **У задачи есть исполнитель с момента её заведения.** Задача без исполнителя выглядит ничьей: по
|
|
67
72
|
очереди работ не видно, кто её взял, и заведённая по ходу правка теряется среди чужих.
|
|
68
73
|
- **Состояние задачи в очереди работ отвечает тому, что с ней происходит.** Взятая в работу видна
|