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.
Files changed (62) hide show
  1. package/README.md +61 -15
  2. package/README.ru.md +62 -15
  3. package/kit/docs/ai/index.md +1 -0
  4. package/kit/docs/ai/operational-gates.md +275 -0
  5. package/kit/gates/_target.sh +53 -0
  6. package/kit/gates/ci-actually-fails/check.sh +18 -3
  7. package/kit/gates/ci-actually-fails/green/.github/workflows/ci.yml +10 -0
  8. package/kit/gates/entry-commands-exist/check.sh +88 -12
  9. package/kit/gates/hook-actually-fires/README.md +12 -0
  10. package/kit/gates/hook-actually-fires/check.sh +66 -6
  11. package/kit/gates/hook-actually-fires/gate.yml +2 -2
  12. package/kit/gates/hook-actually-fires/green/.claude/hooks/auto-format.sh +3 -0
  13. package/kit/gates/hook-actually-fires/green/.claude/hooks/block-dangerous.sh +3 -0
  14. package/kit/gates/hook-actually-fires/green/.claude/hooks/done.sh +3 -0
  15. package/kit/gates/hook-actually-fires/green/.claude/hooks/idle.sh +3 -0
  16. package/kit/gates/hook-actually-fires/green/.claude/hooks/prompt.sh +3 -0
  17. package/kit/gates/hook-actually-fires/green/.claude/hooks/session.mjs +1 -0
  18. package/kit/gates/hook-actually-fires/green/.claude/hooks/stop-gate.sh +3 -0
  19. package/kit/gates/hook-actually-fires/green/.claude/settings.json +12 -0
  20. package/kit/gates/test-not-adjusted/README.md +31 -0
  21. package/llms.txt +26 -7
  22. package/package.json +1 -1
  23. package/tool/commands/context.mjs +3 -1
  24. package/tool/commands/doctor-catalog.mjs +35 -10
  25. package/tool/commands/feedback.mjs +75 -1
  26. package/tool/commands/gates.mjs +12 -6
  27. package/tool/commands/report.mjs +19 -3
  28. package/tool/commands/vitals.mjs +9 -3
  29. package/tool/i18n/en-docs.mjs +18 -2
  30. package/tool/i18n/en-gates.mjs +9 -1
  31. package/tool/i18n/en.mjs +24 -2
  32. package/tool/i18n/ru-docs.mjs +17 -2
  33. package/tool/i18n/ru-gates.mjs +9 -1
  34. package/tool/i18n/ru.mjs +20 -2
  35. package/tool/lib/adopt.mjs +58 -4
  36. package/tool/lib/core.mjs +42 -6
  37. package/tool/lib/execution.mjs +32 -1
  38. package/tool/lib/manifest.mjs +39 -13
  39. package/tool/lib/prove.mjs +3 -3
  40. package/tool/lib/run.mjs +25 -6
  41. package/tool/selfcheck/smoke/_fixture.mjs +13 -1
  42. package/tool/selfcheck/smoke/feedback-send.test.mjs +87 -0
  43. package/tool/selfcheck/smoke/first-run.test.mjs +67 -3
  44. package/tool/selfcheck/smoke/preflight.test.mjs +83 -0
  45. package/tool/selfcheck/smoke/verdict.test.mjs +50 -4
  46. package/tool/selfcheck/smoke/version-sync.test.mjs +140 -0
  47. package/tool/selfcheck/smoke.sh +106 -4
  48. package/tool/selfcheck/units-execution.mjs +37 -1
  49. package/tool/selfcheck/units-level.mjs +41 -1
  50. package/tool/selfcheck/units-repo.mjs +75 -0
  51. package/tool/selfcheck/units-vitals.mjs +27 -0
  52. package/kit/gates/entry-links-exist/README.md +0 -27
  53. package/kit/gates/entry-links-exist/check.sh +0 -33
  54. package/kit/gates/entry-links-exist/gate.yml +0 -17
  55. package/kit/gates/entry-links-exist/green/AGENTS.md +0 -10
  56. package/kit/gates/entry-links-exist/green/rules/general.md +0 -3
  57. package/kit/gates/entry-links-exist/red/AGENTS.md +0 -3
  58. package/kit/gates/no-phantom-package/README.md +0 -84
  59. package/kit/gates/no-phantom-package/check.sh +0 -168
  60. package/kit/gates/no-phantom-package/gate.yml +0 -20
  61. package/kit/gates/no-phantom-package/green/AGENTS.md +0 -15
  62. package/kit/gates/no-phantom-package/red/AGENTS.md +0 -15
