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,539 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# tool/selfcheck/smoke.sh — проверка комплекта на чистой папке, как у постороннего человека.
|
|
3
|
+
#
|
|
4
|
+
# ЗАЧЕМ. До этого правильность проверялась глазами и памятью: «вроде работало». Инструмент,
|
|
5
|
+
# который ставят одной командой в чужие проекты, так проверять нельзя — сломанный `init`
|
|
6
|
+
# обнаружится у пользователя, а не у автора.
|
|
7
|
+
#
|
|
8
|
+
# ЧЕГО ЭТА ПРОВЕРКА НЕ ДЕЛАЕТ. Не проверяет содержание разложенных документов и не ходит в сеть
|
|
9
|
+
# (установку с GitHub проверяет человек перед выпуском). Инструмент, молчащий о своих пробелах,
|
|
10
|
+
# хуже отсутствующего.
|
|
11
|
+
#
|
|
12
|
+
# bash tool/selfcheck/smoke.sh
|
|
13
|
+
|
|
14
|
+
set -uo pipefail
|
|
15
|
+
|
|
16
|
+
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
|
17
|
+
CLI="$ROOT/tool/program.mjs"
|
|
18
|
+
PASS=0
|
|
19
|
+
FAIL=0
|
|
20
|
+
|
|
21
|
+
ok() { printf ' \033[32m✔\033[0m %s\n' "$1"; PASS=$((PASS + 1)); }
|
|
22
|
+
bad() { printf ' \033[31m✘\033[0m %s\n' "$1"; printf ' %s\n' "${2:-}"; FAIL=$((FAIL + 1)); }
|
|
23
|
+
|
|
24
|
+
WORK="$(mktemp -d)"
|
|
25
|
+
trap 'rm -rf "$WORK"' EXIT
|
|
26
|
+
|
|
27
|
+
printf '\n\033[1mtool/selfcheck/smoke.sh\033[0m\n\n'
|
|
28
|
+
|
|
29
|
+
# --- 1. синтаксис самой программы ------------------------------------------
|
|
30
|
+
if node --check "$CLI" 2>/dev/null; then ok "программа разбирается"; else bad "программа не разбирается"; fi
|
|
31
|
+
|
|
32
|
+
cd "$WORK" || exit 1
|
|
33
|
+
git init -q .
|
|
34
|
+
|
|
35
|
+
# --- 2. init раскладывает комплект -----------------------------------------
|
|
36
|
+
node "$CLI" init >/dev/null 2>&1
|
|
37
|
+
# Считаем не «сколько-то файлов», а ровно то, что лежит в комплекте: жёсткое число
|
|
38
|
+
# устаревает при первой же правке состава и роняет проверку на пустом месте.
|
|
39
|
+
EXPECT=$(find "$ROOT/kit/docs" "$ROOT/kit/rules" -type f | wc -l)
|
|
40
|
+
GOT=$(find .aqk -type f 2>/dev/null | wc -l)
|
|
41
|
+
if [ "$GOT" -eq "$EXPECT" ]; then
|
|
42
|
+
ok "init разложил весь комплект: $GOT файлов"
|
|
43
|
+
else
|
|
44
|
+
bad "разложено $GOT из $EXPECT файлов комплекта"
|
|
45
|
+
fi
|
|
46
|
+
|
|
47
|
+
# Ищем по имени на любой глубине: раскладка внутри .aqk/docs — дело владельца комплекта,
|
|
48
|
+
# а обязательность файла от неё не зависит.
|
|
49
|
+
for f in .aqk.yml AGENTS.md CLAUDE.md; do
|
|
50
|
+
[ -f "$f" ] || bad "не создан обязательный файл" "$f"
|
|
51
|
+
done
|
|
52
|
+
for n in general.md index.md project-baseline.md; do
|
|
53
|
+
find .aqk -type f -name "$n" | grep -q . || bad "не создан обязательный файл" "$n"
|
|
54
|
+
done
|
|
55
|
+
ok "обязательные файлы на месте"
|
|
56
|
+
|
|
57
|
+
# --- 3. ЗЕЛЁНЫЙ ОБРАЗЕЦ: повтор ничего не портит ---------------------------
|
|
58
|
+
# Именно этого свойства ждут от команды, которую запускают вслепую по инструкции.
|
|
59
|
+
echo "правка человека" >> AGENTS.md
|
|
60
|
+
node "$CLI" init >/dev/null 2>&1
|
|
61
|
+
if grep -q "правка человека" AGENTS.md; then ok "повторный init не перезаписал чужие правки"; else bad "повторный init затёр правки человека"; fi
|
|
62
|
+
|
|
63
|
+
# --- 4. init --force перезаписывает, раз его просили явно -------------------
|
|
64
|
+
node "$CLI" init --force >/dev/null 2>&1
|
|
65
|
+
if grep -q "правка человека" AGENTS.md; then bad "init --force не перезаписал, хотя обязан"; else ok "init --force перезаписал, как просили"; fi
|
|
66
|
+
|
|
67
|
+
# --- 5. doctor считает ступени ---------------------------------------------
|
|
68
|
+
OUT="$(node "$CLI" doctor 2>&1)"
|
|
69
|
+
case "$OUT" in
|
|
70
|
+
*"AQK-0"*) ok "doctor печатает ступени" ;;
|
|
71
|
+
*) bad "doctor не напечатал ступени" "$OUT" ;;
|
|
72
|
+
esac
|
|
73
|
+
|
|
74
|
+
node "$CLI" doctor --min 0 >/dev/null 2>&1 && ok "порог AQK-0 пройден" || bad "порог AQK-0 не пройден на свежем проекте"
|
|
75
|
+
node "$CLI" doctor --min 3 >/dev/null 2>&1 && bad "порог AQK-3 пройден на пустом проекте — проверка врёт" || ok "порог AQK-3 честно не пройден"
|
|
76
|
+
|
|
77
|
+
# --- 6. КРАСНЫЙ ОБРАЗЕЦ: журнал не принимает запись без вывода --------------
|
|
78
|
+
if echo "просто сломалось" | node "$CLI" note "проверка" >/dev/null 2>&1; then
|
|
79
|
+
bad "журнал принял запись без раздела «Вывод»"
|
|
80
|
+
else
|
|
81
|
+
ok "журнал отклонил запись без вывода"
|
|
82
|
+
fi
|
|
83
|
+
|
|
84
|
+
# --- 7. сверка по намерению: находит своё и не выдумывает чужого -------------
|
|
85
|
+
# ЗЕЛЁНЫЙ: описание чужими словами обязано найти существующую запись.
|
|
86
|
+
OUT="$(node "$CLI" find "отладочная печать в проде" 2>&1)"
|
|
87
|
+
case "$OUT" in
|
|
88
|
+
*"no-print-in-prod"*) ok "find нашёл запись по описанию другими словами" ;;
|
|
89
|
+
*) bad "find не нашёл no-print-in-prod" "$OUT" ;;
|
|
90
|
+
esac
|
|
91
|
+
|
|
92
|
+
# КРАСНЫЙ: намерения, которого нет, находить нельзя. Ложное «такое уже есть» хуже
|
|
93
|
+
# отсутствия поиска: человек не заведёт нужный гейт, решив, что он есть.
|
|
94
|
+
OUT="$(node "$CLI" find "проверять что миграции базы обратимы" 2>&1)"
|
|
95
|
+
case "$OUT" in
|
|
96
|
+
*"Такого намерения в каталоге нет"*) ok "find не выдумал совпадение" ;;
|
|
97
|
+
*) bad "find нашёл несуществующее намерение" "$OUT" ;;
|
|
98
|
+
esac
|
|
99
|
+
|
|
100
|
+
# --- 5а. подсказки печатают вызов, который сработает ------------------------
|
|
101
|
+
# Через npx команды `aqk` в системе нет. Подсказка «aqk doctor» отправляет человека
|
|
102
|
+
# в «команда не найдена» на первом же шаге — проверено на живой установке с GitHub.
|
|
103
|
+
OUT="$(node "$CLI" 2>&1)"
|
|
104
|
+
case "$OUT" in
|
|
105
|
+
*"node "*"doctor"*) ok "справка печатает тот вызов, которым программу запустили" ;;
|
|
106
|
+
*) bad "справка советует команду, которой у человека нет" "$OUT" ;;
|
|
107
|
+
esac
|
|
108
|
+
|
|
109
|
+
# --- 5б. без манифеста уровень не выносит приговор проекту ------------------
|
|
110
|
+
# «Уровень не достигнут» на зрелом проекте читается как «проект плохой». Сообщение обязано
|
|
111
|
+
# объяснять, что мерится машиночитаемость, а не практика.
|
|
112
|
+
N="$WORK/nomanifest"; mkdir -p "$N"; cd "$N" || exit 1
|
|
113
|
+
git init -q .
|
|
114
|
+
OUT="$(node "$CLI" doctor 2>&1)"
|
|
115
|
+
case "$OUT" in
|
|
116
|
+
*"стандарт в этом репозитории не заведён"*"не оценка проекта"*)
|
|
117
|
+
ok "без манифеста сказано, что это не оценка проекта" ;;
|
|
118
|
+
*) bad "без манифеста доктор выносит приговор проекту" "$OUT" ;;
|
|
119
|
+
esac
|
|
120
|
+
cd "$WORK" || exit 1
|
|
121
|
+
|
|
122
|
+
# --- 7б. составной триггер: скрыто и названа причина ------------------------
|
|
123
|
+
# Запись про конвейер касается тех, у кого есть И гейты, И конвейер. В свежем проекте
|
|
124
|
+
# конвейера нет — она обязана быть скрыта, и обязана сказать почему.
|
|
125
|
+
# Гейт объявляем — значит первое условие выполнено; конвейера нет — второе нет.
|
|
126
|
+
# Так проверяется именно СЛОЖЕНИЕ условий, а не первое попавшееся.
|
|
127
|
+
C="$WORK/trigger"; mkdir -p "$C"; cd "$C" || exit 1
|
|
128
|
+
git init -q .
|
|
129
|
+
node "$CLI" init >/dev/null 2>&1
|
|
130
|
+
node "$CLI" add secrets-not-in-code >/dev/null 2>&1
|
|
131
|
+
OUT="$(node "$CLI" doctor 2>&1)"
|
|
132
|
+
case "$OUT" in
|
|
133
|
+
*"gates-run-in-ci"*"нет конвейера"*) ok "составной триггер скрыл запись и назвал причину" ;;
|
|
134
|
+
*) bad "запись про конвейер показана там, где конвейера нет" "$OUT" ;;
|
|
135
|
+
esac
|
|
136
|
+
cd "$WORK" || exit 1
|
|
137
|
+
|
|
138
|
+
# --- 7в. запись без переносимого рецепта не выдаётся за проверенную --------
|
|
139
|
+
# «Нечем проверить здесь» обязано отличаться и от «принято», и от «отклонено»: иначе
|
|
140
|
+
# непроверенная запись едет в каталог как рабочая.
|
|
141
|
+
OUT="$(bash "$ROOT/tool/selfcheck/gates.sh" 2>&1)"
|
|
142
|
+
case "$OUT" in
|
|
143
|
+
*"НЕ ПРОВЕРЕНА здесь"*|*"принято"*) ok "непроверенные записи считаются отдельно" ;;
|
|
144
|
+
*) bad "проверка каталога не различает непроверенные записи" "$OUT" ;;
|
|
145
|
+
esac
|
|
146
|
+
|
|
147
|
+
# --- 7г. проверка не зависает на проекте не на своём языке -------------------
|
|
148
|
+
# awk без файловых аргументов читает поток ввода и ждёт вечно. В терминале незаметно, в
|
|
149
|
+
# конвейере и в хуке коммита — намертво. Поток ввода здесь держим открытым нарочно.
|
|
150
|
+
E="$WORK/чужой-язык"; mkdir -p "$E"; printf 'fn main() {}\n' > "$E/main.rs"
|
|
151
|
+
# Поток ввода из /dev/zero: он никогда не кончается, но и ждать его не надо — здоровый гейт
|
|
152
|
+
# в него не заглядывает и выходит сразу. Спящий процесс в конвейере заставлял ждать себя.
|
|
153
|
+
STUCK=0
|
|
154
|
+
for G in "$ROOT"/kit/gates/*/check.sh; do
|
|
155
|
+
timeout 5 bash "$G" "$E" >/dev/null 2>&1 < /dev/zero
|
|
156
|
+
[ $? -eq 124 ] && { bad "гейт зависает на чужом языке" "$(basename "$(dirname "$G")")"; STUCK=1; }
|
|
157
|
+
done
|
|
158
|
+
[ $STUCK -eq 0 ] && ok "ни один гейт не зависает там, где его языка нет"
|
|
159
|
+
|
|
160
|
+
# --- 8. храповик: старое пропускает, новое не пускает ------------------------
|
|
161
|
+
# Главный вопрос к храповику: «может ли новый код добавить нарушение и пройти?»
|
|
162
|
+
# Может — значит это советчик, а не гейт.
|
|
163
|
+
R="$WORK/ratchet"; mkdir -p "$R"; cd "$R" || exit 1
|
|
164
|
+
git init -q .
|
|
165
|
+
printf 'def a():\n print("старое")\n' > old.py
|
|
166
|
+
node "$CLI" init >/dev/null 2>&1
|
|
167
|
+
node "$CLI" add no-print-in-prod >/dev/null 2>&1
|
|
168
|
+
node "$CLI" ratchet no-print-in-prod >/dev/null 2>&1
|
|
169
|
+
|
|
170
|
+
# Судим по вердикту гейта, а не по коду возврата doctor: он ненулевой и по другим
|
|
171
|
+
# причинам (в свежей папке нет .gitignore), и проверка бы врала о храповике.
|
|
172
|
+
OUT="$(node "$CLI" doctor --run 2>&1)"
|
|
173
|
+
case "$OUT" in
|
|
174
|
+
*"новых нарушений"*) bad "храповик не пропустил старое — правило нельзя ввести в живой проект" ;;
|
|
175
|
+
*) ok "храповик пропустил старое нарушение" ;;
|
|
176
|
+
esac
|
|
177
|
+
|
|
178
|
+
printf 'def b():\n print("новое")\n' > new.py
|
|
179
|
+
OUT="$(node "$CLI" doctor --run 2>&1)"
|
|
180
|
+
case "$OUT" in
|
|
181
|
+
*"новых нарушений"*) ok "храповик не пустил новое нарушение" ;;
|
|
182
|
+
*) bad "храповик пропустил НОВОЕ нарушение — это советчик, а не гейт" ;;
|
|
183
|
+
esac
|
|
184
|
+
|
|
185
|
+
# Второй прогон с тем же новым нарушением обязан краснеть так же. Пока реестр перезаписывался
|
|
186
|
+
# всем текущим списком, одно исправленное нарушение затягивало в долг ВСЕ новые: один красный
|
|
187
|
+
# прогон — и дальше зелено навсегда. «Может ли новый код добавить нарушение и пройти?» — мог.
|
|
188
|
+
printf 'def c():\n print("ещё одно")\n' > another.py
|
|
189
|
+
node "$CLI" doctor --run >/dev/null 2>&1
|
|
190
|
+
rm old.py
|
|
191
|
+
OUT="$(node "$CLI" doctor --run 2>&1)"
|
|
192
|
+
OUT2="$(node "$CLI" doctor --run 2>&1)"
|
|
193
|
+
case "$OUT2" in
|
|
194
|
+
*"новых нарушений"*) ok "новое нарушение не попадает в реестр вслед за исправленным" ;;
|
|
195
|
+
*) bad "исправление одного нарушения затянуло новые в долг" "второй прогон зелёный" ;;
|
|
196
|
+
esac
|
|
197
|
+
rm -f another.py
|
|
198
|
+
printf 'def a():\n print("старое")\n' > old.py
|
|
199
|
+
node "$CLI" ratchet no-print-in-prod >/dev/null 2>&1 || true
|
|
200
|
+
|
|
201
|
+
rm -f new.py old.py
|
|
202
|
+
node "$CLI" doctor --run >/dev/null 2>&1
|
|
203
|
+
if grep -q 'old.py' ratchets/no-print-in-prod.txt; then
|
|
204
|
+
bad "исправленное осталось в реестре — храповик не затягивается"
|
|
205
|
+
else
|
|
206
|
+
ok "исправленное вычеркнуто из реестра"
|
|
207
|
+
fi
|
|
208
|
+
cd "$WORK" || exit 1
|
|
209
|
+
|
|
210
|
+
# --- 8а. порядок «с нуля»: сторожа дня 0 ставятся до первой строки кода -------
|
|
211
|
+
# Сторож, поставленный на пустой проект, долга не создаёт. Он же, поставленный через полгода,
|
|
212
|
+
# краснеет на всём старом коде — и его выключают. Поэтому проверяем ровно это: после start
|
|
213
|
+
# гейты стоят И все зелёные.
|
|
214
|
+
S0="$WORK/start"; mkdir -p "$S0"; cd "$S0" || exit 1
|
|
215
|
+
git init -q .
|
|
216
|
+
node "$CLI" start >/dev/null 2>&1
|
|
217
|
+
N=$(awk '/^gates:/{g=1;next} /^[A-Za-z]/{g=0} g && /^[[:space:]]+[A-Za-z0-9_-]+:[[:space:]]*"/' .aqk.yml | wc -l)
|
|
218
|
+
if [ "$N" -ge 5 ]; then ok "start поставил сторожей дня 0 ($N)"; else bad "start почти ничего не поставил" "объявлено $N"; fi
|
|
219
|
+
|
|
220
|
+
OUT="$(node "$CLI" doctor --run 2>&1)"
|
|
221
|
+
case "$OUT" in
|
|
222
|
+
*"✘"*"код "*) bad "сторожа дня 0 краснеют на пустом проекте" "$(printf '%s' "$OUT" | grep -A2 '✘' | head -6)" ;;
|
|
223
|
+
*) ok "все сторожа дня 0 зелёные — долга нет" ;;
|
|
224
|
+
esac
|
|
225
|
+
|
|
226
|
+
# Образцы, скопированные в проект, — не код проекта. Пока они им считались, пустой репозиторий
|
|
227
|
+
# «становился» проектом на Python, и ему показывались записи про мёртвый код.
|
|
228
|
+
# Признак — не отсутствие слова, а причина рядом с ним: запись обязана быть СКРЫТА с
|
|
229
|
+
# пояснением «нет языков». Первая редакция искала само слово и падала на строке про скрытое.
|
|
230
|
+
case "$OUT" in
|
|
231
|
+
*"dead-code"*"нет языков"*) ok "образцы гейтов не считаются кодом проекта" ;;
|
|
232
|
+
*) bad "образцы гейтов посчитаны кодом проекта" "$(printf '%s' "$OUT" | grep 'dead-code')" ;;
|
|
233
|
+
esac
|
|
234
|
+
cd "$WORK" || exit 1
|
|
235
|
+
|
|
236
|
+
# На проекте, где код уже есть, это другой сценарий — и start обязан сказать об этом, а не
|
|
237
|
+
# красить весь старый код разом.
|
|
238
|
+
S1="$WORK/start-big"; mkdir -p "$S1"; cd "$S1" || exit 1
|
|
239
|
+
git init -q .
|
|
240
|
+
i=0; while [ $i -lt 40 ]; do printf 'x = %s\n' "$i" > "m$i.py"; i=$((i + 1)); done
|
|
241
|
+
node "$CLI" init >/dev/null 2>&1
|
|
242
|
+
OUT="$(node "$CLI" start 2>&1)"
|
|
243
|
+
case "$OUT" in
|
|
244
|
+
*"другой сценарий"*) ok "start на живом коде отправляет в doctor, а не красит всё" ;;
|
|
245
|
+
*) bad "start не отличил пустой проект от живого" "$OUT" ;;
|
|
246
|
+
esac
|
|
247
|
+
cd "$WORK" || exit 1
|
|
248
|
+
|
|
249
|
+
# --- 8б. проект называет свои каталоги, где печать — интерфейс ---------------
|
|
250
|
+
# Исключение объявляется В МАНИФЕСТЕ и потому видно глазами. Проверяем обе стороны: названный
|
|
251
|
+
# каталог пропускается, все остальные — нет. Исключение, которое прячет всё, бесполезно.
|
|
252
|
+
P="$WORK/printok"; mkdir -p "$P/cli" "$P/src"; cd "$P" || exit 1
|
|
253
|
+
printf 'def a():\n print("вывод программы")\n' > cli/main.py
|
|
254
|
+
printf 'def b():\n print("забытая отладка")\n' > src/service.py
|
|
255
|
+
OUT="$(AQK_PRINT_OK_DIRS=cli bash "$ROOT/kit/gates/no-print-in-prod/check.sh" . 2>&1)"
|
|
256
|
+
case "$OUT" in
|
|
257
|
+
*"cli/main.py"*) bad "названный каталог не исключён" "$OUT" ;;
|
|
258
|
+
*"src/service.py"*) ok "названный каталог пропущен, остальные проверяются" ;;
|
|
259
|
+
*) bad "проверка печати не нашла отладку вне названного каталога" "$OUT" ;;
|
|
260
|
+
esac
|
|
261
|
+
cd "$WORK" || exit 1
|
|
262
|
+
|
|
263
|
+
# --- 9. разбор ошибки: три случая различаются прогоном, а не памятью ---------
|
|
264
|
+
# «Сторожа не было», «сторож был и не сработал», «сторож был и его обошли» — разные починки.
|
|
265
|
+
# Пока их различает человек по памяти, чинят обычно не то. Здесь их различает прогон.
|
|
266
|
+
Y="$WORK/why"; mkdir -p "$Y"; cd "$Y" || exit 1
|
|
267
|
+
git init -q .
|
|
268
|
+
node "$CLI" init >/dev/null 2>&1
|
|
269
|
+
printf 'def a():\n print("отладка")\n' > x.py
|
|
270
|
+
|
|
271
|
+
OUT="$(node "$CLI" why "миграция базы применена задом наперёд" 2>&1)"
|
|
272
|
+
case "$OUT" in
|
|
273
|
+
*"Сторожа не было"*) ok "why: намерения нет в каталоге — сторожа не было" ;;
|
|
274
|
+
*) bad "why не сказал, что сторожа не было" "$OUT" ;;
|
|
275
|
+
esac
|
|
276
|
+
|
|
277
|
+
OUT="$(node "$CLI" why "отладочная печать уехала в прод" 2>&1)"
|
|
278
|
+
case "$OUT" in
|
|
279
|
+
*"не поставлен"*) ok "why: запись есть в каталоге, но в проекте не поставлена" ;;
|
|
280
|
+
*) bad "why не отличил «не поставлен» от «не было»" "$OUT" ;;
|
|
281
|
+
esac
|
|
282
|
+
|
|
283
|
+
node "$CLI" add no-print-in-prod >/dev/null 2>&1
|
|
284
|
+
OUT="$(node "$CLI" why "отладочная печать уехала в прод" 2>&1)"
|
|
285
|
+
case "$OUT" in
|
|
286
|
+
*"его обошли"*) ok "why: сторож стоит и ловит — значит его обошли" ;;
|
|
287
|
+
*) bad "why не отличил «обошли» от «не сработал»" "$OUT" ;;
|
|
288
|
+
esac
|
|
289
|
+
|
|
290
|
+
rm x.py
|
|
291
|
+
OUT="$(node "$CLI" why "отладочная печать уехала в прод" 2>&1)"
|
|
292
|
+
case "$OUT" in
|
|
293
|
+
*"этой поломки не видит"*) ok "why: сторож стоит, а поломки не видит" ;;
|
|
294
|
+
*) bad "why не отличил «не сработал» от «обошли»" "$OUT" ;;
|
|
295
|
+
esac
|
|
296
|
+
|
|
297
|
+
# Неуверенное совпадение не выбирается за человека: неверно названный случай отправляет
|
|
298
|
+
# чинить не то, а это дороже лишнего вопроса.
|
|
299
|
+
OUT="$(node "$CLI" why "файл вырос до девяти тысяч строк" 2>&1)"
|
|
300
|
+
case "$OUT" in
|
|
301
|
+
*"Уверенного совпадения нет"*) ok "why не гадает при слабом совпадении" ;;
|
|
302
|
+
*) bad "why выбрал запись при слабом совпадении" "$OUT" ;;
|
|
303
|
+
esac
|
|
304
|
+
cd "$WORK" || exit 1
|
|
305
|
+
|
|
306
|
+
# --- 20. new заводит заготовку в ПРОЕКТЕ, а не в комплекте -------------------
|
|
307
|
+
# Заготовка, созданная в каталоге пакета, для человека не существует: через npx пакет лежит во
|
|
308
|
+
# временной папке и исчезает вместе с ней. Работа сделана, результата нет.
|
|
309
|
+
NEWDIR="$(mktemp -d)"
|
|
310
|
+
( cd "$NEWDIR" && git init -q . && node "$CLI" new proba-obraztsa >/dev/null 2>&1 )
|
|
311
|
+
if [ -f "$NEWDIR/gates/proba-obraztsa/gate.yml" ]; then
|
|
312
|
+
ok "new заводит заготовку в проекте"
|
|
313
|
+
else
|
|
314
|
+
bad "new завёл заготовку не в проекте" "$(ls -d "$NEWDIR"/gates/* 2>/dev/null || echo 'в проекте пусто')"
|
|
315
|
+
fi
|
|
316
|
+
if [ -d "$ROOT/kit/gates/proba-obraztsa" ]; then
|
|
317
|
+
bad "new написал в каталог комплекта из чужого проекта" "$ROOT/kit/gates/proba-obraztsa"
|
|
318
|
+
rm -rf "$ROOT/kit/gates/proba-obraztsa"
|
|
319
|
+
else
|
|
320
|
+
ok "new не трогает каталог комплекта из чужого проекта"
|
|
321
|
+
fi
|
|
322
|
+
# Заготовка обязана нести метки ЗАПОЛНИ: незаполненная запись не должна проехать как готовая.
|
|
323
|
+
if grep -rq 'ЗАПОЛНИ' "$NEWDIR/gates/proba-obraztsa" 2>/dev/null; then
|
|
324
|
+
ok "заготовка помечена как незаполненная"
|
|
325
|
+
else
|
|
326
|
+
bad "в заготовке нет меток ЗАПОЛНИ" "фильтр каталога не отличит её от готовой записи"
|
|
327
|
+
fi
|
|
328
|
+
rm -rf "$NEWDIR"
|
|
329
|
+
|
|
330
|
+
# --- 21. blob собирает все методички в один файл ------------------------------
|
|
331
|
+
BLOBDIR="$(mktemp -d)"
|
|
332
|
+
( cd "$BLOBDIR" && node "$CLI" blob >/dev/null 2>&1 )
|
|
333
|
+
EXPECT_MD=$(find "$ROOT/kit/docs" -name '*.md' | wc -l)
|
|
334
|
+
if [ -f "$BLOBDIR/GOD_AI.md" ]; then
|
|
335
|
+
# Считаем только подписи, которые ставит сама склейка: путь в них начинается с kit/docs.
|
|
336
|
+
# Первая редакция считала все строки «источник» и намеряла 20 при 13 файлах — сами методички
|
|
337
|
+
# несут такие подписи внутри, указывая на проект, откуда перенесены.
|
|
338
|
+
GOT_MD=$(grep -c '^<!-- источник: kit/docs' "$BLOBDIR/GOD_AI.md")
|
|
339
|
+
if [ "$GOT_MD" -eq "$EXPECT_MD" ]; then
|
|
340
|
+
ok "blob собрал все методички ($GOT_MD)"
|
|
341
|
+
else
|
|
342
|
+
bad "blob собрал не все методички" "в kit/docs $EXPECT_MD, в склейке $GOT_MD"
|
|
343
|
+
fi
|
|
344
|
+
# Ссылки на соседние файлы внутри склейки ведут в никуда: соседей рядом больше нет.
|
|
345
|
+
if grep -qE '\]\((?!https?:)[^)]*\.md\)' "$BLOBDIR/GOD_AI.md" 2>/dev/null; then
|
|
346
|
+
bad "в склейке остались ссылки на соседние файлы" "внутри одного файла они ведут в никуда"
|
|
347
|
+
else
|
|
348
|
+
ok "ссылки на соседние файлы в склейке сняты"
|
|
349
|
+
fi
|
|
350
|
+
else
|
|
351
|
+
bad "blob не создал GOD_AI.md" "$BLOBDIR"
|
|
352
|
+
fi
|
|
353
|
+
rm -rf "$BLOBDIR"
|
|
354
|
+
|
|
355
|
+
# --- 22. doctor на самом комплекте ------------------------------------------
|
|
356
|
+
# В исходнике комплекта разложенной копии `.aqk/` нет и быть не должно. Пока это не различалось,
|
|
357
|
+
# `doctor` краснел на собственном репозитории и требовал разложить комплект в комплект — то есть
|
|
358
|
+
# врал ровно там, где его показывают первым делом.
|
|
359
|
+
if ( cd "$ROOT" && node "$CLI" doctor >/dev/null 2>&1 ); then
|
|
360
|
+
ok "doctor не краснеет на самом комплекте"
|
|
361
|
+
else
|
|
362
|
+
bad "doctor краснеет на самом комплекте" "оригиналы лежат в kit/, разложенной копии здесь не бывает"
|
|
363
|
+
fi
|
|
364
|
+
|
|
365
|
+
# --- 23. Go-конвенция *_test.go опознаётся как тесты ------------------------
|
|
366
|
+
# Найдено прогоном на gin-gonic/gin: тесты лежат рядом с кодом как foo_test.go, без
|
|
367
|
+
# отдельной папки tests/ — признак has_tests молчал на полностью протестированном репозитории.
|
|
368
|
+
GODIR="$(mktemp -d)"
|
|
369
|
+
mkdir -p "$GODIR/pkg"
|
|
370
|
+
printf 'package pkg\nfunc Foo() {}\n' > "$GODIR/pkg/foo.go"
|
|
371
|
+
printf 'package pkg\nfunc TestFoo(t *testing.T) {}\n' > "$GODIR/pkg/foo_test.go"
|
|
372
|
+
( cd "$GODIR" && node "$CLI" init >/dev/null 2>&1 )
|
|
373
|
+
GOOUT=$( cd "$GODIR" && node "$CLI" doctor 2>&1 )
|
|
374
|
+
if printf '%s' "$GOOUT" | grep -E 'есть:.*\btests\b' >/dev/null; then
|
|
375
|
+
ok "*_test.go опознаётся как тесты (Go)"
|
|
376
|
+
else
|
|
377
|
+
bad "*_test.go не опознан как тесты" "признак has_tests молчит на репозитории gin-типа"
|
|
378
|
+
fi
|
|
379
|
+
rm -rf "$GODIR"
|
|
380
|
+
|
|
381
|
+
# --- 24. просьба про звезду и обратную связь — один раз на машину -----------
|
|
382
|
+
# Печатается один раз на установку (не на проект): второй init на этой же HOME её не повторяет.
|
|
383
|
+
# Отметка живёт вне репозитория — внутри .aqk/ она либо закоммитится в чужой проект, либо
|
|
384
|
+
# потеряется при повторном init --force.
|
|
385
|
+
FAKEHOME="$(mktemp -d)"
|
|
386
|
+
D1="$(mktemp -d)"
|
|
387
|
+
OUT1=$( cd "$D1" && HOME="$FAKEHOME" node "$CLI" init 2>&1 )
|
|
388
|
+
if printf '%s' "$OUT1" | grep -qi 'звезд'; then
|
|
389
|
+
ok "первый init на новой машине зовёт поставить звезду"
|
|
390
|
+
else
|
|
391
|
+
bad "первый init не упомянул звезду/обратную связь" "$OUT1"
|
|
392
|
+
fi
|
|
393
|
+
D2="$(mktemp -d)"
|
|
394
|
+
OUT2=$( cd "$D2" && HOME="$FAKEHOME" node "$CLI" init 2>&1 )
|
|
395
|
+
if printf '%s' "$OUT2" | grep -qi 'звезд'; then
|
|
396
|
+
bad "init повторил просьбу про звезду на той же машине" "второй проект, та же HOME"
|
|
397
|
+
else
|
|
398
|
+
ok "просьба про звезду не повторяется на той же машине"
|
|
399
|
+
fi
|
|
400
|
+
rm -rf "$FAKEHOME" "$D1" "$D2"
|
|
401
|
+
|
|
402
|
+
# --- 25. ссылки в kit/docs/ не битые -----------------------------------------
|
|
403
|
+
# entry-links-exist проверяет только корень проекта-получателя, а не kit/docs/ комплекта —
|
|
404
|
+
# методички туда не попадают вовсе. Нашли переносом файла: ../app-owner-strategy.md указывал
|
|
405
|
+
# мимо после того, как файл переехал в тот же каталог, что и index.md, — .md-ссылка молчала,
|
|
406
|
+
# доктор комплекта не краснел, потому что не туда смотрит.
|
|
407
|
+
BROKEN=0
|
|
408
|
+
while IFS= read -r -d '' MD; do
|
|
409
|
+
DIR=$(dirname "$MD")
|
|
410
|
+
grep -oE '\]\([^)]+\.md[^)]*\)' "$MD" 2>/dev/null | sed -E 's/^\]\(//; s/\)$//' | while IFS= read -r LINK; do
|
|
411
|
+
TARGET="${LINK%%#*}"
|
|
412
|
+
[ -z "$TARGET" ] && continue
|
|
413
|
+
case "$TARGET" in http*) continue ;; esac
|
|
414
|
+
[ -e "$DIR/$TARGET" ] || echo "$MD -> $LINK"
|
|
415
|
+
done
|
|
416
|
+
done < <(find "$ROOT/kit/docs" -name '*.md' -print0) > /tmp/aqk-broken-doc-links.$$
|
|
417
|
+
if [ -s /tmp/aqk-broken-doc-links.$$ ]; then
|
|
418
|
+
bad "в kit/docs/ есть битые ссылки на .md" "$(cat /tmp/aqk-broken-doc-links.$$)"
|
|
419
|
+
else
|
|
420
|
+
ok "ссылки в kit/docs/ не битые"
|
|
421
|
+
fi
|
|
422
|
+
rm -f /tmp/aqk-broken-doc-links.$$
|
|
423
|
+
|
|
424
|
+
# --- 26. doctor печатает версию комплекта ------------------------------------
|
|
425
|
+
# Баг-репорт без версии нечем привязать к коммиту — заметили, заполняя .github/ISSUE_TEMPLATE/,
|
|
426
|
+
# где просили версию из шапки doctor, а шапка её не печатала вовсе.
|
|
427
|
+
PKGVER=$(node -e "console.log(require('$ROOT/package.json').version)")
|
|
428
|
+
DOCVER=$( cd "$ROOT" && node "$CLI" doctor 2>&1 | head -3)
|
|
429
|
+
if printf '%s' "$DOCVER" | grep -qF "$PKGVER"; then
|
|
430
|
+
ok "doctor печатает версию комплекта ($PKGVER)"
|
|
431
|
+
else
|
|
432
|
+
bad "doctor не печатает версию" "package.json: $PKGVER; шапка doctor: $(printf '%s' "$DOCVER" | tr '\n' ' ')"
|
|
433
|
+
fi
|
|
434
|
+
|
|
435
|
+
# --- 27. пользовательская red/green не путается с образцами каталога -------
|
|
436
|
+
# `--exclude-dir=red` смотрит только на имя папки, не на путь — реальный секрет в чужой red/
|
|
437
|
+
# (red-team тесты, что угодно) был невидим во всех проектах, куда ставили гейт. Проверка на
|
|
438
|
+
# самом опасном случае: secrets-not-in-code.
|
|
439
|
+
REDDIR="$(mktemp -d)"
|
|
440
|
+
mkdir -p "$REDDIR/red"
|
|
441
|
+
# Собран из частей, а не написан буквально: иначе secrets-not-in-code находит эту строку
|
|
442
|
+
# в собственном исходнике smoke.sh — гейт теперь смотрит по всему дереву, включая себя.
|
|
443
|
+
printf 'AKIA%s\n' 'ABCDEFGHIJKLMNOP' > "$REDDIR/red/config.py"
|
|
444
|
+
if bash "$ROOT/kit/gates/secrets-not-in-code/check.sh" "$REDDIR" >/dev/null 2>&1; then
|
|
445
|
+
bad "секрет в пользовательской red/ невидим" "own_samples_filter смотрит по имени, не по пути"
|
|
446
|
+
else
|
|
447
|
+
ok "секрет в пользовательской red/ виден (не путается с образцами каталога)"
|
|
448
|
+
fi
|
|
449
|
+
rm -rf "$REDDIR"
|
|
450
|
+
|
|
451
|
+
# --- 28. note требует ту же отметку, что потом проверяет lesson-has-outcome -
|
|
452
|
+
# Раньше note принимал любой текст со словом "вывод" — запись проходила note и тут же
|
|
453
|
+
# краснела на doctor --run, потому что гейт требует одну из трёх настоящих отметок.
|
|
454
|
+
NOTEJ="$(mktemp -d)"
|
|
455
|
+
( cd "$NOTEJ" && git init -q . && git config user.email t@t.com && git config user.name t \
|
|
456
|
+
&& mkdir -p incidents && echo "# Журнал" > incidents/README.md && git add -A && git commit -q -m init )
|
|
457
|
+
NOWORD=$(cd /tmp && AQK_HOME="$NOTEJ" sh -c 'echo "слово вывод здесь есть, но отметки нет" | node "'"$CLI"'" note "без отметки"' 2>&1; echo "EXIT:$?")
|
|
458
|
+
if printf '%s' "$NOWORD" | grep -q "EXIT:0"; then
|
|
459
|
+
bad "note принял запись без настоящей отметки решения" "$(printf '%s' "$NOWORD" | head -3)"
|
|
460
|
+
else
|
|
461
|
+
ok "note требует настоящую отметку (✅🔧📜👤), не просто слово «вывод»"
|
|
462
|
+
fi
|
|
463
|
+
rm -rf "$NOTEJ"
|
|
464
|
+
|
|
465
|
+
# --- 29. заготовка aqk new использует own_samples_filter, а не голое имя red/green -
|
|
466
|
+
# CHECK_SH_TEMPLATE нёс тот же баг, что чинили в семи существующих гейтах: --exclude-dir=red
|
|
467
|
+
# по имени, а не по пути. Каждый новый гейт, заведённый через `aqk new`, наследовал бы дыру.
|
|
468
|
+
NEWDIR2="$(mktemp -d)"
|
|
469
|
+
( cd "$NEWDIR2" && node "$CLI" init >/dev/null 2>&1 && node "$CLI" new probe-template >/dev/null 2>&1 )
|
|
470
|
+
if [ -f "$NEWDIR2/gates/_skip.sh" ] && grep -q 'own_samples_filter' "$NEWDIR2/gates/probe-template/check.sh"; then
|
|
471
|
+
ok "заготовка aqk new использует own_samples_filter и несёт _skip.sh"
|
|
472
|
+
else
|
|
473
|
+
bad "заготовка aqk new не подключает own_samples_filter или не копирует _skip.sh" "$NEWDIR2"
|
|
474
|
+
fi
|
|
475
|
+
rm -rf "$NEWDIR2"
|
|
476
|
+
|
|
477
|
+
# --- 30. doctor --run пишет короткий отчёт .aqk/last-run.md -----------------
|
|
478
|
+
# Список объявленных гейтов молчит о том, сколько из них реально работают именно сейчас —
|
|
479
|
+
# короткий отчёт после каждого прогона нужен и агенту в следующей сессии, и владельцу.
|
|
480
|
+
RUNDIR="$(mktemp -d)"
|
|
481
|
+
( cd "$RUNDIR" && node "$CLI" start >/dev/null 2>&1 && node "$CLI" doctor --run >/dev/null 2>&1 )
|
|
482
|
+
if [ -f "$RUNDIR/.aqk/last-run.md" ] && grep -q 'итого:' "$RUNDIR/.aqk/last-run.md"; then
|
|
483
|
+
ok "doctor --run пишет .aqk/last-run.md"
|
|
484
|
+
else
|
|
485
|
+
bad "doctor --run не написал отчёт" "$RUNDIR/.aqk/last-run.md"
|
|
486
|
+
fi
|
|
487
|
+
rm -rf "$RUNDIR"
|
|
488
|
+
|
|
489
|
+
# --- 31. commit-explains-itself и note не спорят друг с другом ---------------
|
|
490
|
+
# Собственная команда `note` делает коммит сама, и её тело — заголовок записи, без разделов
|
|
491
|
+
# «Сделано:»/«Не уверен:». Пока гейт этого не различал, каждая запись в журнал красила
|
|
492
|
+
# репозиторий, где стоят оба, — то есть инструмент воевал сам с собой.
|
|
493
|
+
CEDIR="$(mktemp -d)"
|
|
494
|
+
(
|
|
495
|
+
cd "$CEDIR" && git init -q . && git config user.email t@t && git config user.name t
|
|
496
|
+
mkdir -p incidents && echo "# журнал" > incidents/README.md
|
|
497
|
+
# С комментарием в той же строке намеренно: программа режет «#…» при разборе манифеста
|
|
498
|
+
# (tool/lib/manifest.mjs), и гейт обязан читать тот же файл по тем же правилам.
|
|
499
|
+
printf 'lessons: incidents # где копятся уроки\n' > .aqk.yml
|
|
500
|
+
echo "код" > a.js
|
|
501
|
+
git add -A && git commit -q -m "первый"
|
|
502
|
+
echo "## запись" >> incidents/README.md && git add -A && git commit -q -m "lesson(aqk): шишка"
|
|
503
|
+
)
|
|
504
|
+
if bash "$ROOT/kit/gates/commit-explains-itself/check.sh" "$CEDIR" >/dev/null 2>&1; then
|
|
505
|
+
ok "коммит только в журнал не требует мини-отчёта: запись и есть отчёт"
|
|
506
|
+
else
|
|
507
|
+
bad "гейт краснеет на записи в журнал — спорит с собственной командой note"
|
|
508
|
+
fi
|
|
509
|
+
( cd "$CEDIR" && echo "ещё" >> a.js && git add -A && git commit -q -m "fix: без отчёта" )
|
|
510
|
+
if bash "$ROOT/kit/gates/commit-explains-itself/check.sh" "$CEDIR" >/dev/null 2>&1; then
|
|
511
|
+
bad "гейт молчит на коммите в код без мини-отчёта"
|
|
512
|
+
else
|
|
513
|
+
ok "коммит, трогающий код, мини-отчёт всё так же обязан нести"
|
|
514
|
+
fi
|
|
515
|
+
|
|
516
|
+
# В мелком клоне (actions/checkout берёт один коммит) git считает коммит корневым и выдаёт
|
|
517
|
+
# всё дерево как изменённое — состав коммита узнать нельзя. Молча требовать отчёт в этом
|
|
518
|
+
# случае нельзя: конвейер краснел бы на каждой записи журнала. Пропускаем, назвав причину
|
|
519
|
+
# и способ починки, — так же, как doctor называет причину в списке «не применимо».
|
|
520
|
+
SHDIR="$(mktemp -d)"
|
|
521
|
+
git clone -q --depth 1 "file://$CEDIR" "$SHDIR/r" 2>/dev/null
|
|
522
|
+
OUT=$(bash "$ROOT/kit/gates/commit-explains-itself/check.sh" "$SHDIR/r" 2>&1)
|
|
523
|
+
if [ $? -eq 0 ] && printf '%s' "$OUT" | grep -q "fetch-depth"; then
|
|
524
|
+
ok "мелкий клон: гейт пропускает, назвав причину и способ починки"
|
|
525
|
+
else
|
|
526
|
+
bad "в мелком клоне гейт врёт про состав коммита" "$OUT"
|
|
527
|
+
fi
|
|
528
|
+
rm -rf "$SHDIR" "$CEDIR"
|
|
529
|
+
|
|
530
|
+
# --- итог -------------------------------------------------------------------
|
|
531
|
+
printf '\n'
|
|
532
|
+
if [ "$FAIL" -eq 0 ]; then
|
|
533
|
+
printf ' \033[32mвсё зелёное: %s проверок\033[0m\n\n' "$PASS"
|
|
534
|
+
else
|
|
535
|
+
printf ' \033[31mпровалено: %s из %s\033[0m\n\n' "$FAIL" "$((PASS + FAIL))"
|
|
536
|
+
fi
|
|
537
|
+
|
|
538
|
+
printf ' \033[2mне покрыто: содержание документов, установка с GitHub через npx\033[0m\n\n'
|
|
539
|
+
exit "$FAIL"
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
#!/usr/bin/env sh
|
|
2
|
+
# Все исходники комплекта разбираются: программа на нескольких файлах и скрипты гейтов.
|
|
3
|
+
#
|
|
4
|
+
# ЗАЧЕМ ОТДЕЛЬНЫМ ФАЙЛОМ. `node --check` берёт ровно один файл. Пока программа была одним
|
|
5
|
+
# файлом, этого хватало; после разделения проверка точки входа означала бы «семь из восьми не
|
|
6
|
+
# проверены» — ровно тот класс, где отсутствие сигнала читается как успех.
|
|
7
|
+
#
|
|
8
|
+
# Цикл в манифесте вместо скрипта не годится: проверка «объявленный гейт запускается» разбирает
|
|
9
|
+
# команду на слова и ищет пути — кусок `for f in $(...)` она читает как несуществующий файл.
|
|
10
|
+
set -u
|
|
11
|
+
ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
|
|
12
|
+
cd "$ROOT" || exit 1
|
|
13
|
+
BAD=0
|
|
14
|
+
|
|
15
|
+
for F in $(git ls-files 'tool/*.mjs' 'tool/**/*.mjs' 2>/dev/null); do
|
|
16
|
+
node --check "$F" || BAD=1
|
|
17
|
+
done
|
|
18
|
+
for F in $(git ls-files '*.sh' 2>/dev/null); do
|
|
19
|
+
bash -n "$F" || BAD=1
|
|
20
|
+
done
|
|
21
|
+
|
|
22
|
+
[ "$BAD" -eq 0 ] || echo " почини: файл не разбирается — до него не доедет ни одна другая проверка."
|
|
23
|
+
exit $BAD
|