@rt-tools/agent-kit 0.4.0 → 0.5.1
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/README.md +5 -0
- package/assets/checks/board.github.mjs +45 -2
- package/assets/checks/check-board.github.mjs +7 -14
- package/assets/checks/check-doc-paths.mjs +200 -30
- package/assets/checks/check-specs.mjs +131 -17
- package/assets/checks/rt-kit-checks.config.mjs +12 -0
- package/assets/commands/next-session.md +122 -0
- package/assets/defaults/gate-map.sh +29 -3
- package/assets/defaults/project.sh +50 -0
- package/assets/docs/GLOSSARY.md +74 -0
- package/assets/hooks/git-guard-delivery.sh +87 -4
- package/assets/hooks/grill-gate.sh +96 -0
- package/assets/hooks/task-flow-guard.sh +14 -3
- package/assets/hooks/window-fill-guard.sh +150 -0
- package/assets/laws/code-structure.md +10 -0
- package/assets/laws/delivery.md +8 -1
- package/assets/laws/project-documentation.md +14 -0
- package/assets/laws/verifiability.md +13 -0
- package/assets/laws/work-conduct.md +19 -0
- package/assets/patterns/git-workflow-commit.github.md +4 -0
- package/assets/patterns/spec-driven-domain.md +35 -0
- package/assets/patterns/task-flow-close.md +72 -8
- package/assets/patterns/task-flow-handoff.md +115 -0
- package/assets/patterns/task-flow-resume.md +2 -2
- package/assets/patterns/task-flow-start.md +18 -2
- package/assets/rules/angular-patterns.md +4 -0
- package/assets/rules/browser-verification.md +4 -3
- package/assets/rules/doc-style.md +53 -1
- package/assets/rules/git-workflow.azure.md +24 -0
- package/assets/rules/git-workflow.github.md +23 -0
- package/assets/rules/git-workflow.gitlab.md +23 -0
- package/assets/rules/spec-driven.md +19 -1
- package/assets/rules/task-flow.md +88 -2
- package/assets/skills/agent-kit.md +4 -0
- package/assets/templates/rule.md +1 -1
- package/lib/commands.d.ts.map +1 -1
- package/lib/commands.js +2 -1
- package/lib/commands.js.map +1 -1
- package/lib/config.d.ts +3 -1
- package/lib/config.d.ts.map +1 -1
- package/lib/config.js +2 -0
- package/lib/config.js.map +1 -1
- package/lib/hooks-map.d.ts +20 -5
- package/lib/hooks-map.d.ts.map +1 -1
- package/lib/hooks-map.js +56 -15
- package/lib/hooks-map.js.map +1 -1
- package/lib/sync.d.ts.map +1 -1
- package/lib/sync.js +2 -5
- package/lib/sync.js.map +1 -1
- package/package.json +1 -1
- package/rt-tools-agent-kit-0.5.1.tgz +0 -0
- package/rt-tools-agent-kit-0.4.0.tgz +0 -0
|
@@ -79,6 +79,14 @@ title_re="${RT_TASK_TITLE_RE:-^\[[A-Za-z]+-[0-9]+\][[:space:]]+[^[:space:]]}"
|
|
|
79
79
|
task_new="${RT_TASK_NEW_CMD:-npm run task:new}"
|
|
80
80
|
board_check="${RT_BOARD_CHECK_CMD:-npm run check:board}"
|
|
81
81
|
task_bot="${RT_TASK_BOT:-}"
|
|
82
|
+
tasks_dir="${RT_TASKS_DIR:-}"
|
|
83
|
+
archive_dir="${RT_ARCHIVE_DIR:-}"
|
|
84
|
+
main_branch="${RT_MAIN_BRANCH:-main}"
|
|
85
|
+
|
|
86
|
+
# Обход требования: строка с причиной. Причина видна тому, кто вливает, поэтому обход законен.
|
|
87
|
+
# Без причины это просто молчаливый пропуск, поэтому она обязательна. Порог в три знака — тот
|
|
88
|
+
# же, что у гарда документа: если сделать по-разному, две формы одного обхода разойдутся.
|
|
89
|
+
folder_skip_re='Task-folder-skip:[[:space:]]*[^[:space:]"'"'"']{3,}'
|
|
82
90
|
|
|
83
91
|
deny() {
|
|
84
92
|
jq -n --arg r "$1" '{hookSpecificOutput:{hookEventName:"PreToolUse",permissionDecision:"deny",permissionDecisionReason:$r}}' 2>/dev/null \
|
|
@@ -86,6 +94,21 @@ deny() {
|
|
|
86
94
|
exit 0
|
|
87
95
|
}
|
|
88
96
|
|
|
97
|
+
# Подсказка вместо отказа: на открытии отчёта папка ещё нужна. Решения подсказка не несёт,
|
|
98
|
+
# команда идёт дальше своим ходом.
|
|
99
|
+
hint() {
|
|
100
|
+
jq -n --arg c "$1" '{hookSpecificOutput:{hookEventName:"PreToolUse",additionalContext:$c}}' 2>/dev/null
|
|
101
|
+
exit 0
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
# Есть ли папка задачи в ветке. Смотрим содержимое ветки, а не рабочее дерево: если папку
|
|
105
|
+
# удалили, но не закоммитили, проверка прошла бы, а папка всё равно уехала бы в main. Имя
|
|
106
|
+
# ветки подставляем целиком, вместе с косой: у ветки вида `chore/312-slug` папка лежит во
|
|
107
|
+
# вложенном каталоге.
|
|
108
|
+
folder_in_branch() {
|
|
109
|
+
git ls-tree -d --name-only HEAD -- "$1" 2>/dev/null | head -1
|
|
110
|
+
}
|
|
111
|
+
|
|
89
112
|
check_task() {
|
|
90
113
|
number="$1"
|
|
91
114
|
where="$2"
|
|
@@ -127,11 +150,62 @@ if [ -n "$branch_arg" ]; then
|
|
|
127
150
|
exit 0
|
|
128
151
|
fi
|
|
129
152
|
|
|
153
|
+
# --- слияние заявки ----------------------------------------------------------------------
|
|
154
|
+
#
|
|
155
|
+
# Папку задачи разбирают тем же PR, что и работу. После слияния этого уже никто не сделает:
|
|
156
|
+
# работа перешла к следующей задаче, а PR закрыт. Раньше слияния требовать нельзя — пока идёт
|
|
157
|
+
# ревью, plan.md нужен на диске, иначе гард хода работы не даст править код.
|
|
158
|
+
# Команду ищем от начала строки или после разделителя, а не где угодно в тексте. Иначе гард
|
|
159
|
+
# отбивает сообщение, где `gh pr merge` просто упомянут в кавычках, — так он и сработал на
|
|
160
|
+
# правке этого же текста. Полностью подстроку в кавычках так не отсечь, но случайное упоминание
|
|
161
|
+
# внутри слова или пути мимо уже не пройдёт.
|
|
162
|
+
if printf '%s' "$cmd" | grep -qE '(^|[;&|(]|&&|\|\|)[[:space:]]*(gh[[:space:]]+pr[[:space:]]+merge|glab[[:space:]]+mr[[:space:]]+merge|az[[:space:]]+repos[[:space:]]+pr[[:space:]]+update)([[:space:]]|$)'; then
|
|
163
|
+
[ -n "$tasks_dir" ] || exit 0 # ведения работы папкой в дереве нет
|
|
164
|
+
|
|
165
|
+
merge_branch="$(git branch --show-current 2>/dev/null)"
|
|
166
|
+
[ -z "$merge_branch" ] && exit 0
|
|
167
|
+
rt_task_branch_ok "$merge_branch" || exit 0 # за беззадачной веткой папки не стоит
|
|
168
|
+
|
|
169
|
+
folder="$tasks_dir/$merge_branch"
|
|
170
|
+
|
|
171
|
+
# Сначала ищем обход в самой команде — это работает и без сети. Если читать только
|
|
172
|
+
# тело PR, то без сети гард отбил бы слияние, причина которого в этом теле и написана.
|
|
173
|
+
printf '%s' "$cmd" | grep -qiE "$folder_skip_re" && exit 0
|
|
174
|
+
|
|
175
|
+
merge_number="$(printf '%s' "$cmd" | sed -nE 's/.*(pr|mr)[[:space:]]+(merge|update)[[:space:]]+([0-9]+).*/\3/p' | head -1)"
|
|
176
|
+
if [ -n "$merge_number" ] && command -v rt_report_body >/dev/null 2>&1; then
|
|
177
|
+
body="$(cd "$root" && rt_report_body "$merge_number" 2>/dev/null)"
|
|
178
|
+
[ -n "$body" ] && printf '%s' "$body" | grep -qiE "$folder_skip_re" && exit 0
|
|
179
|
+
fi
|
|
180
|
+
|
|
181
|
+
lying="$(folder_in_branch "$folder")"
|
|
182
|
+
[ -n "$lying" ] \
|
|
183
|
+
&& deny "BLOCKED: в ветке осталась папка задачи «${lying}» — она уедет в главную. Разобрать её потом будет некому: работа перейдёт к следующей задаче, а этот PR закроется. Перенеси в «${archive_dir:-архив}» то, что объясняет принятые решения, остальное удали и повтори. Если работа вливается частями, поставь в тело PR строку «Task-folder-skip: <причина>»."
|
|
184
|
+
|
|
185
|
+
# Запись в архиве спрашиваем только у ветки, которая папку удалила. Иначе проверка
|
|
186
|
+
# цеплялась бы к работе, у которой папки и не было. Без общего предка с главной веткой
|
|
187
|
+
# сравнивать не с чем — тогда молчим.
|
|
188
|
+
[ -n "$archive_dir" ] || exit 0
|
|
189
|
+
base="$(git merge-base "$main_branch" HEAD 2>/dev/null)"
|
|
190
|
+
[ -z "$base" ] && exit 0
|
|
191
|
+
|
|
192
|
+
had="$(git ls-tree -d --name-only "$base" -- "$folder" 2>/dev/null | head -1)"
|
|
193
|
+
[ -z "$had" ] && had="$(git log "$base..HEAD" --diff-filter=A --name-only --pretty=format: -- "$folder" 2>/dev/null | head -1)"
|
|
194
|
+
[ -z "$had" ] && exit 0
|
|
195
|
+
|
|
196
|
+
gained="$(git diff --name-only --diff-filter=A "$base" HEAD -- "$archive_dir" 2>/dev/null | head -1)"
|
|
197
|
+
[ -z "$gained" ] \
|
|
198
|
+
&& deny "BLOCKED: папку задачи удалили, но в «${archive_dir}» ветка ничего не добавила. Удалить проще, чем разобрать, — и вместе с папкой пропадает разбор просьбы, единственная запись слов владельца. Перенеси то, что объясняет принятые решения, одним файлом с понятным именем и повтори."
|
|
199
|
+
|
|
200
|
+
exit 0
|
|
201
|
+
fi
|
|
202
|
+
|
|
130
203
|
# --- открытие заявки на слияние ----------------------------------------------------------
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
204
|
+
# Команду ищем от начала строки или после разделителя — по той же причине, что и слияние:
|
|
205
|
+
# упоминание в кавычках командой не является.
|
|
206
|
+
printf '%s' "$cmd" \
|
|
207
|
+
| grep -qE '(^|[;&|(]|&&|\|\|)[[:space:]]*(gh[[:space:]]+pr[[:space:]]+create|glab[[:space:]]+mr[[:space:]]+create|az[[:space:]]+repos[[:space:]]+pr[[:space:]]+create)([[:space:]]|$)' \
|
|
208
|
+
|| exit 0
|
|
135
209
|
|
|
136
210
|
branch="$(git branch --show-current 2>/dev/null)"
|
|
137
211
|
[ -z "$branch" ] && exit 0 # открепившийся HEAD — не про этот случай
|
|
@@ -164,4 +238,13 @@ fi
|
|
|
164
238
|
|
|
165
239
|
check_task "$number" "заявка с ветки «${branch}»"
|
|
166
240
|
|
|
241
|
+
# Сейчас папка ещё нужна: правки по замечаниям ревью идут в эту же ветку, а без plan.md их не
|
|
242
|
+
# пропустит гард хода работы. Поэтому здесь только напоминание. Требование стоит на слиянии —
|
|
243
|
+
# там папка уже не нужна, а вред от неё как раз и наступает.
|
|
244
|
+
if [ -n "$tasks_dir" ]; then
|
|
245
|
+
lying="$(folder_in_branch "$tasks_dir/$branch")"
|
|
246
|
+
[ -n "$lying" ] \
|
|
247
|
+
&& hint "В ветке лежит папка задачи «${lying}». Разбери её до слияния, этим же PR: потом за неё уже никто не возьмётся. На слиянии это будет отказ, а не напоминание."
|
|
248
|
+
fi
|
|
249
|
+
|
|
167
250
|
exit 0
|
|
@@ -0,0 +1,96 @@
|
|
|
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
|
+
# РАЗРЕШАЕТСЯ (exit 0). Сломанный гард не имеет права заклинить разговор.
|
|
20
|
+
|
|
21
|
+
input="$(cat 2>/dev/null)"
|
|
22
|
+
[ -z "$input" ] && exit 0
|
|
23
|
+
|
|
24
|
+
command -v jq >/dev/null 2>&1 || exit 0
|
|
25
|
+
|
|
26
|
+
# Повторный заход по тому же ходу не судится: иначе ход не кончится никогда — гард сказал своё
|
|
27
|
+
# один раз и отпускает.
|
|
28
|
+
active="$(printf '%s' "$input" | jq -r '.stop_hook_active // false' 2>/dev/null)"
|
|
29
|
+
[ "$active" = "true" ] && exit 0
|
|
30
|
+
|
|
31
|
+
transcript="$(printf '%s' "$input" | jq -r '.transcript_path // empty' 2>/dev/null)"
|
|
32
|
+
[ -z "$transcript" ] && exit 0
|
|
33
|
+
[ -f "$transcript" ] || exit 0
|
|
34
|
+
|
|
35
|
+
# Профиль дерева: каталоги законов, правил и спеков у каждого свои, а знать их надо и для
|
|
36
|
+
# признака чтения, и для подсказки в отказе.
|
|
37
|
+
rt_hooks_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
38
|
+
for profile in "$rt_hooks_dir/../rt-kit/defaults/project.sh" "$rt_hooks_dir/../defaults/project.sh" "${CLAUDE_PROJECT_DIR:-.}/.claude/rt-kit/defaults/project.sh" "${CLAUDE_PROJECT_DIR:-.}/.claude/rt-kit/project.sh"; do
|
|
39
|
+
# shellcheck disable=SC1090
|
|
40
|
+
[ -f "$profile" ] && . "$profile" 2>/dev/null
|
|
41
|
+
done
|
|
42
|
+
|
|
43
|
+
# Подстановка без двоеточия намеренно: заданное пустым — это отказ дерева от требования, и
|
|
44
|
+
# подменять его умолчанием нельзя. Умолчание достаётся только тому, кто не задал переменной
|
|
45
|
+
# вовсе.
|
|
46
|
+
laws_dir="${RT_LAWS_DIR-docs/constitution}"
|
|
47
|
+
rules_dir="${RT_RULES_DIR-.claude/skills}"
|
|
48
|
+
specs_dir="${RT_SPECS_DIR-docs/specs}"
|
|
49
|
+
|
|
50
|
+
# Дерево, у которого нет ни законов, ни правил, требования не получает: читать нечего.
|
|
51
|
+
[ -z "$laws_dir" ] && [ -z "$rules_dir" ] && exit 0
|
|
52
|
+
|
|
53
|
+
# Образец, по которому вызов инструмента считается чтением правил. Каталоги идут в него как
|
|
54
|
+
# есть: точка в `.claude` совпадает с любым знаком и лишнего сюда не приводит.
|
|
55
|
+
read_re="$(printf '%s' "$laws_dir|$rules_dir|$specs_dir" | sed 's/^|*//; s/|*$//; s/||*/|/g')"
|
|
56
|
+
[ -z "$read_re" ] && exit 0
|
|
57
|
+
|
|
58
|
+
# Ход — это всё, что записано после последнего настоящего ввода владельца. Ответ инструмента
|
|
59
|
+
# приходит той же ролью `user`, поэтому строки с `tool_result` вводом не считаются: иначе ходом
|
|
60
|
+
# оказался бы кусок после последнего вызова инструмента, и чтение правил в его начале потерялось
|
|
61
|
+
# бы.
|
|
62
|
+
#
|
|
63
|
+
# Хвост в 400 строк: запись хода растёт всю сессию, а судится только последний ход.
|
|
64
|
+
verdict="$(tail -n 400 "$transcript" 2>/dev/null | jq -s -r --arg re "$read_re" '
|
|
65
|
+
def is_input:
|
|
66
|
+
.type == "user"
|
|
67
|
+
and (((.message.content // []) | if type == "array"
|
|
68
|
+
then ([.[] | select(.type == "tool_result")] | length)
|
|
69
|
+
else 0 end) == 0);
|
|
70
|
+
|
|
71
|
+
(map(is_input) | rindex(true)) as $i
|
|
72
|
+
| (if $i == null then . else .[$i + 1:] end) as $turn
|
|
73
|
+
| [$turn[] | select(.type == "assistant") | (.message.content // [])[] | select(.type == "text") | .text] as $texts
|
|
74
|
+
| [$turn[] | select(.type == "assistant") | (.message.content // [])[] | select(.type == "tool_use")] as $uses
|
|
75
|
+
| ($uses | map(
|
|
76
|
+
(.name == "Skill")
|
|
77
|
+
or ((.name // "") | test("^(Read|Grep|Glob)$")) and ((.input | tostring) | test($re))
|
|
78
|
+
or ((.name == "Bash") and ((.input.command // "") | test($re)))
|
|
79
|
+
) | any) as $read
|
|
80
|
+
| (($texts | join("\n")) | test("\\?[[:space:]]*$"; "m")) as $asked_prose
|
|
81
|
+
| ($uses | map(.name == "AskUserQuestion") | any) as $asked_menu
|
|
82
|
+
| if ($asked_prose or $asked_menu) and ($read | not) then "ask" else "pass" end
|
|
83
|
+
' 2>/dev/null)"
|
|
84
|
+
|
|
85
|
+
[ "$verdict" = "ask" ] || exit 0
|
|
86
|
+
|
|
87
|
+
reason="BLOCKED by grill-gate: в ответе есть вопрос владельцу, а законы и правила за этот ход не читались. Вопрос, ответ на который уже записан, владельцу не задаётся — правило ведения работы. Прогони поиск по словам темы и ответь по найденному; спрашивай только то, что документацией не покрыто:
|
|
88
|
+
|
|
89
|
+
grep -rn -i \"<слово темы>\" $laws_dir $rules_dir $specs_dir
|
|
90
|
+
|
|
91
|
+
Гард судит один ход: следующий заход не отбивается."
|
|
92
|
+
|
|
93
|
+
jq -n --arg r "$reason" '{decision:"block",reason:$r}' 2>/dev/null \
|
|
94
|
+
|| printf '{"decision":"block","reason":"grill-gate: прочитай законы и правила по теме, прежде чем спрашивать владельца."}\n'
|
|
95
|
+
|
|
96
|
+
exit 0
|
|
@@ -100,8 +100,19 @@ case "$draft" in
|
|
|
100
100
|
*) draft_path="$root/$draft" ;;
|
|
101
101
|
esac
|
|
102
102
|
|
|
103
|
-
if [
|
|
104
|
-
|
|
103
|
+
if [ -e "$draft_path" ]; then
|
|
104
|
+
exit 0
|
|
105
|
+
fi
|
|
106
|
+
|
|
107
|
+
# Договорённость, влитая в спек домена, с диска уходит — так и задумано: в главной ветке
|
|
108
|
+
# директории «предложено» быть не должно. Но замысел на неё ссылается до конца работы, и без
|
|
109
|
+
# этой развилки последний коммит отчёта запирал бы ветку: ни правки по замечаниям разбора, ни
|
|
110
|
+
# записи в журнал изменений после вливания уже не сделать.
|
|
111
|
+
#
|
|
112
|
+
# Влитое от незаведённого отличает история ветки: путь, которого в ней никогда не было,
|
|
113
|
+
# договорённостью не был. Спросить об этом нечем, кроме git, поэтому нет git — отказ остаётся.
|
|
114
|
+
if git -C "$root" log --oneline -1 -- "$draft" 2>/dev/null | grep -q .; then
|
|
115
|
+
exit 0
|
|
105
116
|
fi
|
|
106
117
|
|
|
107
|
-
|
|
118
|
+
deny "BLOCKED by task-flow: замысел называет договорённость '${draft}', а её на диске нет и в истории ветки не было. Заведи её с образца (docs/specs/_template) или поправь путь в '${tasks_dir}/${branch}/plan.md'. Правило — скил task-flow."
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# rt-hook: PostToolUse .*
|
|
3
|
+
# rt-hook: PreToolUse .*
|
|
4
|
+
# Заполнение окна: заход доводится до логической точки заранее, а не обрывается на середине.
|
|
5
|
+
#
|
|
6
|
+
# Зачем именно так. Место, где исполнитель помнит ход работы, ограничено, и заполнив его, он
|
|
7
|
+
# теряет не последнее действие, а всю картину разом. Изнутри захода этот предел не виден ничем:
|
|
8
|
+
# ни одна проверка дерева его не показывает, а сжатие контекста срабатывает, когда доводить
|
|
9
|
+
# работу до точки уже нечем.
|
|
10
|
+
#
|
|
11
|
+
# Гард стоит на двух событиях сразу — разводить его по двум файлам значило бы держать два
|
|
12
|
+
# разбора одной записи и два места, где правится один порог:
|
|
13
|
+
# PostToolUse — на первом пороге отдаёт напоминание: пора выбирать точку остановки;
|
|
14
|
+
# PreToolUse — на втором отбивает всё, кроме записи хода работы, передачи и команд поставки.
|
|
15
|
+
# Место между порогами и есть то, на что закрывается заход: дописать ход работы, написать
|
|
16
|
+
# передачу, закоммитить проверенное.
|
|
17
|
+
#
|
|
18
|
+
# Размер окна берётся из настройки дерева. Из записи захода он не выводится: модель записана
|
|
19
|
+
# там без пометки о расширенном окне, и заход на широкое окно от захода на узкое неотличим.
|
|
20
|
+
#
|
|
21
|
+
# ОТКАЗ В ПОЛЬЗУ РАБОТЫ: нет размера окна, нет записи захода, нет разборщика, битый разбор —
|
|
22
|
+
# работа РАЗРЕШАЕТСЯ (exit 0). Сломанный гард не имеет права заклинить работу.
|
|
23
|
+
|
|
24
|
+
input="$(cat 2>/dev/null)"
|
|
25
|
+
[ -z "$input" ] && exit 0
|
|
26
|
+
|
|
27
|
+
command -v jq >/dev/null 2>&1 || exit 0
|
|
28
|
+
|
|
29
|
+
# Профиль дерева: размер окна, пороги, каталоги задач и передачи. Дерево, не задавшее размера
|
|
30
|
+
# окна, стража не получает — считать долю не от чего.
|
|
31
|
+
rt_hooks_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
32
|
+
for profile in "$rt_hooks_dir/../rt-kit/defaults/project.sh" "$rt_hooks_dir/../defaults/project.sh" "${CLAUDE_PROJECT_DIR:-.}/.claude/rt-kit/defaults/project.sh" "${CLAUDE_PROJECT_DIR:-.}/.claude/rt-kit/project.sh"; do
|
|
33
|
+
# shellcheck disable=SC1090
|
|
34
|
+
[ -f "$profile" ] && . "$profile" 2>/dev/null
|
|
35
|
+
done
|
|
36
|
+
|
|
37
|
+
window="${RT_WINDOW_TOKENS:-}"
|
|
38
|
+
case "$window" in
|
|
39
|
+
'' | *[!0-9]*) exit 0 ;;
|
|
40
|
+
esac
|
|
41
|
+
[ "$window" -gt 0 ] 2>/dev/null || exit 0
|
|
42
|
+
|
|
43
|
+
warn_pct="${RT_WINDOW_WARN_PCT:-40}"
|
|
44
|
+
stop_pct="${RT_WINDOW_STOP_PCT:-50}"
|
|
45
|
+
tasks_dir="${RT_TASKS_DIR:-docs/tasks}"
|
|
46
|
+
handoff_dir="${RT_HANDOFF_DIR:-.claude/handoff}"
|
|
47
|
+
|
|
48
|
+
event="$(printf '%s' "$input" | jq -r '.hook_event_name // empty' 2>/dev/null)"
|
|
49
|
+
transcript="$(printf '%s' "$input" | jq -r '.transcript_path // empty' 2>/dev/null)"
|
|
50
|
+
[ -n "$transcript" ] || exit 0
|
|
51
|
+
[ -f "$transcript" ] || exit 0
|
|
52
|
+
|
|
53
|
+
# Заполнение — это последняя запись ответа с расходом: вход, разовая запись в кэш, прочитанное
|
|
54
|
+
# из кэша и вывод. Сумма по всем записям тут не годится вовсе — прочитанное из кэша повторяется
|
|
55
|
+
# в каждой из них, и сумма выходит в разы больше окна.
|
|
56
|
+
#
|
|
57
|
+
# Хвост в 200 строк: запись захода растёт весь заход, а нужна из неё одна последняя строка.
|
|
58
|
+
fill="$(tail -n 200 "$transcript" 2>/dev/null | jq -s -r '
|
|
59
|
+
[.[] | select(.type == "assistant") | .message.usage | select(. != null)]
|
|
60
|
+
| last
|
|
61
|
+
| if . == null then empty
|
|
62
|
+
else ((.input_tokens // 0) + (.cache_creation_input_tokens // 0)
|
|
63
|
+
+ (.cache_read_input_tokens // 0) + (.output_tokens // 0))
|
|
64
|
+
end
|
|
65
|
+
' 2>/dev/null)"
|
|
66
|
+
|
|
67
|
+
case "$fill" in
|
|
68
|
+
'' | *[!0-9]*) exit 0 ;;
|
|
69
|
+
esac
|
|
70
|
+
|
|
71
|
+
pct=$((fill * 100 / window))
|
|
72
|
+
fill_k=$((fill / 1000))
|
|
73
|
+
window_k=$((window / 1000))
|
|
74
|
+
|
|
75
|
+
# --- первый порог: напоминание, работа не отбивается -------------------------------------
|
|
76
|
+
|
|
77
|
+
if [ "$event" = "PostToolUse" ]; then
|
|
78
|
+
[ "$pct" -ge "$warn_pct" ] || exit 0
|
|
79
|
+
|
|
80
|
+
# Напоминание повторяется не на каждом вызове, а на каждой следующей ступени в пять
|
|
81
|
+
# процентов: иначе оно занимает то самое место, которое бережёт.
|
|
82
|
+
step=$(((pct / 5) * 5))
|
|
83
|
+
session="$(printf '%s' "$input" | jq -r '.session_id // "unknown"' 2>/dev/null)"
|
|
84
|
+
mark_dir="${TMPDIR:-/tmp}/claude-window-fill"
|
|
85
|
+
mark="$mark_dir/$session.step"
|
|
86
|
+
mkdir -p "$mark_dir" 2>/dev/null
|
|
87
|
+
last="$(cat "$mark" 2>/dev/null)"
|
|
88
|
+
case "$last" in
|
|
89
|
+
'' | *[!0-9]*) last=0 ;;
|
|
90
|
+
esac
|
|
91
|
+
[ "$step" -gt "$last" ] || exit 0
|
|
92
|
+
printf '%s' "$step" > "$mark" 2>/dev/null
|
|
93
|
+
|
|
94
|
+
if [ "$pct" -ge "$stop_pct" ]; then
|
|
95
|
+
text="ЗАПОЛНЕНИЕ ОКНА ${pct}% (${fill_k}k из ${window_k}k) — заход закрывается сейчас. Всё, кроме записи хода работы, передачи и команд поставки, уже отбивается."
|
|
96
|
+
else
|
|
97
|
+
text="ЗАПОЛНЕНИЕ ОКНА ${pct}% (${fill_k}k из ${window_k}k). Пора выбирать точку остановки: с ${stop_pct}% останется только закрыть заход. Доведи текущий шаг до состояния, с которого следующий заход продолжит, перепиши «Где стоим» в ходе работы, напиши передачу и отдай владельцу путь к ней — паттерн task-flow-handoff."
|
|
98
|
+
fi
|
|
99
|
+
|
|
100
|
+
jq -n --arg t "$text" \
|
|
101
|
+
'{hookSpecificOutput:{hookEventName:"PostToolUse",additionalContext:$t}}' 2>/dev/null
|
|
102
|
+
exit 0
|
|
103
|
+
fi
|
|
104
|
+
|
|
105
|
+
# --- второй порог: работа отбивается, закрытие захода пропускается ------------------------
|
|
106
|
+
|
|
107
|
+
[ "$event" = "PreToolUse" ] || exit 0
|
|
108
|
+
[ "$pct" -ge "$stop_pct" ] || exit 0
|
|
109
|
+
|
|
110
|
+
tool="$(printf '%s' "$input" | jq -r '.tool_name // empty' 2>/dev/null)"
|
|
111
|
+
path="$(printf '%s' "$input" | jq -r '.tool_input.file_path // empty' 2>/dev/null)"
|
|
112
|
+
cmd="$(printf '%s' "$input" | jq -r '.tool_input.command // empty' 2>/dev/null)"
|
|
113
|
+
|
|
114
|
+
allowed=0
|
|
115
|
+
case "$tool" in
|
|
116
|
+
# Разговор с владельцем и чтение того, что правится при закрытии.
|
|
117
|
+
AskUserQuestion | TodoWrite | Read | SendUserFile)
|
|
118
|
+
allowed=1
|
|
119
|
+
;;
|
|
120
|
+
Edit | Write | MultiEdit | mcp__webstorm__create_new_file)
|
|
121
|
+
# Ход работы и передача. Остальное — работа, а её заход уже не начинает.
|
|
122
|
+
case "$path" in
|
|
123
|
+
"$tasks_dir"/* | */"$tasks_dir"/* | "$handoff_dir"/* | */"$handoff_dir"/* | */scratchpad/*) allowed=1 ;;
|
|
124
|
+
esac
|
|
125
|
+
;;
|
|
126
|
+
Bash | mcp__webstorm__execute_terminal_command)
|
|
127
|
+
# Поставка и сверки: коммит, пуш, отчёт, колонка задачи, состояние дерева. Список
|
|
128
|
+
# дописывается профилем дерева — клиент хостинга и имена команд у каждого свои.
|
|
129
|
+
if command -v rt_handoff_allowed_cmd >/dev/null 2>&1 && rt_handoff_allowed_cmd "$cmd"; then
|
|
130
|
+
allowed=1
|
|
131
|
+
fi
|
|
132
|
+
;;
|
|
133
|
+
esac
|
|
134
|
+
|
|
135
|
+
[ "$allowed" -eq 1 ] && exit 0
|
|
136
|
+
|
|
137
|
+
reason="BLOCKED by window-fill-guard: заполнение окна ${pct}% (${fill_k}k из ${window_k}k), порог остановки ${stop_pct}%. Заход дальше не работает — он закрывается.
|
|
138
|
+
|
|
139
|
+
Что осталось сделать этим заходом:
|
|
140
|
+
1. Перепиши раздел «Где стоим» в ходе работы и добавь запись захода — что сделано, чем подтверждено, что не вышло.
|
|
141
|
+
2. Закоммить проверенное: незакоммиченное не переживёт перерыв.
|
|
142
|
+
3. Напиши передачу в ${handoff_dir}/ и отдай владельцу путь к ней — что в неё входит, говорит паттерн task-flow-handoff.
|
|
143
|
+
|
|
144
|
+
Пропускаются при этом: правка ${tasks_dir}/**, запись передачи, команды поставки и сверки, чтение файлов и вопрос владельцу."
|
|
145
|
+
|
|
146
|
+
jq -n --arg r "$reason" \
|
|
147
|
+
'{hookSpecificOutput:{hookEventName:"PreToolUse",permissionDecision:"deny",permissionDecisionReason:$r}}' 2>/dev/null \
|
|
148
|
+
|| printf '{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"deny","permissionDecisionReason":"window-fill-guard: окно заполнено, заход закрывается передачей."}}\n'
|
|
149
|
+
|
|
150
|
+
exit 0
|
|
@@ -20,3 +20,13 @@
|
|
|
20
20
|
причина названа рядом.
|
|
21
21
|
- **Отметка об устаревании — повод убрать, а не повод оставить.** Устаревшее объявление,
|
|
22
22
|
которое молча продолжает работать, переживает того, кто его пометил.
|
|
23
|
+
|
|
24
|
+
## Открытые вопросы
|
|
25
|
+
|
|
26
|
+
- **Q-CS-3 — имя файла без обещания рода не судится ничем.** Проверка спрашивает файл, чьё имя
|
|
27
|
+
род называет; имя из одних слов о содержимом не говорит вовсе, и назвать род обязанным можно
|
|
28
|
+
только решением о продукте.
|
|
29
|
+
- **Q-CS-4 — одноступенчатое приведение остаётся непроверенным.** Запрещено обходить сверку
|
|
30
|
+
через промежуточное «неизвестно», а обычное приведение принято: часть его обязательна, и
|
|
31
|
+
запрет отбивал бы её вместе с остальным. Решение изменит, появится ли требование к причине у
|
|
32
|
+
каждого оставшегося.
|
package/assets/laws/delivery.md
CHANGED
|
@@ -16,7 +16,14 @@
|
|
|
16
16
|
которая в одну ветку не влезает, делится на задачи до того, как ветка заводится. Признак
|
|
17
17
|
деления — раздельный откат, а не объём: числа файлов, строк или коммитов, за которым работа
|
|
18
18
|
становится двумя задачами, нет. Правка одного рода остаётся одной задачей, сколько бы файлов
|
|
19
|
-
она ни задела.
|
|
19
|
+
она ни задела. Объём захода исполнителя признаком деления не является тоже: он говорит, какого
|
|
20
|
+
размера задачу стоит заводить среди тех, что делятся законно, и не даёт делить то, что
|
|
21
|
+
откатывается только вместе.
|
|
22
|
+
- **Правка самой поставки проверяется её прогоном, а не рассуждением.** Проверить её иначе
|
|
23
|
+
нечем: она исполняется только там, где выкатывает, и в среде, которой на месте работы нет — с
|
|
24
|
+
чужими правами, чужим хранилищем ключей и чужой сетью. «Проверю после слияния» решением
|
|
25
|
+
исполнителя не бывает: за этими словами стоит выкатка, которой уже не будет, если правка
|
|
26
|
+
окажется неверной. Отложить проверку может только владелец, и он говорит это словами.
|
|
20
27
|
- **Две задачи, которые чинятся одной правкой, — одна задача.** Вторая стирается вместе со
|
|
21
28
|
своим номером, а то, чего в первой не было, дописывается в неё до этого. Две строки об
|
|
22
29
|
одной работе хуже дыры в нумерации: по ним потом не понять, что сделано, а что нет. Слить
|
|
@@ -19,6 +19,10 @@
|
|
|
19
19
|
символ ничего не исполняет, а проверка на нём остаётся зелёной.
|
|
20
20
|
- **Путь, названный в документе, существует.** Ссылка на переехавший файл читается как
|
|
21
21
|
действующее указание, и следующий читатель заводит снятое заново.
|
|
22
|
+
- **Указатель каталога перечисляет всё, что в каталоге лежит.** Записи, которой в указателе нет,
|
|
23
|
+
для читателя не существует: он ищет по указателю, а не обходом каталога, и заводит разбор
|
|
24
|
+
заново. Это обратная сторона предыдущей статьи — не только путь из текста ведёт в файл, но и
|
|
25
|
+
файл назван в тексте, по которому его ищут.
|
|
22
26
|
- **Документ, разошедшийся с приложением, правится тогда же, когда замечено расхождение.**
|
|
23
27
|
Отложенная правка не случается: расхождение перестаёт быть заметным на следующий день.
|
|
24
28
|
- **Расхождение чинится в той стороне, которая неправа, и это не всегда документ.**
|
|
@@ -29,7 +33,17 @@
|
|
|
29
33
|
— это намерение, а не свойство приложения: сверить его не с чем, и оно проходит любую
|
|
30
34
|
проверку. Машине это не поручить: открытый вопрос пишется теми же словами, что и обещание,
|
|
31
35
|
и проверка отбивала бы оба.
|
|
36
|
+
- **Документ утверждает о состоявшемся, а не о том, что должно сработать.** Лечение,
|
|
37
|
+
записанное готовым до того, как его прогнали, дороже отсутствия записи: следующий читатель
|
|
38
|
+
берёт его за проверенное — и берёт в тот день, когда лечение понадобилось, а времени на
|
|
39
|
+
разбор нет. Непрогнанное либо не пишется вовсе, либо названо непроверенным тем же
|
|
40
|
+
предложением.
|
|
32
41
|
- **Число в тексте пересчитывается тем же изменением, которым пишется, и за этим тоже следит
|
|
33
42
|
автор.** Устаревшее число выглядит так же, как свежее, а машине их не различить: дата,
|
|
34
43
|
версия и номер — такие же числа, и проверка, которая знает один способ записи, на другом
|
|
35
44
|
ошибается молча.
|
|
45
|
+
- **Отказ от слова распространяется на всё, что уже прочитано снаружи, а не только на файлы.**
|
|
46
|
+
Название работы, её описание и запись о правке живут вне дерева: поиск по файлам их не
|
|
47
|
+
видит, проверки текстов на них не смотрят, и отказ выглядит сделанным ровно до того, как
|
|
48
|
+
читатель наткнётся на снятое слово в заголовке. Читатель при этом заключает, что от слова
|
|
49
|
+
не отказывались вовсе.
|
|
@@ -21,6 +21,10 @@
|
|
|
21
21
|
выкинутый сценарий: без этого он пропадает молча.
|
|
22
22
|
- **Работающее приложение проверяется там, где его видит пользователь.** Отладочный режим
|
|
23
23
|
ведёт себя иначе рабочего, и проверка в нём подтверждает не то, что будет у пользователя.
|
|
24
|
+
- **Перед отправкой правка проверяется тем же набором, что и конвейер, и теми же командами.**
|
|
25
|
+
Набор, собранный по изменённым файлам, пропускает то, до чего правка дошла связями: проверка
|
|
26
|
+
зелёная, а конвейер красный. Что проверять, считает инструмент от той же базы, а не память
|
|
27
|
+
автора.
|
|
24
28
|
- **Проверка признака окружения относится только к тому пути запуска, на котором она
|
|
25
29
|
сделана.** Пути, которыми одно и то же приложение поднимается, задают признаки по-разному,
|
|
26
30
|
и подтверждённое на одном из них на остальных неверно — а выглядит проверенным целиком.
|
|
@@ -30,6 +34,15 @@
|
|
|
30
34
|
наступило.** Часть запросов выполняется наполовину, и об отклонённой части в ответе ничего
|
|
31
35
|
нет: по коду возврата такой вызов не отличить от исполненного. Поэтому результат читают
|
|
32
36
|
отдельным запросом, и в отчёт идёт то, что прочитали, а не то, что заказывали.
|
|
37
|
+
- **Служба считается поднятой, когда она выполнила задание, а не когда сообщила о
|
|
38
|
+
готовности.** Сообщение о готовности говорит лишь, что служба себя объявила: та, которой не
|
|
39
|
+
досталось ни одного задания, выглядит в нём точно так же, как работающая. Проверяются обе
|
|
40
|
+
стороны связи — что заказчик выбирает именно её и что задание через неё прошло.
|
|
41
|
+
- **Причина отказа, на которой строится решение, подтверждается измерением, а не
|
|
42
|
+
правдоподобием.** Объяснение, пришедшее первым, объясняет наблюдаемое не хуже верного:
|
|
43
|
+
свойство среды и собственный промах выглядят в отказе одинаково, и разводит их только замер,
|
|
44
|
+
поставленный так, чтобы одно из двух не прошло. Решение, выведенное из неподтверждённой
|
|
45
|
+
причины, лечит не то — и стоит отката всей работы, а не одной правки.
|
|
33
46
|
- **Если инструмент проверки запрещает приём, которым здесь пользуются постоянно, его правило
|
|
34
47
|
выключают в настройке инструмента, а не обходят в каждом месте.** Обход приходится повторять
|
|
35
48
|
столько раз, сколько таких мест, и ни в одном из них не написано, зачем он: со стороны это
|
|
@@ -15,6 +15,10 @@
|
|
|
15
15
|
всего.
|
|
16
16
|
- **Владельцу не задаётся вопрос, ответ на который уже записан.** Записанное читают до
|
|
17
17
|
разговора, а не вместо ответа: вопрос о том, что уже решено, обесценивает и остальные.
|
|
18
|
+
- **Решение, однажды записанное, действует, пока его не отменили, и читается до того, как
|
|
19
|
+
принимается заново.** Отменённое решение следа в работе не оставляет — по результату не
|
|
20
|
+
видно ни того, что его принимали, ни того, что от него отказались. Принятое заново оно
|
|
21
|
+
расходится с прежним молча и стоит той же работы второй раз.
|
|
18
22
|
- **Понимание записано там, где идёт работа.** Оставленное в переписке живёт у одного
|
|
19
23
|
участника и до следующего дня; работу продолжает тот, у кого этой переписки нет.
|
|
20
24
|
- **Сказанное владельцем записывается его словами и задним числом не переписывается.**
|
|
@@ -22,9 +26,21 @@
|
|
|
22
26
|
- **Договорённость о том, как продукт себя ведёт, записана раньше кода, который её
|
|
23
27
|
исполняет.** Записанная после, она пишется по коду и повторяет его ошибки: разойтись ей
|
|
24
28
|
уже не с чем, а значит она ничего не проверяет.
|
|
29
|
+
- **Описание приведено к сделанному прежде, чем работа закрыта.** Договорённость пишется до
|
|
30
|
+
кода и описывает замысел, а к концу работы приложение отличается и от замысла тоже.
|
|
31
|
+
Расхождение, отложенное на потом, не находится: назавтра оно уже незаметно, а описание
|
|
32
|
+
продолжают читать как верное.
|
|
25
33
|
- **Состояние незаконченной работы восстанавливается без участия владельца.** Иначе каждый
|
|
26
34
|
перерыв стоит ему пересказа, а очередь работ показывает начатое и не говорит, что внутри
|
|
27
35
|
него сделано.
|
|
36
|
+
- **Заход исполнителя конечен, и его конец не совпадает с концом работы.** Место, где
|
|
37
|
+
исполнитель помнит ход работы, ограничено; заполнив его, он теряет не последнее, а всё
|
|
38
|
+
сразу. Заход, доведённый до логической точки заранее, стоит одной записи; оборванный на
|
|
39
|
+
середине — целого захода на восстановление.
|
|
40
|
+
- **Прерванная работа передаётся следующему заходу готовым текстом, а не пересказом
|
|
41
|
+
владельца.** Владелец знает, что работа не кончена, но не знает, где именно она стоит;
|
|
42
|
+
пересказ он даёт по своей памяти, а не по ходу работы, и следующий заход начинает с чужой
|
|
43
|
+
картины.
|
|
28
44
|
- **Замысел и ход работы — разные записи.** Замысел — то, с чем сверяют результат при
|
|
29
45
|
приёмке; правленный по ходу, он перестаёт отличаться от отчёта, и приёмке сверять нечего.
|
|
30
46
|
- **Сделанное отмечается в одном месте.** Две записи об одном разъезжаются молча, и после
|
|
@@ -39,6 +55,9 @@
|
|
|
39
55
|
нетронутое, и второй заход начинает его заново.
|
|
40
56
|
- **Записи о законченной работе не лежат среди записей о текущей.** Закрытое, лежащее рядом
|
|
41
57
|
с действующим, читается как действующее — тем убедительнее, чем оно старше.
|
|
58
|
+
- **Законченная работа убирает за собой до того, как войдёт в общее дерево.** Потом за
|
|
59
|
+
оставленным уже никто не следит: работа перешла к следующей задаче, а правки, которой это
|
|
60
|
+
убрали бы заодно, больше нет.
|
|
42
61
|
- **Достаточность понимания судит тот, кто просил.** Машине видно наличие записи, но не то,
|
|
43
62
|
что в ней закрыты все пробелы: запись из одной строки проходит так же, как разбор на сто.
|
|
44
63
|
Признак достаточности, выведенный из объёма или числа вопросов, сам становится целью —
|
|
@@ -305,6 +305,10 @@ in-review`, — и `npm run check:board` прогоняется ещё раз:
|
|
|
305
305
|
`git commit --amend` при этом уносит в коммит всё, что осталось в индексе, — так в коммит
|
|
306
306
|
уехало удаление файла, принадлежавшее соседней ветке. Состав коммита читается
|
|
307
307
|
`git show --stat` сразу после него, а не на разборе PR.
|
|
308
|
+
- Состав индекса читается `git diff --cached --stat` **до** коммита, а не только `git show --stat`
|
|
309
|
+
после него. Команды дерева кладут файлы в индекс сами: `npm run task:new` добавляет папку
|
|
310
|
+
задачи, и вместе с ней уезжает всё, что лежало рядом, — так в индексе оказался временный
|
|
311
|
+
каталог диагностики.
|
|
308
312
|
- `gh project` с `--owner` отвечает `unknown owner type`: владелец борды — другая учётная
|
|
309
313
|
запись, и правка идёт только через GraphQL.
|
|
310
314
|
- Заведённый тикет на борду сама она не забирает: репозиторий с ней не связан, и добавление
|
|
@@ -23,11 +23,16 @@ docs/specs/<домен>/
|
|
|
23
23
|
spec.md — как домен работает
|
|
24
24
|
implementation.md — таблица «правило → файл:символ»
|
|
25
25
|
scenarios.md — сценарии SC-<ПРЕФИКС>-<НОМЕР>
|
|
26
|
+
<поддомен>/ — свой spec.md, implementation.md и scenarios.md
|
|
26
27
|
proposed/<фича>/ — только то, чего ещё нет
|
|
27
28
|
```
|
|
28
29
|
|
|
29
30
|
Шаблон — `docs/specs/_template/spec.md`, указатель с префиксами — `docs/specs/README.md`.
|
|
30
31
|
|
|
32
|
+
Поддомен спрашивается наравне с доменом: те же обязательные разделы, тот же компаньон рядом,
|
|
33
|
+
та же связь сценариев с тестами. Домен, у которого половина поддоменов описана, а половина
|
|
34
|
+
заведена пустыми каталогами, зелёным не бывает.
|
|
35
|
+
|
|
31
36
|
## Обязательные разделы
|
|
32
37
|
|
|
33
38
|
`## Зачем` · `## Терминология` с подразделом `### Как это называется в интерфейсе` ·
|
|
@@ -84,6 +89,18 @@ docs/specs/<домен>/
|
|
|
84
89
|
`Не покрыто: <причина>`, сценарий с неполным тестом — `Покрытие: частичное — <чего не
|
|
85
90
|
хватает>`.
|
|
86
91
|
|
|
92
|
+
Номер в идентификаторе живёт так:
|
|
93
|
+
|
|
94
|
+
| Что случилось | Что делается с номером |
|
|
95
|
+
| ----------------------- | ---------------------------------------------------------------------------------------------------- |
|
|
96
|
+
| сценарий добавили | берётся следующий свободный — наибольший выданный в домене плюс один, а не дырка в середине |
|
|
97
|
+
| обещание изменили | номер тот же, заголовок теста правится тем же коммитом |
|
|
98
|
+
| сценарий удалили | номер остаётся пустым и новому сценарию не отдаётся; тест удаляется вместе со сценарием |
|
|
99
|
+
| номера захотелось сжать | не пересчитываются: связь с тестами держит только номер, а прогон остаётся зелёным при обеих правках |
|
|
100
|
+
|
|
101
|
+
Номер записывается так же, как у соседей в этом же файле: сверка ищет его шаблоном, и номер,
|
|
102
|
+
записанный иначе, не совпадёт ни в спеке, ни в заголовке теста.
|
|
103
|
+
|
|
87
104
|
## Порядок работы
|
|
88
105
|
|
|
89
106
|
1. Задача заводится сценариями: что станет верно, когда работа закончится.
|
|
@@ -94,6 +111,14 @@ docs/specs/<домен>/
|
|
|
94
111
|
|
|
95
112
|
## Частые промахи
|
|
96
113
|
|
|
114
|
+
- Выросший домен делят на новые домены, а не на поддомены: новый домен приходится заводить в
|
|
115
|
+
указателе, сверять с кодом отдельно и объяснять, чем он соседу не поддомен, — а поддомен
|
|
116
|
+
остаётся в своём домене и наследует его контракт. Соседний домен заводится только тогда,
|
|
117
|
+
когда предмет живёт своей сущностью. Границу проводит владелец: деление переписывает номера
|
|
118
|
+
во всех заголовках тестов домена, и вернуть его назад тем же движением нельзя.
|
|
119
|
+
- Счётчик правил или сценариев в указателе доменов: он пересчитывается при каждой правке
|
|
120
|
+
любого спека, и через год большая часть таких чисел молча описывает позавчерашний спек.
|
|
121
|
+
Указатель держит домен, префикс и одну строку «о чём».
|
|
97
122
|
- `tasks.md` в спеке: шаги — артефакт сессии, им место в ветке или в описании PR.
|
|
98
123
|
- Скопированная из контракта таблица полей: источник — `libs/common/proto/proto/<область>/v1/`,
|
|
99
124
|
и компилируется из двух только одна.
|
|
@@ -102,6 +127,16 @@ docs/specs/<домен>/
|
|
|
102
127
|
- Место, где правило исполняется, внутри текста правила: оно меняется при первом рефакторинге,
|
|
103
128
|
и для него заведён `implementation.md`.
|
|
104
129
|
- Пометка «Не покрыто» при существующем тесте — отказ: долг закрыли, а отметку не сняли.
|
|
130
|
+
- Пометка «Не покрыто» читается дословно и с начала строки. Любое слово между ней и двоеточием
|
|
131
|
+
— «Не покрыто, и прогоном не покрывается вовсе: …» — и сценарий считается непомеченным вовсе,
|
|
132
|
+
а причина, ради которой пометку и писали, до отчёта не доезжает.
|
|
105
133
|
- Закон, названный в тексте, но забытый в строке `**Законы:**`: по закону тогда не узнать,
|
|
106
134
|
какие домены на нём стоят.
|
|
107
135
|
- Правка `.proto` без спеков задетых доменов: `docs-guard` отбивает такой коммит.
|
|
136
|
+
- **Выросший домен делится на поддомены, а не на новые домены.** Новый домен пришлось бы
|
|
137
|
+
заводить в указателе, сверять с кодом отдельно и объяснять, чем он соседу не поддомен;
|
|
138
|
+
поддомен остаётся в своём домене и наследует его контракт. Соседний домен заводится только
|
|
139
|
+
тогда, когда предмет живёт своей сущностью.
|
|
140
|
+
- **Границу между доменами проводит владелец, а не автор очередной правки.** Автор видит свою
|
|
141
|
+
правку, а не то, чем предмет обрастёт: домен, заведённый по ходу дела, через месяц оказывается
|
|
142
|
+
половиной соседнего, и разводить их приходится вместе с номерами сценариев.
|