agent-quality-kit 0.7.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +135 -9
- package/README.ru.md +167 -23
- package/kit/docs/ai/project-baseline.md +14 -0
- package/kit/docs/ready-made-rules.md +103 -0
- package/kit/gates/_skip.sh +61 -1
- package/kit/gates/ci-not-hijackable/README.md +56 -0
- package/kit/gates/ci-not-hijackable/check.sh +73 -0
- package/kit/gates/ci-not-hijackable/gate.yml +19 -0
- package/kit/gates/ci-not-hijackable/green/.github/workflows/triage.yml +19 -0
- package/kit/gates/ci-not-hijackable/red/.github/workflows/triage.yml +18 -0
- package/kit/gates/color-from-token/check.sh +10 -2
- package/kit/gates/color-from-token/green/Button.tsx +2 -0
- package/kit/gates/complexity-limit/check.sh +6 -7
- package/kit/gates/duplicate-code/check.sh +5 -1
- package/kit/gates/entry-links-exist/check.sh +4 -1
- package/kit/gates/entry-links-exist/green/AGENTS.md +2 -0
- package/kit/gates/file-size-limit/check.sh +1 -1
- package/kit/gates/lesson-has-outcome/check.sh +5 -1
- package/kit/gates/mcp-server-resolves/README.md +62 -0
- package/kit/gates/mcp-server-resolves/check.sh +110 -0
- package/kit/gates/mcp-server-resolves/gate.yml +18 -0
- package/kit/gates/mcp-server-resolves/green/.mcp.json +20 -0
- package/kit/gates/mcp-server-resolves/red/.mcp.json +16 -0
- package/kit/gates/secrets-not-in-code/check.sh +16 -3
- package/kit/gates/secrets-not-in-code/green/testdata/certificate/key.pem +3 -0
- package/kit/gates/todo-without-task/check.sh +1 -1
- package/kit/gates/todo-without-task/green/app.py +1 -0
- package/llms.txt +38 -2
- package/package.json +2 -3
- package/tool/commands/context.mjs +264 -0
- package/tool/commands/doctor.mjs +104 -28
- package/tool/commands/learn.mjs +159 -0
- package/tool/commands/project.mjs +19 -2
- package/tool/commands/prove.mjs +1 -0
- package/tool/commands/report.mjs +33 -1
- package/tool/commands/vitals.mjs +159 -0
- package/tool/i18n/en-docs.mjs +125 -1
- package/tool/i18n/en.mjs +39 -36
- package/tool/i18n/index.mjs +36 -3
- package/tool/i18n/ru-docs.mjs +127 -1
- package/tool/i18n/ru.mjs +39 -36
- package/tool/lib/banner.mjs +59 -0
- package/tool/lib/brief.mjs +192 -0
- package/tool/lib/core.mjs +32 -1
- package/tool/lib/evidence.mjs +124 -0
- package/tool/lib/manifest.mjs +173 -15
- package/tool/lib/prove.mjs +24 -2
- package/tool/lib/repo.mjs +31 -1
- package/tool/lib/scope.mjs +10 -1
- package/tool/lib/templates.mjs +1 -0
- package/tool/program.mjs +45 -23
- package/tool/selfcheck/smoke.sh +592 -3
- package/tool/selfcheck/units-banner.mjs +65 -0
- package/tool/selfcheck/units-brief.mjs +97 -0
- package/tool/selfcheck/units-context.mjs +188 -0
- package/tool/selfcheck/units-evidence.mjs +83 -0
- package/tool/selfcheck/units-learn.mjs +88 -0
- package/tool/selfcheck/units-level.mjs +211 -3
- package/tool/selfcheck/units-repo.mjs +134 -0
- package/tool/selfcheck/units-vitals.mjs +62 -0
- package/tool/selfcheck/units.mjs +4 -75
package/tool/selfcheck/smoke.sh
CHANGED
|
@@ -25,6 +25,13 @@ FAIL=0
|
|
|
25
25
|
ok() { printf ' \033[32m✔\033[0m %s\n' "$1"; PASS=$((PASS + 1)); }
|
|
26
26
|
bad() { printf ' \033[31m✘\033[0m %s\n' "$1"; printf ' %s\n' "${2:-}"; FAIL=$((FAIL + 1)); }
|
|
27
27
|
|
|
28
|
+
# Node на Windows видит мир глазами Windows, а Git Bash — глазами POSIX: путь вида
|
|
29
|
+
# /tmp/tmp.XXXX, отданный в `node -e`, там не существует, и проверка падает не на том, что
|
|
30
|
+
# проверяет. Поймано дважды на windows-прогоне — сперва на `learn`, потом на установке хука.
|
|
31
|
+
# Поэтому node запускается ИЗ каталога и получает относительный путь: помощник, а не памятка,
|
|
32
|
+
# потому что памятку третий раз забудут ровно так же, как забыли второй.
|
|
33
|
+
node_in() { D="$1"; shift; ( cd "$D" && node "$@" ); }
|
|
34
|
+
|
|
28
35
|
WORK="$(mktemp -d)"
|
|
29
36
|
trap 'rm -rf "$WORK"' EXIT
|
|
30
37
|
|
|
@@ -1183,7 +1190,11 @@ rm -rf "$VDIR"
|
|
|
1183
1190
|
# v0.4.2, отставание на два выпуска. Проверка сравнивает обе с package.json, а не друг с другом:
|
|
1184
1191
|
# совпасть друг с другом они могут и будучи одинаково устаревшими.
|
|
1185
1192
|
VERS_BAD=""
|
|
1186
|
-
|
|
1193
|
+
# README.ru.md попал сюда 2026-09-08, при выпуске 0.8.0: в нём стояли v0.6.0 и v0.5.0 —
|
|
1194
|
+
# отставание на два и на три выпуска. Проверка его не смотрела, и русский читатель ставил
|
|
1195
|
+
# прошлогодний комплект молча. Ровно та же дыра, ради которой проверку и заводили, — просто
|
|
1196
|
+
# в файле, который забыли перечислить.
|
|
1197
|
+
for VF in README.md README.ru.md llms.txt; do
|
|
1187
1198
|
[ -f "$ROOT/$VF" ] || continue
|
|
1188
1199
|
# Берём только версии AQK — «v1.2.3» в примерах чужих действий (actions/checkout@v4) не наши.
|
|
1189
1200
|
for V in $(grep -oE '(rev:[[:space:]]*|Agent_Quality_Kit@)v[0-9]+\.[0-9]+\.[0-9]+' "$ROOT/$VF" \
|
|
@@ -1192,11 +1203,589 @@ for VF in README.md llms.txt; do
|
|
|
1192
1203
|
done
|
|
1193
1204
|
done
|
|
1194
1205
|
if [ -z "$VERS_BAD" ]; then
|
|
1195
|
-
ok "версия в README и llms.txt совпадает с package.json (v$PKGVER)"
|
|
1206
|
+
ok "версия в обоих README и llms.txt совпадает с package.json (v$PKGVER)"
|
|
1196
1207
|
else
|
|
1197
1208
|
bad "версия в документах разошлась с package.json" "package.json: v$PKGVER; найдено — $VERS_BAD"
|
|
1198
1209
|
fi
|
|
1199
1210
|
|
|
1211
|
+
# --- 82. report --since называет, чем доказан диф ---------------------------
|
|
1212
|
+
# Три состояния у файла, и разница между вторым и третьим — весь смысл раздела: «проверка
|
|
1213
|
+
# обошла и промолчала» не то же самое, что «никто не смотрел». Двух состояний хватило ровно до
|
|
1214
|
+
# первого прогона — `tool/commands/doctor.mjs` попал в «никем не проверен», хотя его обходят
|
|
1215
|
+
# пять проверок; они молчали, потому что нашли чисто.
|
|
1216
|
+
EVDIR="$(mktemp -d)"
|
|
1217
|
+
(
|
|
1218
|
+
cd "$EVDIR" || exit 1
|
|
1219
|
+
git init -q . && git config user.email a@b.c && git config user.name t
|
|
1220
|
+
mkdir -p src gates/noisy
|
|
1221
|
+
printf 'x = 1\n' > src/kept.py
|
|
1222
|
+
# Гейт, который печатает путь: по нему файл становится «назван».
|
|
1223
|
+
printf '#!/usr/bin/env sh\necho "src/kept.py:1: нашёл"\nexit 0\n' > gates/noisy/check.sh
|
|
1224
|
+
printf 'aqk: "1"\nentry: [AGENTS.md]\ngates:\n noisy: "sh gates/noisy/check.sh ."\n' > .aqk.yml
|
|
1225
|
+
printf '# правила\n' > AGENTS.md
|
|
1226
|
+
git add -A >/dev/null 2>&1 && git commit -qm base >/dev/null 2>&1
|
|
1227
|
+
printf 'y = 2\n' > src/quiet.py
|
|
1228
|
+
git add -A >/dev/null 2>&1 && git commit -qm second >/dev/null 2>&1
|
|
1229
|
+
) >/dev/null 2>&1
|
|
1230
|
+
EV_OUT=$( cd "$EVDIR" && AQK_LANG=ru node "$CLI" report --since HEAD~1 2>&1 )
|
|
1231
|
+
EV_FILE="$EVDIR/.aqk/report.md"
|
|
1232
|
+
if [ -f "$EV_FILE" ] && grep -q "src/quiet.py" "$EV_FILE" && grep -q "Чем доказан" "$EV_FILE"; then
|
|
1233
|
+
ok "report --since называет, чем доказан диф"
|
|
1234
|
+
else
|
|
1235
|
+
bad "report --since не отчитался о покрытии" "$(printf '%s' "$EV_OUT" | tail -2 | head -1)"
|
|
1236
|
+
fi
|
|
1237
|
+
# Ссылка, которой нет, обязана быть названа: «сравнили не с тем» не должно читаться как «чисто».
|
|
1238
|
+
EV_BAD=$( cd "$EVDIR" && AQK_LANG=ru node "$CLI" report --since no-such-ref 2>&1 )
|
|
1239
|
+
if printf '%s' "$EV_BAD" | grep -q "no-such-ref"; then
|
|
1240
|
+
ok "report --since называет неразобранную ссылку"
|
|
1241
|
+
else
|
|
1242
|
+
bad "report --since проглотил неверную ссылку" "$(printf '%s' "$EV_BAD" | tail -2 | head -1)"
|
|
1243
|
+
fi
|
|
1244
|
+
rm -rf "$EVDIR"
|
|
1245
|
+
|
|
1246
|
+
# --- 84. learn читает только напечатанное человеком --------------------------
|
|
1247
|
+
# Поле promptSource отделяет реплику от результата инструмента. Без него первая версия отбора
|
|
1248
|
+
# выдавала вставленные пути и ссылки вместо правил — «agent quality kit» 44 раза.
|
|
1249
|
+
# Каталог логов кладём ВНУТРЬ проекта и передаём относительным путём. Абсолютный сюда не
|
|
1250
|
+
# годится: Git Bash на Windows отдаёт «/tmp/…», а Node в том же окружении читает это как
|
|
1251
|
+
# «C:\tmp\…» — переменная указывает в никуда, и проверка падает не по делу.
|
|
1252
|
+
LRNP="$(mktemp -d)"; LRN=".cfg"
|
|
1253
|
+
# Имя каталога логов спрашиваем у самой программы, а не считаем здесь. На Windows оболочка
|
|
1254
|
+
# отдаёт «/tmp/…», а Node видит «C:\Users\…» — две разные строки, и тест падал не по делу.
|
|
1255
|
+
# Правило перевода пути в имя сторожит модульная проверка logSlug, здесь проверяется конвейер.
|
|
1256
|
+
SLUG=$( cd "$LRNP" && node -e "const {pathToFileURL}=require('node:url');import(pathToFileURL(process.argv[1]).href).then(m=>console.log(m.logSlug(process.cwd())))" "$ROOT/tool/commands/learn.mjs" )
|
|
1257
|
+
mkdir -p "$LRNP/$LRN/projects/$SLUG"
|
|
1258
|
+
{
|
|
1259
|
+
printf '{"type":"user","promptSource":"typed","timestamp":"2026-09-08T10:00:00Z","message":{"role":"user","content":"никогда не коммить прямо в основную ветку"}}\n'
|
|
1260
|
+
printf '{"type":"user","promptSource":"typed","timestamp":"2026-09-08T10:01:00Z","message":{"role":"user","content":"ок го дальше"}}\n'
|
|
1261
|
+
printf '{"type":"user","timestamp":"2026-09-08T10:02:00Z","message":{"role":"user","content":[{"type":"tool_result","content":"нельзя обязательно всегда"}]}}\n'
|
|
1262
|
+
} > "$LRNP/$LRN/projects/$SLUG/s1.jsonl"
|
|
1263
|
+
printf '# правила\n- Ничего особенного.\n' > "$LRNP/AGENTS.md"
|
|
1264
|
+
printf 'aqk: "1"\nentry: [AGENTS.md]\n' > "$LRNP/.aqk.yml"
|
|
1265
|
+
LRN_OUT=$( cd "$LRNP" && CLAUDE_CONFIG_DIR="$LRN" AQK_LANG=ru node "$CLI" learn 2>&1 )
|
|
1266
|
+
if printf '%s' "$LRN_OUT" | grep -q "основную ветку" &&
|
|
1267
|
+
! printf '%s' "$LRN_OUT" | grep -q "го дальше" &&
|
|
1268
|
+
printf '%s' "$LRN_OUT" | grep -q "напечатано человеком: 2"; then
|
|
1269
|
+
ok "learn берёт напечатанное человеком и не берёт вывод инструментов"
|
|
1270
|
+
else
|
|
1271
|
+
bad "learn отобрал не то" "$(printf '%s' "$LRN_OUT" | tr '\n' ' ' | cut -c1-150)"
|
|
1272
|
+
fi
|
|
1273
|
+
# Правило, уже стоящее в точке входа, показывать незачем: команда не пересказывает свод.
|
|
1274
|
+
printf '# правила\n- Никогда не коммить прямо в основную ветку.\n' > "$LRNP/AGENTS.md"
|
|
1275
|
+
LRN_W=$( cd "$LRNP" && CLAUDE_CONFIG_DIR="$LRN" AQK_LANG=ru node "$CLI" learn 2>&1 )
|
|
1276
|
+
if printf '%s' "$LRN_W" | grep -q "уже стоит в точке входа"; then
|
|
1277
|
+
ok "learn молчит о правиле, которое уже записано"
|
|
1278
|
+
else
|
|
1279
|
+
bad "learn повторил записанное правило" "$(printf '%s' "$LRN_W" | tr '\n' ' ' | cut -c1-150)"
|
|
1280
|
+
fi
|
|
1281
|
+
# Читает переписку — значит на диск не пишет ничего. Проверяем буквально.
|
|
1282
|
+
if [ ! -d "$LRNP/.aqk" ] || [ -z "$(ls -A "$LRNP/.aqk" 2>/dev/null)" ]; then
|
|
1283
|
+
ok "learn ничего не записал на диск"
|
|
1284
|
+
else
|
|
1285
|
+
bad "learn создал файлы" "$(ls -A "$LRNP/.aqk" | tr '\n' ' ')"
|
|
1286
|
+
fi
|
|
1287
|
+
rm -rf "$LRNP"
|
|
1288
|
+
|
|
1289
|
+
# --- 87. --baseline не совмещается с --run и --min ---------------------------
|
|
1290
|
+
# `--baseline` выходит с нулём всегда: это осмотр, а не прогон. Совмещённый с порогом он давал
|
|
1291
|
+
# конвейер, который НЕ МОЖЕТ покраснеть — порог назван, гейты не запущены, код нулевой. Найдено
|
|
1292
|
+
# ревью 2026-09-08. Отказ должен быть громким: молчаливое зелёное здесь дороже сломанной команды.
|
|
1293
|
+
BLP="$(mktemp -d)"
|
|
1294
|
+
( cd "$BLP" && git init -q . && printf 'aqk: "1"\nentry: [AGENTS.md]\n' > .aqk.yml && printf '# правила\n' > AGENTS.md ) >/dev/null 2>&1
|
|
1295
|
+
BL_OK=$( cd "$BLP" && AQK_LANG=ru node "$CLI" doctor --baseline 2>&1 ); BL_OK_C=$?
|
|
1296
|
+
BL_BAD=$( cd "$BLP" && AQK_LANG=ru node "$CLI" doctor --baseline --min 1 2>&1 ); BL_BAD_C=$?
|
|
1297
|
+
BL_RUN=$( cd "$BLP" && AQK_LANG=ru node "$CLI" doctor --baseline --run 2>&1 ); BL_RUN_C=$?
|
|
1298
|
+
if [ "$BL_OK_C" -eq 0 ] && [ "$BL_BAD_C" -ne 0 ] && [ "$BL_RUN_C" -ne 0 ] &&
|
|
1299
|
+
printf '%s' "$BL_BAD" | grep -q "не может покраснеть"; then
|
|
1300
|
+
ok "--baseline с --min и --run отказывает вслух, сам по себе работает"
|
|
1301
|
+
else
|
|
1302
|
+
bad "--baseline не отказал на пороге" "коды: сам $BL_OK_C, с --min $BL_BAD_C, с --run $BL_RUN_C"
|
|
1303
|
+
fi
|
|
1304
|
+
rm -rf "$BLP"
|
|
1305
|
+
|
|
1306
|
+
# --- 88. записи каталога не ломаются от цвета в выводе арбитра ---------------
|
|
1307
|
+
# Внутри GitHub Actions чужие инструменты КРАСЯТ вывод: там цвет поддержан, вне конвейера они
|
|
1308
|
+
# его выключают сами. Разбор по началу строки тогда промахивается — строка начинается с
|
|
1309
|
+
# escape-последовательности. Поймано конвейером 2026-09-08 на записи ci-not-hijackable: локально
|
|
1310
|
+
# зелёная, в конвейере «формат сменился». Тот же класс уже записан в scope.mjs.
|
|
1311
|
+
CLR_BAD=""
|
|
1312
|
+
for CG in kit/gates/*/check.sh; do
|
|
1313
|
+
CS=$(basename "$(dirname "$CG")")
|
|
1314
|
+
# Только записи с готовым арбитром: у них вывод чужой, и красит его не наш код.
|
|
1315
|
+
grep -q "^requires:" "$(dirname "$CG")/gate.yml" 2>/dev/null || continue
|
|
1316
|
+
command -v "$(sed -n 's/^requires:[[:space:]]*//p' "$(dirname "$CG")/gate.yml" | head -1 | awk '{print $1}')" >/dev/null 2>&1 || continue
|
|
1317
|
+
PLAIN=$(bash "$CG" "$(dirname "$CG")/green" >/dev/null 2>&1; echo $?)
|
|
1318
|
+
COLOR=$(GITHUB_ACTIONS=true CI=true bash "$CG" "$(dirname "$CG")/green" >/dev/null 2>&1; echo $?)
|
|
1319
|
+
[ "$PLAIN" = "$COLOR" ] || CLR_BAD="${CLR_BAD:+$CLR_BAD, }$CS ($PLAIN vs $COLOR)"
|
|
1320
|
+
done
|
|
1321
|
+
if [ -z "$CLR_BAD" ]; then
|
|
1322
|
+
ok "вердикт записи не зависит от того, красит ли арбитр вывод"
|
|
1323
|
+
else
|
|
1324
|
+
bad "цвет в выводе арбитра меняет вердикт" "$CLR_BAD"
|
|
1325
|
+
fi
|
|
1326
|
+
|
|
1327
|
+
# --- 89. шапка doctor читает манифест, а не литеральные пути ------------------
|
|
1328
|
+
# Отзыв второго пользователя 2026-09-08: у проекта `rules: .temper/rules`, правила на месте,
|
|
1329
|
+
# СТУПЕНЬ по манифесту берётся — а шапка рисовала кресты за `.aqk/rules` и `.aqk/docs` и
|
|
1330
|
+
# советовала сделать сделанное. Вывод расходился с собственным вердиктом программы, напечатанным
|
|
1331
|
+
# на десять строк ниже. Модульная проверка сторожит функцию; эта — то, что её кто-то зовёт.
|
|
1332
|
+
LYP="$(mktemp -d)"
|
|
1333
|
+
( cd "$LYP" && git init -q . && mkdir -p .temper/rules .temper/docs &&
|
|
1334
|
+
printf 'правило\n' > .temper/rules/r.md && printf 'методичка\n' > .temper/docs/d.md &&
|
|
1335
|
+
printf '# вход\n' > CLAUDE.md && printf '.x\n' > .gitignore &&
|
|
1336
|
+
printf 'aqk: 1\nentry:\n - CLAUDE.md\nrules: .temper/rules\ndocs: .temper/docs\ngates:\n smoke: "true"\n' > .aqk.yml ) >/dev/null 2>&1
|
|
1337
|
+
LY=$( cd "$LYP" && AQK_LANG=ru node "$CLI" doctor 2>&1 )
|
|
1338
|
+
if printf '%s' "$LY" | grep -q "\.temper/rules" &&
|
|
1339
|
+
printf '%s' "$LY" | grep -q "\.temper/docs" &&
|
|
1340
|
+
printf '%s' "$LY" | grep -q "CLAUDE\.md" &&
|
|
1341
|
+
! printf '%s' "$LY" | grep -q "\.aqk/rules" &&
|
|
1342
|
+
! printf '%s' "$LY" | grep -q "неизвестное поле"; then
|
|
1343
|
+
ok "шапка doctor берёт правила, методички и вход из манифеста"
|
|
1344
|
+
else
|
|
1345
|
+
bad "doctor проверил не то, что объявлено в манифесте" "$(printf '%s' "$LY" | head -8)"
|
|
1346
|
+
fi
|
|
1347
|
+
rm -rf "$LYP"
|
|
1348
|
+
|
|
1349
|
+
# --- 90. блок состояния: тишина не выдаётся за «чисто» -----------------------
|
|
1350
|
+
# Читатель этого блока — машина. Человек, увидев пустое место, переспросит; агент примет его
|
|
1351
|
+
# за утверждение и пойдёт писать код по несуществующему разрешению. Поэтому главное здесь одно:
|
|
1352
|
+
# без прогона блок обязан сказать «неизвестно» СЛОВОМ.
|
|
1353
|
+
CTXP="$(mktemp -d)"
|
|
1354
|
+
( cd "$CTXP" && git init -q . ) >/dev/null 2>&1
|
|
1355
|
+
CTX=$( cd "$CTXP" && AQK_LANG=ru node "$CLI" context 2>&1 ); CTX_C=$?
|
|
1356
|
+
if [ "$CTX_C" -eq 0 ] &&
|
|
1357
|
+
printf '%s' "$CTX" | grep -q "НЕИЗВЕСТНО" &&
|
|
1358
|
+
printf '%s' "$CTX" | grep -q "не вычислен" &&
|
|
1359
|
+
! printf '%s' "$CTX" | grep -q "AGENTS.md"; then
|
|
1360
|
+
ok "context без прогона говорит «неизвестно» и не называет несуществующий свод"
|
|
1361
|
+
else
|
|
1362
|
+
bad "context выдал незнание за чистоту" "код $CTX_C: $(printf '%s' "$CTX" | head -5)"
|
|
1363
|
+
fi
|
|
1364
|
+
|
|
1365
|
+
# --- 91. хук ставится в общий файл и не затирает чужие настройки --------------
|
|
1366
|
+
mkdir -p "$CTXP/.claude"
|
|
1367
|
+
printf '{ "permissions": { "deny": ["Read(./.env)"] } }\n' > "$CTXP/.claude/settings.json"
|
|
1368
|
+
( cd "$CTXP" && AQK_LANG=ru node "$CLI" context --install ) >/dev/null 2>&1
|
|
1369
|
+
AGAIN=$( cd "$CTXP" && AQK_LANG=ru node "$CLI" context --install 2>&1 )
|
|
1370
|
+
HOOKS=$(node_in "$CTXP" -e 'const s=require("./.claude/settings.json");
|
|
1371
|
+
console.log([s.hooks?.SessionStart?.length, s.permissions?.deny?.length,
|
|
1372
|
+
/[/\\]program\.mjs/.test(JSON.stringify(s.hooks?.SessionStart||[]))].join(" "))' 2>&1)
|
|
1373
|
+
if [ "$HOOKS" = "1 1 false" ] && printf '%s' "$AGAIN" | grep -q "уже стоит"; then
|
|
1374
|
+
ok "хук ставится один раз, переносимой командой, чужие настройки целы"
|
|
1375
|
+
else
|
|
1376
|
+
bad "установка хука испортила настройки или задвоилась" "разбор: $HOOKS"
|
|
1377
|
+
fi
|
|
1378
|
+
rm -rf "$CTXP"
|
|
1379
|
+
|
|
1380
|
+
# --- 92. полный блок: карта и свод дословно ----------------------------------
|
|
1381
|
+
# Решение владельца 2026-09-08, принятое ПОСЛЕ возражения про длину входа: агент читает файлы
|
|
1382
|
+
# плохо, и лишние токены — плата за то, чтобы он не ошибался. Раз плата внесена, товар обязан
|
|
1383
|
+
# быть доставлен: свод дословно, а не пересказ, и карта команд, а не половина карты.
|
|
1384
|
+
FULP="$(mktemp -d)"
|
|
1385
|
+
( cd "$FULP" && git init -q . &&
|
|
1386
|
+
printf '# Свод\n\n- Правило-маячок-для-проверки. <!-- aqk: человек -->\n' > AGENTS.md &&
|
|
1387
|
+
printf 'aqk: 1\nentry:\n - AGENTS.md\n' > .aqk.yml ) >/dev/null 2>&1
|
|
1388
|
+
FUL=$( cd "$FULP" && AQK_LANG=ru node "$CLI" context --full 2>&1 )
|
|
1389
|
+
SHORT=$( cd "$FULP" && AQK_LANG=ru node "$CLI" context 2>&1 )
|
|
1390
|
+
if printf '%s' "$FUL" | grep -q "Правило-маячок-для-проверки" &&
|
|
1391
|
+
printf '%s' "$FUL" | grep -q "doctor --run --since main" &&
|
|
1392
|
+
printf '%s' "$FUL" | grep -q "ЧТО УМЕЕТ ЭТОТ ИНСТРУМЕНТ" &&
|
|
1393
|
+
! printf '%s' "$SHORT" | grep -q "Правило-маячок-для-проверки"; then
|
|
1394
|
+
ok "context --full несёт карту и свод дословно, обычный — нет"
|
|
1395
|
+
else
|
|
1396
|
+
bad "полный блок не донёс свод или карту" "$(printf '%s' "$FUL" | head -4)"
|
|
1397
|
+
fi
|
|
1398
|
+
|
|
1399
|
+
# --- 93. хук с --full ставится именно с флагом -------------------------------
|
|
1400
|
+
( cd "$FULP" && AQK_LANG=ru node "$CLI" context --full --install ) >/dev/null 2>&1
|
|
1401
|
+
FLAG=$(node_in "$FULP" -e 'const s=require("./.claude/settings.json");
|
|
1402
|
+
console.log(JSON.stringify(s.hooks?.SessionStart||[]).includes("--full"))' 2>&1)
|
|
1403
|
+
if [ "$FLAG" = "true" ]; then
|
|
1404
|
+
ok "хук, поставленный с --full, зовёт полный блок, а не короткий"
|
|
1405
|
+
else
|
|
1406
|
+
bad "хук потерял --full — вливался бы короткий блок" "разбор: $FLAG"
|
|
1407
|
+
fi
|
|
1408
|
+
rm -rf "$FULP"
|
|
1409
|
+
|
|
1410
|
+
# --- 94. отсев сгенерированных файлов: края, а не середина -------------------
|
|
1411
|
+
# `drop_generated` заменил цикл `while read; do is_generated; done` — два процесса на файл.
|
|
1412
|
+
# Замер на шести чужих проектах дал ноль расхождений, но отсеяно там было 5 файлов из 1242:
|
|
1413
|
+
# такая сверка почти ничего не доказывает. Набор ниже нарочный, и он сразу нашёл ДВА
|
|
1414
|
+
# расхождения, которых на живых проектах не было, — пробел и двоеточие в имени файла.
|
|
1415
|
+
GENP="$(mktemp -d)"
|
|
1416
|
+
mkg() { printf '%b' "$2" > "$GENP/$1"; }
|
|
1417
|
+
mkg 'l1.js' '// @generated\ncode\n'
|
|
1418
|
+
mkg 'l5.js' 'a\nb\nc\nd\n// @generated\n'
|
|
1419
|
+
mkg 'l6.js' 'a\nb\nc\nd\ne\n// @generated\n'
|
|
1420
|
+
mkg 'upper.js' '// @GENERATED\n'
|
|
1421
|
+
mkg 'donotedit.js' '// Do Not Edit\n'
|
|
1422
|
+
mkg 'autogen.js' '// auto-generated by tool\n'
|
|
1423
|
+
mkg 'genby.js' '// Generated by protoc\n'
|
|
1424
|
+
mkg 'cyr.js' '// сгенерирован автоматически\n'
|
|
1425
|
+
mkg 'cyrup.js' '// СГЕНЕРИРОВАН\n'
|
|
1426
|
+
mkg 'with space.js' '// @generated\n'
|
|
1427
|
+
mkg 'with:colon.js' '// @generated\n'
|
|
1428
|
+
mkg 'clean.js' 'const x = 1;\n'
|
|
1429
|
+
: > "$GENP/empty.js"
|
|
1430
|
+
KEPT=$( . "$ROOT/kit/gates/_skip.sh"; find "$GENP" -type f | sort | drop_generated | sed "s#$GENP/##" | sort | tr '\n' ' ' )
|
|
1431
|
+
# Ожидания абсолютные, а не «как было раньше»: старая функция однажды уйдёт, а края останутся.
|
|
1432
|
+
# l6 остаётся намеренно: маркер на шестой строке — это уже не шапка файла.
|
|
1433
|
+
if [ "$KEPT" = "clean.js empty.js l6.js " ]; then
|
|
1434
|
+
ok "сгенерированные отсеяны по всем маркерам; пробел, двоеточие и граница 5-й строки учтены"
|
|
1435
|
+
else
|
|
1436
|
+
bad "отсев сгенерированных изменился" "осталось: $KEPT"
|
|
1437
|
+
fi
|
|
1438
|
+
rm -rf "$GENP"
|
|
1439
|
+
|
|
1440
|
+
# --- 95. covers: запись закрыта другим арбитром, но только объявленным ---------
|
|
1441
|
+
# Просьба первого чужого пользователя, названная им первой: у него complexity-limit,
|
|
1442
|
+
# no-print-in-prod и swallowed-error держит biome, а doctor каждый прогон печатал «применимо,
|
|
1443
|
+
# но не поставлено: 5» — неправду. Неправда в СОБСТВЕННОМ выводе дороже всех прочих: весь
|
|
1444
|
+
# стандарт стоит на том, что вывод не врёт. Но поле не признание на слово: гейт, который
|
|
1445
|
+
# «закрывает», обязан быть объявлен в gates:, иначе covers становится способом объявить защиту,
|
|
1446
|
+
# которой нет. Проверяются оба конца сразу.
|
|
1447
|
+
COVP="$(mktemp -d)"
|
|
1448
|
+
( cd "$COVP" && git init -q . && mkdir -p src rules &&
|
|
1449
|
+
printf 'const x=1;\n' > src/a.ts && printf '# вход\n' > AGENTS.md &&
|
|
1450
|
+
printf 'правило\n' > rules/r.md && printf '.x\n' > .gitignore &&
|
|
1451
|
+
printf 'aqk: 1\nentry: [AGENTS.md]\nrules: rules\ngates:\n lint: "true"\ncovers:\n lint: [no-print-in-prod, swallowed-error]\n biome: [duplicate-code]\n' > .aqk.yml ) >/dev/null 2>&1
|
|
1452
|
+
COV=$( cd "$COVP" && AQK_LANG=ru node "$CLI" doctor 2>&1 )
|
|
1453
|
+
if printf '%s' "$COV" | grep -q "закрыто другим арбитром 2" &&
|
|
1454
|
+
printf '%s' "$COV" | grep -q "covers называет гейт, которого нет" &&
|
|
1455
|
+
printf '%s' "$COV" | grep -q "biome" &&
|
|
1456
|
+
! printf '%s' "$COV" | grep -q "неизвестное поле"; then
|
|
1457
|
+
ok "covers снимает запись с долга — и только когда закрывающий гейт объявлен"
|
|
1458
|
+
else
|
|
1459
|
+
bad "covers посчитан неверно" "$(printf '%s' "$COV" | grep -E 'Итого|covers' | head -3)"
|
|
1460
|
+
fi
|
|
1461
|
+
rm -rf "$COVP"
|
|
1462
|
+
|
|
1463
|
+
# --- 96. язык вывода — настройка проекта, а не машины -------------------------
|
|
1464
|
+
# Просьба первого чужого пользователя: «язык берётся из LC_ALL/LANG, а на Windows их просто нет:
|
|
1465
|
+
# русский проект получает английский вывод». Проверяются оба конца порядка сразу: манифест
|
|
1466
|
+
# сильнее локали, но переменная окружения сильнее манифеста — иначе у человека, набравшего
|
|
1467
|
+
# AQK_LANG=en руками, не остаётся способа получить английский.
|
|
1468
|
+
LNGP="$(mktemp -d)"
|
|
1469
|
+
( cd "$LNGP" && git init -q . && printf '# вход\n' > AGENTS.md &&
|
|
1470
|
+
printf 'aqk: 1\nlang: ru\nentry: [AGENTS.md]\n' > .aqk.yml ) >/dev/null 2>&1
|
|
1471
|
+
BY_MAN=$( cd "$LNGP" && env -u AQK_LANG LANG=en_US.UTF-8 LC_ALL=en_US.UTF-8 node "$CLI" context 2>&1 | head -1 )
|
|
1472
|
+
BY_ENV=$( cd "$LNGP" && AQK_LANG=en node "$CLI" context 2>&1 | head -1 )
|
|
1473
|
+
if printf '%s' "$BY_MAN" | grep -q "состояние этого репозитория" &&
|
|
1474
|
+
printf '%s' "$BY_ENV" | grep -q "the state of this repository"; then
|
|
1475
|
+
ok "язык берётся из манифеста поверх локали, а переменная окружения — поверх манифеста"
|
|
1476
|
+
else
|
|
1477
|
+
bad "порядок выбора языка нарушен" "по манифесту: $BY_MAN | по окружению: $BY_ENV"
|
|
1478
|
+
fi
|
|
1479
|
+
rm -rf "$LNGP"
|
|
1480
|
+
|
|
1481
|
+
# --- 97. совет про браузер у агента — только тому, кому он нужен --------------
|
|
1482
|
+
# Решение владельца 2026-09-08: инструмент, дающий агенту браузер, надо рекомендовать. Но совет,
|
|
1483
|
+
# показанный не тому, стоит доверия всем остальным советам — та же норма, что у записей каталога.
|
|
1484
|
+
# Проверяются все три конца: интерфейс есть и сервера нет → совет; интерфейса нет → тишина;
|
|
1485
|
+
# сервер уже стоит → тишина.
|
|
1486
|
+
BRWP="$(mktemp -d)"
|
|
1487
|
+
( cd "$BRWP" && git init -q . && mkdir -p src && printf 'body{color:#fff}\n' > src/a.css &&
|
|
1488
|
+
printf 'export const A=1\n' > src/a.tsx && printf '# вход\n' > AGENTS.md ) >/dev/null 2>&1
|
|
1489
|
+
WITH_UI=$( cd "$BRWP" && AQK_LANG=ru node "$CLI" doctor 2>&1 )
|
|
1490
|
+
( cd "$BRWP" && printf '{"mcpServers":{"b":{"command":"npx","args":["-y","chrome-devtools-mcp@1.9.0"]}}}\n' > .mcp.json )
|
|
1491
|
+
WITH_SRV=$( cd "$BRWP" && AQK_LANG=ru node "$CLI" doctor 2>&1 )
|
|
1492
|
+
NOUIP="$(mktemp -d)"
|
|
1493
|
+
( cd "$NOUIP" && git init -q . && printf 'print(1)\n' > a.py && printf '# вход\n' > AGENTS.md ) >/dev/null 2>&1
|
|
1494
|
+
NO_UI=$( cd "$NOUIP" && AQK_LANG=ru node "$CLI" doctor 2>&1 )
|
|
1495
|
+
if printf '%s' "$WITH_UI" | grep -q "нет браузера" &&
|
|
1496
|
+
! printf '%s' "$WITH_SRV" | grep -q "нет браузера" &&
|
|
1497
|
+
! printf '%s' "$NO_UI" | grep -q "нет браузера"; then
|
|
1498
|
+
ok "совет про браузер даётся проекту с интерфейсом и молчит, когда сервер уже есть"
|
|
1499
|
+
else
|
|
1500
|
+
bad "совет про браузер показан не тому" "с ui: $(printf '%s' "$WITH_UI" | grep -c 'нет браузера'), с сервером: $(printf '%s' "$WITH_SRV" | grep -c 'нет браузера'), без ui: $(printf '%s' "$NO_UI" | grep -c 'нет браузера')"
|
|
1501
|
+
fi
|
|
1502
|
+
rm -rf "$BRWP" "$NOUIP"
|
|
1503
|
+
|
|
1504
|
+
# --- 98. заявка covers сверяется, а не принимается на слово -------------------
|
|
1505
|
+
# Поле covers завели утром того же дня, и тогда же честно записали: оно снимает запись с долга
|
|
1506
|
+
# ПО СЛОВУ человека. К вечеру это перестало быть теорией: запуск на настоящем ruff.toml из живого
|
|
1507
|
+
# проекта показал девятнадцать групп правил в extend-select и НЕ пойманный print() — группы T20
|
|
1508
|
+
# среди них нет. Заявка «no-print-in-prod держит наш lint» была бы ложной, а запись ушла бы из
|
|
1509
|
+
# долга. Поле, снимающее неправду из вывода, само стало бы способом её произвести.
|
|
1510
|
+
CVUP="$(mktemp -d)"
|
|
1511
|
+
( cd "$CVUP" && git init -q . && printf 'x=1\n' > a.py && printf '# вход\n' > AGENTS.md &&
|
|
1512
|
+
printf 'aqk: 1\nentry: [AGENTS.md]\ngates:\n lint: "ruff check ."\ncovers:\n lint: [no-print-in-prod]\n' > .aqk.yml ) >/dev/null 2>&1
|
|
1513
|
+
( cd "$CVUP" && printf 'extend-select = ["I","B","UP","SIM"]\n' > ruff.toml )
|
|
1514
|
+
WITHOUT=$( cd "$CVUP" && AQK_LANG=ru node "$CLI" doctor 2>&1 )
|
|
1515
|
+
( cd "$CVUP" && printf 'extend-select = ["I","B","UP","SIM","T20"]\n' > ruff.toml )
|
|
1516
|
+
WITHT20=$( cd "$CVUP" && AQK_LANG=ru node "$CLI" doctor 2>&1 )
|
|
1517
|
+
if printf '%s' "$WITHOUT" | grep -q "заявка не подтверждена" &&
|
|
1518
|
+
printf '%s' "$WITHOUT" | grep -q "T20" &&
|
|
1519
|
+
! printf '%s' "$WITHT20" | grep -q "заявка не подтверждена"; then
|
|
1520
|
+
ok "covers сверяется с кодами правил: без T20 говорит вслух, с T20 молчит"
|
|
1521
|
+
else
|
|
1522
|
+
bad "сверка заявки covers не работает" "без T20: $(printf '%s' "$WITHOUT" | grep -c 'не подтверждена'), с T20: $(printf '%s' "$WITHT20" | grep -c 'не подтверждена')"
|
|
1523
|
+
fi
|
|
1524
|
+
rm -rf "$CVUP"
|
|
1525
|
+
|
|
1526
|
+
# --- 99. краткий режим: присутствие видно, совет не надоедает -----------------
|
|
1527
|
+
# Хук pre-commit молчит на успехе — это его умолчание. Комплект, который всё держит, для
|
|
1528
|
+
# человека неотличим от невставленного: «скачал и че дальше» — дословная жалоба владельца.
|
|
1529
|
+
# Это ровно тот порок, против которого написан комплект, только у нас самих.
|
|
1530
|
+
# Проверяются три вещи сразу: строка есть всегда; совет приходит один раз, а не на каждый
|
|
1531
|
+
# прогон; переменная его выключает.
|
|
1532
|
+
BRFP="$(mktemp -d)"
|
|
1533
|
+
( cd "$BRFP" && git init -q . && printf 'x=1\n' > a.py && printf '# вход\n' > AGENTS.md &&
|
|
1534
|
+
printf '.x\n' > .gitignore && mkdir -p rules .aqk/docs && printf 'п\n' > rules/r.md &&
|
|
1535
|
+
printf 'aqk: 1\nentry: [AGENTS.md]\nrules: rules\ngates:\n ok: "true"\n' > .aqk.yml ) >/dev/null 2>&1
|
|
1536
|
+
B1=$( cd "$BRFP" && AQK_LANG=ru node "$CLI" doctor --run --min 1 --brief 2>&1 ); B1C=$?
|
|
1537
|
+
B2=$( cd "$BRFP" && AQK_LANG=ru node "$CLI" doctor --run --min 1 --brief 2>&1 )
|
|
1538
|
+
rm -f "$BRFP/.aqk/advice-shown"
|
|
1539
|
+
B3=$( cd "$BRFP" && AQK_ADVICE=0 AQK_LANG=ru node "$CLI" doctor --run --min 1 --brief 2>&1 )
|
|
1540
|
+
if [ "$B1C" -eq 0 ] &&
|
|
1541
|
+
printf '%s' "$B1" | grep -qE "^(❖ )?AQK держит" &&
|
|
1542
|
+
printf '%s' "$B1" | grep -q "поставить:" &&
|
|
1543
|
+
printf '%s' "$B2" | grep -qE "^(❖ )?AQK держит" &&
|
|
1544
|
+
! printf '%s' "$B2" | grep -q "поставить:" &&
|
|
1545
|
+
! printf '%s' "$B3" | grep -q "поставить:" &&
|
|
1546
|
+
[ "$(printf '%s\n' "$B2" | wc -l)" -le 2 ]; then
|
|
1547
|
+
ok "краткий режим: строка есть всегда, совет один раз в сутки и выключается"
|
|
1548
|
+
else
|
|
1549
|
+
bad "краткий режим ведёт себя не так" "первый: $(printf '%s' "$B1" | head -1) · второй строк: $(printf '%s\n' "$B2" | wc -l)"
|
|
1550
|
+
fi
|
|
1551
|
+
rm -rf "$BRFP"
|
|
1552
|
+
|
|
1553
|
+
# --- 100. в awk-программах гейтов нет повторителей {n} ------------------------
|
|
1554
|
+
# Часть сборок mawk не понимает повторители в регулярках. Условие с {4} не
|
|
1555
|
+
# совпадает НИКОГДА, и гейт молча выходит с нулём на собственном красном образце: ровно тот
|
|
1556
|
+
# отказ, против которого написан комплект. Поймано сборкой docker-образа 2026-09-08 — на хосте
|
|
1557
|
+
# gawk, и ни один прогон не краснел. Проверка статическая и потому дешёвая; настоящий прогон
|
|
1558
|
+
# под старым awk делает задание `image` в конвейере — там образ, в котором дефект и нашёлся.
|
|
1559
|
+
# ВАЖНО, ЧТО ИМЕННО СБОРКА: mawk 1.3.4 20240123 (ubuntu-latest, эта машина) повторители
|
|
1560
|
+
# ПОНИМАЕТ, mawk 1.3.4 20200120 (node:22-slim) — нет. Замерено 2026-09-09. Поэтому «прогнать
|
|
1561
|
+
# под mawk» ничего не значит без указания, под каким,
|
|
1562
|
+
# а прочитать исходники — можем.
|
|
1563
|
+
AWKBAD=""
|
|
1564
|
+
for G in "$ROOT"/kit/gates/*/check.sh "$ROOT"/kit/gates/_skip.sh; do
|
|
1565
|
+
[ -f "$G" ] || continue
|
|
1566
|
+
# Только строки внутри awk-программ; комментарии оболочки (#) не считаем.
|
|
1567
|
+
# Только регулярки AWK: `t ~ /…/` и образец-действие `/…/ {`. Шаблоны для grep, лежащие в
|
|
1568
|
+
# переменных оболочки, сюда не попадают намеренно — GNU grep и busybox grep повторители
|
|
1569
|
+
# понимают оба, и краснеть на них значит краснеть на нормальном укладе.
|
|
1570
|
+
HIT=$(sed 's/^[[:space:]]*#.*$//' "$G" | grep -nE '(~[[:space:]]*/|^[[:space:]]*/)' \
|
|
1571
|
+
| grep -E '\{[0-9]+(,[0-9]*)?\}' | head -2)
|
|
1572
|
+
[ -n "$HIT" ] && AWKBAD="${AWKBAD:+$AWKBAD; }$(basename "$(dirname "$G")"): $(printf '%s' "$HIT" | head -1 | cut -c1-60)"
|
|
1573
|
+
done
|
|
1574
|
+
if [ -z "$AWKBAD" ]; then
|
|
1575
|
+
ok "в проверках каталога нет повторителей {n} — они не работают под mawk"
|
|
1576
|
+
else
|
|
1577
|
+
bad "повторитель {n} в проверке: под mawk она молчит" "$AWKBAD"
|
|
1578
|
+
fi
|
|
1579
|
+
|
|
1580
|
+
# --- 101. проверка версии: раз в сутки, не в конвейере, молча при отказе ------
|
|
1581
|
+
# До этой строки комплект не делал ни одного исходящего запроса. Раз делает — обязан делать
|
|
1582
|
+
# ровно то, что о нём написано: спрашивать реестр не чаще раза в сутки и НЕ спрашивать в
|
|
1583
|
+
# конвейере. Само наличие новой версии здесь не проверяется — она зависит от реестра, а
|
|
1584
|
+
# проверка, зависящая от чужого сервера, краснеет по чужой воле. Сравнение версий проверено
|
|
1585
|
+
# модульно; здесь — то, что вокруг него.
|
|
1586
|
+
UPDP="$(mktemp -d)"
|
|
1587
|
+
( cd "$UPDP" && git init -q . && printf 'x=1\n' > a.py && printf '# вход\n' > AGENTS.md &&
|
|
1588
|
+
printf '.x\n' > .gitignore && mkdir -p rules .aqk/docs && printf 'п\n' > rules/r.md &&
|
|
1589
|
+
printf 'aqk: 1\nentry: [AGENTS.md]\nrules: rules\ngates:\n ok: "true"\n' > .aqk.yml ) >/dev/null 2>&1
|
|
1590
|
+
( cd "$UPDP" && env -u CI -u GITHUB_ACTIONS AQK_LANG=ru node "$CLI" doctor --run --min 1 --brief ) >/dev/null 2>&1
|
|
1591
|
+
ASKED=$([ -f "$UPDP/.aqk/update-checked" ] && echo да || echo нет)
|
|
1592
|
+
rm -f "$UPDP/.aqk/update-checked"
|
|
1593
|
+
( cd "$UPDP" && CI=true AQK_LANG=ru node "$CLI" doctor --run --min 1 --brief ) >/dev/null 2>&1
|
|
1594
|
+
IN_CI=$([ -f "$UPDP/.aqk/update-checked" ] && echo да || echo нет)
|
|
1595
|
+
rm -f "$UPDP/.aqk/update-checked"
|
|
1596
|
+
( cd "$UPDP" && env -u CI AQK_UPDATE=0 AQK_LANG=ru node "$CLI" doctor --run --min 1 --brief ) >/dev/null 2>&1
|
|
1597
|
+
OFF=$([ -f "$UPDP/.aqk/update-checked" ] && echo да || echo нет)
|
|
1598
|
+
# «нет сети» — тоже допустимый исход первого случая: молчание при отказе и есть требование.
|
|
1599
|
+
if [ "$IN_CI" = "нет" ] && [ "$OFF" = "нет" ]; then
|
|
1600
|
+
ok "версия спрашивается вне конвейера ($ASKED), не спрашивается в конвейере и при AQK_UPDATE=0"
|
|
1601
|
+
else
|
|
1602
|
+
bad "проверка версии спрашивает там, где не должна" "вне CI: $ASKED, в CI: $IN_CI, выключено: $OFF"
|
|
1603
|
+
fi
|
|
1604
|
+
rm -rf "$UPDP"
|
|
1605
|
+
|
|
1606
|
+
# --- 102. строка манифеста, которую разбор не понял, называется вслух ---------
|
|
1607
|
+
# Найдено случайно 2026-09-09: подсадили падающий гейт с именем «плохой», чтобы посмотреть на
|
|
1608
|
+
# вывод, — прогон вышел с НУЛЁМ. Гейт не упал: его не существовало. Разбор берёт имена только
|
|
1609
|
+
# латиницей, а строку, не подошедшую под это, выбрасывал без единого слова. Человек видит
|
|
1610
|
+
# проверку в файле, а её нет — наш класс в чистом виде, и хуже опечатки в имени поля: ту мы
|
|
1611
|
+
# называем с 2026-09-06, а эту не называли вовсе.
|
|
1612
|
+
UNPP="$(mktemp -d)"
|
|
1613
|
+
( cd "$UNPP" && git init -q . && printf '# вход\n' > AGENTS.md &&
|
|
1614
|
+
printf 'aqk: 1\nentry: [AGENTS.md]\nlang: ru\ngates:\n ok: "true"\n плохой: "false"\n' > .aqk.yml ) >/dev/null 2>&1
|
|
1615
|
+
UNP=$( cd "$UNPP" && node "$CLI" doctor 2>&1 )
|
|
1616
|
+
( cd "$UNPP" && printf 'aqk: 1\nentry: [AGENTS.md]\nlang: ru\ngates:\n ok: "true"\n bad: "false"\n' > .aqk.yml )
|
|
1617
|
+
OKM=$( cd "$UNPP" && node "$CLI" doctor 2>&1 )
|
|
1618
|
+
if printf '%s' "$UNP" | grep -q "НЕ ДЕЙСТВУЕТ" &&
|
|
1619
|
+
printf '%s' "$UNP" | grep -q "строка 6" &&
|
|
1620
|
+
! printf '%s' "$OKM" | grep -q "НЕ ДЕЙСТВУЕТ"; then
|
|
1621
|
+
ok "непонятая строка манифеста называется с номером, понятая — молчит"
|
|
1622
|
+
else
|
|
1623
|
+
bad "потерянная строка манифеста не названа" "$(printf '%s' "$UNP" | grep -c 'НЕ ДЕЙСТВУЕТ') на кириллице, $(printf '%s' "$OKM" | grep -c 'НЕ ДЕЙСТВУЕТ') на латинице"
|
|
1624
|
+
fi
|
|
1625
|
+
rm -rf "$UNPP"
|
|
1626
|
+
|
|
1627
|
+
# --- 103. vitals: отказ роняет, выбор и незнание — нет ------------------------
|
|
1628
|
+
# `doctor` смотрит на репозиторий, `prove` — на гейты, `context` — на состояние. На саму
|
|
1629
|
+
# обвязку не смотрел никто: стоят ли инструменты объявленных гейтов, прописан ли хук в
|
|
1630
|
+
# .git/hooks НА САМОМ ДЕЛЕ. Раньше это выяснялось красным гейтом посреди коммита.
|
|
1631
|
+
# Первая версия ставила крест хуку, которого нет, — и на нашем же репозитории вышло два креста
|
|
1632
|
+
# за сознательное решение (pre-commit локально не ставим, гоняем в CI). Команда, которая кричит
|
|
1633
|
+
# «сломано» про выбор, перестаёт читаться вместе с настоящими отказами. Проверяются оба конца.
|
|
1634
|
+
VITP="$(mktemp -d)"
|
|
1635
|
+
( cd "$VITP" && git init -q . && printf '# вход\n' > AGENTS.md &&
|
|
1636
|
+
printf 'aqk: 1\nentry: [AGENTS.md]\nlang: ru\ngates:\n ok: "true"\n' > .aqk.yml ) >/dev/null 2>&1
|
|
1637
|
+
VOK=$( cd "$VITP" && AQK_LANG=ru node "$CLI" vitals 2>&1 ); VOK_C=$?
|
|
1638
|
+
( cd "$VITP" && printf 'aqk: 1\nentry: [AGENTS.md]\nlang: ru\ngates:\n lint: "инструментакоторогонет ."\n' > .aqk.yml )
|
|
1639
|
+
VBAD=$( cd "$VITP" && AQK_LANG=ru node "$CLI" vitals 2>&1 ); VBAD_C=$?
|
|
1640
|
+
if [ "$VOK_C" -eq 0 ] && [ "$VBAD_C" -eq 1 ] &&
|
|
1641
|
+
printf '%s' "$VBAD" | grep -q "НЕ НАЙДЕНЫ" &&
|
|
1642
|
+
printf '%s' "$VOK" | grep -q "хук pre-commit"; then
|
|
1643
|
+
ok "vitals: пропавший инструмент роняет прогон, отсутствие хука — нет"
|
|
1644
|
+
else
|
|
1645
|
+
bad "vitals путает отказ с выбором" "исправный код $VOK_C, сломанный код $VBAD_C"
|
|
1646
|
+
fi
|
|
1647
|
+
rm -rf "$VITP"
|
|
1648
|
+
|
|
1649
|
+
# --- 106. совещательный гейт назван всегда и не роняет прогон никогда ---------
|
|
1650
|
+
# Два отказа, найденные 2026-09-09 сверкой документации с кодом. ПЕРВЫЙ: зелёный совещательный
|
|
1651
|
+
# печатался обычной галочкой — гейт, который уронить сборку НЕ МОЖЕТ, по выводу неотличим от
|
|
1652
|
+
# того, который может, и список `advisory:` был виден только в день, когда он покраснел. Ровно
|
|
1653
|
+
# та тишина, против которой написан стандарт, только про сам прибор. ВТОРОЙ: при `--since` гейт,
|
|
1654
|
+
# чей вывод не содержит путей, шёл через ветку «сузить нечем» с безусловным failed++ — то есть
|
|
1655
|
+
# совещательный ронял прогон. Проверено сравнением с кодом до починки: было 1, стало 0.
|
|
1656
|
+
# Команда гейта — ФАЙЛ, а не строка с `;`. На windows `spawnSync(shell: true)` запускает
|
|
1657
|
+
# cmd.exe, а не sh: там `echo x; exit 1` печатается целиком и выходит с НУЛЁМ. Первая версия
|
|
1658
|
+
# этой проверки так и провалилась в конвейере на windows — фикстура, а не код. Заодно урок про
|
|
1659
|
+
# сам тест: утверждение «зелёный помечен» искало подстроку «совещательный», которая есть и в
|
|
1660
|
+
# пометке КРАСНОГО совещательного, — то есть проходило бы и при полном отсутствии починки.
|
|
1661
|
+
# Теперь ищется текст, который бывает только на зелёной строке.
|
|
1662
|
+
ADVP="$(mktemp -d)"
|
|
1663
|
+
(
|
|
1664
|
+
cd "$ADVP" && git init -q . && git config user.email a@b && git config user.name a
|
|
1665
|
+
printf '# вход\n' > AGENTS.md && mkdir -p r && printf 'x\n' > r/a.md
|
|
1666
|
+
printf '#!/bin/sh\necho "вердикт без путей"\nexit 1\n' > noscope.sh
|
|
1667
|
+
printf '#!/bin/sh\nexit 0\n' > quiet.sh
|
|
1668
|
+
printf 'aqk: 1\nentry: [AGENTS.md]\nrules: r\ngates:\n adv: "bash quiet.sh"\nadvisory:\n - adv\n' > .aqk.yml
|
|
1669
|
+
git add -A && git commit -qm "Сделано: основа. Не уверен: ничего"
|
|
1670
|
+
) >/dev/null 2>&1
|
|
1671
|
+
ADVBASE=$( cd "$ADVP" && git rev-parse --abbrev-ref HEAD )
|
|
1672
|
+
( cd "$ADVP" && git checkout -qb feat && printf 'y\n' > r/b.md && git add -A &&
|
|
1673
|
+
git commit -qm "Сделано: файл. Не уверен: ничего" ) >/dev/null 2>&1
|
|
1674
|
+
ADVGREEN=$( cd "$ADVP" && AQK_LANG=ru node "$CLI" doctor --run --min 1 2>&1 )
|
|
1675
|
+
( cd "$ADVP" && printf 'aqk: 1\nentry: [AGENTS.md]\nrules: r\ngates:\n adv: "bash noscope.sh"\nadvisory:\n - adv\n' > .aqk.yml )
|
|
1676
|
+
( cd "$ADVP" && node "$CLI" doctor --run --min 1 --since "$ADVBASE" >/dev/null 2>&1 ); ADV_C=$?
|
|
1677
|
+
( cd "$ADVP" && printf 'aqk: 1\nentry: [AGENTS.md]\nrules: r\ngates:\n adv: "bash noscope.sh"\n' > .aqk.yml )
|
|
1678
|
+
( cd "$ADVP" && node "$CLI" doctor --run --min 1 --since "$ADVBASE" >/dev/null 2>&1 ); BLOCK_C=$?
|
|
1679
|
+
if printf '%s' "$ADVGREEN" | grep -q "уронить прогон не может" && [ "$ADV_C" -eq 0 ] && [ "$BLOCK_C" -eq 1 ]; then
|
|
1680
|
+
ok "совещательный назван и на зелёном, и не роняет прогон даже когда сузить нечем"
|
|
1681
|
+
else
|
|
1682
|
+
bad "совещательный гейт неотличим или роняет прогон" "зелёный помечен: $(printf '%s' "$ADVGREEN" | grep -c 'уронить прогон не может'), код совещательного $ADV_C, код блокирующего $BLOCK_C"
|
|
1683
|
+
fi
|
|
1684
|
+
rm -rf "$ADVP"
|
|
1685
|
+
|
|
1686
|
+
# --- 107. prove пропускает запись без её программы, а не обвиняет её ----------
|
|
1687
|
+
# ТО ЖЕ, ЧТО НАШЁЛ ВТОРОЙ ПОЛЬЗОВАТЕЛЬ, ТОЛЬКО ДРУГОЙ ДВЕРЬЮ. Переносимый рецепт бывает обёрткой
|
|
1688
|
+
# вокруг готового инструмента: первое слово команды тогда `bash`, и по нему не видно, чего не
|
|
1689
|
+
# хватает. Без программы обёртка краснеет на ОБОИХ образцах, и prove объявлял исправный гейт
|
|
1690
|
+
# сломанным — «краснеет на исправном коде». От prove зависят ступень AQK-2 и значок, то есть
|
|
1691
|
+
# отсутствие чужой программы отбирало у проекта уровень. Обвинение вместо диагноза.
|
|
1692
|
+
# Приёмка (gates.sh) и мутационная проверка поле `requires` читают давно; prove — не читал.
|
|
1693
|
+
# Проверено 2026-09-09 на своём же репозитории: не стояли slopcheck и zizmor, prove дал два
|
|
1694
|
+
# обвинения и код 1. Проверяются ОБА конца: без поля — обвинение и код 1, с полем — пропуск и 0.
|
|
1695
|
+
PRVP="$(mktemp -d)"
|
|
1696
|
+
(
|
|
1697
|
+
cd "$PRVP" && git init -q . && printf '# вход\n' > AGENTS.md && mkdir -p r s/wrap/red s/wrap/green
|
|
1698
|
+
printf 'x\n' > s/wrap/red/a.txt && printf 'x\n' > s/wrap/green/a.txt
|
|
1699
|
+
# Обёртка вокруг программы, которой на машине нет: краснеет на обоих образцах.
|
|
1700
|
+
printf '#!/bin/sh\nпрограммыкоторойнет "$1" || exit 2\n' > wrap.sh
|
|
1701
|
+
# Настоящая проверка: prove требует, чтобы хоть один гейт был ДОКАЗАН, иначе проект,
|
|
1702
|
+
# у которого всё недоказуемо, получал бы уровень ни за что. Команда обязана кончаться
|
|
1703
|
+
# каталогом — иначе подставлять образец некуда, и гейт сам станет недоказуемым.
|
|
1704
|
+
printf '#!/bin/sh\ngrep -rq НЕЛЬЗЯ "$1" && exit 1\nexit 0\n' > real.sh
|
|
1705
|
+
printf 'aqk: 1\nentry: [AGENTS.md]\nrules: r\nsamples: s\ngates:\n wrap: "sh wrap.sh ."\n real: "sh real.sh ."\n' > .aqk.yml
|
|
1706
|
+
) >/dev/null 2>&1
|
|
1707
|
+
# Второй гейт обязан быть доказуемым: prove требует хотя бы одного доказанного.
|
|
1708
|
+
( cd "$PRVP" && mkdir -p s/real/red s/real/green &&
|
|
1709
|
+
printf 'НЕЛЬЗЯ\n' > s/real/red/a.txt && printf 'можно\n' > s/real/green/a.txt )
|
|
1710
|
+
PRV_NOREQ=$( cd "$PRVP" && AQK_LANG=ru node "$CLI" prove 2>&1 ); PRV_NOREQ_C=$?
|
|
1711
|
+
( cd "$PRVP" && printf 'intent: обёртка\nrequires: программыкоторойнет\nrecipes:\n any: "sh wrap.sh {dir}"\n' > s/wrap/gate.yml )
|
|
1712
|
+
PRV_REQ=$( cd "$PRVP" && AQK_LANG=ru node "$CLI" prove 2>&1 ); PRV_REQ_C=$?
|
|
1713
|
+
# Тот же вопрос задаёт `vitals`, и до 2026-09-09 он отвечал на него иначе: печатал «все
|
|
1714
|
+
# инструменты на месте», потому что смотрел только на ПЕРВОЕ СЛОВО команды — `sh`, который есть
|
|
1715
|
+
# всегда. Две команды об одном репозитории говорили разное; читатель верил той, что зеленее.
|
|
1716
|
+
PRV_VIT=$( cd "$PRVP" && AQK_LANG=ru node "$CLI" vitals 2>&1 )
|
|
1717
|
+
if [ "$PRV_NOREQ_C" -ne 0 ] && [ "$PRV_REQ_C" -eq 0 ] &&
|
|
1718
|
+
printf '%s' "$PRV_REQ" | grep -q "НЕ ПРОВЕРЕНА здесь" &&
|
|
1719
|
+
printf '%s' "$PRV_VIT" | grep -q "программыкоторойнет"; then
|
|
1720
|
+
ok "prove и vitals одинаково видят программу из requires: пропуск, а не обвинение"
|
|
1721
|
+
else
|
|
1722
|
+
bad "prove путает «нечем проверить» со «сломан», либо vitals её не видит" \
|
|
1723
|
+
"без поля код $PRV_NOREQ_C, с полем код $PRV_REQ_C, vitals назвал: $(printf '%s' "$PRV_VIT" | grep -c 'программыкоторойнет')"
|
|
1724
|
+
fi
|
|
1725
|
+
rm -rf "$PRVP"
|
|
1726
|
+
|
|
1727
|
+
# --- 104. опись в AGENTS.md покрывает все исходники ---------------------------
|
|
1728
|
+
# AGENTS.md — первое, что читает агент, и раздел «Где что лежит» для него карта. Карта,
|
|
1729
|
+
# отставшая от дерева, хуже её отсутствия: агент уверен, что видел всё. Проверено 2026-09-09 —
|
|
1730
|
+
# в описи не было восьми файлов из четырнадцати добавленных за неделю (`brief.mjs`, `banner.mjs`
|
|
1731
|
+
# и шесть `units-*.mjs`), и заметить это можно было только сверкой руками. Тот же класс, что
|
|
1732
|
+
# «гейт объявлен и не существует»: расхождение молчит.
|
|
1733
|
+
MISSING=""
|
|
1734
|
+
for F in "$ROOT"/tool/lib/*.mjs "$ROOT"/tool/commands/*.mjs "$ROOT"/tool/selfcheck/*; do
|
|
1735
|
+
B=$(basename "$F")
|
|
1736
|
+
grep -qF "$B" "$ROOT/AGENTS.md" || MISSING="$MISSING $B"
|
|
1737
|
+
done
|
|
1738
|
+
if [ -z "$MISSING" ]; then
|
|
1739
|
+
ok "опись «Где что лежит» называет каждый исходник"
|
|
1740
|
+
else
|
|
1741
|
+
bad "AGENTS.md не называет файлы" "$MISSING"
|
|
1742
|
+
fi
|
|
1743
|
+
|
|
1744
|
+
# --- 105. два README не расходятся структурно --------------------------------
|
|
1745
|
+
# AGENTS.md требует, чтобы README и его перевод не расходились, и до сегодня требование не
|
|
1746
|
+
# работало: на 2026-09-09 в английском было 23 раздела, в русском 22 — «Первый прогон на
|
|
1747
|
+
# настоящем проекте» отсутствовал целиком, а «Как ввести правило» стояло в другом разделе.
|
|
1748
|
+
# Читатель одного языка получал инструкцию, которой у читателя другого не было. Сверяется
|
|
1749
|
+
# ПОСЛЕДОВАТЕЛЬНОСТЬ уровней, а не тексты: заголовки на разных языках сравнивать нельзя.
|
|
1750
|
+
# Строки внутри ``` не считаются: в примере .aqkignore есть строка, начинающаяся с #.
|
|
1751
|
+
levels() {
|
|
1752
|
+
awk '/^```/ { inf = !inf; next } !inf && /^#/ { sub(/[^#].*/, ""); print }' "$1"
|
|
1753
|
+
}
|
|
1754
|
+
if [ "$(levels "$ROOT/README.md")" = "$(levels "$ROOT/README.ru.md")" ]; then
|
|
1755
|
+
ok "README.md и README.ru.md несут одни и те же разделы ($(levels "$ROOT/README.md" | wc -l | tr -d ' '))"
|
|
1756
|
+
else
|
|
1757
|
+
bad "README и перевод разошлись структурно" \
|
|
1758
|
+
"англ. $(levels "$ROOT/README.md" | wc -l | tr -d ' '), рус. $(levels "$ROOT/README.ru.md" | wc -l | tr -d ' ')"
|
|
1759
|
+
fi
|
|
1760
|
+
|
|
1761
|
+
# --- 108. init доходит до конца, когда дом недоступен для записи ---------------
|
|
1762
|
+
# НАШЁЛ КОНВЕЙЕР, А НЕ ПРОГОН. Одноразовая пометка «просьбу про звезду уже показывали» пишется
|
|
1763
|
+
# в $HOME/.config/aqk. В контейнере, запущенном `--user 1001:127`, у этого uid нет записи в
|
|
1764
|
+
# /etc/passwd, homedir() даёт «/», и mkdir падает с EACCES на «/.config» — роняя ВЕСЬ init.
|
|
1765
|
+
# То есть любой, кто набрал команду из нашей же документации по docker, получал падение вместо
|
|
1766
|
+
# установки. Локально не воспроизводилось по случайности: uid разработчика 1000 совпадает с
|
|
1767
|
+
# пользователем `node` в образе, у которого дом есть; на раннере uid 1001 — не совпадает.
|
|
1768
|
+
# Удобство не имеет права ронять работу команды. Молчать о нём тоже нельзя — это swallowed-error.
|
|
1769
|
+
HOMEP="$(mktemp -d)"; HOMEH="$(mktemp -d)"
|
|
1770
|
+
chmod 500 "$HOMEH" 2>/dev/null
|
|
1771
|
+
# На windows chmod прав не отнимает: там эта проверка ничего не проверяет и говорит об этом,
|
|
1772
|
+
# а не зеленеет молча. Тот же приём, что «НЕ ПРОВЕРЕНА здесь» в приёмке каталога.
|
|
1773
|
+
if ( : > "$HOMEH/проба" ) 2>/dev/null; then
|
|
1774
|
+
rm -f "$HOMEH/проба"
|
|
1775
|
+
ok "init при недоступном доме — НЕ ПРОВЕРЕНА здесь: chmod прав не отнимает"
|
|
1776
|
+
else
|
|
1777
|
+
( cd "$HOMEP" && git init -q . ) >/dev/null 2>&1
|
|
1778
|
+
HOMEOUT=$( cd "$HOMEP" && HOME="$HOMEH" AQK_LANG=ru node "$CLI" init 2>&1 ); HOME_C=$?
|
|
1779
|
+
if [ "$HOME_C" -eq 0 ] && [ -f "$HOMEP/.aqk.yml" ] &&
|
|
1780
|
+
printf '%s' "$HOMEOUT" | grep -q "запомнить не удалось"; then
|
|
1781
|
+
ok "init доходит до конца при недоступном доме и называет, чего не смог"
|
|
1782
|
+
else
|
|
1783
|
+
bad "init падает или молчит, когда дом недоступен для записи" \
|
|
1784
|
+
"код $HOME_C, манифест $([ -f "$HOMEP/.aqk.yml" ] && echo есть || echo нет)"
|
|
1785
|
+
fi
|
|
1786
|
+
fi
|
|
1787
|
+
chmod 700 "$HOMEH" 2>/dev/null; rm -rf "$HOMEP" "$HOMEH"
|
|
1788
|
+
|
|
1200
1789
|
# --- итог -------------------------------------------------------------------
|
|
1201
1790
|
printf '\n'
|
|
1202
1791
|
if [ "$FAIL" -eq 0 ]; then
|
|
@@ -1205,5 +1794,5 @@ else
|
|
|
1205
1794
|
printf ' \033[31mпровалено: %s из %s\033[0m\n\n' "$FAIL" "$((PASS + FAIL))"
|
|
1206
1795
|
fi
|
|
1207
1796
|
|
|
1208
|
-
printf ' \033[2mне покрыто:
|
|
1797
|
+
printf ' \033[2mне покрыто: СОДЕРЖАНИЕ документов (сверяется опись и структура, не текст),\n установка с GitHub через npx\033[0m\n\n'
|
|
1209
1798
|
exit "$FAIL"
|