agent-quality-kit 0.15.0 → 0.16.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 +61 -15
- package/README.ru.md +62 -15
- package/kit/docs/ai/index.md +1 -0
- package/kit/docs/ai/operational-gates.md +275 -0
- package/kit/gates/_target.sh +53 -0
- package/kit/gates/ci-actually-fails/check.sh +18 -3
- package/kit/gates/ci-actually-fails/green/.github/workflows/ci.yml +10 -0
- package/kit/gates/entry-commands-exist/check.sh +88 -12
- package/kit/gates/hook-actually-fires/README.md +12 -0
- package/kit/gates/hook-actually-fires/check.sh +66 -6
- package/kit/gates/hook-actually-fires/gate.yml +2 -2
- package/kit/gates/hook-actually-fires/green/.claude/hooks/auto-format.sh +3 -0
- package/kit/gates/hook-actually-fires/green/.claude/hooks/block-dangerous.sh +3 -0
- package/kit/gates/hook-actually-fires/green/.claude/hooks/done.sh +3 -0
- package/kit/gates/hook-actually-fires/green/.claude/hooks/idle.sh +3 -0
- package/kit/gates/hook-actually-fires/green/.claude/hooks/prompt.sh +3 -0
- package/kit/gates/hook-actually-fires/green/.claude/hooks/session.mjs +1 -0
- package/kit/gates/hook-actually-fires/green/.claude/hooks/stop-gate.sh +3 -0
- package/kit/gates/hook-actually-fires/green/.claude/settings.json +12 -0
- package/kit/gates/test-not-adjusted/README.md +31 -0
- package/llms.txt +26 -7
- package/package.json +1 -1
- package/tool/commands/context.mjs +3 -1
- package/tool/commands/doctor-catalog.mjs +35 -10
- package/tool/commands/feedback.mjs +75 -1
- package/tool/commands/gates.mjs +12 -6
- package/tool/commands/report.mjs +19 -3
- package/tool/commands/vitals.mjs +9 -3
- package/tool/i18n/en-docs.mjs +18 -2
- package/tool/i18n/en-gates.mjs +9 -1
- package/tool/i18n/en.mjs +24 -2
- package/tool/i18n/ru-docs.mjs +17 -2
- package/tool/i18n/ru-gates.mjs +9 -1
- package/tool/i18n/ru.mjs +20 -2
- package/tool/lib/adopt.mjs +58 -4
- package/tool/lib/core.mjs +42 -6
- package/tool/lib/execution.mjs +32 -1
- package/tool/lib/manifest.mjs +39 -13
- package/tool/lib/prove.mjs +3 -3
- package/tool/lib/run.mjs +25 -6
- package/tool/selfcheck/smoke/_fixture.mjs +13 -1
- package/tool/selfcheck/smoke/feedback-send.test.mjs +87 -0
- package/tool/selfcheck/smoke/first-run.test.mjs +67 -3
- package/tool/selfcheck/smoke/preflight.test.mjs +83 -0
- package/tool/selfcheck/smoke/verdict.test.mjs +50 -4
- package/tool/selfcheck/smoke/version-sync.test.mjs +140 -0
- package/tool/selfcheck/smoke.sh +106 -4
- package/tool/selfcheck/units-execution.mjs +37 -1
- package/tool/selfcheck/units-level.mjs +41 -1
- package/tool/selfcheck/units-repo.mjs +75 -0
- package/tool/selfcheck/units-vitals.mjs +27 -0
- package/kit/gates/entry-links-exist/README.md +0 -27
- package/kit/gates/entry-links-exist/check.sh +0 -33
- package/kit/gates/entry-links-exist/gate.yml +0 -17
- package/kit/gates/entry-links-exist/green/AGENTS.md +0 -10
- package/kit/gates/entry-links-exist/green/rules/general.md +0 -3
- package/kit/gates/entry-links-exist/red/AGENTS.md +0 -3
- package/kit/gates/no-phantom-package/README.md +0 -84
- package/kit/gates/no-phantom-package/check.sh +0 -168
- package/kit/gates/no-phantom-package/gate.yml +0 -20
- package/kit/gates/no-phantom-package/green/AGENTS.md +0 -15
- package/kit/gates/no-phantom-package/red/AGENTS.md +0 -15
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env sh
|
|
2
|
-
# Каждая ссылка на локальный файл из markdown-файлов каталога обязана вести на
|
|
3
|
-
# существующий файл. Ссылка в никуда — это документ, утверждающий защиту,
|
|
4
|
-
# которой нет: планирование опирается на неё и ломается молча.
|
|
5
|
-
DIR="${1:-.}"
|
|
6
|
-
MISS=0
|
|
7
|
-
for MD in "$DIR"/*.md; do
|
|
8
|
-
[ -f "$MD" ] || continue
|
|
9
|
-
# вытащить цели ссылок вида [текст](путь)
|
|
10
|
-
# Строки со вставками в обратных кавычках чистятся ДО разбора: «`[name](url)`» — это пример
|
|
11
|
-
# оформления ссылки, а не ссылка. Замер 2026-09-08: в uv/STYLE.md именно так и было, и гейт
|
|
12
|
-
# требовал создать файл с именем «url».
|
|
13
|
-
TARGETS=$(sed 's/`[^`]*`//g' "$MD" | sed -n 's/.*](\([^)]*\)).*/\1/p')
|
|
14
|
-
for T in $TARGETS; do
|
|
15
|
-
# автоссылки бывают обёрнуты как <(https://...)> — искать http где угодно внутри,
|
|
16
|
-
# не только в начале строки.
|
|
17
|
-
case "$T" in *http://*|*https://*|\#*|mailto:*) continue ;; esac
|
|
18
|
-
T=${T%%#*}
|
|
19
|
-
[ -z "$T" ] && continue
|
|
20
|
-
# Путь, кончающийся косой чертой, — адрес страницы опубликованного сайта, а не файл на
|
|
21
|
-
# диске. Так ссылаются mkdocs, docusaurus и jekyll: `[руководство](tutorial/#install)`
|
|
22
|
-
# работает у читателя и не существует в репозитории. Найдено замером по fastapi — гейт
|
|
23
|
-
# объявлял битой рабочую ссылку из их README. Проверять такие адреса умеют lychee и
|
|
24
|
-
# markdown-link-check: они ходят в сеть, а мы смотрим только на диск.
|
|
25
|
-
case "$T" in */) continue ;; esac
|
|
26
|
-
if [ ! -e "$DIR/$T" ]; then
|
|
27
|
-
echo "$MD: ссылка в никуда — $T"
|
|
28
|
-
echo " почини: создай файл или убери ссылку. Документ, обещающий несуществующее, хуже отсутствующего."
|
|
29
|
-
MISS=1
|
|
30
|
-
fi
|
|
31
|
-
done
|
|
32
|
-
done
|
|
33
|
-
exit $MISS
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
# Запись каталога AQK. Норма и все поля — kit/gates/README.md.
|
|
2
|
-
# Читается программой; всё, что нельзя выполнить, живёт в README.md рядом.
|
|
3
|
-
|
|
4
|
-
intent: файлы, на которые ссылается точка входа, существуют на диске
|
|
5
|
-
intent_en: files the entry point links to actually exist on disk
|
|
6
|
-
|
|
7
|
-
# Когда запись показывается человеку. Отсутствие триггера сделало бы её шумом
|
|
8
|
-
# для тех, кого она не касается.
|
|
9
|
-
trigger:
|
|
10
|
-
always: true
|
|
11
|
-
|
|
12
|
-
# Команда-арбитр под каждый стек. {dir} — каталог, который проверяют.
|
|
13
|
-
# `any` — команда без зависимостей, работает везде, где есть sh и grep.
|
|
14
|
-
recipes:
|
|
15
|
-
any: bash {gate}/check.sh {dir}
|
|
16
|
-
|
|
17
|
-
proof: incidents/README.md — «2026-08-24 документ четыре недели утверждал защиту, которой не было»
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
# Свод правил
|
|
2
|
-
|
|
3
|
-
Стандарты: [общие правила](rules/general.md).
|
|
4
|
-
Внешняя ссылка: [semver](https://semver.org).
|
|
5
|
-
Автоссылка в скобках, как в CHANGELOG.md gin: [#1](<(https://example.com/pull/1)>).
|
|
6
|
-
Ссылка на страницу сайта документации: [руководство](tutorial/#install) — путь опубликованного
|
|
7
|
-
сайта, а не файл на диске. Найдено замером по fastapi: `[installation guide](tutorial/#install-fastapi)`
|
|
8
|
-
в README читалась как битая, хотя на сайте работает. Так ссылаются mkdocs, docusaurus и jekyll.
|
|
9
|
-
|
|
10
|
-
Пример оформления ссылки: `[name](url)` — это пример, а не ссылка.
|
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
# Пакета с таким именем нет в реестре
|
|
2
|
-
|
|
3
|
-
**Намерение.** Агент, не знающий инструмента, придумывает правдоподобное имя: `reactCodemodHelper`,
|
|
4
|
-
`eslint-plugin-async-safe`, `@types/fetch-retry`. Имя попадает в `AGENTS.md`, в `SKILL.md`, в
|
|
5
|
-
пример из README. Дальше его читает следующий агент — человек или машина — и выполняет
|
|
6
|
-
`npm install`. Если к тому времени имя занято, в проект приезжает чужой код.
|
|
7
|
-
|
|
8
|
-
**Какой отказ это поймало.** README самого `slopcheck` открывается историей: модель написала
|
|
9
|
-
команду `npx` с именем `react-codeshift` в 47 файлах, пакета не существовало, кто-то его
|
|
10
|
-
зарегистрировал, и 237 репозиториев уже на него ссылались. Имя намеренно оторвано здесь от слова
|
|
11
|
-
`npx`: иначе эта строка стала бы находкой самой записи, а вердикт на `main` зависел бы от того,
|
|
12
|
-
не снимет ли реестр захваченный пакет с публикации. Красный образец выбирается так, чтобы его
|
|
13
|
-
нельзя было погасить снаружи, — и текст вокруг него тоже. Мы проверили этот пакет 2026-09-08:
|
|
14
|
-
|
|
15
|
-
```
|
|
16
|
-
react-codeshift 1.0.0 создан 2026-01-14 maintainer: debugducky
|
|
17
|
-
```
|
|
18
|
-
|
|
19
|
-
Доля: по [USENIX Security 2025](https://arxiv.org/abs/2406.10279) около 20% сгенерированного
|
|
20
|
-
кода ссылается на несуществующие пакеты, и 58% выдуманных имён повторяются от запроса к
|
|
21
|
-
запросу — то есть предсказуемы для того, кто захочет их занять.
|
|
22
|
-
|
|
23
|
-
**Почему машина, а не внимательность.** Отличить `jscodeshift` от `reactCodemodHelper` глазами
|
|
24
|
-
нельзя: оба выглядят как настоящие. Единственный арбитр — реестр.
|
|
25
|
-
|
|
26
|
-
**Готовый аналог есть, и мы его зовём.**
|
|
27
|
-
[`slopcheck`](https://github.com/mattschaller/slopcheck) (npm, MIT, ноль зависимостей) достаёт
|
|
28
|
-
имена из команд установки в `.md`, `.mdc`, `.yml`, `.yaml`, `.json`, `.cursorrules` и сверяет с
|
|
29
|
-
`registry.npmjs.org`. Своего разбора мы не писали. Обёртка отвечает за три вещи, которых он не
|
|
30
|
-
делает: какие файлы ему дать (иначе он находит наши собственные образцы), что считать браком и
|
|
31
|
-
что делать, когда реестр не ответил.
|
|
32
|
-
|
|
33
|
-
**Почему обёртка, а не прямой вызов.** Без сети `slopcheck` печатает `? имя — validation error`
|
|
34
|
-
и выходит **с нулём**. Для гейта это худший из возможных ответов: сборка зелёная, а не проверено
|
|
35
|
-
ничего — ровно та тишина, ради запрета которой существует весь стандарт. Обёртка в этом случае
|
|
36
|
-
выходит с кодом **2** и говорит «проверка не состоялась», отдельно от вердикта «брак».
|
|
37
|
-
Тот же код — если счётчик находок ненулевой, а разобрать их не вышло: значит `slopcheck` сменил
|
|
38
|
-
формат вывода, и молчать об этом нельзя. И тот же — если блока со счётчиками в ответе не нашлось
|
|
39
|
-
вовсе: так будет, если он начнёт печатать JSON одной строкой. Эту ветку нашли прогоном
|
|
40
|
-
подставного вывода **после** того, как написали «формат стабилен»: без неё все счётчики
|
|
41
|
-
оставались нулями и гейт выходил с нулём, не проверив ничего.
|
|
42
|
-
|
|
43
|
-
**Образцы.** В `red/AGENTS.md` кодмод вызван именем `reactCodemodHelper`. Имя выбрано не наугад: `npm`
|
|
44
|
-
запрещает заглавные буквы в именах новых пакетов, и это подтверждает та же библиотека, которой
|
|
45
|
-
пользуется реестр:
|
|
46
|
-
|
|
47
|
-
```
|
|
48
|
-
$ node -e "console.log(require('validate-npm-package-name')('reactCodemodHelper'))"
|
|
49
|
-
validForNewPackages: false errors: [ 'name can no longer contain capital letters' ]
|
|
50
|
-
```
|
|
51
|
-
|
|
52
|
-
Значит образец не сможет протухнуть: занять это имя нельзя никому, а выглядит оно как настоящая
|
|
53
|
-
галлюцинация — модели постоянно пишут camelCase. `green/AGENTS.md` — тот же файл, где кодмод
|
|
54
|
-
назван настоящим именем `jscodeshift`. Второй пакет, `prettier`, стоит в обоих: он показывает,
|
|
55
|
-
что зелёным гейт становится не от пустоты.
|
|
56
|
-
|
|
57
|
-
**Чего НЕ ловит.**
|
|
58
|
-
|
|
59
|
-
- **Пакет, который сквоттер уже зарегистрировал.** Проверка спрашивает у реестра только «есть
|
|
60
|
-
ли», и гаснет ровно в тот момент, когда становится опасно: `react-codeshift` сегодня зелёный.
|
|
61
|
-
Это предел приёма, а не недоделка — «есть ли пакет» и «чей он» разные вопросы, и второй
|
|
62
|
-
решают Socket и Snyk, которых мы не дублируем. Смысл записи в другом: до регистрации проходит
|
|
63
|
-
время, и большинство выдуманных имён так и остаются свободными.
|
|
64
|
-
- **Только npm.** PyPI, crates.io, Go-модули не проверяются: `slopcheck` их не умеет, а писать
|
|
65
|
-
своё — заводить второй разбор команд установки. Питоновский проект, где агент придумал имя
|
|
66
|
-
пакета, эта запись не прикроет.
|
|
67
|
-
- **Имя из прозы, принятое за пакет.** Разбор берёт слово после `npm i`/`npx`/`yarn add` и
|
|
68
|
-
спотыкается о текст, где эти слова стоят не как команда. Проверено на README самого
|
|
69
|
-
`slopcheck`: слово `Commands`, стоящее в прозе после имени команды, даёт находку
|
|
70
|
-
`Commands — not found on npm`. На
|
|
71
|
-
нашем репозитории — 89 файлов, 9 имён — ложных нет, но на чужом такое встретится. Первым,
|
|
72
|
-
кого эта запись покрасила, был её собственный README: имена образцов стояли там рядом с
|
|
73
|
-
`npx`. Текст переписан, проверка — нет. Ослепить её на README записей каталога значило бы
|
|
74
|
-
перестать видеть настоящие команды установки, которых там хватает.
|
|
75
|
-
- **Пакет, названный в исходнике, а не в документации.** `import` из несуществующего модуля —
|
|
76
|
-
предмет сборки, она об этом скажет сама.
|
|
77
|
-
- **Сеть.** Запись — единственная в каталоге, которой нужен интернет. В конвейере без выхода
|
|
78
|
-
наружу она честно выйдет с кодом 2, а не соврёт зелёным. Тем же кодом отвечает ответ реестра
|
|
79
|
-
«слишком часто» (HTTP 429): снаружи он неотличим от обрыва связи, и сообщение называет обе
|
|
80
|
-
причины, а не выбирает одну наугад.
|
|
81
|
-
- **Свой предел времени.** `doctor` даёт гейту 300 секунд, а здесь каждое имя — запрос наружу
|
|
82
|
-
(до трёх попыток по десять секунд, по десять имён разом). Проект с сотнями команд установки
|
|
83
|
-
при медленном реестре упрётся в предел и будет показан как `timeout` — не как «проверка не
|
|
84
|
-
состоялась». Эту разницу изнутри проверки не выразить: её съедает тот, кто её обрывает.
|
|
@@ -1,168 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env sh
|
|
2
|
-
# Имена пакетов, названные в документации, сверяются с реестром npm.
|
|
3
|
-
#
|
|
4
|
-
# ЗАЧЕМ. Агент, не знающий инструмента, придумывает правдоподобное имя. Дальше его читает
|
|
5
|
-
# другой агент и выполняет `npm install` — и если имя к тому времени кем-то занято, в проект
|
|
6
|
-
# приезжает чужой код. Обычные проверки цепочки поставок смотрят на пакеты, которые ЕСТЬ;
|
|
7
|
-
# здесь опасен как раз тот, которого пока нет.
|
|
8
|
-
#
|
|
9
|
-
# Работу делает slopcheck (MIT, ноль зависимостей). Мы отвечаем за три вещи, которых он не
|
|
10
|
-
# делает: какие файлы ему дать, что считать браком и что делать, когда ответа не было.
|
|
11
|
-
# Каждая ветка «ответа не было» кончается кодом 2 и словами «проверка не состоялась»: у
|
|
12
|
-
# инструмента, написанного против молчаливого зелёного, своего молчаливого зелёного быть не может.
|
|
13
|
-
DIR="${1:-.}"
|
|
14
|
-
|
|
15
|
-
# Существование файла проверяется ДО `.`, а не запасной веткой после. В POSIX-оболочке (здесь
|
|
16
|
-
# dash) неудачный `.` завершает скрипт немедленно — привычное «. файл || запасной_вариант» не
|
|
17
|
-
# выполняется НИКОГДА, и гейт умирает с кодом 2 без единого слова о причине. Проверено прогоном
|
|
18
|
-
# 2026-09-08; та же мёртвая ветка стоит ещё в девяти проверках каталога.
|
|
19
|
-
SKIP_LIB="$(dirname "$0")/../_skip.sh"
|
|
20
|
-
if [ ! -f "$SKIP_LIB" ]; then
|
|
21
|
-
echo "рядом с проверкой нет _skip.sh — обход не собран, проверка не состоялась"
|
|
22
|
-
echo " почини: скопируй гейт вместе с файлом kit/gates/_skip.sh, он общий на весь каталог"
|
|
23
|
-
exit 2
|
|
24
|
-
fi
|
|
25
|
-
. "$SKIP_LIB"
|
|
26
|
-
# Файл на месте — этого мало: подмена содержимого давала код 0. Метка стоит в КОНЦЕ _skip.sh,
|
|
27
|
-
# поэтому проверка ловит и обрыв файла на середине.
|
|
28
|
-
if [ "${AQK_SKIP_READY:-}" != 1 ]; then
|
|
29
|
-
echo "_skip.sh есть, но обход не собрался — проверка НЕ СОСТОЯЛАСЬ, а не прошла"
|
|
30
|
-
echo " почини: замени kit/gates/_skip.sh целым файлом из каталога"
|
|
31
|
-
exit 2
|
|
32
|
-
fi
|
|
33
|
-
|
|
34
|
-
# Даже когда файл на месте, нужных функций в нём может не оказаться: без них конвейер ниже не
|
|
35
|
-
# напечатает ни одного пути, список окажется пуст, а пустой список — это `exit 0`. Гейт вышел бы
|
|
36
|
-
# зелёным, не посмотрев ни в один файл.
|
|
37
|
-
if ! command -v own_samples_filter >/dev/null 2>&1; then
|
|
38
|
-
echo "в _skip.sh нет обхода own_samples_filter — проверка не состоялась"
|
|
39
|
-
echo " почини: обнови kit/gates/_skip.sh до версии, идущей с этим гейтом"
|
|
40
|
-
exit 2
|
|
41
|
-
fi
|
|
42
|
-
|
|
43
|
-
if ! command -v slopcheck >/dev/null 2>&1; then
|
|
44
|
-
echo "не найден slopcheck — эта проверка делегирована ему"
|
|
45
|
-
echo " почини: npm i -g slopcheck@0.2.0"
|
|
46
|
-
exit 2
|
|
47
|
-
fi
|
|
48
|
-
|
|
49
|
-
# Свой обход, а не встроенный в slopcheck: только так работает own_samples_filter. Без него
|
|
50
|
-
# красный образец этой же записи выдаётся за находку, и гейт краснеет на самом комплекте.
|
|
51
|
-
LIST=$(mktemp) || exit 2
|
|
52
|
-
# shellcheck disable=SC2046
|
|
53
|
-
find "$DIR" $(skip_find "$DIR") -type f \
|
|
54
|
-
\( -name '*.md' -o -name '*.mdc' -o -name '*.yml' -o -name '*.yaml' \
|
|
55
|
-
-o -name '*.json' -o -name '.cursorrules' \) -print 2>/dev/null \
|
|
56
|
-
| own_samples_filter "$DIR" > "$LIST"
|
|
57
|
-
|
|
58
|
-
if [ ! -s "$LIST" ]; then rm -f "$LIST"; exit 0; fi
|
|
59
|
-
|
|
60
|
-
# Через NUL, а не через $(...): путь с пробелом иначе распадается на два аргумента, slopcheck
|
|
61
|
-
# не находит ни одного из них и молча выходит с нулём. Та же ловушка уже стоила нам зелёного
|
|
62
|
-
# вердикта в hook-actually-fires.
|
|
63
|
-
ERRF=$(mktemp) || { rm -f "$LIST"; exit 2; }
|
|
64
|
-
OUT=$(tr '\n' '\0' < "$LIST" | xargs -0 slopcheck --json 2>"$ERRF")
|
|
65
|
-
# Код xargs: 0 — все вызовы прошли, 123 — хотя бы один вернул 1..125. Именно 123 приходит и на
|
|
66
|
-
# честной находке (slopcheck выходит с 1), поэтому сам по себе он ничего не значит. А вот всё
|
|
67
|
-
# остальное — сбой обвязки, и его нельзя принимать за вердикт.
|
|
68
|
-
XS=$?
|
|
69
|
-
ERR=$(cat "$ERRF" 2>/dev/null)
|
|
70
|
-
rm -f "$ERRF" "$LIST"
|
|
71
|
-
|
|
72
|
-
# Файлов может оказаться больше, чем влезает в одну команду, и тогда xargs зовёт slopcheck
|
|
73
|
-
# несколько раз. Упавший вызов пишет в stderr, а уцелевшие всё равно печатают свой JSON —
|
|
74
|
-
# счётчики сходятся, и гейт вышел бы с нулём, не проверив целую партию файлов.
|
|
75
|
-
if [ -n "$ERR" ] || { [ "$XS" -ne 0 ] && [ "$XS" -ne 123 ]; }; then
|
|
76
|
-
echo "slopcheck не отработал (код $XS) — проверка не состоялась, это не вердикт «чисто»"
|
|
77
|
-
[ -n "$ERR" ] && printf '%s\n' "$ERR" | head -5 | sed 's/^/ /'
|
|
78
|
-
echo " почини: прогони «slopcheck --json .» руками и посмотри, на чём он споткнулся"
|
|
79
|
-
exit 2
|
|
80
|
-
fi
|
|
81
|
-
|
|
82
|
-
[ -n "$OUT" ] || {
|
|
83
|
-
echo "slopcheck ничего не ответил — проверка не состоялась, это не вердикт «чисто»"
|
|
84
|
-
echo " почини: прогони «slopcheck --json .» руками и посмотри, на чём он споткнулся"
|
|
85
|
-
exit 2
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
printf '%s\n' "$OUT" | awk '
|
|
89
|
-
function val(s) { sub(/^[^:]*:[[:space:]]*/, "", s); sub(/,$/, "", s); gsub(/^"|"$/, "", s); return s }
|
|
90
|
-
function label(s) {
|
|
91
|
-
if (s == "not_found") return "в реестре такого пакета нет"
|
|
92
|
-
if (s == "unpublished") return "пакет был и снят с публикации — имя свободно для захвата"
|
|
93
|
-
if (s == "security_hold") return "реестр пометил пакет как вредоносный"
|
|
94
|
-
return ""
|
|
95
|
-
}
|
|
96
|
-
# xargs может позвать slopcheck несколько раз, если путей слишком много для одной команды.
|
|
97
|
-
# Тогда JSON-документов на входе будет несколько, и счётчики надо складывать, а не брать
|
|
98
|
-
# последний: иначе находки первой партии исчезнут.
|
|
99
|
-
/^ "packages"/ { inpkg = 1; sawpkg = 1; next }
|
|
100
|
-
inpkg && /^ \}/ { inpkg = 0; next }
|
|
101
|
-
inpkg && /"errors"/ { errs += val($0); next }
|
|
102
|
-
inpkg && /"notFound"/ { bad += val($0); next }
|
|
103
|
-
inpkg && /"unpublished"/ { bad += val($0); next }
|
|
104
|
-
inpkg && /"securityHold"/ { bad += val($0); next }
|
|
105
|
-
/^ "package"/ { pkg = val($0); next }
|
|
106
|
-
/^ "status"/ { st = val($0); next }
|
|
107
|
-
/^ "file"/ { f = val($0); next }
|
|
108
|
-
/^ "line"/ { ln = val($0); next }
|
|
109
|
-
# Печатаем на «command»: он идёт последним в записи о месте, значит к этому моменту известны
|
|
110
|
-
# и пакет, и состояние, и файл со строкой.
|
|
111
|
-
/^ "command"/ {
|
|
112
|
-
cmd = val($0)
|
|
113
|
-
if (st == "error") next
|
|
114
|
-
# Состояние, которого мы не знаем. Раньше здесь стояло «всё, что не error, — брак», и
|
|
115
|
-
# новое состояние в следующей версии slopcheck превратило бы КАЖДУЮ команду установки в
|
|
116
|
-
# находку с английским словом вместо объяснения. Незнакомое состояние — повод сказать
|
|
117
|
-
# «не разобрали», а не вынести вердикт.
|
|
118
|
-
if (label(st) == "") { unknown = st; next }
|
|
119
|
-
if (shown < 20) print f ":" ln ": «" pkg "» — " label(st) " · " cmd
|
|
120
|
-
shown++
|
|
121
|
-
next
|
|
122
|
-
}
|
|
123
|
-
END {
|
|
124
|
-
if (unknown != "") {
|
|
125
|
-
print "slopcheck вернул незнакомое состояние «" unknown "» — разобрать ответ не вышло"
|
|
126
|
-
print " почини: сверь версию slopcheck с той, что названа в gate.yml"
|
|
127
|
-
exit 2
|
|
128
|
-
}
|
|
129
|
-
if (shown > 20) print " … и ещё " (shown - 20)
|
|
130
|
-
if (shown > 0) {
|
|
131
|
-
# Находки и «не проверено» показываются вместе. Свернуть второе в первое значило бы
|
|
132
|
-
# потерять ровно ту разницу, ради которой написана эта обёртка.
|
|
133
|
-
if (errs > 0) print " кроме того, реестр не ответил по именам: " errs " — они НЕ проверены"
|
|
134
|
-
print " почини: проверь имя в реестре и впиши настоящее. Если пакета нет, его может"
|
|
135
|
-
print " зарегистрировать кто угодно — и следующий агент выполнит установку чужого кода."
|
|
136
|
-
exit 1
|
|
137
|
-
}
|
|
138
|
-
# Реестр не ответил. Молчать здесь нельзя: тишина неотличима от «всё хорошо», а именно
|
|
139
|
-
# это наш стандарт и запрещает. Красим отдельным кодом, чтобы вердикт не путали с браком.
|
|
140
|
-
# Причина названа обеими: slopcheck помечает так и недоступную сеть, и ответ 429 «слишком
|
|
141
|
-
# часто», и любой другой не-200 — снаружи они неразличимы, и врать про сеть мы не будем.
|
|
142
|
-
if (errs > 0) {
|
|
143
|
-
print "реестр npm не ответил: не проверено имён — " errs
|
|
144
|
-
print " проверка не состоялась. Это не вердикт «чисто»."
|
|
145
|
-
print " почини: если сети нет — прогони гейт там, где она есть. Если сеть есть, реестр"
|
|
146
|
-
print " мог ограничить частоту запросов: повтори через минуту."
|
|
147
|
-
exit 2
|
|
148
|
-
}
|
|
149
|
-
# Блока со счётчиками не нашлось вовсе. Так будет, если slopcheck начнёт печатать JSON
|
|
150
|
-
# одной строкой: ни одно правило выше не сработает, все счётчики останутся нулями — и
|
|
151
|
-
# проверка выйдет с нулём, ничего не проверив. Нашли это прогоном подставного вывода уже
|
|
152
|
-
# после того, как записали «формат стабилен»: догадка о стабильности стоила бы молчаливого
|
|
153
|
-
# зелёного на каждом прогоне.
|
|
154
|
-
if (!sawpkg) {
|
|
155
|
-
print "ответ slopcheck не разобран: блока «packages» в нём нет"
|
|
156
|
-
print " почини: сверь версию slopcheck с той, что названа в gate.yml"
|
|
157
|
-
exit 2
|
|
158
|
-
}
|
|
159
|
-
# Счётчик говорит о браке, а печатать нечего — значит мы разучились читать вывод slopcheck
|
|
160
|
-
# (сменился формат). Тоже не вердикт «чисто».
|
|
161
|
-
if (bad > 0) {
|
|
162
|
-
print "slopcheck насчитал находок: " bad ", но разобрать их не вышло — сменился формат"
|
|
163
|
-
print " почини: сверь версию slopcheck с той, что названа в gate.yml"
|
|
164
|
-
exit 2
|
|
165
|
-
}
|
|
166
|
-
exit 0
|
|
167
|
-
}
|
|
168
|
-
'
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
intent: имя пакета, названное в документации, существует в реестре
|
|
2
|
-
intent_en: a package name mentioned in the documentation exists in the registry
|
|
3
|
-
|
|
4
|
-
# Там, где агента используют: предмет проверки — текст, который агент читает и по которому
|
|
5
|
-
# выполняет установку. В проекте без такого текста ставить запись незачем.
|
|
6
|
-
trigger:
|
|
7
|
-
has_agent_entry: true
|
|
8
|
-
|
|
9
|
-
recipes:
|
|
10
|
-
any: bash {gate}/check.sh {dir}
|
|
11
|
-
|
|
12
|
-
# Программа, без которой запись не работает. По первому слову команды этого не видно: обёртка
|
|
13
|
-
# начинается с `bash`, который есть всегда. Версия названа: обёртка разбирает JSON slopcheck,
|
|
14
|
-
# и смена формата обязана быть видимой, а не тихой.
|
|
15
|
-
requires: slopcheck
|
|
16
|
-
|
|
17
|
-
proof: incidents/README.md, 2026-09-08 «пакет-призрак из чужого README оказался
|
|
18
|
-
зарегистрирован» — `react-codeshift`, канонический пример slopsquatting из README самого
|
|
19
|
-
slopcheck, на 2026-09-08 существует в npm (создан 2026-01-14, maintainer debugducky);
|
|
20
|
-
доля выдуманных имён в сгенерированном коде — USENIX Security 2025, arxiv 2406.10279
|