agent-quality-kit 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +155 -0
- package/kit/docs/ai/agent-harness-playbook.md +596 -0
- package/kit/docs/ai/ai-native-development.md +371 -0
- package/kit/docs/ai/ai-sdlc.md +221 -0
- package/kit/docs/ai/anthropic-ai-native-sdlc-2026-08.md +294 -0
- package/kit/docs/ai/app-owner-strategy.md +921 -0
- package/kit/docs/ai/deep-research-2026-07.md +161 -0
- package/kit/docs/ai/harness-best-practices.md +385 -0
- package/kit/docs/ai/index.md +64 -0
- package/kit/docs/ai/project-baseline.md +261 -0
- package/kit/docs/ai/quality-gates-checklist.md +322 -0
- package/kit/docs/ai/sources-building-with-agents.md +111 -0
- package/kit/docs/ai/stream-2026-08-ai-coding-panel.md +304 -0
- package/kit/docs/ready-made-rules.md +170 -0
- package/kit/gates/README.md +231 -0
- package/kit/gates/_skip.sh +75 -0
- package/kit/gates/commit-explains-itself/README.md +45 -0
- package/kit/gates/commit-explains-itself/check.sh +63 -0
- package/kit/gates/commit-explains-itself/gate.yml +10 -0
- package/kit/gates/commit-explains-itself/green/COMMIT_MSG +6 -0
- package/kit/gates/commit-explains-itself/red/COMMIT_MSG +3 -0
- package/kit/gates/complexity-limit/README.md +37 -0
- package/kit/gates/complexity-limit/check.sh +44 -0
- package/kit/gates/complexity-limit/gate.yml +13 -0
- package/kit/gates/complexity-limit/green/flat.py +10 -0
- package/kit/gates/complexity-limit/red/deep.py +9 -0
- package/kit/gates/dead-code/README.md +30 -0
- package/kit/gates/dead-code/gate.yml +23 -0
- package/kit/gates/dead-code/green/mod.py +9 -0
- package/kit/gates/dead-code/red/mod.py +9 -0
- package/kit/gates/deps-are-pinned/README.md +29 -0
- package/kit/gates/deps-are-pinned/check.sh +49 -0
- package/kit/gates/deps-are-pinned/gate.yml +9 -0
- package/kit/gates/deps-are-pinned/green/nodep-go/go.mod +3 -0
- package/kit/gates/deps-are-pinned/green/package-lock.json +3 -0
- package/kit/gates/deps-are-pinned/green/package.json +4 -0
- package/kit/gates/deps-are-pinned/green/requirements.txt +2 -0
- package/kit/gates/deps-are-pinned/red/package.json +4 -0
- package/kit/gates/deps-are-pinned/red/requirements.txt +2 -0
- package/kit/gates/deps-are-pinned/red/withdep-go/go.mod +5 -0
- package/kit/gates/duplicate-code/README.md +40 -0
- package/kit/gates/duplicate-code/check.sh +58 -0
- package/kit/gates/duplicate-code/gate.yml +12 -0
- package/kit/gates/duplicate-code/green/common.py +9 -0
- package/kit/gates/duplicate-code/green/use.py +9 -0
- package/kit/gates/duplicate-code/red/a.py +12 -0
- package/kit/gates/duplicate-code/red/b.py +12 -0
- package/kit/gates/entry-links-exist/README.md +22 -0
- package/kit/gates/entry-links-exist/check.sh +24 -0
- package/kit/gates/entry-links-exist/gate.yml +16 -0
- package/kit/gates/entry-links-exist/green/AGENTS.md +5 -0
- package/kit/gates/entry-links-exist/green/rules/general.md +3 -0
- package/kit/gates/entry-links-exist/red/AGENTS.md +3 -0
- package/kit/gates/file-size-limit/README.md +22 -0
- package/kit/gates/file-size-limit/check.sh +34 -0
- package/kit/gates/file-size-limit/gate.yml +9 -0
- package/kit/gates/file-size-limit/green/a.py +251 -0
- package/kit/gates/file-size-limit/green/b.py +251 -0
- package/kit/gates/file-size-limit/red/big.py +601 -0
- package/kit/gates/gate-has-samples/README.md +29 -0
- package/kit/gates/gate-has-samples/check.sh +48 -0
- package/kit/gates/gate-has-samples/gate.yml +9 -0
- package/kit/gates/gate-has-samples/green/.aqk.yml +10 -0
- package/kit/gates/gate-has-samples/green/gates/no-print-in-prod/check.sh +2 -0
- package/kit/gates/gate-has-samples/green/gates/no-print-in-prod/green/good.py +2 -0
- package/kit/gates/gate-has-samples/green/gates/no-print-in-prod/red/bad.py +1 -0
- package/kit/gates/gate-has-samples/red/.aqk.yml +10 -0
- package/kit/gates/gate-has-samples/red/gates/no-print-in-prod/check.sh +2 -0
- package/kit/gates/gates-are-runnable/README.md +23 -0
- package/kit/gates/gates-are-runnable/check.sh +35 -0
- package/kit/gates/gates-are-runnable/gate.yml +9 -0
- package/kit/gates/gates-are-runnable/green/.aqk.yml +9 -0
- package/kit/gates/gates-are-runnable/green/checks/lint.sh +2 -0
- package/kit/gates/gates-are-runnable/red/.aqk.yml +6 -0
- package/kit/gates/gates-run-in-ci/README.md +29 -0
- package/kit/gates/gates-run-in-ci/check.sh +42 -0
- package/kit/gates/gates-run-in-ci/gate.yml +12 -0
- package/kit/gates/gates-run-in-ci/green/.aqk.yml +6 -0
- package/kit/gates/gates-run-in-ci/green/.github/workflows/ci.yml +7 -0
- package/kit/gates/gates-run-in-ci/green/checks/lint.sh +2 -0
- package/kit/gates/gates-run-in-ci/red/.aqk.yml +6 -0
- package/kit/gates/gates-run-in-ci/red/.github/workflows/ci.yml +7 -0
- package/kit/gates/gates-run-in-ci/red/checks/lint.sh +2 -0
- package/kit/gates/lesson-has-outcome/README.md +37 -0
- package/kit/gates/lesson-has-outcome/check.sh +50 -0
- package/kit/gates/lesson-has-outcome/gate.yml +11 -0
- package/kit/gates/lesson-has-outcome/green/.aqk.yml +2 -0
- package/kit/gates/lesson-has-outcome/green/incidents/README.md +32 -0
- package/kit/gates/lesson-has-outcome/red/.aqk.yml +2 -0
- package/kit/gates/lesson-has-outcome/red/incidents/README.md +13 -0
- package/kit/gates/no-print-in-prod/README.md +44 -0
- package/kit/gates/no-print-in-prod/check.sh +36 -0
- package/kit/gates/no-print-in-prod/gate.yml +15 -0
- package/kit/gates/no-print-in-prod/green/docs.ts +15 -0
- package/kit/gates/no-print-in-prod/green/legacy.py +9 -0
- package/kit/gates/no-print-in-prod/green/main.go +8 -0
- package/kit/gates/no-print-in-prod/green/main.rs +4 -0
- package/kit/gates/no-print-in-prod/green/service.py +8 -0
- package/kit/gates/no-print-in-prod/red/main.go +8 -0
- package/kit/gates/no-print-in-prod/red/main.rs +4 -0
- package/kit/gates/no-print-in-prod/red/service.py +3 -0
- package/kit/gates/secrets-not-in-code/README.md +29 -0
- package/kit/gates/secrets-not-in-code/check.sh +18 -0
- package/kit/gates/secrets-not-in-code/gate.yml +9 -0
- package/kit/gates/secrets-not-in-code/green/settings.py +4 -0
- package/kit/gates/secrets-not-in-code/red/settings.py +2 -0
- package/kit/gates/swallowed-error/README.md +26 -0
- package/kit/gates/swallowed-error/check.sh +54 -0
- package/kit/gates/swallowed-error/gate.yml +12 -0
- package/kit/gates/swallowed-error/green/loader.py +11 -0
- package/kit/gates/swallowed-error/green/run.js +8 -0
- package/kit/gates/swallowed-error/red/loader.py +5 -0
- package/kit/gates/swallowed-error/red/run.js +3 -0
- package/kit/gates/todo-without-task/README.md +26 -0
- package/kit/gates/todo-without-task/check.sh +19 -0
- package/kit/gates/todo-without-task/gate.yml +12 -0
- package/kit/gates/todo-without-task/green/order.py +9 -0
- package/kit/gates/todo-without-task/red/order.py +8 -0
- package/kit/ratchet/ratchet.sh +62 -0
- package/kit/rules/general.md +55 -0
- package/kit/rules/security.md +33 -0
- package/kit/rules/testing.md +46 -0
- package/package.json +41 -0
- package/tool/commands/doctor.mjs +230 -0
- package/tool/commands/gates.mjs +445 -0
- package/tool/commands/project.mjs +316 -0
- package/tool/lib/core.mjs +98 -0
- package/tool/lib/manifest.mjs +140 -0
- package/tool/lib/repo.mjs +270 -0
- package/tool/lib/templates.mjs +187 -0
- package/tool/program.mjs +81 -0
- package/tool/selfcheck/conditional.sh +24 -0
- package/tool/selfcheck/gates.sh +127 -0
- package/tool/selfcheck/smoke.sh +539 -0
- package/tool/selfcheck/syntax.sh +23 -0
- package/tool/selfcheck/units.mjs +105 -0
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# Объявленный гейт существует и запускается
|
|
2
|
+
|
|
3
|
+
**Намерение.** Гейт, записанный в манифест, но не выполняющийся, — худший класс отказа:
|
|
4
|
+
отсутствие сигнала неотличимо от успеха.
|
|
5
|
+
|
|
6
|
+
**Какой отказ это поймало.** В `audit_project` пути к конфигам проверок не были в белом списке
|
|
7
|
+
конвейера. Правка конфига не роняла сборку — сборки **не было вовсе**. Вместе с ней не появлялось
|
|
8
|
+
и кнопки выката, то есть работа стояла молча, а тишина читалась как «всё в порядке».
|
|
9
|
+
Запись в журнале: `incidents/README.md`, 2026-08-24.
|
|
10
|
+
|
|
11
|
+
**Что именно проверяется.** Не результат работы гейта, а то, что его есть чем выполнить:
|
|
12
|
+
программа из первого слова команды установлена, все пути-аргументы существуют, команда не пуста.
|
|
13
|
+
Полный прогон чужих гейтов — не наше дело: он может быть долгим и с побочными действиями.
|
|
14
|
+
|
|
15
|
+
**Чего НЕ ловит.** Гейт может запускаться и при этом ничего не проверять — «зомби» и
|
|
16
|
+
«декоративный» из `kit/gates/README.md`. От этого защищают красный и зелёный образцы.
|
|
17
|
+
|
|
18
|
+
**Готового аналога нет.** Проверено: запись про устройство самой оснастки — соответствие
|
|
19
|
+
манифеста и файловой системы. Ни `ruff`, ни `eslint`, ни `semgrep`, ни линтеры конфигов таких
|
|
20
|
+
правил не содержат: они проверяют код, а не то, исполнимо ли объявленное в вашем манифесте.
|
|
21
|
+
|
|
22
|
+
**Образцы.** `red/` — манифест объявляет `bash checks/lint.sh`, файла нет.
|
|
23
|
+
`green/` — тот же манифест, файл на месте.
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
#!/usr/bin/env sh
|
|
2
|
+
# Гейт, объявленный в манифесте, но не запускающийся, — худший из возможных: отсутствие
|
|
3
|
+
# сигнала неотличимо от успеха. Проверяем не результат работы гейта, а то, что его вообще
|
|
4
|
+
# есть чем выполнить: файл скрипта на месте, программа установлена.
|
|
5
|
+
DIR="${1:-.}"
|
|
6
|
+
MAN="$DIR/.aqk.yml"
|
|
7
|
+
[ -f "$MAN" ] || { echo "нет .aqk.yml — проверять нечего"; exit 0; }
|
|
8
|
+
|
|
9
|
+
# строки вида ` имя: "команда"` внутри блока gates:
|
|
10
|
+
LINES=$(awk '/^gates:/{g=1;next} /^[A-Za-z]/{g=0} g && /^[[:space:]]+[A-Za-z0-9_-]+:/{print}' "$MAN")
|
|
11
|
+
[ -z "$LINES" ] && { echo "гейтов не объявлено"; exit 0; }
|
|
12
|
+
|
|
13
|
+
BAD=0
|
|
14
|
+
echo "$LINES" | while IFS= read -r L; do
|
|
15
|
+
NAME=$(echo "$L" | sed 's/^[[:space:]]*\([A-Za-z0-9_-]*\):.*/\1/')
|
|
16
|
+
CMD=$(echo "$L" | sed 's/^[[:space:]]*[A-Za-z0-9_-]*:[[:space:]]*//; s/^"//; s/"$//')
|
|
17
|
+
[ -z "$CMD" ] && { echo "гейт «$NAME»: команда пустая"; echo " почини: впиши команду или убери строку — объявление без команды защиты не даёт"; exit 1; }
|
|
18
|
+
|
|
19
|
+
# Первое слово — программа, но только после присваиваний переменных: `VAR=1 bash x.sh` —
|
|
20
|
+
# обычная форма записи в оболочке, и программа здесь bash, а не «VAR=1». Пока это не
|
|
21
|
+
# различалось, проверка объявляла ненайденной программу с именем «AQK_PRINT_OK_DIRS=tool».
|
|
22
|
+
PROG=$(echo "$CMD" | awk '{ i = 1; while ($i ~ /^[A-Za-z_][A-Za-z0-9_]*=/) i++; print $i }')
|
|
23
|
+
command -v "$PROG" >/dev/null 2>&1 || {
|
|
24
|
+
echo "гейт «$NAME»: программа «$PROG» не установлена"
|
|
25
|
+
echo " почини: поставь её или замени команду — сейчас гейт молча не выполняется"
|
|
26
|
+
exit 1
|
|
27
|
+
}
|
|
28
|
+
for A in $CMD; do
|
|
29
|
+
case "$A" in
|
|
30
|
+
[A-Za-z_]*=*) continue ;;
|
|
31
|
+
*/*) [ -e "$DIR/$A" ] || { echo "гейт «$NAME»: файл «$A» не существует"; echo " почини: путь в манифесте указывает в никуда — отсутствие сигнала читается как успех"; exit 1; } ;;
|
|
32
|
+
esac
|
|
33
|
+
done
|
|
34
|
+
done || BAD=1
|
|
35
|
+
exit $BAD
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
aqk: 1
|
|
2
|
+
entry:
|
|
3
|
+
- AGENTS.md
|
|
4
|
+
rules: rules
|
|
5
|
+
gates:
|
|
6
|
+
lint: "bash checks/lint.sh"
|
|
7
|
+
# Присваивание переменной перед командой — обычная форма записи в оболочке. Программа здесь
|
|
8
|
+
# по-прежнему bash, а не «STRICT=1»: проверка обязана это различать.
|
|
9
|
+
lint-strict: "STRICT=1 bash checks/lint.sh"
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Объявленный гейт запускается конвейером
|
|
2
|
+
|
|
3
|
+
**Намерение.** Проверка, которую гоняет только человек, работает ровно до первого «забыл».
|
|
4
|
+
|
|
5
|
+
**Какой отказ это поймало.** В `audit_project` часть проверок жила только в конвейере, а часть
|
|
6
|
+
только локально, и «прогнать всё как CI» означало десяток команд, которые надо помнить.
|
|
7
|
+
Итог одного дня: четыре красных круга по 15–18 минут, три из четырёх падений ловились локально
|
|
8
|
+
за секунды. Запись в журнале: `incidents/README.md`, 2026-08-25.
|
|
9
|
+
|
|
10
|
+
**Что именно проверяется.** Каждая команда из блока `gates:` манифеста упомянута в конфиге
|
|
11
|
+
конвейера — по пути к скрипту, если он есть, иначе по команде целиком.
|
|
12
|
+
|
|
13
|
+
**Прогон разом тоже считается.** Если конвейер запускает `aqk doctor --run`, он гоняет всё,
|
|
14
|
+
что объявлено в манифесте, и отдельный шаг на каждый гейт не нужен. Больше того, так лучше:
|
|
15
|
+
добавление гейта в манифест само добавляет его в конвейер, и они не расходятся.
|
|
16
|
+
|
|
17
|
+
**Чего НЕ ловит.** Что шаг конвейера действительно выполняется: он может стоять под условием,
|
|
18
|
+
которое никогда не наступает, или быть помечен необязательным. Упоминание — необходимое условие,
|
|
19
|
+
а не достаточное.
|
|
20
|
+
|
|
21
|
+
**Почему условий два.** Запись включается, только если есть и гейты, и конвейер. Нет конвейера —
|
|
22
|
+
сначала он: требовать «запусти гейты конвейером» там, где конвейера нет, значит показывать
|
|
23
|
+
человеку работу, которую он сделать не может.
|
|
24
|
+
|
|
25
|
+
**Готового аналога нет.** Проверено: `actionlint` и подобные проверяют синтаксис конфига
|
|
26
|
+
конвейера, а не то, что в нём запускается всё объявленное в вашем манифесте. Такой сверки нет ни
|
|
27
|
+
в одном известном инструменте — она возможна только там, где манифест существует.
|
|
28
|
+
|
|
29
|
+
**Образцы.** `red/` — гейт объявлен, конвейер его не упоминает. `green/` — упоминает.
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
#!/usr/bin/env sh
|
|
2
|
+
# Гейт, который запускает только человек, работает ровно до первого «забыл». Конвейер не
|
|
3
|
+
# забывает. Проверяем: каждая команда из манифеста упомянута в конфиге конвейера.
|
|
4
|
+
DIR="${1:-.}"
|
|
5
|
+
MAN="$DIR/.aqk.yml"
|
|
6
|
+
[ -f "$MAN" ] || { echo "нет .aqk.yml — проверять нечего"; exit 0; }
|
|
7
|
+
|
|
8
|
+
CI=$(find "$DIR/.github/workflows" "$DIR/.gitlab-ci.yml" "$DIR/.circleci" "$DIR/Jenkinsfile" \
|
|
9
|
+
-type f 2>/dev/null)
|
|
10
|
+
[ -z "$CI" ] && { echo "конвейера нет — эта проверка не про тебя"; exit 0; }
|
|
11
|
+
|
|
12
|
+
# Конвейер может гонять гейты не поимённо, а разом: `aqk doctor --run` запускает всё
|
|
13
|
+
# объявленное в манифесте. Тогда добавление гейта само добавляет его в конвейер, и требовать
|
|
14
|
+
# отдельный шаг на каждый — значит требовать лишней работы и ловить несуществующий брак.
|
|
15
|
+
# shellcheck disable=SC2086
|
|
16
|
+
if grep -qE 'doctor[[:space:]]+--run|--run[[:space:]]+.*doctor' $CI 2>/dev/null; then
|
|
17
|
+
echo "конвейер запускает все объявленные гейты разом: doctor --run"
|
|
18
|
+
exit 0
|
|
19
|
+
fi
|
|
20
|
+
|
|
21
|
+
NAMES=$(awk '/^gates:/{g=1;next} /^[A-Za-z]/{g=0} g && /^[[:space:]]+[A-Za-z0-9_-]+:/{print}' "$MAN")
|
|
22
|
+
[ -z "$NAMES" ] && { echo "гейтов не объявлено"; exit 0; }
|
|
23
|
+
|
|
24
|
+
BAD=0
|
|
25
|
+
echo "$NAMES" | while IFS= read -r L; do
|
|
26
|
+
NAME=$(echo "$L" | sed 's/^[[:space:]]*\([A-Za-z0-9_-]*\):.*/\1/')
|
|
27
|
+
CMD=$(echo "$L" | sed 's/^[[:space:]]*[A-Za-z0-9_-]*:[[:space:]]*//; s/^"//; s/"$//')
|
|
28
|
+
[ -z "$CMD" ] && continue
|
|
29
|
+
|
|
30
|
+
# Ищем самую опознаваемую часть команды: путь к скрипту, иначе всю команду целиком.
|
|
31
|
+
KEY=$(echo "$CMD" | tr ' ' '\n' | grep '/' | head -1)
|
|
32
|
+
[ -z "$KEY" ] && KEY="$CMD"
|
|
33
|
+
|
|
34
|
+
# shellcheck disable=SC2086
|
|
35
|
+
if ! grep -qF "$KEY" $CI 2>/dev/null; then
|
|
36
|
+
echo "гейт «$NAME» не запускается конвейером: $CMD"
|
|
37
|
+
echo " почини: добавь шаг с этой командой в конфиг конвейера."
|
|
38
|
+
echo " гейт, который гоняет только человек, работает до первого «забыл»."
|
|
39
|
+
exit 1
|
|
40
|
+
fi
|
|
41
|
+
done || BAD=1
|
|
42
|
+
exit $BAD
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
intent: каждый объявленный гейт запускается конвейером, а не только руками
|
|
2
|
+
|
|
3
|
+
# Условия складываются: запись касается только тех, у кого есть и гейты, и конвейер.
|
|
4
|
+
# Нет конвейера — сначала он, а не эта проверка.
|
|
5
|
+
trigger:
|
|
6
|
+
has_gates: true
|
|
7
|
+
has_ci: true
|
|
8
|
+
|
|
9
|
+
recipes:
|
|
10
|
+
any: bash {gate}/check.sh {dir}
|
|
11
|
+
|
|
12
|
+
proof: incidents/README.md — «2026-08-25 четыре красных круга конвейера за день, три ловились локально»
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# У каждой шишки есть решение
|
|
2
|
+
|
|
3
|
+
**Намерение.** Записанный отказ кончается решением, а не рассказом.
|
|
4
|
+
|
|
5
|
+
**Какой отказ это поймало.** В журнале самого AQK лежало двадцать шишек. Отметку о том, во
|
|
6
|
+
что она вылилась, носили **две**. Остальные восемнадцать читались как истории: понятно, что
|
|
7
|
+
случилось, непонятно, что после этого поменялось. Из-за этого в отчётах о состоянии проекта
|
|
8
|
+
полтора месяца повторялось «шишек 18, гейтов из них 2» — цифра, которая была неправдой, но выглядела
|
|
9
|
+
измерением. Запись в журнале: `incidents/README.md`, 2026-09-03.
|
|
10
|
+
|
|
11
|
+
**Почему машина, а не внимательность.** Отметка ставится в тот день, когда решение принято, и
|
|
12
|
+
не ставится никогда, если про неё забыли. Забывают всегда: работа в этот момент уже сделана,
|
|
13
|
+
и дописать строку в журнал кажется формальностью.
|
|
14
|
+
|
|
15
|
+
**Три отметки, и третья — тоже решение.**
|
|
16
|
+
|
|
17
|
+
| Отметка | Когда |
|
|
18
|
+
|---|---|
|
|
19
|
+
| ✅ **Стало гейтом** | появилась запись каталога, которая ловит этот класс |
|
|
20
|
+
| 🔧 **Стало правкой оснастки** | починили сам инструмент, отдельной записи каталога не появилось |
|
|
21
|
+
| 👤 **Гейтом не станет** | правило дисциплины, решение человека, разовый случай — и почему |
|
|
22
|
+
|
|
23
|
+
Третья отметка обязательна именно потому, что соблазн — промолчать. Названный отказ не даёт
|
|
24
|
+
вернуться к тому же спору через полгода.
|
|
25
|
+
|
|
26
|
+
**Что считается шишкой.** Раздел, у которого назван **класс** отказа. Оглавления, вводные
|
|
27
|
+
разделы и списки открытых работ класса не имеют и решением кончаться не обязаны.
|
|
28
|
+
|
|
29
|
+
**Чего НЕ ловит.** Правдивость отметки. `✅ Стало гейтом: gates/чего-нет` пройдёт: проверка
|
|
30
|
+
видит форму, а не факт. Что гейт существует и работает, показывают `gate-has-samples` и прогон.
|
|
31
|
+
|
|
32
|
+
**Готового аналога нет.** Проверено: правило про устройство журнала инцидентов, а не про язык.
|
|
33
|
+
Ни `ruff`, ни `eslint`, ни `semgrep`, ни `markdownlint` таких правил не содержат — они смотрят
|
|
34
|
+
на разметку и код, а не на то, чем кончилась запись.
|
|
35
|
+
|
|
36
|
+
**Образцы.** `red/` — шишка с классом и без отметки. `green/` — две шишки с отметками разного
|
|
37
|
+
рода плюс раздел без класса, который проверку не касается.
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
#!/usr/bin/env sh
|
|
2
|
+
# Журнал без отметки о решении — это дневник, а не журнал. Разница видна не сразу: обе формы
|
|
3
|
+
# читаются одинаково, но по дневнику нельзя ответить на вопрос «что мы после этого поменяли».
|
|
4
|
+
#
|
|
5
|
+
# Отметка обязана быть одной из трёх, и третья — тоже решение:
|
|
6
|
+
# ✅ стало сторожем 🔧 стало правкой оснастки 👤 гейтом не станет, и вот почему
|
|
7
|
+
# Отказ, названный вслух, стоит дороже молчания: он не даёт вернуться к тому же спору.
|
|
8
|
+
DIR="${1:-.}"
|
|
9
|
+
MAN="$DIR/.aqk.yml"
|
|
10
|
+
|
|
11
|
+
# Где журнал — говорит манифест. Своего мнения у проверки быть не должно: путь у каждого свой.
|
|
12
|
+
LESSONS=$(sed -n 's/^lessons:[[:space:]]*"\{0,1\}\([^"#]*\)"\{0,1\}[[:space:]]*$/\1/p' "$MAN" 2>/dev/null | head -1)
|
|
13
|
+
case "$LESSONS" in
|
|
14
|
+
"" ) echo "в .aqk.yml не указано поле lessons — журнала нет"; exit 0 ;;
|
|
15
|
+
http*://* ) echo "журнал вынесен наружу ($LESSONS) — здесь не проверить"; exit 0 ;;
|
|
16
|
+
esac
|
|
17
|
+
[ -d "$DIR/$LESSONS" ] || { echo "каталога журнала нет: $LESSONS"; exit 0; }
|
|
18
|
+
|
|
19
|
+
FILES=$(find "$DIR/$LESSONS" -type f -name '*.md' 2>/dev/null)
|
|
20
|
+
[ -z "$FILES" ] && { echo "в журнале нет записей"; exit 0; }
|
|
21
|
+
|
|
22
|
+
# Шишка — раздел верхнего уровня, чей заголовок начинается с даты (YYYY-MM-DD — …). Оглавления
|
|
23
|
+
# и вводные разделы-контейнеры («Перенесённое из…», «Открытые работы…») дате не соответствуют
|
|
24
|
+
# и решением кончаться не обязаны. Отметка ищется где угодно в разделе, не только в цитате:
|
|
25
|
+
# старые записи несут её строкой ">", новые — абзацем "**Вывод.**"; оба варианта законны.
|
|
26
|
+
printf '%s\n' $FILES | while read -r F; do
|
|
27
|
+
awk -v FILE="$F" '
|
|
28
|
+
/^## [0-9]{4}-[0-9]{2}-[0-9]{2}/ { flush(); title = $0; sub(/^## /, "", title); is_entry = 1; has_mark = 0; next }
|
|
29
|
+
/^## / { flush(); is_entry = 0; next }
|
|
30
|
+
(/✅/ || /🔧/ || /📜/ || /👤/) { has_mark = 1 }
|
|
31
|
+
END { flush() }
|
|
32
|
+
function flush() {
|
|
33
|
+
if (is_entry && title != "" && !has_mark)
|
|
34
|
+
print FILE ": «" title "» — нет отметки о решении"
|
|
35
|
+
title = ""
|
|
36
|
+
}
|
|
37
|
+
' "$F"
|
|
38
|
+
done > /tmp/.lesson.$$ 2>/dev/null
|
|
39
|
+
|
|
40
|
+
if [ -s /tmp/.lesson.$$ ]; then
|
|
41
|
+
cat /tmp/.lesson.$$
|
|
42
|
+
echo " почини: припиши строку-цитату сразу под заголовком —"
|
|
43
|
+
echo " > ✅ **Стало гейтом:** <имя записи каталога>"
|
|
44
|
+
echo " > 🔧 **Стало правкой оснастки:** <что именно изменено>"
|
|
45
|
+
echo " > 👤 **Гейтом не станет:** <почему — правило дисциплины, решение человека, разовый случай>"
|
|
46
|
+
rm -f /tmp/.lesson.$$
|
|
47
|
+
exit 1
|
|
48
|
+
fi
|
|
49
|
+
rm -f /tmp/.lesson.$$
|
|
50
|
+
exit 0
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
intent: каждая записанная шишка кончается решением, а не рассказом
|
|
2
|
+
|
|
3
|
+
# Проверять нечего там, где журнала нет: запись сама скажет об этом и промолчит.
|
|
4
|
+
trigger:
|
|
5
|
+
always: true
|
|
6
|
+
|
|
7
|
+
recipes:
|
|
8
|
+
any: bash {gate}/check.sh {dir}
|
|
9
|
+
|
|
10
|
+
proof: incidents/README.md, 2026-09-03 «журнал рассказывал, но не отчитывался» — из 20 шишек
|
|
11
|
+
отметку о решении носили две, и петля «шишка → сторож» была невидима на своём же журнале
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# Журнал шишек
|
|
2
|
+
|
|
3
|
+
## Как это читать
|
|
4
|
+
|
|
5
|
+
Раздел без класса отказа — не шишка: оглавления и списки работ решением кончаться не обязаны.
|
|
6
|
+
|
|
7
|
+
## 2026-01-14 — сборка молча ехала со старой схемой
|
|
8
|
+
|
|
9
|
+
> ✅ **Стало гейтом:** `gates/schema-is-fresh` — расхождение схемы роняет отдельный шаг конвейера.
|
|
10
|
+
|
|
11
|
+
**Проект:** пример
|
|
12
|
+
**Класс:** гейт молчал
|
|
13
|
+
|
|
14
|
+
**Что случилось.** Сверка свежести схемы стояла внутри необязательной обёртки и ничего не
|
|
15
|
+
блокировала.
|
|
16
|
+
|
|
17
|
+
**Чем это стоило.** Две недели расхождения клиента и сервера.
|
|
18
|
+
|
|
19
|
+
**Вывод.** 🔧 Блокирующую проверку нельзя сцеплять с необязательной: обёртка сильнее содержимого.
|
|
20
|
+
|
|
21
|
+
## 2026-01-20 — план переписали задним числом
|
|
22
|
+
|
|
23
|
+
> 👤 **Гейтом не станет:** правило дисциплины, а не хук — решение владельца.
|
|
24
|
+
|
|
25
|
+
**Проект:** пример
|
|
26
|
+
**Класс:** правило обошли
|
|
27
|
+
|
|
28
|
+
**Что случилось.** Из принятого плана вычеркнули критерий приёмки.
|
|
29
|
+
|
|
30
|
+
**Чем это стоило.** Проверить, что именно приняли, стало нечем.
|
|
31
|
+
|
|
32
|
+
**Вывод.** 📜 Реализованный план неизменяем.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# Журнал шишек
|
|
2
|
+
|
|
3
|
+
## 2026-01-14 — сборка молча ехала со старой схемой
|
|
4
|
+
|
|
5
|
+
**Проект:** пример
|
|
6
|
+
**Класс:** гейт молчал
|
|
7
|
+
|
|
8
|
+
**Что случилось.** Сверка свежести схемы стояла внутри необязательной обёртки и ничего не
|
|
9
|
+
блокировала.
|
|
10
|
+
|
|
11
|
+
**Чем это стоило.** Две недели расхождения клиента и сервера.
|
|
12
|
+
|
|
13
|
+
**Вывод.** Надо будет что-то с этим сделать.
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# Отладочная печать не доезжает до прод-кода
|
|
2
|
+
|
|
3
|
+
**Намерение.** `print` и `console.log` не попадают в прод: они проходят мимо системы логов,
|
|
4
|
+
не имеют уровня и могут вынести наружу то, чего в выводе быть не должно.
|
|
5
|
+
|
|
6
|
+
**Какой отказ это поймало.** Прогон по `audit_project`: 40 находок, из них **5 настоящих** —
|
|
7
|
+
отладочный хук `useScrollDebugger.ts`, уехавший во фронтенд и печатавший размеры окна в консоль
|
|
8
|
+
пользователя. Остальные 35 разобраны поимённо и оказались законной печатью или шумом проверки.
|
|
9
|
+
Запись в журнале: `incidents/README.md`, 2026-09-03.
|
|
10
|
+
|
|
11
|
+
До этого прогона доказательства у записи не было, и она полтора месяца висела условной. Так и
|
|
12
|
+
надо: каталог не выбрасывает слабое доказательство и не делает вид, что оно сильное, — он его
|
|
13
|
+
помечает и ждёт факта.
|
|
14
|
+
|
|
15
|
+
**Для программы командной строки печать — это интерфейс.** В `main` консольной утилиты
|
|
16
|
+
`fmt.Println` или `println!` — не забытая отладка, а способ выдать результат. Такие каталоги
|
|
17
|
+
проект называет сам, рядом с командой в манифесте:
|
|
18
|
+
|
|
19
|
+
```yaml
|
|
20
|
+
no-print-in-prod: "AQK_PRINT_OK_DIRS=cli bash .aqk/gates/no-print-in-prod/check.sh ."
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
**Не храповиком.** Сначала мы записали такие места долгом — и каждая новая строка вывода красила
|
|
24
|
+
проверку, требуя переснять реестр. Долг — это то, что собираются погасить; печать из программы
|
|
25
|
+
командной строки убирать никто не будет. Долг, который нельзя погасить работой, — не долг, а
|
|
26
|
+
неверная мера. Храповик остаётся для настоящего долга: печати в прод-коде, которую уберут, но
|
|
27
|
+
не сегодня.
|
|
28
|
+
|
|
29
|
+
**Где печать законна.** Вспомогательные скрипты, оснастка агента, примеры, записные книжки,
|
|
30
|
+
миграции — там печать это способ говорить с человеком, а не забытая отладка. Эти каталоги
|
|
31
|
+
проверка пропускает. Различие важное: секрет в `scripts/` — такой же секрет, а печать — нет.
|
|
32
|
+
|
|
33
|
+
**Готовый аналог есть и он точнее.** В Python это `ruff --select T20`, в TypeScript — правило
|
|
34
|
+
`no-console` в eslint. Рецепты под эти стеки берут готовое; переносимая проверка остаётся для
|
|
35
|
+
Go, Rust и всего остального, где готового под рукой нет.
|
|
36
|
+
|
|
37
|
+
**Чего НЕ ловит.** Печать через обёртку — `myprint(x)`, `logger.print`, свой хелпер — проверка не
|
|
38
|
+
видит: она ищет известные конструкции языка, а не «вывод в поток». Не видит и печать в строке,
|
|
39
|
+
собранной по частям. Найдено на комментариях: печать внутри `//`, `#` и `/* */` отбрасывается,
|
|
40
|
+
потому что комментарий не выполняется.
|
|
41
|
+
|
|
42
|
+
**Образцы.** `red/` — модуль с отладочной печатью на трёх языках. `green/` — тот же модуль через
|
|
43
|
+
систему логов, плюс пример в JSDoc и закомментированная строка, на которых проверка обязана
|
|
44
|
+
молчать.
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
#!/usr/bin/env sh
|
|
2
|
+
# Отладочная печать переживает задачу и уезжает в прод: она не ошибка сборки и
|
|
3
|
+
# не видна в ревью диффа на 400 строк. В проде это либо шум, либо утечка данных
|
|
4
|
+
# мимо системы логов.
|
|
5
|
+
DIR="${1:-.}"
|
|
6
|
+
. "$(dirname "$0")/../_skip.sh" 2>/dev/null || SKIP_NAMES=".git .aqk node_modules .venv"
|
|
7
|
+
# Красный образец — намеренно сломанный код в репозитории. Сканирующий гейт обязан его
|
|
8
|
+
# пропускать, иначе будет вечно краснеть на том, что сам же и положил. Исключение снимается,
|
|
9
|
+
# когда проверяют сам образец: тогда каталог red и есть цель проверки.
|
|
10
|
+
|
|
11
|
+
# Где печать законна и дефектом не является: вспомогательные скрипты, оснастка, примеры,
|
|
12
|
+
# записные книжки. Это НЕ общий список исключений: секрет в scripts/ — такой же секрет,
|
|
13
|
+
# а печать там — обычный способ говорить с человеком.
|
|
14
|
+
#
|
|
15
|
+
# На настоящем проекте без этого различия 285 находок из 330 пришли из scripts/ и оснастки.
|
|
16
|
+
# Гейт, который на 86% состоит из ложных сработок, выключают целиком.
|
|
17
|
+
TOOLING="--exclude-dir=scripts --exclude-dir=tools --exclude-dir=bin --exclude-dir=examples --exclude-dir=notebooks --exclude-dir=docs --exclude-dir=.claude --exclude-dir=gates --exclude-dir=gates-reference"
|
|
18
|
+
|
|
19
|
+
# Проект называет СВОИ каталоги, где печать — интерфейс, а не отладка: у программы командной
|
|
20
|
+
# строки это её исходники целиком. Объявляется в манифесте, рядом с командой, и потому видно
|
|
21
|
+
# глазами: AQK_PRINT_OK_DIRS="tool cli" bash .../check.sh .
|
|
22
|
+
#
|
|
23
|
+
# ЗАЧЕМ ОТДЕЛЬНО ОТ ХРАПОВИКА. Сначала такие места записали долгом — и каждая новая строка
|
|
24
|
+
# вывода красила проверку, требуя переснять реестр. Но долг — это то, что собираются погасить;
|
|
25
|
+
# печать из программы командной строки убирать никто не будет. Долг, который нельзя погасить
|
|
26
|
+
# работой, — не долг, а неверная мера: журнал, 2026-09-03.
|
|
27
|
+
for D in ${AQK_PRINT_OK_DIRS:-}; do
|
|
28
|
+
TOOLING="$TOOLING --exclude-dir=$D"
|
|
29
|
+
done
|
|
30
|
+
HITS=$(grep -rnE $(skip_grep "$DIR") $(include_code) $TOOLING '(^|[^A-Za-z_.])(print\(|console\.log\(|fmt\.Print|println!|print!|dbg!|System\.out\.print|Console\.Write)' "$DIR" 2>/dev/null | drop_comments | own_samples_filter "$DIR")
|
|
31
|
+
if [ -n "$HITS" ]; then
|
|
32
|
+
echo "$HITS"
|
|
33
|
+
echo " почини: замени на вызов системы логов — тогда запись попадёт в общий журнал и уровень можно приглушить."
|
|
34
|
+
exit 1
|
|
35
|
+
fi
|
|
36
|
+
exit 0
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
intent: отладочная печать не доезжает до прод-кода
|
|
2
|
+
|
|
3
|
+
# Запись касается только языков, где есть эта конструкция. В проекте на Go или
|
|
4
|
+
# Rust она не показывается вовсе.
|
|
5
|
+
trigger:
|
|
6
|
+
langs: python, javascript, typescript
|
|
7
|
+
|
|
8
|
+
recipes:
|
|
9
|
+
any: bash {gate}/check.sh {dir}
|
|
10
|
+
python: ruff check --select T20 {dir}
|
|
11
|
+
typescript: eslint --rule '{"no-console":"error"}' {dir}
|
|
12
|
+
|
|
13
|
+
proof: incidents/README.md, 2026-09-03 «печать внутри комментария считалась печатью» —
|
|
14
|
+
прогон по audit_project: 40 находок, из них 5 настоящих (отладочный хук во фронтенде,
|
|
15
|
+
печатавший размеры окна в консоль пользователя)
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// Печать внутри комментария — не печать. Пример в документации к функции и закомментированная
|
|
2
|
+
// строка отладки одинаково не выполняются: находка здесь означает, что проверка читает текст,
|
|
3
|
+
// а не код. Найдено прогоном по audit_project — семь находок из JSDoc и закомментированных строк.
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Скачивает файл.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* const result = await download(id);
|
|
10
|
+
* console.log('Done:', result.file_url);
|
|
11
|
+
*/
|
|
12
|
+
export async function download(id: string): Promise<string> {
|
|
13
|
+
// console.log('отладка', id);
|
|
14
|
+
return `/files/${id}`;
|
|
15
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Секреты не попадают в код
|
|
2
|
+
|
|
3
|
+
**Намерение.** Приватный ключ, токен доступа или ключ платёжной системы не должны существовать
|
|
4
|
+
в файлах репозитория — ни в коде, ни в примерах, ни в тестах.
|
|
5
|
+
|
|
6
|
+
**Почему машина, а не внимательность.** Секрет попадает в историю один раз и остаётся там
|
|
7
|
+
навсегда: переписать ветку можно, но копии уже у всех, кто её тянул. Поэтому проверка обязана
|
|
8
|
+
стоять до коммита, а не «иногда руками».
|
|
9
|
+
|
|
10
|
+
**Что именно ищется.** Приватные ключи в текстовом формате и опознаваемые по префиксу токены:
|
|
11
|
+
платёжные `sk_live_`/`pk_test_`, ключи доступа облака `AKIA…`, токены репозитория `ghp_…`,
|
|
12
|
+
токены мессенджера `xox…`.
|
|
13
|
+
|
|
14
|
+
**Чего НЕ ловит.** Пароль в виде обычной строки — у него нет опознаваемой формы. Если продукт
|
|
15
|
+
выдаёт **свои** ключи, им нужен собственный узнаваемый префикс: тогда сканер сможет их находить,
|
|
16
|
+
а без префикса ключ неотличим от любой другой строки.
|
|
17
|
+
|
|
18
|
+
**Готовый аналог есть и он сильнее.** `gitleaks` знает сотни форматов
|
|
19
|
+
токенов и умеет читать историю, а не только рабочее дерево. Наша проверка — запасная: она без
|
|
20
|
+
зависимостей и работает там, где ставить лишний бинарь не хотят.
|
|
21
|
+
|
|
22
|
+
**На настоящем материале не подтверждена.** Два прогона по живым проектам — 36 664 и 414
|
|
23
|
+
файлов — дали ноль находок. Это не доказательство работоспособности: либо оба проекта чисты,
|
|
24
|
+
либо форма поиска слишком узка. Запись остаётся условной и записана долгом в
|
|
25
|
+
`ratchets/proof-from-journal.txt`; доказательство появится, когда проверка поймает настоящий
|
|
26
|
+
секрет, а не раньше.
|
|
27
|
+
|
|
28
|
+
**Образцы.** `red/` — файл настроек с ключом платёжной системы. `green/` — тот же файл, значение
|
|
29
|
+
берётся из окружения.
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
#!/usr/bin/env sh
|
|
2
|
+
# Секрет, попавший в историю, из неё уже не убрать: ветку перепишут, а копии останутся у всех,
|
|
3
|
+
# кто её тянул. Поэтому проверка стоит до коммита, а не «иногда руками».
|
|
4
|
+
DIR="${1:-.}"
|
|
5
|
+
. "$(dirname "$0")/../_skip.sh" 2>/dev/null || SKIP_NAMES=".git .aqk node_modules .venv"
|
|
6
|
+
# Красный образец — намеренно сломанный код в репозитории. Сканирующий гейт обязан его
|
|
7
|
+
# пропускать, иначе будет вечно краснеть на том, что сам же и положил. Исключение снимается,
|
|
8
|
+
# когда проверяют сам образец: тогда каталог red и есть цель проверки.
|
|
9
|
+
HITS=$(grep -rInE $(skip_grep "$DIR") -e \
|
|
10
|
+
'-----BEGIN [A-Z ]*PRIVATE KEY-----|(sk|pk)_(live|test)_[A-Za-z0-9]{16,}|AKIA[0-9A-Z]{16}|ghp_[A-Za-z0-9]{30,}|xox[baprs]-[A-Za-z0-9-]{10,}' \
|
|
11
|
+
"$DIR" 2>/dev/null | grep -v '/\.git/' | own_samples_filter "$DIR")
|
|
12
|
+
if [ -n "$HITS" ]; then
|
|
13
|
+
echo "$HITS" | cut -c1-160
|
|
14
|
+
echo " почини: убери значение из файла, положи его в переменную окружения и отзови старый ключ."
|
|
15
|
+
echo " из истории секрет уже не вычистить — считай его скомпрометированным."
|
|
16
|
+
exit 1
|
|
17
|
+
fi
|
|
18
|
+
exit 0
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
intent: ключи, пароли и приватные ключи не попадают в код
|
|
2
|
+
|
|
3
|
+
trigger:
|
|
4
|
+
always: true
|
|
5
|
+
|
|
6
|
+
recipes:
|
|
7
|
+
any: bash {gate}/check.sh {dir}
|
|
8
|
+
|
|
9
|
+
proof: kit/docs/ai/project-baseline.md, пункт 5 — «секреты не хранятся в коде вообще: ни в истории, ни в примерах, ни в тестах»
|