package/README.md CHANGED
@@ -5,27 +5,56 @@
5
5
  [![npm](https://img.shields.io/npm/v/agent-quality-kit)](https://www.npmjs.com/package/agent-quality-kit)
6
6
  [![checks](https://github.com/arsen-ask-lx/Agent_Quality_Kit/actions/workflows/ci.yml/badge.svg)](https://github.com/arsen-ask-lx/Agent_Quality_Kit/actions/workflows/ci.yml)
7
7
  [![MIT licence](https://img.shields.io/npm/l/agent-quality-kit)](LICENSE)
8
- [![AQK-3](https://img.shields.io/badge/AQK-3-2ea44f)](https://github.com/arsen-ask-lx/Agent_Quality_Kit)
9
8
 
10
- **Check whether a repository is ready to have its code written by AI coding agents — and turn
11
- the rules it promises to follow into commands with exit codes.**
9
+ **A check that cannot fail looks exactly like a check that passes.**
10
+
11
+ `|| true`, `continue-on-error: true`, a linter pointed at an empty directory, a test with no
12
+ assertion, a hook nobody ever installed, a command your `AGENTS.md` names that no longer exists —
13
+ every one of them prints a green tick. AQK plants a known defect into a **copy** of your code,
14
+ runs the checks your repository **declares**, and says which of them noticed and which stayed
15
+ silent.
12
16
 
13
- Your `AGENTS.md` says what the project promises. Nothing checks that those promises are true, or
14
- that the commands it lists even run. AQK is that missing layer: one command reads the repository,
15
- reports a level from AQK-0 to AQK-3, and names every guard that is missing.
17
+ It never reads "nothing printed" as "nothing wrong". Three outcomes, never two:
18
+
19
+ | `✔` | `✘` | `?` |
20
+ |---|---|---|
21
+ | ran and found nothing | a finding about the code | **the check itself failed** — fix the tooling, not the file it named while dying |
16
22
 
17
- And one step further than the tools next door: `probe` plants a known defect into a copy of your
18
- project and checks whether your **declared** guards actually go red. "Tests exist" and "tests
19
- catch" are different claims — readiness scores measure the first one.
23
+ The third one is the whole point. Counting it as either of the other two is how a repository ends
24
+ up protected by checks that cannot go red.
20
25
 
21
26
  ```bash
22
- npx agent-quality-kit doctor # code already exists: your level and what to install
27
+ npx agent-quality-kit doctor # code already exists: what it declares, what nothing is watching
23
28
  npx agent-quality-kit start # no code yet: day-zero guards, right away
24
29
  ```
25
30
 
31
+ Here is the first run on a repository whose pipeline is green and whose checks cannot go red.
32
+ Nothing declared, nothing installed, no config written — it read the project's own `package.json`:
33
+
34
+ ```text
35
+ Checks you ALREADY have (3) — found in your own files, not invented:
36
+ ✘ test npm test ← package.json
37
+ cannot fail: the verdict is swallowed right in the script — «|| true»
38
+ ✔ lint npm run lint ← package.json
39
+ ✘ typecheck npm run typecheck ← package.json
40
+ proves nothing: the whole script is a printout — «echo 'todo: turn this on'»
41
+
42
+ 2 of them cannot go red. Declaring a check that cannot fail only makes the silence
43
+ machine-readable — fix the command first, then declare it.
44
+ ```
45
+
26
46
  `doctor` only reads. It writes no file and sends nothing anywhere — safe to point at a repository
27
- you have decided nothing about yet. Nothing to install: `npx` fetches the package (574.1 kB, measured 2026-09-10 nothing guards this number, so check it
28
- when it matters).
47
+ you have decided nothing about yet. Nothing to install: `npx` fetches the package — **≈0.7 MB**,
48
+ a number a machine re-checks on every run rather than our memory.
49
+
50
+ For Claude Code there is a plugin: the repository's real state reaches the agent's context before
51
+ its first action, plus two skills — whether the declared checks can actually fail, and what to fix
52
+ first. Installable from our own marketplace, with nobody's approval to wait for:
53
+
54
+ ```bash
55
+ /plugin marketplace add arsen-ask-lx/Agent_Quality_Kit
56
+ /plugin install aqk@agent-quality-kit
57
+ ```
29
58
 
30
59
  The one exception, named here because it is the only one: with `--brief` (how the hooks run it)
31
60
  `doctor` asks the npm registry for its own latest version — **at most once a day, never in CI**,
@@ -109,6 +138,8 @@ aqk prompt one task to paste into an agent: what to fix, in order,
109
138
  aqk vitals is what the kit runs on wired up: gate tools, hooks, freshness
110
139
  aqk feedback feedback to the author: a report from the last run and probe,
111
140
  plus a prefilled link. No paths, no code; nothing is sent for you
141
+ aqk feedback --send send it in one command — with your own gh account, as a comment
142
+ in an open discussion. Without the flag nothing ever leaves
112
143
  aqk doctor --run --brief one line on success, the whole run on failure — for hooks
113
144
  aqk context --full the same plus the command map and the rulebook verbatim (~7000
114
145
  tokens against ~500: the price of an agent that does not guess)
@@ -148,7 +179,7 @@ $ npx agent-quality-kit doctor --run # runs them
148
179
  ./src/api/mailer.py:8: print("sent", to)
149
180
  ✘ todo-without-task exit 1
150
181
  ./src/web/app.js:1:// TODO: rewrite this
151
- ✔ file-size-limit · entry-links-exist · complexity-limit
182
+ ✔ file-size-limit · deps-are-pinned · complexity-limit
152
183
  ```
153
184
 
154
185
  The failure text is written for an agent: it says **what exactly to do**. The exit code is for
@@ -195,6 +226,8 @@ gates: # what must pass — as commands, not as prose
195
226
  secrets-not-in-code: "bash gates/secrets-not-in-code/check.sh ."
196
227
  covers: # what a declared gate already holds — not counted as debt
197
228
  lint: [no-print-in-prod, swallowed-error]
229
+ requires: # what a gate runs on, when the command does not show it
230
+ secrets-not-in-code: gitleaks
198
231
  samples: gates # a red and a green sample for every entry
199
232
  ratchets: ratchets # debt registries: the list may only get shorter
200
233
  probe: 100 # run the probe itself every N commits; 0 turns it off
@@ -224,6 +257,13 @@ catches things" are different claims, and the output says so out loud.
224
257
 
225
258
  ## Four levels
226
259
 
260
+ [![AQK-3](https://img.shields.io/badge/AQK-3-2ea44f)](https://github.com/arsen-ask-lx/Agent_Quality_Kit)
261
+
262
+ **A level measures equipment, not quality.** It says which guards a repository has and has proven
263
+ on samples — not that the code is good, and not that the guards caught anything in *your* files.
264
+ That is what `probe` is for, and `doctor` prints what the level does **not** prove right under it.
265
+ The badge above is this repository's own, kept honest by `aqk badge --check` in its pipeline.
266
+
227
267
  | Level | Required | What it proves |
228
268
  |---|---|---|
229
269
  | **AQK-0** | a manifest and an entry point | the tooling knows what to read |
@@ -353,7 +393,7 @@ Already using [pre-commit](https://pre-commit.com)? Three lines in the file you
353
393
  ```yaml
354
394
  repos:
355
395
  - repo: https://github.com/arsen-ask-lx/Agent_Quality_Kit
356
- rev: v0.15.0
396
+ rev: v0.16.0
357
397
  hooks:
358
398
  - id: aqk # runs what the repository declares; blocks below AQK-1
359
399
  # - id: aqk-doctor # read-only: the level and what is missing, blocks nothing
@@ -374,7 +414,7 @@ layer AQK adds.
374
414
  [![on the GitHub Marketplace](https://img.shields.io/badge/GitHub%20Marketplace-Agent%20Quality%20Kit-2ea44f?logo=github)](https://github.com/marketplace/actions/agent-quality-kit-aqk)
375
415
 
376
416
  ```yaml
377
- - uses: arsen-ask-lx/Agent_Quality_Kit@v0.15.0
417
+ - uses: arsen-ask-lx/Agent_Quality_Kit@v0.16.0
378
418
  with:
379
419
  min: 1 # the build fails below AQK-1, or if any declared gate failed
380
420
  ```
@@ -596,6 +636,12 @@ aqk blob # assembles GOD_AI.md out of kit/docs — to hand the guides to a c
596
636
  The file is **assembled, not stored**: edit the originals. A hand-edited copy drifts from its
597
637
  source within a week, and then nobody knows which one is real.
598
638
 
639
+ A single gate is waited on for **five minutes**; past that it is "could not check", not
640
+ "clean". Change it with `AQK_GATE_TIMEOUT` (seconds): `AQK_GATE_TIMEOUT=900 aqk doctor --run`.
641
+ The default is not arbitrary — SonarQube waits exactly as long for its quality gate. There is
642
+ deliberately no per-gate `timeout` field in the manifest: neither pre-commit nor lefthook has
643
+ one, and a long check is more honestly declared as a separate command than allowed to hang.
644
+
599
645
  ## Contributing a gate
600
646
 
601
647
  The catalogue lives on other people's bruises. The procedure and the bar are in
package/README.ru.md CHANGED
@@ -5,28 +5,57 @@
5
5
  [![npm](https://img.shields.io/npm/v/agent-quality-kit)](https://www.npmjs.com/package/agent-quality-kit)
6
6
  [![проверки](https://github.com/arsen-ask-lx/Agent_Quality_Kit/actions/workflows/ci.yml/badge.svg)](https://github.com/arsen-ask-lx/Agent_Quality_Kit/actions/workflows/ci.yml)
7
7
  [![лицензия MIT](https://img.shields.io/npm/l/agent-quality-kit)](LICENSE)
8
- [![AQK-3](https://img.shields.io/badge/AQK-3-2ea44f)](https://github.com/arsen-ask-lx/Agent_Quality_Kit)
9
8
 
10
- **Проверить, готов ли репозиторий к тому, что код в нём пишет ИИ-агент — и превратить правила,
11
- которые проект обещает соблюдать, в команды с кодом возврата.**
9
+ **Проверка, которая не может провалиться, выглядит точно так же, как проверка, которая прошла.**
10
+
11
+ `|| true`, `continue-on-error: true`, линтер, направленный в пустой каталог, тест без единого
12
+ утверждения, хук, который никто не поставил, команда из вашего же `AGENTS.md`, которой больше не
13
+ существует, — каждое печатает зелёную галочку. AQK подсаживает заведомый дефект в **копию** вашего
14
+ кода, запускает проверки, которые репозиторий **объявил**, и говорит, какая из них заметила, а
15
+ какая промолчала.
12
16
 
13
- `AGENTS.md` говорит, что проект обещает. Никто не проверяет, правда ли это и запускаются ли
14
- вообще перечисленные там команды. AQK — тот самый недостающий слой: одна команда читает
15
- репозиторий, называет ступень от AQK-0 до AQK-3 и перечисляет каждого недостающего сторожа.
17
+ Он никогда не читает «ничего не напечатано» как «ничего страшного». Три исхода, а не два:
18
+
19
+ | `✔` | `✘` | `?` |
20
+ |---|---|---|
21
+ | отработала, чисто | находка о коде | **сама проверка не смогла** — чинить инструмент, а не файл, который она назвала, падая |
16
22
 
17
- И один шаг дальше, чем соседние инструменты: `probe` подсаживает заведомый дефект в копию вашего
18
- проекта и смотрит, покраснеет ли **объявленная** защита. «Тесты есть» и «тесты ловят» — разные
19
- утверждения, и оценки готовности меряют первое.
23
+ Третий и есть весь смысл. Считать его одним из первых двух так и получается репозиторий,
24
+ защищённый проверками, которые не умеют покраснеть.
20
25
 
21
26
  ```bash
22
- npx agent-quality-kit doctor # код уже есть: уровень и что поставить
27
+ npx agent-quality-kit doctor # код уже есть: что объявлено и чего не сторожит никто
23
28
  npx agent-quality-kit start # кода ещё нет: сторожа дня 0 сразу
24
29
  ```
25
30
 
31
+ Вот первый запуск на репозитории, где конвейер зелёный, а покраснеть не может ни одна проверка.
32
+ Ничего не объявлено, ничего не поставлено, ни одного файла не записано — прочитан его же
33
+ `package.json`:
34
+
35
+ ```text
36
+ Проверки, которые у вас УЖЕ ЕСТЬ (3) — прочитаны в ваших файлах, не выдуманы:
37
+ ✘ test npm test ← package.json
38
+ не может провалиться: исход погашен прямо в скрипте — «|| true»
39
+ ✔ lint npm run lint ← package.json
40
+ ✘ typecheck npm run typecheck ← package.json
41
+ ничего не доказывает: всё тело скрипта — печать — «echo 'todo: turn this on'»
42
+
43
+ из них 2 покраснеть не могут. Объявить проверку, которая не может провалиться, — значит
44
+ сделать молчание машинно-читаемым. Сначала почините команду.
45
+ ```
46
+
26
47
  `doctor` только читает: ни одного файла не пишет и никуда ничего не отправляет — его можно
27
48
  направить на репозиторий, о котором ещё ничего не решено. Ставить ничего не нужно, `npx` скачает
28
- пакет сам (574.1 kB, замер 2026-09-10 — это число не сторожит никто, так что при случае
29
- перемерьте).
49
+ пакет сам **≈0,7 МБ**; это число сверяет машина при каждом прогоне, а не наша память.
50
+
51
+ Для Claude Code есть плагин — состояние репозитория попадает в контекст агента до его первого
52
+ действия, плюс два умения: «работают ли проверки» и «что чинить по порядку». Ставится из нашей
53
+ же витрины, без ожидания чьего-либо одобрения:
54
+
55
+ ```bash
56
+ /plugin marketplace add arsen-ask-lx/Agent_Quality_Kit
57
+ /plugin install aqk@agent-quality-kit
58
+ ```
30
59
 
31
60
  Единственное исключение, и названо оно здесь именно потому, что единственное: с `--brief`
32
61
  (так его запускают хуки) `doctor` спрашивает у реестра npm свою последнюю версию — **не чаще
@@ -111,6 +140,8 @@ aqk prompt одно задание для агента: что по
111
140
  aqk vitals подключено ли то, чем комплект работает: инструменты, хуки, свежесть
112
141
  aqk feedback отзыв автору: отчёт из последнего прогона и пробы плюс готовая
113
142
  ссылка. Без путей и без кода; ничего не отправляет само
143
+ aqk feedback --send отправить его одной командой — вашей же учётной записью gh,
144
+ комментарием в открытое обсуждение. Без флага не уходит ничего
114
145
  aqk doctor --run --brief одна строка на успехе, весь прогон при провале — для хуков
115
146
  aqk context --full то же плюс карта команд и свод правил дословно (≈7000 токенов
116
147
  против ≈500 — плата за то, чтобы агент не догадывался)
@@ -151,7 +182,7 @@ $ npx agent-quality-kit doctor --run # запускает их
151
182
  ./src/api/mailer.py:8: print("sent", to)
152
183
  ✘ todo-without-task код 1
153
184
  ./src/web/app.js:1:// TODO: переписать
154
- ✔ file-size-limit · entry-links-exist · complexity-limit
185
+ ✔ file-size-limit · deps-are-pinned · complexity-limit
155
186
  ```
156
187
 
157
188
  Текст отказа написан для агента: в нём сказано, **что именно сделать**. Код возврата — для
@@ -198,6 +229,8 @@ gates: # что обязано пройти — команд
198
229
  secrets-not-in-code: "bash gates/secrets-not-in-code/check.sh ."
199
230
  covers: # что уже держит объявленный гейт — в долг не пишется
200
231
  lint: [no-print-in-prod, swallowed-error]
232
+ requires: # чем гейт работает, если по команде этого не видно
233
+ secrets-not-in-code: gitleaks
201
234
  samples: gates # красный и зелёный образец каждой записи
202
235
  ratchets: ratchets # реестры долга: список может только укорачиваться
203
236
  probe: 100 # раз во столько коммитов проба делается сама; 0 — не делать
@@ -227,6 +260,14 @@ aqk doctor --baseline # ✔/✘ по пунктам, которые машин
227
260
 
228
261
  ## Четыре ступени
229
262
 
263
+ [![AQK-3](https://img.shields.io/badge/AQK-3-2ea44f)](https://github.com/arsen-ask-lx/Agent_Quality_Kit)
264
+
265
+ **Ступень меряет оснащённость, а не качество.** Она говорит, какие сторожа у репозитория есть и
266
+ доказаны на образцах, — но не что код хорош и не что сторожа поймали хоть что-то в **ваших**
267
+ файлах. Для этого есть `probe`, а `doctor` прямо под ступенью печатает, чего она **не**
268
+ доказывает. Значок выше — наш собственный, и его правдивость держит `aqk badge --check` в
269
+ конвейере.
270
+
230
271
  | Уровень | Требуется | Что доказано |
231
272
  |---|---|---|
232
273
  | **AQK-0** | манифест и точка входа | инструмент знает, что читать |
@@ -357,7 +398,7 @@ aqk badge --check # в конвейере: код 1 в тот день, ког
357
398
  ```yaml
358
399
  repos:
359
400
  - repo: https://github.com/arsen-ask-lx/Agent_Quality_Kit
360
- rev: v0.15.0
401
+ rev: v0.16.0
361
402
  hooks:
362
403
  - id: aqk # запускает объявленное; роняет коммит ниже AQK-1
363
404
  # - id: aqk-doctor # только осмотр: уровень и чего не хватает, ничего не роняет
@@ -376,7 +417,7 @@ repos:
376
417
  [![в GitHub Marketplace](https://img.shields.io/badge/GitHub%20Marketplace-Agent%20Quality%20Kit-2ea44f?logo=github)](https://github.com/marketplace/actions/agent-quality-kit-aqk)
377
418
 
378
419
  ```yaml
379
- - uses: arsen-ask-lx/Agent_Quality_Kit@v0.15.0
420
+ - uses: arsen-ask-lx/Agent_Quality_Kit@v0.16.0
380
421
  with:
381
422
  min: 1 # сборка падает ниже AQK-1 или если упал любой объявленный гейт
382
423
  ```
@@ -594,6 +635,12 @@ aqk blob # собирает GOD_AI.md из kit/docs — чтобы разо
594
635
  Файл **собирается, а не хранится**: править надо оригиналы. Копия, которую правят руками, через
595
636
  неделю расходится с источником, и непонятно, какая настоящая.
596
637
 
638
+ Один гейт ждут **пять минут**, дальше это «не смогли проверить», а не «чисто». Срок меняется
639
+ переменной `AQK_GATE_TIMEOUT` (в секундах): `AQK_GATE_TIMEOUT=900 aqk doctor --run`. Умолчание
640
+ взято не с потолка — столько же ждёт quality gate у SonarQube. Поля `timeout` у гейта в
641
+ манифесте нет намеренно: его нет ни у pre-commit, ни у lefthook, а долгую проверку честнее
642
+ объявить отдельной командой, чем разрешить ей висеть.
643
+
597
644
  ## Принести свой гейт
598
645
 
599
646
  Каталог живёт чужими шишками. Порядок и порог — в [`CONTRIBUTING.md`](CONTRIBUTING.md):
@@ -26,6 +26,7 @@
26
26
 
27
27
  | Документ | О чём | Класс |
28
28
  |---|---|---|
29
+ | [`operational-gates.md`](operational-gates.md) | актуальная карта OOM, N+1, Celery, метрик и нагрузки: что закрыто, что только видно и как подключать через AQK без дублей | PORTABLE |
29
30
  | [`agent-harness-playbook.md`](agent-harness-playbook.md) | чек-лист «День 0»: конфиги, линтеры, хуки, CI, Docker. Раздел 18 — дисциплина в каждой задаче | PORTABLE |
30
31
  | [`project-baseline.md`](project-baseline.md) | что обязано быть на любом проекте, чтобы работу можно было отдать машине. Назначение без названий инструментов | PORTABLE |
31
32
  | [`ai-sdlc.md`](ai-sdlc.md) | процесс по этапам: от «зачем» до эксплуатации. Отвечает «в каком порядке», а не «каким инструментом» | CURRENT |
@@ -0,0 +1,275 @@
1
+ # Эксплуатационные гейты из живого AI-проекта: что переносить, а что не притворять универсальным
2
+
3
+ > Проверено по коду `audit_project` 15.09.2026, ссылки на первоисточники сверены 16.09.2026. Это актуальная карта практики, а не ручной
4
+ > снимок production-метрик. Живые значения остаются в Prometheus, логах и отчётах прогонов.
5
+ >
6
+ > Главная граница: здесь описаны **намерения и устройство арбитров**. Реализация конкретного
7
+ > Django/Celery/Compose-проекта остаётся в его репозитории и объявляется в `.aqk.yml` командой,
8
+ > а не копируется в AQK.
9
+
10
+ ## Один вывод
11
+
12
+ Зелёная фича ещё не означает зелёную систему. Query budget одной ручки, memory budget одной
13
+ задачи и тест одного worker не отвечают на вопрос, выдержит ли одновременно работающий флот
14
+ общую RAM, PostgreSQL, Redis, диск и очередь.
15
+
16
+ Поэтому бюджетов всегда два:
17
+
18
+ 1. **локальный** — SQL, время, память и корректность одного endpoint/task;
19
+ 2. **системный** — p95, error rate, суммарный working set, очереди и OOM под смешанной нагрузкой.
20
+
21
+ ## Карта по факту
22
+
23
+ | Область | Что действительно держит машина | Честная граница |
24
+ |---|---|---|
25
+ | N+1 | 21 тестовый файл и 63 вызова `assertNumQueries` / `django_assert_max_num_queries`; полный pytest блокирует рост известных бюджетов | нет требования, что **каждая новая** list/paginated-ручка получила budget-тест |
26
+ | DirectPG N+1 | отдельный тест проверяет не только число запросов, но и отсутствие линейного роста на увеличенном наборе | это проектный арбитр, обычный Django-счётчик его не заменяет |
27
+ | OOM после факта | `ContainerOOM` на росте `container_oom_events_total`; выражение имеет firing и quiet сценарии `promtool` | cAdvisor в основном описывает работающие контейнеры; история exited/restart требует отдельного источника |
28
+ | Лимиты observability | тест разбирает Compose и держит сумму лимитов tier под явным капом | это кап одного tier, не бюджет всего production-флота и не резерв RAM хоста |
29
+ | Celery topology | объявленные/используемые очереди сравниваются с `worker -Q` в production Compose | имена и структура конфигурации проектные |
30
+ | Celery redelivery | `visibility_timeout` сравнивается с максимальным hard time limit и запасом | длинный timeout ухудшает скорость возврата работы после принудительной смерти worker |
31
+ | Публичные метрики | publisher обязан попасть в реестр; быстрый pre-commit ловит забытое имя, pytest проверяет глубокую проводку | наличие имени не доказывает, что production реально его собирает |
32
+ | Prometheus rules | синтаксис и выбранная семантика правил проверяются `promtool`; после deploy сверяется набор реально загруженных правил | не каждое правило уже имеет отдельные firing/quiet samples |
33
+ | Скрипты-гейты | `check-gate-wiring.py` требует автоматический запуск либо честную запись в реестре ручных инструментов | «запускается» не означает «полезен»; это другой вопрос |
34
+ | Ошибки и trace | GlitchTip, Sentry SDK, Loki/Alloy и `request_id` связывают исключение с окружением запроса | доступность самой цепочки также нуждается в heartbeat/alert |
35
+ | Backup | расписание, ротация, метрика успеха, `BackupStale` и тесты скрипта | копия на том же сервере не переживёт потерю сервера; restore надо репетировать отдельно |
36
+
37
+ ## Что ещё только видно, но не закрыто
38
+
39
+ Наличие графика или Telegram-alert не равно предотвращению.
40
+
41
+ ### Память
42
+
43
+ - нет ранней полосы «контейнер долго держится выше доли своего лимита»;
44
+ - нет отношения `sum(container working_set) / host RAM` для всего флота;
45
+ - нет host-level RAM/swap/PSI прибора уровня node exporter;
46
+ - нет долговечной истории OOM/restart для уже остановившихся контейнеров;
47
+ - нет memory budgets для всех тяжёлых pytest-сценариев;
48
+ - сумма отдельных потолков не является резервированием общей памяти.
49
+
50
+ Последний пункт особенно важен. Docker ограничивает отдельный сервис, но несколько сервисов
51
+ могут одновременно приблизиться к своим пределам и исчерпать хост. Поэтому static Compose gate
52
+ и runtime fleet gate решают разные задачи; один нельзя выдавать за другой.
53
+
54
+ ### PostgreSQL и Redis
55
+
56
+ Экспортёры есть, но нужны отдельные решения по сигналам:
57
+
58
+ - насыщение соединений и ожидание PgBouncer;
59
+ - длинные транзакции, deadlocks, temp bytes и блокировки;
60
+ - память Redis broker при `noeviction`;
61
+ - rejected writes, а не только evictions;
62
+ - возраст старейшей работы и ожидаемое время разбора очереди, а не одна глубина.
63
+
64
+ ### Нагрузка
65
+
66
+ - Python load harness измеряет p50/p95/p99 и RPS, но плохие числа сами не дают красный код;
67
+ - k6 thresholds существуют лишь у отдельных ручных сценариев;
68
+ - нет обязательного смешанного прогона HTTP + Celery + отчёты + документы + интеграции;
69
+ - нет nightly/pre-release запуска;
70
+ - нет регулярного soak для утечек, high-watermark процессов и накопления соединений.
71
+
72
+ ## Как строится честный OOM-контур
73
+
74
+ Одного правила `any OOM` мало. Нужны пять независимых слоёв.
75
+
76
+ ### 1. Статические лимиты сервисов
77
+
78
+ Production Compose задаёт `deploy.resources.limits.memory` или эквивалент. Проверка разбирает
79
+ эффективную конфигурацию, а не ищет слово `memory` grep-ом. Для каждого обязательного сервиса
80
+ должно быть ясно, почему лимит такой и каким замером он получен.
81
+
82
+ ### 2. Конфигурационный бюджет
83
+
84
+ Сумма лимитов выбранного tier не растёт молча. Это храповик конфигурации, а не обещание, что RAM
85
+ зарезервирована. Поднять кап можно только вместе с измерением хоста и записанной причиной.
86
+
87
+ ### 3. Runtime budget флота
88
+
89
+ Под смешанной нагрузкой считается:
90
+
91
+ ```text
92
+ max_over_time((sum(container_memory_working_set_bytes))[15m:1m]) / host_memory_bytes
93
+ ```
94
+
95
+ Точный PromQL зависит от labels и источника host memory. Порог нельзя копировать вслепую; для
96
+ малого сервера разумная стартовая красная полоса должна оставлять запас ядру, Docker, page cache
97
+ и пикам PostgreSQL. В исследованном проекте рабочей гипотезой остаётся 70%, но она ещё не стала
98
+ доказанным гейтом.
99
+
100
+ ### 4. Раннее предупреждение контейнера
101
+
102
+ Устойчивое превышение доли лимита предупреждает до OOM. Рабочая гипотеза проекта — около 85%,
103
+ но её надо откалибровать по пикам и шуму. Мгновенный spike и десять минут давления — разные
104
+ события.
105
+
106
+ ### 5. OOM и restart после факта
107
+
108
+ Рост `container_oom_events_total` — critical. Но исчезновение ряда остановленного контейнера не
109
+ должно превратить аварию в тишину: Docker events/inspect или другой collector хранит exit reason,
110
+ restart count и timestamp независимо от текущей жизни контейнера.
111
+
112
+ ### Доказательство правила
113
+
114
+ Для alert нужны минимум два `promtool`-сценария:
115
+
116
+ - counter вырос внутри окна → alert firing с ожидаемыми labels/annotations;
117
+ - counter не растёт → alert отсутствует.
118
+
119
+ Синтаксически корректное выражение `vector(0)` обязано сделать первый сценарий красным. Если вся
120
+ батарея остаётся зелёной, семантика OOM не проверяется.
121
+
122
+ ## Как строится N+1-гейт
123
+
124
+ Один статический поиск `select_related` ничего не доказывает. Арбитр должен наблюдать полный
125
+ операционный путь.
126
+
127
+ 1. Создать больше одного связанного объекта; для list endpoint обычно нужны десятки строк.
128
+ 2. Выполнить настоящий HTTP-запрос или функцию задачи.
129
+ 3. Зафиксировать постоянный максимум SQL, а не `текущее число + 1`.
130
+ 4. Увеличить объём fixture и проверить, что число запросов не растёт линейно.
131
+ 5. Запускать тест в общей блокирующей батарее.
132
+
133
+ Django официально предоставляет `assertNumQueries`. Для pytest допустима обёртка с верхним
134
+ пределом, если она печатает выполненные запросы при превышении. Direct SQL, Trino и внешние базы
135
+ нуждаются в своём счётчике — ORM-инструмент не видит их по построению.
136
+
137
+ Честная граница: наличие двадцати одного budget-теста не доказывает покрытие двадцать второй ручки.
138
+ Отдельный coverage-gate возможен только тогда, когда проект умеет машинно определить множество
139
+ ручек, для которых budget обязателен. Проверять слово в имени теста недостаточно.
140
+
141
+ ## Два инварианта Celery
142
+
143
+ ### Очередь имеет потребителя
144
+
145
+ Сопоставляются три факта:
146
+
147
+ - очереди, объявленные в настройках;
148
+ - очереди, используемые routes, task decorators и `apply_async`;
149
+ - очереди, которые реально слушают production workers.
150
+
151
+ Новая очередь без consumer красит CI. Документ или диаграмма не являются источником истины.
152
+
153
+ ### Доставка не опережает hard timeout
154
+
155
+ Redis visibility timeout определяет, когда неacknowledged message возвращается в очередь. Если
156
+ задача законно выполняется дольше, второй worker может получить её повторно. Проектный гейт
157
+ сравнивает отношения настроек и fail-closed обрабатывает неразрешимые значения.
158
+
159
+ Это не универсальная формула для всех Celery-систем: ETA/retry и принудительное завершение
160
+ создают обратную цену слишком большого timeout. Инвариант должен учитывать модель задач проекта.
161
+
162
+ ## Метрика и алерт — это цепочка, а не файл
163
+
164
+ Полный путь выглядит так:
165
+
166
+ ```text
167
+ код публикует имя
168
+ → exporter его принимает
169
+ → Prometheus scrape видит ряд
170
+ → правило вычисляется правильно
171
+ → Alertmanager маршрутизирует
172
+ → человек получает сообщение
173
+ → runbook говорит, что делать
174
+ ```
175
+
176
+ Для каждого шва нужен свой арбитр. Unit-test publisher не доказывает scrape. `promtool check`
177
+ не доказывает смысл выражения. Тест выражения не доказывает, что production загрузил новый файл.
178
+ После deploy полезна read-only сверка repo rule names с Prometheus API.
179
+
180
+ ## Нагрузочный гейт и soak
181
+
182
+ Короткий mixed scenario должен иметь код возврата и одновременно держать:
183
+
184
+ - error rate;
185
+ - p95 ключевых пользовательских путей;
186
+ - throughput или время разбора очередей;
187
+ - суммарный working set флота;
188
+ - отсутствие OOM/restart;
189
+ - при необходимости соединения PostgreSQL и rejected writes Redis.
190
+
191
+ Короткий прогон не ловит медленную утечку. Soak запускается отдельно и реже, с теми же
192
+ инвариантами, но с проверкой тренда памяти, соединений и очередей во времени. Смешивать soak с
193
+ каждым pre-push нельзя: дорогой гейт начнут обходить.
194
+
195
+ ## Где здесь агент
196
+
197
+ Агент не должен решать, случилась ли аномалия. Сначала детерминированный detector:
198
+
199
+ ```text
200
+ Prometheus rule или baseline script
201
+ → фильтрация и группировка события
202
+ → краткоживущий read-only агент
203
+ → гипотеза + evidence + confidence + связанный deploy
204
+ → issue / intent / merge request
205
+ → решение человека
206
+ ```
207
+
208
+ По расписанию работает baseline script. Модель вызывается только по нарушению полосы. На первом
209
+ этапе ей достаточно read-only доступа к Prometheus, Loki, error tracker и истории deploy; права
210
+ на production DB, Docker socket, merge и произвольный runbook не нужны.
211
+
212
+ ## Как подключать это через AQK без дублей
213
+
214
+ AQK-манифест объявляет **существующую команду проекта**:
215
+
216
+ ```yaml
217
+ gates:
218
+ project-verify: "bash scripts/verify.sh"
219
+ prometheus-rules: "docker run --rm ... promtool test rules alerts_test.yml"
220
+ groups:
221
+ code: [project-verify]
222
+ observability: [prometheus-rules]
223
+ ```
224
+
225
+ Внутренние шаги `project-verify` не перечисляются второй раз в манифесте. При падении агрегатор
226
+ обязан назвать конкретный шаг. Отдельным gate становится только команда, которую агрегатор
227
+ честно не запускает.
228
+
229
+ Один gate AQK получает не больше пяти минут. Если полный verifier проекта дольше, в манифесте
230
+ нужен его быстрый блокирующий режим, а `--full` — отдельный прямой или CI-прогон. Иначе AQK будет
231
+ честно возвращать «не смог проверить: timeout», но интеграция не станет рабочим локальным gate.
232
+
233
+ Не надо:
234
+
235
+ - копировать project-specific Python checks в `kit/gates`;
236
+ - объявлять grep по словам `memory` или `assertNumQueries` доказательством;
237
+ - указывать похожий каталог как `samples`, если структура AQK red/green там отсутствует;
238
+ - повышать уровень AQK заглушками;
239
+ - запускать `aqk init --force` поверх зрелого корпуса правил без review.
240
+
241
+ Новая запись каталога AQK появляется после трёх доказательств: устойчиво опознаваемая конструкция,
242
+ red/green samples и переносимый рецепт с понятной границей ложных срабатываний. До этого это
243
+ методика или собственный gate проекта — и это честное состояние.
244
+
245
+ ## Порядок для нового проекта
246
+
247
+ 1. До кода записать измеримые AC: SQL, p95, error rate, memory, очередь, идемпотентность.
248
+ 2. Быструю детерминированную статику поставить в pre-commit.
249
+ 3. Query и task budgets поставить в обычный CI.
250
+ 4. Compose limits и alert rules проверять нативными валидаторами.
251
+ 5. Mixed load запускать nightly и перед релизом; soak — отдельно, например еженедельно.
252
+ 6. Любой OOM сделать красным исходом и сохранить причину после смерти контейнера.
253
+ 7. Только после стабилизации сигналов подключать read-only агента к нескольким critical alerts.
254
+
255
+ ## Официальные источники
256
+
257
+ - [Django 5.2: `assertNumQueries`](https://docs.djangoproject.com/en/5.2/topics/testing/tools/#django.test.TransactionTestCase.assertNumQueries)
258
+ - [Prometheus: unit testing rules через `promtool`](https://prometheus.io/docs/prometheus/latest/configuration/unit_testing_rules/)
259
+ - [cAdvisor: Prometheus metrics, включая `container_oom_events_total`](https://github.com/google/cadvisor/blob/master/docs/storage/prometheus.md)
260
+ - [Docker Compose: resource limits](https://docs.docker.com/reference/compose-file/deploy/#resources)
261
+ - Docker про то, что сумма отдельных limits не гарантирует запас хоста — дословно:
262
+ «Using `--reserve-memory` and `--limit-memory` does not guarantee that Docker will not use more
263
+ memory on your host than you want», страница
264
+ [`docker service create`](https://docs.docker.com/reference/cli/docker/service/create/).
265
+ Якорь раздела не указан намеренно: страница собирается на стороне браузера, и проверить
266
+ существование якоря запросом нельзя — а ссылка на несуществующий якорь молча ведёт наверх.
267
+ - [Celery Redis: visibility timeout и redelivery](https://docs.celeryq.dev/en/main/getting-started/backends-and-brokers/redis.html#visibility-timeout)
268
+
269
+ ## Финальный принцип
270
+
271
+ > Сначала детерминированный сигнал и ограничение. Потом агент. Никогда наоборот.
272
+
273
+ Повторившаяся ошибка должна оставлять после себя не ещё один абзац, а арбитр с кодом возврата.
274
+ Если переносимого арбитра пока нет, граница называется вслух — текст не получает фальшивое имя
275
+ «гейт».
@@ -0,0 +1,53 @@
1
+ # Общий шов для проверок, судящих ПУТЬ, взятый из текста.
2
+ #
3
+ # ЗАЧЕМ ЭТОТ ФАЙЛ. Замеры 14-15 сентября 2026 по 290 чужим репозиториям нашли у нас двенадцать
4
+ # ложных срабатываний. Разбор показал, что это не двенадцать ошибок, а ОДНА, повторённая
5
+ # двенадцатью способами: мы судили строку как путь на диске, не зная, чем она разрешается.
6
+ # Разрешало её то, чего у нас нет, — адрес на github.com, переменная окружения, генератор сайта,
7
+ # соседний репозиторий вики, проза вокруг.
8
+ #
9
+ # Чинить такое по одному значит растить в каждом гейте свой список исключений; через три правки
10
+ # списки разойдутся, и дважды пойманная беда вернётся через тот гейт, куда её не дописали.
11
+ # Поэтому решение одно и здесь.
12
+ #
13
+ # ТРИ ИСХОДА, А НЕ ДВА — тот же договор, что у всего комплекта:
14
+ # disk путь ведёт на файл в этом каталоге, судить можно
15
+ # unresolved разрешается чем-то, чего у нас нет: МОЛЧАТЬ НЕЛЬЗЯ, но и обвинять нельзя
16
+ # skip не путь вовсе (сеть, почта, якорь) — говорить не о чем
17
+
18
+ # Якорь и строка запроса — не часть имени файла. `assets/x.gif?raw=1` лежит на диске как
19
+ # `assets/x.gif`, `tutorial/#install` — это адрес страницы. Чистится ЗДЕСЬ, а не у зовущего:
20
+ # иначе каждый гейт будет чистить по-своему, и один забудет.
21
+ clean_target() {
22
+ T="${1%%#*}"
23
+ printf '%s' "${T%%\?*}"
24
+ }
25
+
26
+ # Возвращает слово исхода, а для unresolved — ещё и причину через двоеточие.
27
+ classify_target() {
28
+ T="$1"
29
+ case "$T" in
30
+ ""|\#*) echo "skip"; return ;;
31
+ # ЛЮБАЯ СХЕМА, В ЛЮБОМ РЕГИСТРЕ. Замер 2026-09-15: `file://Users/...` и
32
+ # `Https://conventionalcommits.org` с заглавной H объявлены битыми файлами. Раньше узнавались
33
+ # только `http://` и `https://` строчными — и это была догадка о том, как люди пишут.
34
+ *://*|mailto:*|MAILTO:*) echo "skip"; return ;;
35
+ # Адрес почты целью ссылки без «mailto:». Замер: три таких объявлены битыми файлами.
36
+ *@*.*) echo "skip"; return ;;
37
+ esac
38
+ case "$T" in
39
+ # Путь, уходящий выше корня: так сам GitHub предлагает ссылаться на выпуски и задачи —
40
+ # `../../releases` считается от адреса файла в вебе. Проверено: страница отдаёт 200.
41
+ ../*) echo "unresolved:адрес на github.com" ;;
42
+ # Путь от корня САЙТА, а не репозитория.
43
+ /*) echo "unresolved:адрес на github.com" ;;
44
+ # Страница вики живёт в отдельном репозитории `<репо>.wiki`, которого мы не скачиваем.
45
+ wiki/*) echo "unresolved:вики в отдельном репозитории" ;;
46
+ # Путь с косой чертой на конце и страница `.html` — это адрес опубликованного сайта:
47
+ # mkdocs, docusaurus, pkgdown собирают их при публикации. Проверено: 200 на их сайтах.
48
+ */|*.html|*.htm) echo "unresolved:страницу собирает генератор сайта" ;;
49
+ # Нераскрытая переменная: значение задаётся снаружи.
50
+ *'$'*) echo "unresolved:путь зависит от переменной" ;;
51
+ *) echo "disk" ;;
52
+ esac
53
+ }