agent-quality-kit 0.4.2 → 0.6.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 +136 -12
- package/README.ru.md +119 -11
- package/kit/docs/ready-made-rules.md +40 -0
- package/kit/gates/README.md +35 -0
- package/kit/gates/_native.sh +18 -2
- package/kit/gates/_skip.sh +18 -0
- package/kit/gates/ci-actually-fails/README.md +42 -0
- package/kit/gates/ci-actually-fails/check.sh +93 -0
- package/kit/gates/ci-actually-fails/gate.yml +14 -0
- package/kit/gates/ci-actually-fails/green/.github/workflows/ci.yml +14 -0
- package/kit/gates/ci-actually-fails/red/.github/workflows/ci.yml +12 -0
- package/kit/gates/color-from-token/README.md +52 -0
- package/kit/gates/color-from-token/check.sh +69 -0
- package/kit/gates/color-from-token/gate.yml +15 -0
- package/kit/gates/color-from-token/green/Button.tsx +4 -0
- package/kit/gates/color-from-token/green/Panel.vue +4 -0
- package/kit/gates/color-from-token/green/card.css +5 -0
- package/kit/gates/color-from-token/green/notes.md +2 -0
- package/kit/gates/color-from-token/green/tokens.css +7 -0
- package/kit/gates/color-from-token/red/Button.tsx +4 -0
- package/kit/gates/color-from-token/red/Panel.vue +4 -0
- package/kit/gates/color-from-token/red/card.css +5 -0
- package/kit/gates/commit-explains-itself/check.sh +25 -2
- package/kit/gates/duplicate-code/check.sh +13 -4
- package/kit/gates/duplicate-code/gate.yml +11 -2
- package/kit/gates/gate-has-samples/check.sh +9 -3
- package/kit/gates/gate-not-weakened/README.md +54 -0
- package/kit/gates/gate-not-weakened/check.sh +72 -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/gates-are-runnable/check.sh +7 -1
- package/kit/gates/gates-run-in-ci/check.sh +7 -1
- package/kit/gates/lesson-has-outcome/check.sh +6 -1
- 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/test-has-assertion/README.md +47 -0
- package/kit/gates/test-has-assertion/check.sh +194 -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/ratchet/ratchet.sh +9 -2
- package/kit/rules/general.md +14 -0
- package/llms.txt +58 -0
- package/package.json +4 -2
- package/tool/commands/doctor.mjs +106 -11
- package/tool/commands/gates.mjs +18 -3
- package/tool/commands/project.mjs +13 -4
- package/tool/commands/report.mjs +2 -2
- package/tool/i18n/en.mjs +55 -0
- package/tool/i18n/ru.mjs +61 -0
- package/tool/i18n/templates-en.mjs +9 -9
- package/tool/i18n/templates-ru.mjs +9 -9
- package/tool/lib/baseline.mjs +87 -0
- package/tool/lib/core.mjs +10 -2
- package/tool/lib/manifest.mjs +69 -2
- package/tool/lib/repo.mjs +7 -0
- package/tool/lib/scope.mjs +96 -0
- package/tool/program.mjs +1 -0
- package/tool/selfcheck/gates.sh +29 -5
- package/tool/selfcheck/lifecycle.mjs +29 -0
- package/tool/selfcheck/mutation.sh +95 -0
- package/tool/selfcheck/smoke.sh +269 -8
- package/tool/selfcheck/syntax.sh +9 -1
- package/tool/selfcheck/units.mjs +160 -1
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
#!/usr/bin/env sh
|
|
2
|
+
# Конвейер, который гасит провал проверки: шаг выполнен, круг зелёный, проверка не сработала.
|
|
3
|
+
#
|
|
4
|
+
# ЗАЧЕМ ОТДЕЛЬНО ОТ «гейт запускается конвейером». Та проверка отвечает на вопрос «упомянут ли»,
|
|
5
|
+
# и на этом останавливается. Мы нашли дыру в собственной оснастке: конфиг с `run: pytest || true`
|
|
6
|
+
# проходил её зелёным — команда упомянута, а провалиться не может никогда. «Упомянут» и
|
|
7
|
+
# «работает» — разные утверждения, и весь этот стандарт стоит на том, чтобы их не путать.
|
|
8
|
+
DIR="${1:-.}"
|
|
9
|
+
|
|
10
|
+
CI=$(find "$DIR/.github/workflows" "$DIR/.gitlab-ci.yml" "$DIR/.circleci" "$DIR/Jenkinsfile" \
|
|
11
|
+
-type f 2>/dev/null)
|
|
12
|
+
[ -z "$CI" ] && { echo "конвейера нет — эта проверка не про тебя"; exit 0; }
|
|
13
|
+
|
|
14
|
+
# Что считается ПРОВЕРКОЙ. Список намеренно закрытый: маскировка бывает законной — необязательная
|
|
15
|
+
# выгрузка отчёта, публикация артефакта, уведомление. Красить всякий `continue-on-error` значит
|
|
16
|
+
# получить гейт, который выключат первым. Красим только гашение того, что выносит вердикт.
|
|
17
|
+
RUNNERS='aqk|doctor --run|pytest|tox|nox|unittest|jest|vitest|mocha|jasmine|karma|playwright|cypress|eslint|tsc|ruff|flake8|pylint|mypy|pyright|bandit|semgrep|gitleaks|trivy|rubocop|golangci-lint|golint|govet|go vet|go test|staticcheck|shellcheck|hadolint|actionlint|codespell|reviewdog|cargo test|cargo clippy|mvn|gradle|phpstan|psalm|npm test|npm run (test|lint|check|typecheck)|yarn (test|lint)|pnpm (test|lint)|make (test|lint|check)'
|
|
18
|
+
|
|
19
|
+
# Закрытый список не поспевает: замер по чужим репозиториям нашёл шаг «Run reviewdog
|
|
20
|
+
# (github-pr-check)» под `continue-on-error: true`, и ни одно имя из списка в нём не звучало.
|
|
21
|
+
# Поэтому вторая примета — СЛОВО в имени шага или в команде. Целым словом: «checkout» не
|
|
22
|
+
# «check», иначе первый же `actions/checkout` красил бы каждый конвейер на свете.
|
|
23
|
+
WORDS='([Ll]int|[Tt]est|[Cc]heck|[Vv]erify|[Aa]udit|[Ss]can|[Tt]ypecheck|[Cc]overage)([^A-Za-z]|$)'
|
|
24
|
+
|
|
25
|
+
# Проверка из манифеста — тоже проверка, как бы она ни называлась в этом проекте.
|
|
26
|
+
MAN="$DIR/.aqk.yml"
|
|
27
|
+
if [ -f "$MAN" ]; then
|
|
28
|
+
KEYS=$(tr -d '\r' < "$MAN" | awk '/^gates:/{g=1;next} /^[A-Za-z]/{g=0} g && /^[[:space:]]+[A-Za-z0-9_-]+:/{
|
|
29
|
+
sub(/^[[:space:]]*[A-Za-z0-9_-]*:[[:space:]]*/,""); gsub(/^"|"$/,"");
|
|
30
|
+
n=split($0,w," "); for(i=1;i<=n;i++) if (index(w[i],"/")) { print w[i]; break }
|
|
31
|
+
}')
|
|
32
|
+
fi
|
|
33
|
+
|
|
34
|
+
BAD=""
|
|
35
|
+
for F in $CI; do
|
|
36
|
+
# Разбор ПО ШАГАМ, а не по строкам. Построчно проверка врала в обе стороны: законный
|
|
37
|
+
# `continue-on-error` на шаге выгрузки отчёта красил соседний шаг с тестами, а слово «test»
|
|
38
|
+
# внутри перечисления типов коммита («feat|fix|test|chore») делало проверкой строку, которая
|
|
39
|
+
# ничего не проверяет. Замер по чужим конвейерам дал 4 ложных из 10 — переписано на блоки.
|
|
40
|
+
#
|
|
41
|
+
# Шаг начинается элементом списка («- ») или ключом верхнего уровня: так устроен и github,
|
|
42
|
+
# и gitlab, где `allow_failure` живёт на уровне задачи.
|
|
43
|
+
RES=$(tr -d '\r' < "$F" | awk -v runners="$RUNNERS" -v words="$WORDS" -v keys="$KEYS" -v file="$F" '
|
|
44
|
+
function isComment(l) { return l ~ /^[[:space:]]*#/ }
|
|
45
|
+
function looksLikeCheck(l, j, nk) {
|
|
46
|
+
if (isComment(l)) return 0
|
|
47
|
+
# `uses:` — чужое действие. Его провал бывает законно необязательным: выгрузка отчёта,
|
|
48
|
+
# комментарий в пул-реквест, уведомление. Вердикт выносит то, что ЗАПУСКАЮТ.
|
|
49
|
+
if (l ~ /^[[:space:]]*(-[[:space:]]+)?uses[[:space:]]*:/) return 0
|
|
50
|
+
if (l ~ runners) return 1
|
|
51
|
+
# Проверка, объявленная в манифесте ЭТОГО проекта, — тоже проверка, как бы она ни
|
|
52
|
+
# называлась. Это самая точная примета из трёх: не догадка по имени, а список, который
|
|
53
|
+
# проект написал сам.
|
|
54
|
+
nk = split(keys, K, "\n")
|
|
55
|
+
for (j = 1; j <= nk; j++) if (K[j] != "" && index(l, K[j])) return 1
|
|
56
|
+
# Слово-примета — ТОЛЬКО в имени шага. В теле команды оно ловит своё же упоминание:
|
|
57
|
+
# «grep -oE (feat|fix|test|chore)» — это разбор заголовка коммита, а не проверка.
|
|
58
|
+
if (l ~ /^[[:space:]]*(-[[:space:]]+)?name[[:space:]]*:/ && l ~ words) return 1
|
|
59
|
+
return 0
|
|
60
|
+
}
|
|
61
|
+
function isMask(l) {
|
|
62
|
+
return !isComment(l) && l ~ /^[[:space:]]*(continue-on-error|allow_failure|ignore_failure)[[:space:]]*:[[:space:]]*(true|yes)/
|
|
63
|
+
}
|
|
64
|
+
function isBoundary(l) { return l ~ /^[[:space:]]*-[[:space:]]/ || l ~ /^[A-Za-z_.-]+[[:space:]]*:/ }
|
|
65
|
+
function flush( ) {
|
|
66
|
+
if (blockStart && blockCheck && blockMask)
|
|
67
|
+
printf "%s:%d: проверка не может провалиться — шаг под %s\n", file, blockCheckLine, blockMaskText
|
|
68
|
+
blockStart = 0; blockCheck = 0; blockMask = 0
|
|
69
|
+
}
|
|
70
|
+
{
|
|
71
|
+
# Гашение прямо в команде красится только для ЗАКРЫТОГО списка запускалок: «|| true» на
|
|
72
|
+
# вспомогательной команде внутри скрипта (`docker network create … || true`) — это
|
|
73
|
+
# идемпотентность, а не выключенная проверка.
|
|
74
|
+
if (!isComment($0) && $0 ~ runners && $0 ~ /\|\|[[:space:]]*(true|:|exit[[:space:]]+0)/) {
|
|
75
|
+
line = $0; sub(/^[[:space:]]+/, "", line)
|
|
76
|
+
printf "%s:%d: провал погашен прямо в команде: %s\n", file, NR, substr(line, 1, 90)
|
|
77
|
+
}
|
|
78
|
+
if (isBoundary($0)) flush()
|
|
79
|
+
if (!blockStart) blockStart = NR
|
|
80
|
+
if (!blockCheck && looksLikeCheck($0)) { blockCheck = 1; blockCheckLine = NR }
|
|
81
|
+
if (isMask($0)) { blockMask = 1; blockMaskText = $0; sub(/^[[:space:]]+/, "", blockMaskText) }
|
|
82
|
+
}
|
|
83
|
+
END { flush() }' 2>/dev/null)
|
|
84
|
+
[ -z "$RES" ] || BAD="$BAD$RES
|
|
85
|
+
"
|
|
86
|
+
done
|
|
87
|
+
|
|
88
|
+
LEFT="$(printf '%s' "$BAD" | grep -v '^$')"
|
|
89
|
+
[ -z "$LEFT" ] && exit 0
|
|
90
|
+
printf '%s\n' "$LEFT"
|
|
91
|
+
echo " почини: убери «|| true» и «continue-on-error» с шага, который выносит вердикт."
|
|
92
|
+
echo " шаг, который не может провалиться, — это не проверка, а строка в логе."
|
|
93
|
+
exit 1
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
intent: шаг конвейера, выносящий вердикт, может провалиться — а не только выполниться
|
|
2
|
+
intent_en: a pipeline step that renders a verdict can actually fail, not merely run
|
|
3
|
+
|
|
4
|
+
# Только там, где конвейер есть. Проверять его отсутствие — дело другой записи.
|
|
5
|
+
trigger:
|
|
6
|
+
has_ci: true
|
|
7
|
+
|
|
8
|
+
recipes:
|
|
9
|
+
any: bash {gate}/check.sh {dir}
|
|
10
|
+
|
|
11
|
+
proof: incidents/README.md, 2026-09-06 «упомянут и работает — разные утверждения» — дыра
|
|
12
|
+
найдена в собственной оснастке (`gates-run-in-ci` пропускал `pytest || true` зелёным),
|
|
13
|
+
замер по пятнадцати чужим репозиториям подтвердил класс: у `reviewdog` два шага
|
|
14
|
+
собственных линтеров идут под `continue-on-error: true`
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# Цвет приходит из токена темы, а не литералом в компоненте
|
|
2
|
+
|
|
3
|
+
**Намерение.** Литеральный цвет тема не перекрашивает. В проекте с несколькими темами экран,
|
|
4
|
+
свёрстанный литералами, в тёмной теме остаётся светлым пятном — и видит это не автор, а
|
|
5
|
+
пользователь, который тему переключил. Дефект тихий по построению: в теме автора всё правильно.
|
|
6
|
+
|
|
7
|
+
**Какой отказ это поймало.** Живой проект на React с двенадцатью темами. Запрет на сырой цвет
|
|
8
|
+
там оказался проверкой №8 из семнадцати в собственной оснастке — и завели его не из вкуса, а
|
|
9
|
+
после экранов, не переживших переключение темы. Запись в журнале: `incidents/README.md`,
|
|
10
|
+
2026-09-06, там же измерено соотношение, ради которого запись переносили в каталог: из тридцати
|
|
11
|
+
с лишним запретов интерфейса машина ловила одиннадцать, остальные держались на глазах человека.
|
|
12
|
+
|
|
13
|
+
**Файл-источник значений называет проект.** Где-то литерал обязан быть — иначе токену неоткуда
|
|
14
|
+
взяться. По умолчанию проверка пропускает файлы, чьё имя содержит `tokens`, `theme`, `palette`,
|
|
15
|
+
`colors`, `variables`, `design-system`. Своё имя проект называет сам, рядом с командой в
|
|
16
|
+
манифесте — тем же приёмом, что каталоги с законной печатью в `no-print-in-prod`:
|
|
17
|
+
|
|
18
|
+
```yaml
|
|
19
|
+
color-from-token: "AQK_COLOR_SOURCE='tokens|brand' bash .aqk/gates/color-from-token/check.sh ."
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
**Готовый аналог есть, но он покрывает меньше, и поэтому его здесь нет.** У `stylelint` есть
|
|
23
|
+
правило `color-no-hex`, и для файлов стилей оно точнее нашего поиска. Но оно видит только стили,
|
|
24
|
+
а литерал в компоненте — `style={{ background: "#2563EB" }}`, `className="bg-[#2563EB]"` —
|
|
25
|
+
остаётся вне его. Родной рецепт покрывал бы половину предмета, и запись означала бы разное в
|
|
26
|
+
зависимости от того, что стоит в системе. Урок этого же дня: родной и переносимый рецепты одной
|
|
27
|
+
записи обязаны мерить одно и то же. Поэтому рецепт один, переносимый; кому нужна точность по
|
|
28
|
+
стилям — ставит `stylelint` отдельно, это не отменяет запись.
|
|
29
|
+
|
|
30
|
+
**Замер на живом проекте.** React, двенадцать тем, 2750 файлов кода. Первый прогон дал 20
|
|
31
|
+
находок, и **все 20 были чужими** — литералы в отдельных html-документах внутри `.claude/`:
|
|
32
|
+
отчёты и справочники оснастки агента. Отдельный html-документ темы не имеет и иметь не может,
|
|
33
|
+
литерал там законен; `.html` убран из списка расширений — не на вкус, а по этому замеру.
|
|
34
|
+
Второй прогон: одна находка во фронтенде — палитра диаграммы загрузки, где цвета треков вписаны
|
|
35
|
+
литералами Tailwind-500 и потому не меняются вместе с темой. Ложных срабатываний: **одно**, и
|
|
36
|
+
оно тоже исправлено — цвет, упомянутый внутри многострочного комментария, читался как находка.
|
|
37
|
+
|
|
38
|
+
**Чего НЕ ловит.**
|
|
39
|
+
|
|
40
|
+
- Цвет, собранный по частям: `"#" + hex`, `rgb(37, 99, 235)`, `hsl(...)`, именованные `red` и
|
|
41
|
+
`white`. Проверка ищет литерал в форме `#rgb`, `#rrggbb`, `#rrggbbaa`, а не «цвет вообще».
|
|
42
|
+
- Селектор по идентификатору, записанный шестнадцатеричными буквами (`#abcdef { … }`), и якорь
|
|
43
|
+
в ссылке той же формы читаются как цвет. Это цена простоты; лечится именем в allowlist.
|
|
44
|
+
- Литерал в разметке и в документации: расширений `.md` и `.html` в списке нет намеренно.
|
|
45
|
+
В разметке цвет упоминают, а не применяют; отдельный html-документ не имеет темы, и литерал
|
|
46
|
+
в нём законен — как печать в скрипте для `no-print-in-prod`.
|
|
47
|
+
- Цвет, вычисленный в рантайме: `lighten(base, 0.2)`, значение из ответа сервера, инлайн-стиль,
|
|
48
|
+
собранный из переменной. Проверка читает исходник, а не то, что окажется в браузере.
|
|
49
|
+
|
|
50
|
+
**Образцы.** `red/` — три стека, где цвет вписан литералом: React со `style`, обычный CSS,
|
|
51
|
+
однофайловый компонент Vue. `green/` — то же самое через токены, плюс `tokens.css`, где литерал
|
|
52
|
+
законен, и заметка в разметке, на которой проверка обязана молчать.
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
#!/usr/bin/env sh
|
|
2
|
+
# Цвет приходит из токена темы, а не из литерала в компоненте.
|
|
3
|
+
#
|
|
4
|
+
# ЗАЧЕМ. Литеральный цвет тема не перекрашивает. Проект с несколькими темами получает экран,
|
|
5
|
+
# который в тёмной теме остаётся светлым пятном, — и это видно не автору, а пользователю,
|
|
6
|
+
# который переключил тему. Дефект тихий: в теме автора всё выглядит правильно.
|
|
7
|
+
DIR="${1:-.}"
|
|
8
|
+
. "$(dirname "$0")/../_skip.sh" 2>/dev/null || SKIP_NAMES=".git .aqk node_modules .venv"
|
|
9
|
+
|
|
10
|
+
# Расширения, где живёт ТЕМИЗИРУЕМЫЙ интерфейс. Своё, а не общий CODE_EXT: там нет ни css, ни
|
|
11
|
+
# vue — они не код в смысле «отладочная печать», но именно в них живёт цвет.
|
|
12
|
+
#
|
|
13
|
+
# `.html` в списке НЕТ, и это измерено, а не выбрано на вкус. Первый прогон по живому проекту
|
|
14
|
+
# (React, 12 тем) дал 20 находок, и ВСЕ 20 — в отдельных html-документах внутри `.claude/`:
|
|
15
|
+
# отчёты и справочники оснастки агента. Во фронтенде — ноль. Отдельный html-документ не имеет
|
|
16
|
+
# темы и не может её иметь: литерал там законен, как печать в скрипте. Гейт, на 100%
|
|
17
|
+
# состоящий из чужих находок, выключают в первый день — см. шапку `_skip.sh`.
|
|
18
|
+
COLOR_EXT="css scss sass less styl ts tsx js jsx mjs cjs vue svelte astro"
|
|
19
|
+
|
|
20
|
+
# Файлы-ИСТОЧНИКИ значений: там литерал законен, иначе токену неоткуда взяться. Имя источника
|
|
21
|
+
# проект называет сам — как и каталоги с законной печатью в no-print-in-prod. По умолчанию
|
|
22
|
+
# общепринятые имена; свои задаются переменной, рядом с командой в манифесте.
|
|
23
|
+
SOURCE_RE="${AQK_COLOR_SOURCE:-tokens|theme|themes|palette|colors|colours|variables|design-system}"
|
|
24
|
+
|
|
25
|
+
EXT_RE="$(printf '%s' "$COLOR_EXT" | tr ' ' '|')"
|
|
26
|
+
|
|
27
|
+
# shellcheck disable=SC2086
|
|
28
|
+
HITS=$(find "$DIR" $(skip_find "$DIR") -type f -print 2>/dev/null \
|
|
29
|
+
| grep -E "\.($EXT_RE)$" \
|
|
30
|
+
| grep -viE "(^|/)[^/]*($SOURCE_RE)[^/]*\.($EXT_RE)$" \
|
|
31
|
+
| while IFS= read -r F; do is_generated "$F" || printf '%s\n' "$F"; done \
|
|
32
|
+
| LC_ALL=C sort \
|
|
33
|
+
| xargs -r awk '
|
|
34
|
+
# Блочные комментарии вырезаются ПО СОСТОЯНИЮ, а не построчно. Однострочный фильтр
|
|
35
|
+
# смотрит только на начало строки, и цвет, упомянутый в середине многострочного
|
|
36
|
+
# пояснения, читался как находка: на живом проекте так и вышло — единственное
|
|
37
|
+
# ложное срабатывание пришло из строки внутри /* … */. Тот же класс, что запись
|
|
38
|
+
# журнала «печать внутри комментария считалась печатью».
|
|
39
|
+
FNR == 1 { inblock = 0 }
|
|
40
|
+
{
|
|
41
|
+
line = $0
|
|
42
|
+
if (inblock) {
|
|
43
|
+
i = index(line, "*/")
|
|
44
|
+
if (i == 0) next
|
|
45
|
+
line = substr(line, i + 2); inblock = 0
|
|
46
|
+
}
|
|
47
|
+
# Вырезаем закрытые /* … */ внутри строки, затем открытый хвост.
|
|
48
|
+
while ((s = index(line, "/*")) > 0) {
|
|
49
|
+
rest = substr(line, s + 2)
|
|
50
|
+
e = index(rest, "*/")
|
|
51
|
+
if (e == 0) { line = substr(line, 1, s - 1); inblock = 1; break }
|
|
52
|
+
line = substr(line, 1, s - 1) substr(rest, e + 2)
|
|
53
|
+
}
|
|
54
|
+
if (line ~ /#[0-9a-fA-F]{8}([^0-9a-fA-F]|$)|#[0-9a-fA-F]{6}([^0-9a-fA-F]|$)|#[0-9a-fA-F]{3}([^0-9a-fA-F]|$)/)
|
|
55
|
+
print FILENAME ":" FNR ":" $0
|
|
56
|
+
}
|
|
57
|
+
' 2>/dev/null \
|
|
58
|
+
| drop_comments \
|
|
59
|
+
| own_samples_filter "$DIR")
|
|
60
|
+
|
|
61
|
+
if [ -n "$HITS" ]; then
|
|
62
|
+
printf '%s\n' "$HITS" | head -20
|
|
63
|
+
N=$(printf '%s\n' "$HITS" | grep -c .); [ "$N" -gt 20 ] && echo " … и ещё $((N - 20))"
|
|
64
|
+
echo " почини: замени литерал на токен темы — var(--color-…), theme('colors.…'), \$color-…"
|
|
65
|
+
echo " литеральный цвет тема не перекрашивает: в другой теме экран останется чужим пятном."
|
|
66
|
+
echo " файл-источник значений называется сам: AQK_COLOR_SOURCE='tokens|brand' перед командой."
|
|
67
|
+
exit 1
|
|
68
|
+
fi
|
|
69
|
+
exit 0
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
intent: цвет приходит из токена темы, а не литералом в компоненте
|
|
2
|
+
intent_en: colour comes from a theme token, not a literal in the component
|
|
3
|
+
|
|
4
|
+
# Не по языку, а по наличию интерфейса. Триггер `langs: javascript, typescript` показывал бы
|
|
5
|
+
# запись каждому бэкенду, библиотеке и утилите командной строки на JS — а их большинство, и
|
|
6
|
+
# интерфейса там нет. Записи, показанной не тому, не верят, и это стоит доверия всему каталогу.
|
|
7
|
+
trigger:
|
|
8
|
+
has_ui: true
|
|
9
|
+
|
|
10
|
+
recipes:
|
|
11
|
+
any: bash {gate}/check.sh {dir}
|
|
12
|
+
|
|
13
|
+
proof: incidents/README.md, 2026-09-06 «из тридцати с лишним запретов интерфейса гейт ловил
|
|
14
|
+
одиннадцать» — прогон по audit_project (Django + React, 12 тем): проверка №8 в собственной
|
|
15
|
+
оснастке проекта родилась после экранов, остававшихся светлыми в тёмной теме
|
|
@@ -26,7 +26,30 @@ else
|
|
|
26
26
|
exit 0
|
|
27
27
|
fi
|
|
28
28
|
|
|
29
|
-
|
|
29
|
+
# Сообщение слияния сочиняет не автор, а инструмент. GitHub делает это дважды и по-разному:
|
|
30
|
+
# при разборе предложения изменений выкладывает синтетический коммит «Merge <sha> into <sha>»,
|
|
31
|
+
# а кнопка Merge на сайте пишет «Merge pull request #N from …». Первую форму гейт научился
|
|
32
|
+
# различать вчера по словам — и на второй покраснел бы снова.
|
|
33
|
+
#
|
|
34
|
+
# Поэтому смотрим не на слова, а на форму: у слияния два родителя. Если при этом в сообщении
|
|
35
|
+
# нет разделов отчёта — его писал инструмент, и спрашивать надо второго родителя, последний
|
|
36
|
+
# коммит автора. Слияние, которое автор описал сам, проходит по HEAD и ничего не теряет.
|
|
37
|
+
REF=HEAD
|
|
38
|
+
PARENTS=$(cd "$DIR" && git rev-list --parents -n 1 HEAD 2>/dev/null | wc -w)
|
|
39
|
+
HEADMSG=$(cd "$DIR" && git log -1 --format=%B 2>/dev/null)
|
|
40
|
+
if [ "${PARENTS:-0}" -ge 3 ] &&
|
|
41
|
+
! printf '%s\n' "$HEADMSG" | grep -q "^Сделано:" &&
|
|
42
|
+
! printf '%s\n' "$HEADMSG" | grep -q "^Не уверен:"; then
|
|
43
|
+
if (cd "$DIR" && git rev-parse -q --verify HEAD^2 >/dev/null 2>&1); then
|
|
44
|
+
REF=HEAD^2
|
|
45
|
+
else
|
|
46
|
+
echo "слияние без отчёта, второго родителя не видно — проверка пропущена"
|
|
47
|
+
echo " дай конвейеру два коммита истории: actions/checkout@v4 с fetch-depth: 2"
|
|
48
|
+
exit 0
|
|
49
|
+
fi
|
|
50
|
+
fi
|
|
51
|
+
|
|
52
|
+
MSG=$(cd "$DIR" && git log -1 --format=%B "$REF" 2>/dev/null)
|
|
30
53
|
|
|
31
54
|
# Коммит, который трогает только журнал, отчёта в теле не требует: сама запись и есть отчёт,
|
|
32
55
|
# причём подробнее — и её сторожит `lesson-has-outcome`. Иначе гейт воюет с командой `note`,
|
|
@@ -38,7 +61,7 @@ else
|
|
|
38
61
|
[ -z "$LESSONS" ] && LESSONS="incidents"
|
|
39
62
|
case "$LESSONS" in http*) LESSONS="" ;; esac # journal по адресу, а не путём — не применимо
|
|
40
63
|
if [ -n "$LESSONS" ]; then
|
|
41
|
-
FILES=$(cd "$DIR" && git show --pretty=format: --name-only
|
|
64
|
+
FILES=$(cd "$DIR" && git show --pretty=format: --name-only "$REF" 2>/dev/null | grep -v '^$')
|
|
42
65
|
if [ -n "$FILES" ]; then
|
|
43
66
|
OUTSIDE=$(printf '%s\n' "$FILES" | grep -v "^$LESSONS/" | grep -v "^$LESSONS\$")
|
|
44
67
|
if [ -z "$OUTSIDE" ]; then
|
|
@@ -19,7 +19,8 @@ find "$DIR" $(skip_find "$DIR") $TESTS -type f \
|
|
|
19
19
|
! -name 'test_*' ! -name '*_test.*' ! -name '*.test.*' ! -name '*.spec.*' \
|
|
20
20
|
-print 2>/dev/null | only_code | own_samples_filter "$DIR" \
|
|
21
21
|
| while IFS= read -r F; do is_generated "$F" || printf '%s\n' "$F"; done \
|
|
22
|
-
|
|
|
22
|
+
| LC_ALL=C sort \
|
|
23
|
+
| xargs -r env LC_ALL=C awk -v WIN="$WIN" '
|
|
23
24
|
FNR == 1 { n = 0; delete buf }
|
|
24
25
|
{
|
|
25
26
|
line = $0
|
|
@@ -37,11 +38,19 @@ find "$DIR" $(skip_find "$DIR") $TESTS -type f \
|
|
|
37
38
|
}
|
|
38
39
|
}
|
|
39
40
|
' 2>/dev/null | sort -u \
|
|
40
|
-
| awk -F' и |: ' '
|
|
41
|
+
| LC_ALL=C awk -F' и |: ' '
|
|
41
42
|
# Один повторённый кусок даёт столько сообщений, на сколько окон он делится: восемь
|
|
42
43
|
# строк — восемь почти одинаковых строк отчёта. Схлопываем в одну на пару файлов.
|
|
43
|
-
|
|
44
|
-
|
|
44
|
+
#
|
|
45
|
+
# Пара упорядочивается лексикографически, а НЕ в порядке обхода. Порядок, в котором
|
|
46
|
+
# find отдаёт файлы, свой на каждой системе (здесь — по хешу имени), и реестр, снятый
|
|
47
|
+
# на одной машине, краснел в конвейере целиком: те же дубли читались как новые, а
|
|
48
|
+
# храповик объявлял их исправленными и вычёркивал. Одной сортировки списка файлов мало:
|
|
49
|
+
# когда кусок лежит в трёх файлах, «первым» становится тот, кого раньше отдал обход.
|
|
50
|
+
{ split($1, a, ":"); split($2, b, ":")
|
|
51
|
+
if (a[1] <= b[1]) { pair = a[1] " и " b[1]; loc = $1 " и " $2 }
|
|
52
|
+
else { pair = b[1] " и " a[1]; loc = $2 " и " $1 }
|
|
53
|
+
if (!(pair in seen)) { seen[pair] = loc }
|
|
45
54
|
cnt[pair]++ }
|
|
46
55
|
END { for (p in seen) print seen[p] ": одинаковый кусок" (cnt[p] > 1 ? " (окон: " cnt[p] ")" : "") }
|
|
47
56
|
' | sort > /tmp/.dup.$$
|
|
@@ -7,7 +7,16 @@ trigger:
|
|
|
7
7
|
recipes:
|
|
8
8
|
any: bash {gate}/check.sh {dir}
|
|
9
9
|
# jscpd умеет и Python, и TypeScript одним прогоном и считает похожесть, а не совпадение.
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
#
|
|
11
|
+
# --format обязателен: без него jscpd считает клонами разметку, JSON и текст. Замер на живом
|
|
12
|
+
# проекте (Django + React, 2750 файлов кода): 1846 клонов, из них 435 — markdown, json и
|
|
13
|
+
# text, то есть 23.6% находок не про код вовсе. Переносимая проверка смотрит только в
|
|
14
|
+
# расширения кода (CODE_EXT в _skip.sh), и родная обязана мерить то же самое: иначе один
|
|
15
|
+
# гейт означает разное на разных машинах.
|
|
16
|
+
#
|
|
17
|
+
# «c#» в списке нет намеренно: разбор манифеста режет строку по «#», и рецепт обрывался бы
|
|
18
|
+
# на «c». Обёртку _native.sh не пишем — её добавляет сама программа при установке гейта.
|
|
19
|
+
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
|
+
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}
|
|
12
21
|
|
|
13
22
|
proof: incidents/README.md — «2026-08-25 разбор 1069 коммитов»: число моделей захардкожено в четырёх местах четырьмя разными значениями
|
|
@@ -15,9 +15,15 @@
|
|
|
15
15
|
DIR="${1:-.}"
|
|
16
16
|
MAN="$DIR/.aqk.yml"
|
|
17
17
|
[ -f "$MAN" ] || { echo "нет .aqk.yml — проверять нечего"; exit 0; }
|
|
18
|
+
# Перевод строк из windows-чекаута снимается ДО разбора. Жадный захват в sed («[^"#]*») и
|
|
19
|
+
# якорь конца строки («s/"$//») проглатывают \r: путь получался с кавычкой на хвосте, а
|
|
20
|
+
# красный образец переставал краснеть. Найдено мутационной проверкой
|
|
21
|
+
# (tool/selfcheck/mutation.sh), а не на Windows-машине — её у нас до сих пор нет.
|
|
22
|
+
MANTEXT="$(tr -d '\r' < "$MAN")"
|
|
18
23
|
|
|
19
|
-
|
|
20
|
-
|
|
24
|
+
|
|
25
|
+
SAMPLES=$(printf '%s\n' "$MANTEXT" | sed -n 's/^samples:[[:space:]]*"\{0,1\}\([^"#]*\)"\{0,1\}[[:space:]]*$/\1/p' | head -1)
|
|
26
|
+
KEYS=$(printf '%s\n' "$MANTEXT" | awk '/^gates:/{g=1;next} /^[A-Za-z]/{g=0} g && /^[[:space:]]+[A-Za-z0-9_-]+:/{sub(/:.*/,"");gsub(/[[:space:]]/,"");print}')
|
|
21
27
|
|
|
22
28
|
[ -z "$KEYS" ] && { echo "гейтов не объявлено — проверять нечего"; exit 0; }
|
|
23
29
|
|
|
@@ -32,7 +38,7 @@ fi
|
|
|
32
38
|
BAD=0
|
|
33
39
|
for K in $KEYS; do
|
|
34
40
|
# Команда записи целиком — по ней видно, сканер это или собственный прогон проекта.
|
|
35
|
-
CMD=$(awk -v k="$K" '/^gates:/{g=1;next} /^[A-Za-z]/{g=0} g && $0 ~ "^[[:space:]]+" k ":" {sub(/^[[:space:]]*[A-Za-z0-9_-]+:[[:space:]]*/,"");gsub(/^"|"$/,"");print;exit}'
|
|
41
|
+
CMD=$(printf '%s\n' "$MANTEXT" | awk -v k="$K" '/^gates:/{g=1;next} /^[A-Za-z]/{g=0} g && $0 ~ "^[[:space:]]+" k ":" {sub(/^[[:space:]]*[A-Za-z0-9_-]+:[[:space:]]*/,"");gsub(/^"|"$/,"");print;exit}')
|
|
36
42
|
case "$CMD" in
|
|
37
43
|
*"$SAMPLES/"*) ;; # сканер из каталога — образцы обязательны
|
|
38
44
|
*) continue ;; # свой прогон — арбитр внутри него
|
|
@@ -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,72 @@
|
|
|
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
|
+
. "$(dirname "$0")/../_skip.sh" 2>/dev/null || SKIP_NAMES=".git .aqk node_modules .venv"
|
|
15
|
+
|
|
16
|
+
OUT=""
|
|
17
|
+
add() { [ -z "$1" ] || OUT="$OUT$1
|
|
18
|
+
"; }
|
|
19
|
+
|
|
20
|
+
# --- безадресные подавления в коде -------------------------------------------
|
|
21
|
+
# `# noqa` без двоеточия с кодом; `type: ignore` без [кода]; файловые выключатели целиком.
|
|
22
|
+
add "$(grep -rnE '(^|[^A-Za-z0-9_])#[[:space:]]*(noqa|type:[[:space:]]*ignore)([[:space:]]*$|[[:space:]]+[^:[])' \
|
|
23
|
+
$(skip_grep) $(include_code) "$DIR" 2>/dev/null)"
|
|
24
|
+
add "$(grep -rnE '#[[:space:]]*(flake8:[[:space:]]*noqa[[:space:]]*$|ruff:[[:space:]]*noqa[[:space:]]*$|mypy:[[:space:]]*ignore-errors|pylint:[[:space:]]*(skip-file|disable=all))' \
|
|
25
|
+
$(skip_grep) $(include_code) "$DIR" 2>/dev/null)"
|
|
26
|
+
|
|
27
|
+
# eslint-disable без имени правила: голая директива гасит ВСЁ в файле или на строке.
|
|
28
|
+
#
|
|
29
|
+
# ВЕДУЩИЙ КОММЕНТАРИЙ ОБЯЗАТЕЛЕН — «(//|/\*|\*)[^\"']*». Замер по пятнадцати чужим
|
|
30
|
+
# репозиториям показал целый класс ложных: строка ПРО подавление внутри кавычек
|
|
31
|
+
# («title: 'No @ts-ignore'», текст правила в наборе тестов) читалась как подавление.
|
|
32
|
+
# Директива живёт в комментарии; упоминание в строковом литерале — не она.
|
|
33
|
+
add "$(grep -rnE '(//|/\*|\*)[^\"'"'"']*eslint-disable(-next-line|-line)?[[:space:]]*(\*/|$|--)' \
|
|
34
|
+
$(skip_grep) $(include_code) "$DIR" 2>/dev/null)"
|
|
35
|
+
|
|
36
|
+
# @ts-ignore и @ts-nocheck. Первый гасит ошибку молча и остаётся, когда ошибки уже нет;
|
|
37
|
+
# @ts-expect-error на его месте краснеет, как только становится лишним.
|
|
38
|
+
add "$(grep -rnE '(//|/\*|\*)[^\"'"'"']*@ts-(ignore|nocheck)' \
|
|
39
|
+
$(skip_grep) $(include_code) "$DIR" 2>/dev/null)"
|
|
40
|
+
|
|
41
|
+
# Остальные экосистемы — только безадресная форма.
|
|
42
|
+
add "$(grep -rnE '#!?\[allow\((warnings|unused)\)\]|@SuppressWarnings\((\{[^}]*)?"all"|//[[:space:]]*nolint[[:space:]]*($|//)|rubocop:disable[[:space:]]+all|shellcheck[[:space:]]+disable[[:space:]]*($|[^=])' \
|
|
43
|
+
$(skip_grep) $(include_code) "$DIR" 2>/dev/null)"
|
|
44
|
+
|
|
45
|
+
# --- обход самого конвейера ---------------------------------------------------
|
|
46
|
+
# `--no-verify` пропускает хуки коммита. В committed-скрипте или конфиге конвейера это не
|
|
47
|
+
# настройка, а выключенная защита: смотрим шире кода — в оболочечные скрипты и конфиги.
|
|
48
|
+
# `-e`, а НЕ `--`. Двойное тире завершает разбор опций, и все флаги `--include` после него
|
|
49
|
+
# grep считает именами файлов: проверка читала весь репозиторий вместо оболочечных скриптов и
|
|
50
|
+
# конфигов. Нашлось замером — в выдаче оказались CHANGELOG чужих проектов.
|
|
51
|
+
add "$(grep -rnE -e '--no-verify' $(skip_grep) \
|
|
52
|
+
--include=*.sh --include=*.yml --include=*.yaml --include=*.json --include=*.toml \
|
|
53
|
+
--include=*.mk --include=Makefile "$DIR" 2>/dev/null)"
|
|
54
|
+
|
|
55
|
+
# Собственное определение записи — не подавление, а перечень того, что ищется. Без этого
|
|
56
|
+
# проверка находит сама себя в любом проекте, куда её поставили: `check.sh` содержит все
|
|
57
|
+
# образцы разом. Тот же класс, что образцы red/green, только файл другой.
|
|
58
|
+
SELFDIR="$(basename "$(dirname "$0")")"
|
|
59
|
+
LEFT="$(printf '%s' "$OUT" | grep -v '^$' | own_samples_filter "$DIR" \
|
|
60
|
+
| grep -vE "(^|/)$SELFDIR/(check\.sh|README\.md)")"
|
|
61
|
+
# Сгенерированные файлы правят не руками: подавление в них поставил инструмент.
|
|
62
|
+
LEFT="$(printf '%s\n' "$LEFT" | while IFS= read -r L; do
|
|
63
|
+
F="${L%%:*}"
|
|
64
|
+
[ -n "$F" ] || continue
|
|
65
|
+
is_generated "$F" || printf '%s\n' "$L"
|
|
66
|
+
done | grep -v '^$')"
|
|
67
|
+
|
|
68
|
+
[ -z "$LEFT" ] && exit 0
|
|
69
|
+
printf '%s\n' "$LEFT"
|
|
70
|
+
echo " почини: назови, ЧТО подавляешь и зачем — «# noqa: E501», «eslint-disable-next-line no-console -- причина»,"
|
|
71
|
+
echo " «@ts-expect-error» вместо «@ts-ignore». Безадресное подавление гасит и то, что сломается завтра."
|
|
72
|
+
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
|