agent-quality-kit 0.5.0 → 0.7.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 +53 -2
- package/README.ru.md +35 -1
- package/kit/docs/ai/agent-harness-playbook.md +1 -1
- package/kit/docs/ready-made-rules.md +65 -0
- package/kit/gates/README.md +60 -0
- package/kit/gates/ci-actually-fails/README.md +54 -0
- package/kit/gates/ci-actually-fails/check.sh +116 -0
- package/kit/gates/ci-actually-fails/gate.yml +14 -0
- package/kit/gates/ci-actually-fails/green/.github/workflows/ci.yml +30 -0
- package/kit/gates/ci-actually-fails/red/.github/workflows/ci.yml +12 -0
- package/kit/gates/ci-actually-fails/red/.github/workflows/soft.yml +15 -0
- package/kit/gates/color-from-token/check.sh +13 -1
- package/kit/gates/commit-explains-itself/README.md +13 -3
- package/kit/gates/commit-explains-itself/check.sh +8 -4
- package/kit/gates/complexity-limit/README.md +5 -0
- package/kit/gates/complexity-limit/check.sh +21 -2
- package/kit/gates/complexity-limit/green/test_fixtures.py +14 -0
- package/kit/gates/deps-are-pinned/README.md +14 -1
- package/kit/gates/deps-are-pinned/check.sh +6 -1
- package/kit/gates/deps-are-pinned/green/pyproject-with-requirements/pyproject.toml +12 -0
- package/kit/gates/deps-are-pinned/green/pyproject-with-requirements/requirements.txt +3 -0
- package/kit/gates/deps-are-pinned/red/pyproject-loose/pyproject.toml +12 -0
- package/kit/gates/deps-are-pinned/red/pyproject-loose/requirements.txt +3 -0
- package/kit/gates/duplicate-code/README.md +11 -2
- package/kit/gates/duplicate-code/check.sh +31 -4
- package/kit/gates/duplicate-code/gate.yml +8 -0
- package/kit/gates/duplicate-code/green/imports_a.go +20 -0
- package/kit/gates/duplicate-code/green/imports_b.go +19 -0
- package/kit/gates/entry-links-exist/README.md +5 -0
- package/kit/gates/entry-links-exist/check.sh +6 -0
- package/kit/gates/entry-links-exist/green/AGENTS.md +3 -0
- package/kit/gates/file-size-limit/README.md +9 -2
- package/kit/gates/file-size-limit/check.sh +13 -1
- package/kit/gates/gate-not-weakened/README.md +54 -0
- package/kit/gates/gate-not-weakened/check.sh +84 -0
- package/kit/gates/gate-not-weakened/gate.yml +15 -0
- package/kit/gates/gate-not-weakened/green/checkout.ts +8 -0
- package/kit/gates/gate-not-weakened/green/payments.py +6 -0
- package/kit/gates/gate-not-weakened/green/release.sh +2 -0
- package/kit/gates/gate-not-weakened/red/checkout.ts +9 -0
- package/kit/gates/gate-not-weakened/red/payments.py +6 -0
- package/kit/gates/gate-not-weakened/red/release.sh +2 -0
- package/kit/gates/hook-actually-fires/README.md +74 -0
- package/kit/gates/hook-actually-fires/check.sh +183 -0
- package/kit/gates/hook-actually-fires/gate.yml +15 -0
- package/kit/gates/hook-actually-fires/green/.claude/hooks/hooks.json +3 -0
- package/kit/gates/hook-actually-fires/green/.claude/settings.json +74 -0
- package/kit/gates/hook-actually-fires/green/.claude/settings.local.json +74 -0
- package/kit/gates/hook-actually-fires/red/.claude/hooks/hooks.json +4 -0
- package/kit/gates/hook-actually-fires/red/.claude/settings.json +53 -0
- package/kit/gates/no-phantom-package/README.md +84 -0
- package/kit/gates/no-phantom-package/check.sh +161 -0
- package/kit/gates/no-phantom-package/gate.yml +20 -0
- package/kit/gates/no-phantom-package/green/AGENTS.md +15 -0
- package/kit/gates/no-phantom-package/red/AGENTS.md +15 -0
- package/kit/gates/no-print-in-prod/README.md +33 -39
- package/kit/gates/no-print-in-prod/gate.yml +14 -6
- package/kit/gates/personal-config-not-shared/README.md +66 -0
- package/kit/gates/personal-config-not-shared/check.sh +103 -0
- package/kit/gates/personal-config-not-shared/gate.yml +16 -0
- package/kit/gates/personal-config-not-shared/green/.aqk-tracked +9 -0
- package/kit/gates/personal-config-not-shared/red/.aqk-tracked +6 -0
- package/kit/gates/promise-has-gate/README.md +50 -0
- package/kit/gates/promise-has-gate/check.sh +88 -0
- package/kit/gates/promise-has-gate/gate.yml +14 -0
- package/kit/gates/promise-has-gate/green/.aqk.yml +6 -0
- package/kit/gates/promise-has-gate/green/AGENTS.md +7 -0
- package/kit/gates/promise-has-gate/red/.aqk.yml +6 -0
- package/kit/gates/promise-has-gate/red/AGENTS.md +7 -0
- package/kit/gates/secrets-not-in-code/check.sh +13 -1
- package/kit/gates/swallowed-error/README.md +36 -18
- package/kit/gates/swallowed-error/gate.yml +13 -3
- package/kit/gates/test-has-assertion/README.md +47 -0
- package/kit/gates/test-has-assertion/check.sh +206 -0
- package/kit/gates/test-has-assertion/gate.yml +15 -0
- package/kit/gates/test-has-assertion/green/checkout.test.ts +9 -0
- package/kit/gates/test-has-assertion/green/test_billing.py +17 -0
- package/kit/gates/test-has-assertion/red/checkout.test.ts +8 -0
- package/kit/gates/test-has-assertion/red/test_billing.py +14 -0
- package/kit/gates/test-not-adjusted/README.md +79 -0
- package/kit/gates/test-not-adjusted/check.sh +136 -0
- package/kit/gates/test-not-adjusted/gate.yml +19 -0
- package/kit/gates/test-not-adjusted/green/after/calc.py +6 -0
- package/kit/gates/test-not-adjusted/green/after/tests/test_calc.py +9 -0
- package/kit/gates/test-not-adjusted/green/before/calc.py +2 -0
- package/kit/gates/test-not-adjusted/green/before/tests/test_calc.py +5 -0
- package/kit/gates/test-not-adjusted/red/after/calc.py +2 -0
- package/kit/gates/test-not-adjusted/red/after/tests/test_calc.py +5 -0
- package/kit/gates/test-not-adjusted/red/before/calc.py +2 -0
- package/kit/gates/test-not-adjusted/red/before/tests/test_calc.py +7 -0
- package/kit/gates/todo-without-task/README.md +6 -0
- package/kit/gates/todo-without-task/check.sh +13 -1
- package/kit/ratchet/ratchet.sh +70 -2
- package/kit/rules/general.md +23 -0
- package/kit/rules-en/general.md +82 -0
- package/kit/rules-en/security.md +33 -0
- package/kit/rules-en/testing.md +48 -0
- package/llms.txt +2 -1
- package/package.json +4 -2
- package/tool/commands/badge.mjs +7 -1
- package/tool/commands/doctor.mjs +90 -10
- package/tool/commands/gates.mjs +19 -5
- package/tool/commands/project.mjs +15 -2
- package/tool/commands/prove.mjs +67 -0
- package/tool/commands/report.mjs +4 -1
- package/tool/i18n/en-docs.mjs +70 -0
- package/tool/i18n/en.mjs +66 -54
- package/tool/i18n/ru-docs.mjs +70 -0
- package/tool/i18n/ru.mjs +66 -54
- package/tool/i18n/templates-en.mjs +9 -9
- package/tool/i18n/templates-ru.mjs +9 -9
- package/tool/lib/core.mjs +7 -1
- package/tool/lib/manifest.mjs +72 -5
- package/tool/lib/prove.mjs +160 -0
- package/tool/lib/repo.mjs +31 -2
- package/tool/lib/scope.mjs +131 -0
- package/tool/lib/templates.mjs +2 -0
- package/tool/program.mjs +6 -0
- package/tool/selfcheck/gates.sh +86 -3
- package/tool/selfcheck/lifecycle.mjs +29 -0
- package/tool/selfcheck/mutation.sh +21 -1
- package/tool/selfcheck/smoke.sh +329 -36
- package/tool/selfcheck/units-level.mjs +60 -0
- package/tool/selfcheck/units.mjs +196 -1
- package/kit/gates/no-print-in-prod/check.sh +0 -38
- package/kit/gates/no-print-in-prod/green/docs.ts +0 -15
- package/kit/gates/no-print-in-prod/green/main.go +0 -8
- package/kit/gates/no-print-in-prod/green/main.rs +0 -4
- package/kit/gates/no-print-in-prod/red/main.go +0 -8
- package/kit/gates/no-print-in-prod/red/main.rs +0 -4
- package/kit/gates/swallowed-error/check.sh +0 -54
- package/kit/gates/swallowed-error/green/run.js +0 -8
- package/kit/gates/swallowed-error/red/run.js +0 -3
- /package/kit/gates/commit-explains-itself/green/{COMMIT_MSG → .aqk-commit-msg} +0 -0
- /package/kit/gates/commit-explains-itself/red/{COMMIT_MSG → .aqk-commit-msg} +0 -0
|
@@ -4,11 +4,15 @@
|
|
|
4
4
|
# оставляет след для человека, читающего историю позже.
|
|
5
5
|
DIR="${1:-.}"
|
|
6
6
|
|
|
7
|
-
# Образцы (gates.sh) читают тело коммита из
|
|
7
|
+
# Образцы (gates.sh) читают тело коммита из `.aqk-commit-msg` — реальный git log там взять неоткуда, а
|
|
8
8
|
# вложенный .git внутри каталога комплекта сам по себе создал бы embedded-репозиторий.
|
|
9
|
-
# В настоящем проекте
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
# В настоящем проекте такого файла не бывает — читается последний реальный коммит.
|
|
10
|
+
# Имя с точкой намеренно: `COMMIT_MSG` в корне проекта — вещь возможная (так зовут заготовку
|
|
11
|
+
# сообщения многие обёртки над git), и такой файл молча подменял бы собой настоящий коммит.
|
|
12
|
+
# Тот же дефект нашло ревью у `personal-config-not-shared` 2026-09-07; здесь он был с самого
|
|
13
|
+
# начала и не всплывал.
|
|
14
|
+
if [ -f "$DIR/.aqk-commit-msg" ]; then
|
|
15
|
+
MSG=$(cat "$DIR/.aqk-commit-msg")
|
|
12
16
|
else
|
|
13
17
|
if ! (cd "$DIR" 2>/dev/null && git rev-parse --git-dir >/dev/null 2>&1); then
|
|
14
18
|
echo "не git-репозиторий — проверять нечего"
|
|
@@ -33,5 +33,10 @@
|
|
|
33
33
|
считает только глубину. Не различает функции внутри файла: меряется худшее место файла, а не
|
|
34
34
|
каждая функция отдельно. Готовые правила умеют и то, и другое.
|
|
35
35
|
|
|
36
|
+
**Тесты не проверяются.** Глубокая вложенность в тесте — это обход таблицы ожиданий, а не
|
|
37
|
+
сложная логика. Замер по `fastapi` дал 303 находки, из которых **101 в `tests/`**; после
|
|
38
|
+
исключения тестов осталось 10. Гейт, который краснеет в основном на тестах, выключают целиком —
|
|
39
|
+
тот же довод и по той же причине, что в `duplicate-code`.
|
|
40
|
+
|
|
36
41
|
**Образцы.** `red/` — восемь уровней вложенности. `green/` — тот же смысл, разбитый на две
|
|
37
42
|
функции с ранним выходом.
|
|
@@ -6,11 +6,30 @@
|
|
|
6
6
|
# ЗАЧЕМ ВООБЩЕ. 29 ветвлений в одной функции — это код, который никто не держит в голове
|
|
7
7
|
# целиком: ни человек, ни агент. Агент в таком месте начинает переписывать вместо правки.
|
|
8
8
|
DIR="${1:-.}"
|
|
9
|
-
|
|
9
|
+
# Существование файла проверяется ДО `.`, а не запасной веткой после. Прежняя строка
|
|
10
|
+
# `. файл 2>/dev/null || запасной_вариант` выглядела страховкой и ею не была: под `sh` (dash)
|
|
11
|
+
# неудачный `.` завершает скрипт немедленно, и ветка после `||` не выполняется никогда; под
|
|
12
|
+
# `bash`, которым гейты и запускаются из манифеста, она выполняется, но подставляет только
|
|
13
|
+
# ПЕРЕМЕННУЮ — функции обхода остаются неопределёнными, конвейер печатает пустоту, и проверка
|
|
14
|
+
# выходит с НУЛЁМ. Замерено 2026-09-08 на файле с настоящим нарушением: гейт сказал «чисто».
|
|
15
|
+
SKIP_LIB="$(dirname "$0")/../_skip.sh"
|
|
16
|
+
if [ ! -f "$SKIP_LIB" ]; then
|
|
17
|
+
echo "рядом с проверкой нет _skip.sh — обход не собран, проверка не состоялась"
|
|
18
|
+
echo " почини: скопируй гейт вместе с файлом kit/gates/_skip.sh, он общий на весь каталог"
|
|
19
|
+
exit 2
|
|
20
|
+
fi
|
|
21
|
+
. "$SKIP_LIB"
|
|
10
22
|
MAX="${AQK_MAX_DEPTH:-5}"
|
|
11
23
|
|
|
24
|
+
# Тесты исключены намеренно — тем же списком, что и в duplicate-code. Глубокая вложенность в
|
|
25
|
+
# тесте это обход таблицы ожиданий, а не сложная логика: замер по fastapi дал 303 находки, из
|
|
26
|
+
# которых 101 в tests/. Гейт, который краснеет в основном на тестах, выключают целиком.
|
|
27
|
+
TESTS="-name test -prune -o -name tests -prune -o -name spec -prune -o -name __tests__ -prune -o"
|
|
28
|
+
|
|
12
29
|
# shellcheck disable=SC2046
|
|
13
|
-
find "$DIR" $(skip_find "$DIR") -type f
|
|
30
|
+
find "$DIR" $(skip_find "$DIR") $TESTS -type f \
|
|
31
|
+
! -name 'test_*' ! -name '*_test.*' ! -name '*.test.*' ! -name '*.spec.*' \
|
|
32
|
+
-print 2>/dev/null | only_code | own_samples_filter "$DIR" \
|
|
14
33
|
| while IFS= read -r F; do is_generated "$F" || printf '%s\n' "$F"; done \
|
|
15
34
|
| xargs -r awk -v MAX="$MAX" '
|
|
16
35
|
# Один обход на все файлы: процесс на каждый файл дал 19 секунд на 4000 файлов.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# Тест с глубокой вложенностью — не сложная логика, а разбор дерева ожиданий. Найдено замером
|
|
2
|
+
# по fastapi: 101 находка из 303 пришлась на tests/, и все они — таблицы ожидаемых ответов,
|
|
3
|
+
# которые никто не станет «упрощать». Гейт, который краснеет в основном на тестах, выключат.
|
|
4
|
+
def test_openapi_schema(client):
|
|
5
|
+
response = client.get("/openapi.json")
|
|
6
|
+
assert response.status_code == 200
|
|
7
|
+
schema = response.json()
|
|
8
|
+
for path, methods in schema["paths"].items():
|
|
9
|
+
for method, spec in methods.items():
|
|
10
|
+
for code, resp in spec["responses"].items():
|
|
11
|
+
if "content" in resp:
|
|
12
|
+
for media, body in resp["content"].items():
|
|
13
|
+
if "schema" in body:
|
|
14
|
+
assert body["schema"] is not None
|
|
@@ -26,4 +26,17 @@
|
|
|
26
26
|
**Готового аналога нет** — ни в линтерах, ни в пакетных менеджерах: они
|
|
27
27
|
умеют создать файл версий, но не умеют требовать, чтобы он существовал и лежал в репозитории.
|
|
28
28
|
|
|
29
|
-
|
|
29
|
+
**Закрепляют не только poetry и uv.** Для `pyproject.toml` файлом закрепления считается и
|
|
30
|
+
`requirements.txt` — но только если он сам проходит проверку, то есть все версии в нём стоят
|
|
31
|
+
через `==`. Иначе «есть requirements.txt» стало бы способом обойти гейт пустым файлом.
|
|
32
|
+
Найдено замером по `httpx`: там инструменты закреплены до патча прямо в `requirements.txt`,
|
|
33
|
+
а гейт требовал ещё и `poetry.lock`, которого в этом укладе не бывает вовсе.
|
|
34
|
+
|
|
35
|
+
**Не различает библиотеку и приложение.** Библиотека намеренно оставляет
|
|
36
|
+
границы своих зависимостей широкими — закрепив их у себя, она ломает сборку всем, кто её
|
|
37
|
+
ставит. Гейт смотрит только на то, закреплено ли окружение самого проекта, и правильно молчит,
|
|
38
|
+
когда библиотека закрепила инструменты и не закрепила зависимости.
|
|
39
|
+
|
|
40
|
+
**Образцы.** `red/` — объявление зависимостей без закрепления, плюс `pyproject.toml` рядом с
|
|
41
|
+
незакреплённым `requirements.txt`. `green/` — с закреплением, включая уклад «pyproject плюс
|
|
42
|
+
requirements.txt с точными версиями».
|
|
@@ -30,7 +30,12 @@ need() {
|
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
need package.json package-lock.json yarn.lock pnpm-lock.yaml npm-shrinkwrap.json
|
|
33
|
-
|
|
33
|
+
# requirements.txt считается закреплением для pyproject.toml наравне с файлами блокировки:
|
|
34
|
+
# закрепляют не только poetry и uv. Замер по httpx: инструменты там закреплены до патча
|
|
35
|
+
# прямо в requirements.txt, а гейт требовал ещё и poetry.lock, которого в этом укладе не
|
|
36
|
+
# бывает вовсе. Файл засчитывается только если он сам проходит проверку ниже — иначе
|
|
37
|
+
# «есть requirements.txt» стало бы способом обойти гейт пустым файлом.
|
|
38
|
+
need pyproject.toml poetry.lock uv.lock pdm.lock requirements.txt
|
|
34
39
|
need go.mod go.sum
|
|
35
40
|
need Cargo.toml Cargo.lock
|
|
36
41
|
need Gemfile Gemfile.lock
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# Питоновская библиотека, которая закрепляет версии не файлом блокировки, а точными версиями
|
|
2
|
+
# в requirements.txt. Так делает httpx и весь класс проектов, живущих без poetry и uv:
|
|
3
|
+
# инструменты закреплены до патча, а границы зависимостей самой библиотеки оставлены широкими
|
|
4
|
+
# намеренно — библиотека, закрепившая их у себя, ломает сборку всем, кто её ставит.
|
|
5
|
+
[project]
|
|
6
|
+
name = "sample-lib"
|
|
7
|
+
version = "1.0.0"
|
|
8
|
+
dependencies = ["httpx"]
|
|
9
|
+
|
|
10
|
+
[build-system]
|
|
11
|
+
requires = ["setuptools"]
|
|
12
|
+
build-backend = "setuptools.build_meta"
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# Питоновская библиотека, которая закрепляет версии не файлом блокировки, а точными версиями
|
|
2
|
+
# в requirements.txt. Так делает httpx и весь класс проектов, живущих без poetry и uv:
|
|
3
|
+
# инструменты закреплены до патча, а границы зависимостей самой библиотеки оставлены широкими
|
|
4
|
+
# намеренно — библиотека, закрепившая их у себя, ломает сборку всем, кто её ставит.
|
|
5
|
+
[project]
|
|
6
|
+
name = "sample-lib"
|
|
7
|
+
version = "1.0.0"
|
|
8
|
+
dependencies = ["httpx"]
|
|
9
|
+
|
|
10
|
+
[build-system]
|
|
11
|
+
requires = ["setuptools"]
|
|
12
|
+
build-backend = "setuptools.build_meta"
|
|
@@ -12,13 +12,22 @@
|
|
|
12
12
|
**Почему агенты плодят дубли особенно охотно.** Скопировать из соседнего файла дешевле, чем
|
|
13
13
|
найти общее место и вынести туда: копия точно работает, вынесение может что-то сломать.
|
|
14
14
|
|
|
15
|
-
**Готовый аналог
|
|
16
|
-
|
|
15
|
+
**Готовый аналог есть, и под каждый стек свой.** `jscpd` для фронта — считает **похожесть**, а не
|
|
16
|
+
только точное совпадение. `pylint --enable=R0801` для Python — сравнивает разобранный код, и
|
|
17
|
+
переименованная переменная его не обманет. Рецепт под Python появился только 2026-09-07: до
|
|
18
|
+
ревизии каталога его не было вовсе, и питоновский проект получал нашу побайтовую самоделку,
|
|
19
|
+
хотя родной инструмент стоял у него уже установленным.
|
|
17
20
|
|
|
18
21
|
**Переносимая проверка грубее.** Она ищет одинаковые восемь строк подряд после снятия отступов —
|
|
19
22
|
совпадение байт в байт. Копия с переименованной переменной её не насторожит. Это запасной
|
|
20
23
|
вариант, а не замена.
|
|
21
24
|
|
|
25
|
+
**Одинаковые импорты — не дубль.** Окно, в котором нет ни одной строки кроме ввоза (`import`,
|
|
26
|
+
`use`, `require`, путь в кавычках внутри `import (…)` в Go) и одиноких скобок, находкой не
|
|
27
|
+
считается. Найдено замером по `cobra`: гейт краснел на `doc/man_docs.go` и `doc/md_docs.go`,
|
|
28
|
+
где совпадали восемь строк подряд из списка импортов и ни одной строки логики. В Go, Java и Rust
|
|
29
|
+
ввоз стоит по одной строке на строку, и в соседних файлах одного пакета он совпадает всегда.
|
|
30
|
+
|
|
22
31
|
**Тесты не проверяются.** Повтор в тестах часто осознанный: читаемость важнее сухости, и три
|
|
23
32
|
похожих теста лучше одного хитрого. Случай «три однотипных — свести в один с набором входов»
|
|
24
33
|
решается по правилам тестирования и глазами. На живом проекте **все двадцать находок были в
|
|
@@ -5,7 +5,19 @@
|
|
|
5
5
|
# ЗАЧЕМ. Дубль опаснее длины: правку вносят в одну копию из четырёх, три остаются со старым
|
|
6
6
|
# поведением, и расхождение всплывает через недели в другом месте.
|
|
7
7
|
DIR="${1:-.}"
|
|
8
|
-
|
|
8
|
+
# Существование файла проверяется ДО `.`, а не запасной веткой после. Прежняя строка
|
|
9
|
+
# `. файл 2>/dev/null || запасной_вариант` выглядела страховкой и ею не была: под `sh` (dash)
|
|
10
|
+
# неудачный `.` завершает скрипт немедленно, и ветка после `||` не выполняется никогда; под
|
|
11
|
+
# `bash`, которым гейты и запускаются из манифеста, она выполняется, но подставляет только
|
|
12
|
+
# ПЕРЕМЕННУЮ — функции обхода остаются неопределёнными, конвейер печатает пустоту, и проверка
|
|
13
|
+
# выходит с НУЛЁМ. Замерено 2026-09-08 на файле с настоящим нарушением: гейт сказал «чисто».
|
|
14
|
+
SKIP_LIB="$(dirname "$0")/../_skip.sh"
|
|
15
|
+
if [ ! -f "$SKIP_LIB" ]; then
|
|
16
|
+
echo "рядом с проверкой нет _skip.sh — обход не собран, проверка не состоялась"
|
|
17
|
+
echo " почини: скопируй гейт вместе с файлом kit/gates/_skip.sh, он общий на весь каталог"
|
|
18
|
+
exit 2
|
|
19
|
+
fi
|
|
20
|
+
. "$SKIP_LIB"
|
|
9
21
|
WIN="${AQK_DUP_LINES:-8}"
|
|
10
22
|
|
|
11
23
|
# Тесты исключены намеренно. Повтор в тестах часто осознанный: читаемость там важнее сухости,
|
|
@@ -21,15 +33,30 @@ find "$DIR" $(skip_find "$DIR") $TESTS -type f \
|
|
|
21
33
|
| while IFS= read -r F; do is_generated "$F" || printf '%s\n' "$F"; done \
|
|
22
34
|
| LC_ALL=C sort \
|
|
23
35
|
| xargs -r env LC_ALL=C awk -v WIN="$WIN" '
|
|
24
|
-
|
|
36
|
+
# Строка-объявление ввоза: `import`, `from … import`, `use …;`, путь в кавычках внутри
|
|
37
|
+
# блока `import (…)` в Go, `require(…)`, а также одинокие скобки и точки с запятой.
|
|
38
|
+
# ЗАЧЕМ. Восемь одинаковых строк ввоза подряд — форма языка, а не размноженный код: в Go,
|
|
39
|
+
# Java и Rust список ввоза стоит по одной строке и в соседних файлах одного пакета
|
|
40
|
+
# совпадает целиком. Замер по cobra: обе находки в doc/ были ровно этим — ни одной
|
|
41
|
+
# строки логики. Окно, где нет ни одной строки кроме ввоза, дублем не считается.
|
|
42
|
+
function isimport(s) {
|
|
43
|
+
return s ~ /^(import|from|use|export|require|package|#include|using)([[:space:](]|$)/ ||
|
|
44
|
+
s ~ /^[A-Za-z_][A-Za-z0-9_]*[[:space:]]+"[^"]*"$/ ||
|
|
45
|
+
s ~ /^[_.]?[[:space:]]*"[^"]*"[,;]?$/ ||
|
|
46
|
+
s ~ /^(const|let|var)[[:space:]].*require\(/ ||
|
|
47
|
+
s ~ /^[(){}\[\];,]+$/
|
|
48
|
+
}
|
|
49
|
+
FNR == 1 { n = 0; delete buf; delete imp }
|
|
25
50
|
{
|
|
26
51
|
line = $0
|
|
27
52
|
gsub(/^[[:space:]]+|[[:space:]]+$/, "", line)
|
|
28
53
|
if (line == "" || line ~ /^([#]|\/\/)/) next # пустые и комментарии не считаем
|
|
29
54
|
buf[++n] = line
|
|
55
|
+
imp[n] = isimport(line)
|
|
30
56
|
if (n >= WIN) {
|
|
31
|
-
key = ""
|
|
32
|
-
for (i = n - WIN + 1; i <= n; i++) key = key buf[i] "\x1e"
|
|
57
|
+
key = ""; code = 0
|
|
58
|
+
for (i = n - WIN + 1; i <= n; i++) { key = key buf[i] "\x1e"; if (!imp[i]) code = 1 }
|
|
59
|
+
if (!code) next # окно целиком из ввоза — не дубль
|
|
33
60
|
if (key in seen && seen[key] != FILENAME ":" (FNR - WIN + 1)) {
|
|
34
61
|
print seen[key] " и " FILENAME ":" (FNR - WIN + 1) ": одинаковые " WIN " строк"
|
|
35
62
|
} else if (!(key in seen)) {
|
|
@@ -16,7 +16,15 @@ recipes:
|
|
|
16
16
|
#
|
|
17
17
|
# «c#» в списке нет намеренно: разбор манифеста режет строку по «#», и рецепт обрывался бы
|
|
18
18
|
# на «c». Обёртку _native.sh не пишем — её добавляет сама программа при установке гейта.
|
|
19
|
+
#
|
|
20
|
+
# Рецепт стоял только под javascript и typescript, хотя список --format покрывает и Python,
|
|
21
|
+
# и Go. Питоновский проект получал переносимую самоделку вместо готового инструмента —
|
|
22
|
+
# найдено ревизией каталога 2026-09-07, замером по пяти стекам.
|
|
19
23
|
javascript: npx --yes jscpd@5 --min-lines 8 --threshold 1 --format "javascript,jsx,typescript,tsx,vue,python,go,ruby,java,php,rust,kotlin,swift,scala" {dir}
|
|
20
24
|
typescript: npx --yes jscpd@5 --min-lines 8 --threshold 1 --format "javascript,jsx,typescript,tsx,vue,python,go,ruby,java,php,rust,kotlin,swift,scala" {dir}
|
|
25
|
+
# Родной инструмент стека, а не jscpd: питоновскому проекту незачем ставить Node ради одной
|
|
26
|
+
# проверки. R0801 сравнивает не байты, а разобранный код — переименованная переменная его
|
|
27
|
+
# не обманет, в отличие от переносимого рецепта.
|
|
28
|
+
python: pylint --disable=all --enable=R0801 --min-similarity-lines=8 {dir}
|
|
21
29
|
|
|
22
30
|
proof: incidents/README.md — «2026-08-25 разбор 1069 коммитов»: число моделей захардкожено в четырёх местах четырьмя разными значениями
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// Одинаковый блок импортов в двух файлах одного пакета — не дубль кода, а форма языка.
|
|
2
|
+
// Найдено замером по cobra: гейт краснел на doc/man_docs.go и doc/md_docs.go, где совпадали
|
|
3
|
+
// восемь строк подряд из списка импортов, и ни одной строки логики.
|
|
4
|
+
package doc
|
|
5
|
+
|
|
6
|
+
import (
|
|
7
|
+
"bytes"
|
|
8
|
+
"fmt"
|
|
9
|
+
"io"
|
|
10
|
+
"os"
|
|
11
|
+
"path/filepath"
|
|
12
|
+
"sort"
|
|
13
|
+
"strconv"
|
|
14
|
+
"strings"
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
func RenderMan(w io.Writer, name string) error {
|
|
18
|
+
_, err := fmt.Fprintf(w, "man page for %s", name)
|
|
19
|
+
return err
|
|
20
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
package doc
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"bytes"
|
|
5
|
+
"fmt"
|
|
6
|
+
"io"
|
|
7
|
+
"os"
|
|
8
|
+
"path/filepath"
|
|
9
|
+
"sort"
|
|
10
|
+
"strconv"
|
|
11
|
+
"strings"
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
func RenderMarkdown(w io.Writer, name string) error {
|
|
15
|
+
var buf bytes.Buffer
|
|
16
|
+
buf.WriteString(strings.ToUpper(name))
|
|
17
|
+
_, err := w.Write(buf.Bytes())
|
|
18
|
+
return err
|
|
19
|
+
}
|
|
@@ -15,6 +15,11 @@
|
|
|
15
15
|
выбираются **по языку проекта**, а эти инструменты к языку не привязаны. Если такой инструмент у
|
|
16
16
|
вас стоит — он лучше нашего.
|
|
17
17
|
|
|
18
|
+
**Адрес страницы сайта не считается битой ссылкой.** Путь, кончающийся косой чертой —
|
|
19
|
+
`[руководство](tutorial/#install)`, — это адрес на опубликованном сайте, а не файл в
|
|
20
|
+
репозитории. Так ссылаются mkdocs, docusaurus и jekyll. Найдено замером по `fastapi`: гейт
|
|
21
|
+
объявлял битой рабочую ссылку из их README.
|
|
22
|
+
|
|
18
23
|
**Чего НЕ ловит.** Только файлы `*.md` в самом каталоге, без обхода вложенных: намерение записи —
|
|
19
24
|
точка входа, а не вся документация. Не проверяет внешние адреса (сеть) и якоря внутри файла.
|
|
20
25
|
|
|
@@ -14,6 +14,12 @@ for MD in "$DIR"/*.md; do
|
|
|
14
14
|
case "$T" in *http://*|*https://*|\#*|mailto:*) continue ;; esac
|
|
15
15
|
T=${T%%#*}
|
|
16
16
|
[ -z "$T" ] && continue
|
|
17
|
+
# Путь, кончающийся косой чертой, — адрес страницы опубликованного сайта, а не файл на
|
|
18
|
+
# диске. Так ссылаются mkdocs, docusaurus и jekyll: `[руководство](tutorial/#install)`
|
|
19
|
+
# работает у читателя и не существует в репозитории. Найдено замером по fastapi — гейт
|
|
20
|
+
# объявлял битой рабочую ссылку из их README. Проверять такие адреса умеют lychee и
|
|
21
|
+
# markdown-link-check: они ходят в сеть, а мы смотрим только на диск.
|
|
22
|
+
case "$T" in */) continue ;; esac
|
|
17
23
|
if [ ! -e "$DIR/$T" ]; then
|
|
18
24
|
echo "$MD: ссылка в никуда — $T"
|
|
19
25
|
echo " почини: создай файл или убери ссылку. Документ, обещающий несуществующее, хуже отсутствующего."
|
|
@@ -3,3 +3,6 @@
|
|
|
3
3
|
Стандарты: [общие правила](rules/general.md).
|
|
4
4
|
Внешняя ссылка: [semver](https://semver.org).
|
|
5
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.
|
|
@@ -16,7 +16,14 @@
|
|
|
16
16
|
**Чего НЕ ловит.** Длину функции и вложенность: файл на 200 строк с одной функцией в 180 строк
|
|
17
17
|
проверку пройдёт. Это отдельная мера — цикломатическая сложность.
|
|
18
18
|
|
|
19
|
-
**Готового аналога
|
|
20
|
-
размер файла нет ни
|
|
19
|
+
**Готового аналога нет — для Python.** Проверено дважды, второй раз 2026-09-07 по каталогу
|
|
20
|
+
правил `ruff`: предела на размер файла там нет ни одного (`pylint C0302 too-many-lines` в `ruff`
|
|
21
|
+
не перенесён). В eslint правило `max-lines` есть, но рецепта под него здесь нет намеренно: оно
|
|
22
|
+
знает один предел на все файлы, а запись держит два — 500 строк прод-коду и 800 тесту. Рецепт,
|
|
23
|
+
который меряет не то же самое, что переносимая проверка, делает запись означающей разное на
|
|
24
|
+
разных машинах; правило каталога это запрещает (см. `color-from-token`).
|
|
25
|
+
|
|
26
|
+
**Замер.** Пять чужих репозиториев (`httpx`, `fastapi`, `zod`, `cobra`, `ripgrep`) — 86 находок,
|
|
27
|
+
ложных **ноль**: мера прямая, файл либо длиннее предела, либо нет.
|
|
21
28
|
|
|
22
29
|
**Образцы.** `red/` — прод-файл на 600 строк. `green/` — тот же код, разделённый надвое.
|
|
@@ -5,7 +5,19 @@
|
|
|
5
5
|
# не по замыслу: каждую новую фичу дописывают в тот же файл, потому что агенту так ближе по
|
|
6
6
|
# контексту. Числа спорные — важно, что предел существует и его считает машина.
|
|
7
7
|
DIR="${1:-.}"
|
|
8
|
-
|
|
8
|
+
# Существование файла проверяется ДО `.`, а не запасной веткой после. Прежняя строка
|
|
9
|
+
# `. файл 2>/dev/null || запасной_вариант` выглядела страховкой и ею не была: под `sh` (dash)
|
|
10
|
+
# неудачный `.` завершает скрипт немедленно, и ветка после `||` не выполняется никогда; под
|
|
11
|
+
# `bash`, которым гейты и запускаются из манифеста, она выполняется, но подставляет только
|
|
12
|
+
# ПЕРЕМЕННУЮ — функции обхода остаются неопределёнными, конвейер печатает пустоту, и проверка
|
|
13
|
+
# выходит с НУЛЁМ. Замерено 2026-09-08 на файле с настоящим нарушением: гейт сказал «чисто».
|
|
14
|
+
SKIP_LIB="$(dirname "$0")/../_skip.sh"
|
|
15
|
+
if [ ! -f "$SKIP_LIB" ]; then
|
|
16
|
+
echo "рядом с проверкой нет _skip.sh — обход не собран, проверка не состоялась"
|
|
17
|
+
echo " почини: скопируй гейт вместе с файлом kit/gates/_skip.sh, он общий на весь каталог"
|
|
18
|
+
exit 2
|
|
19
|
+
fi
|
|
20
|
+
. "$SKIP_LIB"
|
|
9
21
|
|
|
10
22
|
# Один обход и один wc на все файлы разом: на проекте в 36 тысяч файлов цикл с wc на каждый
|
|
11
23
|
# не укладывался в две минуты.
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# Подавление проверки — точечное и с причиной
|
|
2
|
+
|
|
3
|
+
**Намерение.** Когда проверка краснеет, у пишущего два выхода: починить код или заглушить
|
|
4
|
+
проверку. Второй дешевле и с виду неотличим от первого — конвейер зелёный, диф маленький.
|
|
5
|
+
|
|
6
|
+
Для агента это выход по умолчанию: задача сформулирована как «сделай, чтобы прошло», и
|
|
7
|
+
`@ts-ignore` решает её буквально. Дефект при этом остаётся, а сигнал о нём исчезает навсегда —
|
|
8
|
+
это тот же класс, что молчащий гейт, только оплаченный одной строкой.
|
|
9
|
+
|
|
10
|
+
**Какой отказ это поймало.** Замер по пятнадцати чужим репозиториям (20 774 файла): 22 находки,
|
|
11
|
+
19 настоящих. Среди них — `// @ts-nocheck` первой строкой боевой страницы в `kodus-ai`
|
|
12
|
+
(проверка типов выключена для целого файла), `# noqa E501` без двоеточия в `pr-agent`
|
|
13
|
+
(flake8 такую форму читает как безадресный `# noqa`: погашено не одно правило, а все),
|
|
14
|
+
`/** @ts-ignore */` и `.strict().argv // eslint-disable-line` без имени правила в `repolinter`.
|
|
15
|
+
Запись в журнале: `incidents/README.md`, 2026-09-06.
|
|
16
|
+
|
|
17
|
+
**Что именно проверяется.** Красным делается только **безадресное** подавление:
|
|
18
|
+
|
|
19
|
+
| Красное | Зелёное |
|
|
20
|
+
|---|---|
|
|
21
|
+
| `# noqa` | `# noqa: F401 — причина` |
|
|
22
|
+
| `# type: ignore` | `# type: ignore[no-any-return]` |
|
|
23
|
+
| `# flake8: noqa`, `# ruff: noqa`, `# mypy: ignore-errors`, `# pylint: disable=all` | точечная форма с кодом |
|
|
24
|
+
| `/* eslint-disable */`, `// eslint-disable-next-line` без правила | `// eslint-disable-next-line no-console -- причина` |
|
|
25
|
+
| `// @ts-ignore`, `// @ts-nocheck` | `// @ts-expect-error -- причина` |
|
|
26
|
+
| `#[allow(warnings)]`, `@SuppressWarnings("all")`, `//nolint`, `rubocop:disable all`, `shellcheck disable` без `=SC…` | те же с именем правила |
|
|
27
|
+
| `--no-verify` в скрипте или конфиге конвейера | обычный коммит |
|
|
28
|
+
|
|
29
|
+
`@ts-ignore` красный всегда — единственное исключение из правила «безадресное». У него есть
|
|
30
|
+
строго лучший брат: `@ts-expect-error` краснеет сам, как только становится не нужен, то есть
|
|
31
|
+
не переживает починку кода. `@ts-ignore` переживает и молчит дальше.
|
|
32
|
+
|
|
33
|
+
**Готовый аналог.** Есть, и под каждый язык свой:
|
|
34
|
+
[`eslint-plugin-eslint-comments`](https://github.com/mysticatea/eslint-plugin-eslint-comments)
|
|
35
|
+
с правилом `require-description` — для JavaScript и TypeScript;
|
|
36
|
+
[`flake8-noqa`](https://github.com/plinss/flake8-noqa) — для Python, он же ловит сломанную форму
|
|
37
|
+
`# noqa E501`. Если проект на одном языке — ставь их, они точнее: разбирают код, а не текст.
|
|
38
|
+
Эта запись нужна там, где языков несколько или где ставить нечего: она переносима, ничего не
|
|
39
|
+
требует установить и покрывает то, чего нет ни у одного из двоих — `@ts-ignore` как класс,
|
|
40
|
+
`--no-verify` в конвейере, `@SuppressWarnings("all")`.
|
|
41
|
+
|
|
42
|
+
**Чего НЕ ловит.**
|
|
43
|
+
|
|
44
|
+
- **Подавление внутри строкового литерала считается настоящим.** Фикстуры инструментов, которые
|
|
45
|
+
сами обрабатывают подавления, дают ложные срабатывания: 3 из 22 на замере — тесты
|
|
46
|
+
`react-doctor` и `ratchets`. Отличить строку от комментария надёжно можно только разбором
|
|
47
|
+
языка, а его у переносимой проверки нет. Такие места — в `.aqkignore` или под храповик.
|
|
48
|
+
- **Точечное подавление без причины проходит.** `# noqa: E501` принимается, даже если рядом нет
|
|
49
|
+
ни слова о том, зачем. Требовать объяснение от каждой строки значит получить гейт, который
|
|
50
|
+
выключат целиком; за объяснением — к `require-description` из `eslint-plugin-eslint-comments`.
|
|
51
|
+
- **Не видит настройку в конфиге.** Правило, выключенное в `.eslintrc` или `pyproject.toml`, —
|
|
52
|
+
такое же ослабление, но это уже не подавление в коде, а решение проекта, записанное явно.
|
|
53
|
+
- **Не знает, было ли подавление оправдано.** Проверка отвечает на вопрос «названо ли, что
|
|
54
|
+
именно погашено», а не «стоило ли гасить».
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
#!/usr/bin/env sh
|
|
2
|
+
# Подавление проверки без адреса: «выключено всё» вместо «выключено вот это».
|
|
3
|
+
#
|
|
4
|
+
# ЗАЧЕМ ИМЕННО ЭТО. Когда проверка краснеет, у пишущего два выхода: починить код или заглушить
|
|
5
|
+
# проверку. Второй дешевле и с виду неотличим от первого — конвейер зелёный, диф маленький.
|
|
6
|
+
# Для агента это выход по умолчанию: ему поставлена задача «сделай, чтобы прошло».
|
|
7
|
+
#
|
|
8
|
+
# ГРАНИЦА НАМЕРЕННО УЗКАЯ. Красным делается не всякое подавление, а безадресное: `# noqa` без
|
|
9
|
+
# кода, `eslint-disable` без имени правила, `@SuppressWarnings("all")`. Точечное подавление с
|
|
10
|
+
# названным правилом — законный инструмент, и требовать объяснения от каждого значит получить
|
|
11
|
+
# гейт, который выключат. Одно исключение — `@ts-ignore`: у него есть строго лучший брат
|
|
12
|
+
# `@ts-expect-error`, который сам краснеет, когда становится не нужен.
|
|
13
|
+
DIR="${1:-.}"
|
|
14
|
+
# Существование файла проверяется ДО `.`, а не запасной веткой после. Прежняя строка
|
|
15
|
+
# `. файл 2>/dev/null || запасной_вариант` выглядела страховкой и ею не была: под `sh` (dash)
|
|
16
|
+
# неудачный `.` завершает скрипт немедленно, и ветка после `||` не выполняется никогда; под
|
|
17
|
+
# `bash`, которым гейты и запускаются из манифеста, она выполняется, но подставляет только
|
|
18
|
+
# ПЕРЕМЕННУЮ — функции обхода остаются неопределёнными, конвейер печатает пустоту, и проверка
|
|
19
|
+
# выходит с НУЛЁМ. Замерено 2026-09-08 на файле с настоящим нарушением: гейт сказал «чисто».
|
|
20
|
+
SKIP_LIB="$(dirname "$0")/../_skip.sh"
|
|
21
|
+
if [ ! -f "$SKIP_LIB" ]; then
|
|
22
|
+
echo "рядом с проверкой нет _skip.sh — обход не собран, проверка не состоялась"
|
|
23
|
+
echo " почини: скопируй гейт вместе с файлом kit/gates/_skip.sh, он общий на весь каталог"
|
|
24
|
+
exit 2
|
|
25
|
+
fi
|
|
26
|
+
. "$SKIP_LIB"
|
|
27
|
+
|
|
28
|
+
OUT=""
|
|
29
|
+
add() { [ -z "$1" ] || OUT="$OUT$1
|
|
30
|
+
"; }
|
|
31
|
+
|
|
32
|
+
# --- безадресные подавления в коде -------------------------------------------
|
|
33
|
+
# `# noqa` без двоеточия с кодом; `type: ignore` без [кода]; файловые выключатели целиком.
|
|
34
|
+
add "$(grep -rnE '(^|[^A-Za-z0-9_])#[[:space:]]*(noqa|type:[[:space:]]*ignore)([[:space:]]*$|[[:space:]]+[^:[])' \
|
|
35
|
+
$(skip_grep) $(include_code) "$DIR" 2>/dev/null)"
|
|
36
|
+
add "$(grep -rnE '#[[:space:]]*(flake8:[[:space:]]*noqa[[:space:]]*$|ruff:[[:space:]]*noqa[[:space:]]*$|mypy:[[:space:]]*ignore-errors|pylint:[[:space:]]*(skip-file|disable=all))' \
|
|
37
|
+
$(skip_grep) $(include_code) "$DIR" 2>/dev/null)"
|
|
38
|
+
|
|
39
|
+
# eslint-disable без имени правила: голая директива гасит ВСЁ в файле или на строке.
|
|
40
|
+
#
|
|
41
|
+
# ВЕДУЩИЙ КОММЕНТАРИЙ ОБЯЗАТЕЛЕН — «(//|/\*|\*)[^\"']*». Замер по пятнадцати чужим
|
|
42
|
+
# репозиториям показал целый класс ложных: строка ПРО подавление внутри кавычек
|
|
43
|
+
# («title: 'No @ts-ignore'», текст правила в наборе тестов) читалась как подавление.
|
|
44
|
+
# Директива живёт в комментарии; упоминание в строковом литерале — не она.
|
|
45
|
+
add "$(grep -rnE '(//|/\*|\*)[^\"'"'"']*eslint-disable(-next-line|-line)?[[:space:]]*(\*/|$|--)' \
|
|
46
|
+
$(skip_grep) $(include_code) "$DIR" 2>/dev/null)"
|
|
47
|
+
|
|
48
|
+
# @ts-ignore и @ts-nocheck. Первый гасит ошибку молча и остаётся, когда ошибки уже нет;
|
|
49
|
+
# @ts-expect-error на его месте краснеет, как только становится лишним.
|
|
50
|
+
add "$(grep -rnE '(//|/\*|\*)[^\"'"'"']*@ts-(ignore|nocheck)' \
|
|
51
|
+
$(skip_grep) $(include_code) "$DIR" 2>/dev/null)"
|
|
52
|
+
|
|
53
|
+
# Остальные экосистемы — только безадресная форма.
|
|
54
|
+
add "$(grep -rnE '#!?\[allow\((warnings|unused)\)\]|@SuppressWarnings\((\{[^}]*)?"all"|//[[:space:]]*nolint[[:space:]]*($|//)|rubocop:disable[[:space:]]+all|shellcheck[[:space:]]+disable[[:space:]]*($|[^=])' \
|
|
55
|
+
$(skip_grep) $(include_code) "$DIR" 2>/dev/null)"
|
|
56
|
+
|
|
57
|
+
# --- обход самого конвейера ---------------------------------------------------
|
|
58
|
+
# `--no-verify` пропускает хуки коммита. В committed-скрипте или конфиге конвейера это не
|
|
59
|
+
# настройка, а выключенная защита: смотрим шире кода — в оболочечные скрипты и конфиги.
|
|
60
|
+
# `-e`, а НЕ `--`. Двойное тире завершает разбор опций, и все флаги `--include` после него
|
|
61
|
+
# grep считает именами файлов: проверка читала весь репозиторий вместо оболочечных скриптов и
|
|
62
|
+
# конфигов. Нашлось замером — в выдаче оказались CHANGELOG чужих проектов.
|
|
63
|
+
add "$(grep -rnE -e '--no-verify' $(skip_grep) \
|
|
64
|
+
--include=*.sh --include=*.yml --include=*.yaml --include=*.json --include=*.toml \
|
|
65
|
+
--include=*.mk --include=Makefile "$DIR" 2>/dev/null)"
|
|
66
|
+
|
|
67
|
+
# Собственное определение записи — не подавление, а перечень того, что ищется. Без этого
|
|
68
|
+
# проверка находит сама себя в любом проекте, куда её поставили: `check.sh` содержит все
|
|
69
|
+
# образцы разом. Тот же класс, что образцы red/green, только файл другой.
|
|
70
|
+
SELFDIR="$(basename "$(dirname "$0")")"
|
|
71
|
+
LEFT="$(printf '%s' "$OUT" | grep -v '^$' | own_samples_filter "$DIR" \
|
|
72
|
+
| grep -vE "(^|/)$SELFDIR/(check\.sh|README\.md)")"
|
|
73
|
+
# Сгенерированные файлы правят не руками: подавление в них поставил инструмент.
|
|
74
|
+
LEFT="$(printf '%s\n' "$LEFT" | while IFS= read -r L; do
|
|
75
|
+
F="${L%%:*}"
|
|
76
|
+
[ -n "$F" ] || continue
|
|
77
|
+
is_generated "$F" || printf '%s\n' "$L"
|
|
78
|
+
done | grep -v '^$')"
|
|
79
|
+
|
|
80
|
+
[ -z "$LEFT" ] && exit 0
|
|
81
|
+
printf '%s\n' "$LEFT"
|
|
82
|
+
echo " почини: назови, ЧТО подавляешь и зачем — «# noqa: E501», «eslint-disable-next-line no-console -- причина»,"
|
|
83
|
+
echo " «@ts-expect-error» вместо «@ts-ignore». Безадресное подавление гасит и то, что сломается завтра."
|
|
84
|
+
exit 1
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
intent: проверка выключается точечно и с причиной, а не целиком
|
|
2
|
+
intent_en: a check is suppressed narrowly and with a reason, never wholesale
|
|
3
|
+
|
|
4
|
+
# Применимо везде, где есть код: подавление проверки — приём, а не язык. Список маркеров
|
|
5
|
+
# покрывает python, typescript, javascript, go, rust, java, ruby и оболочку сразу.
|
|
6
|
+
trigger:
|
|
7
|
+
always: true
|
|
8
|
+
|
|
9
|
+
recipes:
|
|
10
|
+
any: bash {gate}/check.sh {dir}
|
|
11
|
+
|
|
12
|
+
proof: incidents/README.md, 2026-09-06 «замер по пятнадцати чужим репозиториям» — 22 находки
|
|
13
|
+
на 20 774 файлах, из них 19 настоящих: `@ts-nocheck` на боевой странице в kodus-ai,
|
|
14
|
+
сломанный `# noqa E501` без двоеточия в pr-agent (flake8 читает его как безадресный),
|
|
15
|
+
`/** @ts-ignore */` и `eslint-disable-line` без имени правила в repolinter
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// @ts-expect-error -- у gateway нет типов; строка покраснеет сама, когда они появятся
|
|
2
|
+
import { pay } from "./gateway";
|
|
3
|
+
|
|
4
|
+
export async function checkout(cart: unknown) {
|
|
5
|
+
// eslint-disable-next-line no-console -- это программа командной строки, вывод и есть интерфейс
|
|
6
|
+
console.log(cart);
|
|
7
|
+
return pay(cart as never);
|
|
8
|
+
}
|