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.
Files changed (76) hide show
  1. package/README.md +136 -12
  2. package/README.ru.md +119 -11
  3. package/kit/docs/ready-made-rules.md +40 -0
  4. package/kit/gates/README.md +35 -0
  5. package/kit/gates/_native.sh +18 -2
  6. package/kit/gates/_skip.sh +18 -0
  7. package/kit/gates/ci-actually-fails/README.md +42 -0
  8. package/kit/gates/ci-actually-fails/check.sh +93 -0
  9. package/kit/gates/ci-actually-fails/gate.yml +14 -0
  10. package/kit/gates/ci-actually-fails/green/.github/workflows/ci.yml +14 -0
  11. package/kit/gates/ci-actually-fails/red/.github/workflows/ci.yml +12 -0
  12. package/kit/gates/color-from-token/README.md +52 -0
  13. package/kit/gates/color-from-token/check.sh +69 -0
  14. package/kit/gates/color-from-token/gate.yml +15 -0
  15. package/kit/gates/color-from-token/green/Button.tsx +4 -0
  16. package/kit/gates/color-from-token/green/Panel.vue +4 -0
  17. package/kit/gates/color-from-token/green/card.css +5 -0
  18. package/kit/gates/color-from-token/green/notes.md +2 -0
  19. package/kit/gates/color-from-token/green/tokens.css +7 -0
  20. package/kit/gates/color-from-token/red/Button.tsx +4 -0
  21. package/kit/gates/color-from-token/red/Panel.vue +4 -0
  22. package/kit/gates/color-from-token/red/card.css +5 -0
  23. package/kit/gates/commit-explains-itself/check.sh +25 -2
  24. package/kit/gates/duplicate-code/check.sh +13 -4
  25. package/kit/gates/duplicate-code/gate.yml +11 -2
  26. package/kit/gates/gate-has-samples/check.sh +9 -3
  27. package/kit/gates/gate-not-weakened/README.md +54 -0
  28. package/kit/gates/gate-not-weakened/check.sh +72 -0
  29. package/kit/gates/gate-not-weakened/gate.yml +15 -0
  30. package/kit/gates/gate-not-weakened/green/checkout.ts +8 -0
  31. package/kit/gates/gate-not-weakened/green/payments.py +6 -0
  32. package/kit/gates/gate-not-weakened/green/release.sh +2 -0
  33. package/kit/gates/gate-not-weakened/red/checkout.ts +9 -0
  34. package/kit/gates/gate-not-weakened/red/payments.py +6 -0
  35. package/kit/gates/gate-not-weakened/red/release.sh +2 -0
  36. package/kit/gates/gates-are-runnable/check.sh +7 -1
  37. package/kit/gates/gates-run-in-ci/check.sh +7 -1
  38. package/kit/gates/lesson-has-outcome/check.sh +6 -1
  39. package/kit/gates/promise-has-gate/README.md +50 -0
  40. package/kit/gates/promise-has-gate/check.sh +88 -0
  41. package/kit/gates/promise-has-gate/gate.yml +14 -0
  42. package/kit/gates/promise-has-gate/green/.aqk.yml +6 -0
  43. package/kit/gates/promise-has-gate/green/AGENTS.md +7 -0
  44. package/kit/gates/promise-has-gate/red/.aqk.yml +6 -0
  45. package/kit/gates/promise-has-gate/red/AGENTS.md +7 -0
  46. package/kit/gates/test-has-assertion/README.md +47 -0
  47. package/kit/gates/test-has-assertion/check.sh +194 -0
  48. package/kit/gates/test-has-assertion/gate.yml +15 -0
  49. package/kit/gates/test-has-assertion/green/checkout.test.ts +9 -0
  50. package/kit/gates/test-has-assertion/green/test_billing.py +17 -0
  51. package/kit/gates/test-has-assertion/red/checkout.test.ts +8 -0
  52. package/kit/gates/test-has-assertion/red/test_billing.py +14 -0
  53. package/kit/ratchet/ratchet.sh +9 -2
  54. package/kit/rules/general.md +14 -0
  55. package/llms.txt +58 -0
  56. package/package.json +4 -2
  57. package/tool/commands/doctor.mjs +106 -11
  58. package/tool/commands/gates.mjs +18 -3
  59. package/tool/commands/project.mjs +13 -4
  60. package/tool/commands/report.mjs +2 -2
  61. package/tool/i18n/en.mjs +55 -0
  62. package/tool/i18n/ru.mjs +61 -0
  63. package/tool/i18n/templates-en.mjs +9 -9
  64. package/tool/i18n/templates-ru.mjs +9 -9
  65. package/tool/lib/baseline.mjs +87 -0
  66. package/tool/lib/core.mjs +10 -2
  67. package/tool/lib/manifest.mjs +69 -2
  68. package/tool/lib/repo.mjs +7 -0
  69. package/tool/lib/scope.mjs +96 -0
  70. package/tool/program.mjs +1 -0
  71. package/tool/selfcheck/gates.sh +29 -5
  72. package/tool/selfcheck/lifecycle.mjs +29 -0
  73. package/tool/selfcheck/mutation.sh +95 -0
  74. package/tool/selfcheck/smoke.sh +269 -8
  75. package/tool/selfcheck/syntax.sh +9 -1
  76. package/tool/selfcheck/units.mjs +160 -1
package/README.md CHANGED
@@ -7,24 +7,61 @@
7
7
  [![MIT licence](https://img.shields.io/npm/l/agent-quality-kit)](LICENSE)
8
8
  [![AQK-3](https://img.shields.io/badge/AQK-3-2ea44f)](https://github.com/arsen-ask-lx/Agent_Quality_Kit)
9
9
 
10
- **A standard for whether a repository is ready to have its code written by agents.** Every
11
- promise the project makes turns into a command with an exit code — held by a machine, not by
12
- someone's good intentions.
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.**
13
12
 
14
- What a project needs before that is even possible, in plain words, independent of language and
15
- tooling: [the dark factory and the minimum that isn't optional](kit/docs/ai/project-baseline.md).
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.
16
16
 
17
17
  ```bash
18
- npx agent-quality-kit start # no code yet: day-zero guards, right away
19
18
  npx agent-quality-kit doctor # code already exists: your level and what to install
19
+ npx agent-quality-kit start # no code yet: day-zero guards, right away
20
+ ```
21
+
22
+ `doctor` only reads. It writes no file and sends nothing anywhere — safe to point at a repository
23
+ you have decided nothing about yet. Nothing to install: `npx` fetches the package (230 KB).
24
+
25
+ ### Works with any agent, any language
26
+
27
+ **Any agent.** Claude Code, Codex, Cursor, Gemini CLI, GitHub Copilot, Windsurf, Aider, OpenCode
28
+ — and with no AI at all. AQK reads and writes plain files (`AGENTS.md`, `.aqk.yml`); it calls no
29
+ vendor API, needs no key, and is tied to no model. A promise only one tool can keep is not a
30
+ promise.
31
+
32
+ **Any language.** The portable checks are plain `sh` and work on any stack — Python, TypeScript,
33
+ Go, Rust, Java, Ruby, PHP, C#, Kotlin, Swift, Scala. Where the project already has a native tool
34
+ (`ruff`, `eslint`, `knip`, `jscpd`), the check uses it instead, because it is more precise — and
35
+ says so out loud when it falls back.
36
+
37
+ **Requirements:** Node 18+ and an `sh` shell. Present on macOS, Linux and WSL; Git Bash on Windows.
38
+
39
+ ### One movement, and everything follows from it
40
+
41
+ ```mermaid
42
+ flowchart LR
43
+ A["<b>AGENTS.md</b><br/>“never commit secrets”<br/><br/><i>a human reads it<br/>and may ignore it</i>"]
44
+ B["<b>.aqk.yml</b><br/>secrets-not-in-code:<br/>bash gates/…/check.sh<br/><br/><i>a machine holds it<br/>and cannot forget</i>"]
45
+ C["<b>exit code</b><br/>0 or 1<br/><br/><i>CI acts on it<br/>and cannot argue</i>"]
46
+ A -- "declare" --> B
47
+ B -- "run" --> C
20
48
  ```
21
49
 
22
- `doctor` only reads: it writes no file and sends nothing anywhere. It is safe to point at
23
- a repository you have not decided anything about yet.
50
+ A promise the project makes turns into a command with an exit code. From then on a machine holds
51
+ it, not somebody's attention.
52
+
53
+ What a project needs before that is even possible, in plain words, independent of language and
54
+ tooling: [the dark factory and the minimum that isn't optional](kit/docs/ai/project-baseline.md).
24
55
 
25
- Nothing to install — `npx` fetches the package itself (230 KB). The bleeding edge straight from
26
- the repository is `npx github:arsen-ask-lx/Agent_Quality_Kit doctor`, but the first run that way
27
- stays silent for two or three minutes: it clones the whole repository.
56
+ ```
57
+ █████╗ ██████╗ ██╗ ██╗
58
+ ██╔══██╗ ██╔═══██╗██║ ██╔╝
59
+ ███████║ ██║ ██║█████╔╝
60
+ ██╔══██║ ██║▄▄ ██║██╔═██╗
61
+ ██║ ██║ ╚██████╔╝██║ ██╗
62
+ ╚═╝ ╚═╝ ╚══▀▀═╝ ╚═╝ ╚═╝
63
+ a promise without an exit code is just a sentence
64
+ ```
28
65
 
29
66
  ## What this looks like
30
67
 
@@ -100,6 +137,21 @@ them empty, and they fill in as there becomes something real to put in them.
100
137
  **If a claim cannot be checked by a machine, it is not in this standard.** Otherwise the badge
101
138
  would mean trust in the author rather than a fact.
102
139
 
140
+ ### The minimum a project needs
141
+
142
+ ```bash
143
+ aqk doctor --baseline # ✔/✘ over the points a machine can confirm
144
+ ```
145
+
146
+ The guide [project-baseline.md](kit/docs/ai/project-baseline.md) lists 50 points a project needs
147
+ before the work can be handed to agents. Fourteen of them a machine can confirm from the
148
+ repository — a lockfile of any ecosystem, a linter config of any language, an error tracker in
149
+ the dependencies, a pipeline, tests. It says what proved each one. The remaining 36 are named as
150
+ a number rather than hidden: they are for your eyes.
151
+
152
+ Presence is what gets checked, not whether it works: "a linter is configured" and "a linter
153
+ catches things" are different claims, and the output says so out loud.
154
+
103
155
  ## Four levels
104
156
 
105
157
  | Level | Required | What it proves |
@@ -109,6 +161,18 @@ would mean trust in the author rather than a fact.
109
161
  | **AQK-2** | gates have red and green samples, debt under a ratchet | the gate catches defects and stays quiet on correct code |
110
162
  | **AQK-3** | a lesson journal with conclusions | the same bruise is not collected twice |
111
163
 
164
+ ```mermaid
165
+ flowchart LR
166
+ L0["<b>AQK-0</b><br/>a manifest<br/>and an entry point"]
167
+ L1["<b>AQK-1</b><br/>rules exist,<br/>gates are commands"]
168
+ L2["<b>AQK-2</b><br/>red and green samples,<br/>debt under a ratchet"]
169
+ L3["<b>AQK-3</b><br/>a lesson journal<br/>with conclusions"]
170
+ L0 --> L1 --> L2 --> L3
171
+ ```
172
+
173
+ A level is not a verdict on the project — it measures how **machine-readable** the practice is.
174
+ A hundred working checks with no manifest is AQK-0, and that is honest: nothing can read them.
175
+
112
176
  ```bash
113
177
  aqk doctor --run --min 1 # in CI: fails below AQK-1 OR if any gate failed
114
178
  ```
@@ -125,10 +189,35 @@ replaces. So `aqk badge` prints nothing over a red gate, and `aqk badge --check`
125
189
  pipeline on the day the README and the repository part ways. The badge at the top of this file
126
190
  is checked that way on every push.
127
191
 
192
+ ## As a pre-commit hook
193
+
194
+ Already using [pre-commit](https://pre-commit.com)? Three lines in the file you already have:
195
+
196
+ ```yaml
197
+ repos:
198
+ - repo: https://github.com/arsen-ask-lx/Agent_Quality_Kit
199
+ rev: v0.6.0
200
+ hooks:
201
+ - id: aqk # runs what the repository declares; blocks below AQK-1
202
+ # - id: aqk-doctor # read-only: the level and what is missing, blocks nothing
203
+ # - id: aqk-baseline # the minimum a project needs, confirmed by a run
204
+ ```
205
+
206
+ `pre-commit` installs the package itself — there is nothing else to set up, and the package has
207
+ no dependencies.
208
+
209
+ **This does not replace pre-commit, it sits on top of it.** pre-commit runs checks; it says
210
+ nothing about *which* checks exist here, whether they work, and what this project has already
211
+ been burned by. Its own documentation is explicit about both gaps: no built-in compliance levels,
212
+ scoring or reporting — and it does not verify that a hook catches what it claims. That is the
213
+ layer AQK adds.
214
+
128
215
  ## In your pipeline
129
216
 
217
+ [![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)
218
+
130
219
  ```yaml
131
- - uses: arsen-ask-lx/Agent_Quality_Kit@v0.4.1
220
+ - uses: arsen-ask-lx/Agent_Quality_Kit@v0.5.0
132
221
  with:
133
222
  min: 1 # the build fails below AQK-1, or if any declared gate failed
134
223
  ```
@@ -147,9 +236,26 @@ aqk find "print statements in production" # is there already such a gate — m
147
236
  aqk doctor # what applies to this repository and what is missing
148
237
  aqk add secrets-not-in-code # copies the check and its samples in, declares it
149
238
  aqk doctor --run # runs the declared gates and shows the result
239
+ aqk doctor --run --since main # ... but only what the diff introduced
150
240
  aqk ratchet no-print-in-prod # existing violations become debt, new ones are blocked
151
241
  ```
152
242
 
243
+ ### The first run on a real project
244
+
245
+ An established repository carries years of debt. Run every gate over all of it and you get a wall
246
+ of red that nobody reads — so the tool gets switched off. `--since <ref>` narrows the output to
247
+ files the diff touched:
248
+
249
+ ```bash
250
+ aqk doctor --run --since main # only what this branch introduced
251
+ ```
252
+
253
+ Three outcomes, all of them said out loud. Findings inside the diff — red, as usual. Findings only
254
+ outside it — green, with the number that was hidden, never a silent "all clear". And a gate whose
255
+ output carries no paths at all (a commit-message check, a CI-config check) **cannot** be narrowed:
256
+ it stays red, and says why. Calling it green because there was nothing to narrow would be exactly
257
+ the silence this tool exists to remove.
258
+
153
259
  Every `doctor --run` rewrites `.aqk/last-run.md` — a short report of what actually ran and how
154
260
  long it took. The list of gates in the manifest says nothing about how many of them are alive
155
261
  right now; the report does. The file is ephemeral — keep it in your own `.gitignore`.
@@ -200,6 +306,24 @@ catalogue may grow to hundreds of entries; a given project still sees about a do
200
306
  An entry is accepted only if its arbiter goes red on the red sample, stays quiet on the green
201
307
  one, and names a real failure it caught. A machine checks this: `bash tool/selfcheck/gates.sh`.
202
308
 
309
+ ### Four entries that watch the agent, not the code
310
+
311
+ Ruff, ESLint and gitleaks already find bad code, and AQK calls them where it can rather than
312
+ reinventing them. These four look elsewhere — at the moment the **signal** about bad code is
313
+ switched off, which is what a coding agent does when the task is phrased as "make it pass":
314
+
315
+ | Entry | What it catches |
316
+ |---|---|
317
+ | `gate-not-weakened` | the fix was a suppression, not a fix: bare `# noqa`, `eslint-disable` with no rule named, `@ts-ignore`, `--no-verify` |
318
+ | `ci-actually-fails` | a pipeline step that renders a verdict but cannot fail — `run: pytest \|\| true`, `continue-on-error: true` |
319
+ | `test-has-assertion` | a test that cannot fail: empty body, `assert True`, a skip with no reason given |
320
+ | `promise-has-gate` | a rule in `AGENTS.md` with no enforcer named — neither a gate nor, honestly, a human |
321
+
322
+ Each was measured on nineteen third-party repositories (~25 000 files) before it entered the
323
+ catalogue, and two further entries were **cancelled by that measurement**: one because
324
+ [`agents-lint`](https://github.com/giacomo/agents-lint) already does it better, one because
325
+ 91 of its 120 findings turned out to be a legitimate pattern.
326
+
203
327
  ## The guides as a single file
204
328
 
205
329
  ```bash
package/README.ru.md CHANGED
@@ -7,23 +7,62 @@
7
7
  [![лицензия MIT](https://img.shields.io/npm/l/agent-quality-kit)](LICENSE)
8
8
  [![AQK-3](https://img.shields.io/badge/AQK-3-2ea44f)](https://github.com/arsen-ask-lx/Agent_Quality_Kit)
9
9
 
10
- **Стандарт готовности репозитория к тому, что код в нём пишет агент.** Обещание проекта
11
- становится командой с кодом возврата и его держит машина, а не чья-то добрая воля.
10
+ **Проверить, готов ли репозиторий к тому, что код в нём пишет ИИ-агент и превратить правила,
11
+ которые проект обещает соблюдать, в команды с кодом возврата.**
12
12
 
13
- Что вообще должно быть на проекте, чтобы это было возможно, словами, без привязки к языку и
14
- инструменту: [«тёмная фабрика» и обязательный минимум](kit/docs/ai/project-baseline.md).
13
+ `AGENTS.md` говорит, что проект обещает. Никто не проверяет, правда ли это и запускаются ли
14
+ вообще перечисленные там команды. AQK — тот самый недостающий слой: одна команда читает
15
+ репозиторий, называет ступень от AQK-0 до AQK-3 и перечисляет каждого недостающего сторожа.
15
16
 
16
17
  ```bash
17
- npx agent-quality-kit start # кода ещё нет: сторожа дня 0 сразу
18
18
  npx agent-quality-kit doctor # код уже есть: уровень и что поставить
19
+ npx agent-quality-kit start # кода ещё нет: сторожа дня 0 сразу
19
20
  ```
20
21
 
21
- `doctor` только читает: ни одного файла не пишет и никуда ничего не отправляет. Его можно
22
- направить на репозиторий, о котором ещё ничего не решено.
22
+ `doctor` только читает: ни одного файла не пишет и никуда ничего не отправляет его можно
23
+ направить на репозиторий, о котором ещё ничего не решено. Ставить ничего не нужно, `npx` скачает
24
+ пакет сам (230 КБ).
25
+
26
+ ### Работает с любым агентом и любым языком
23
27
 
24
- Ставить ничего не нужно, `npx` скачает пакет сам (230 КБ). Свежая версия прямо из репозитория —
25
- `npx github:arsen-ask-lx/Agent_Quality_Kit doctor`, но первый запуск такого вида молчит две-три
26
- минуты: он клонирует репозиторий целиком.
28
+ **Любой агент.** Claude Code, Codex, Cursor, Gemini CLI, GitHub Copilot, Windsurf, Aider,
29
+ OpenCode и вообще без ИИ. AQK читает и пишет обычные файлы (`AGENTS.md`, `.aqk.yml`), не
30
+ обращается ни к одному API вендора, не требует ключа и не привязан к модели. Обещание, которое
31
+ держит только один инструмент, — не обещание.
32
+
33
+ **Любой язык.** Переносимые проверки написаны на `sh` и работают на любом стеке — Python,
34
+ TypeScript, Go, Rust, Java, Ruby, PHP, C#, Kotlin, Swift, Scala. Там, где у проекта уже есть
35
+ родной инструмент (`ruff`, `eslint`, `knip`, `jscpd`), проверка берёт его — он точнее, — и вслух
36
+ сообщает, когда откатилась на переносимый.
37
+
38
+ **Требуется:** Node 18+ и оболочка `sh`. Есть на macOS, Linux и в WSL; на Windows — Git Bash.
39
+
40
+ ### Одно движение, из которого следует всё остальное
41
+
42
+ ```mermaid
43
+ flowchart LR
44
+ A["<b>AGENTS.md</b><br/>«не коммить секреты»<br/><br/><i>читает человек<br/>и может забыть</i>"]
45
+ B["<b>.aqk.yml</b><br/>secrets-not-in-code:<br/>bash gates/…/check.sh<br/><br/><i>держит машина<br/>и забыть не может</i>"]
46
+ C["<b>код возврата</b><br/>0 или 1<br/><br/><i>знает конвейер<br/>и спорить не станет</i>"]
47
+ A -- "объявили" --> B
48
+ B -- "запустили" --> C
49
+ ```
50
+
51
+ Обещание проекта становится командой с кодом возврата. Дальше его держит машина, а не чьё-то
52
+ внимание.
53
+
54
+ Что вообще должно быть на проекте, чтобы это было возможно, — словами, без привязки к языку и
55
+ инструменту: [«тёмная фабрика» и обязательный минимум](kit/docs/ai/project-baseline.md).
56
+
57
+ ```
58
+ █████╗ ██████╗ ██╗ ██╗
59
+ ██╔══██╗ ██╔═══██╗██║ ██╔╝
60
+ ███████║ ██║ ██║█████╔╝
61
+ ██╔══██║ ██║▄▄ ██║██╔═██╗
62
+ ██║ ██║ ╚██████╔╝██║ ██╗
63
+ ╚═╝ ╚═╝ ╚══▀▀═╝ ╚═╝ ╚═╝
64
+ обещание без кода возврата — просто предложение
65
+ ```
27
66
 
28
67
  ## Что это выглядит так
29
68
 
@@ -99,6 +138,21 @@ lessons: incidents # где копятся уроки
99
138
  **Если утверждение нельзя проверить машиной — его в стандарте нет.** Иначе значок означает
100
139
  доверие к автору, а не факт.
101
140
 
141
+ ### Обязательный минимум проекта
142
+
143
+ ```bash
144
+ aqk doctor --baseline # ✔/✘ по пунктам, которые машина может подтвердить
145
+ ```
146
+
147
+ Методичка [project-baseline.md](kit/docs/ai/project-baseline.md) перечисляет 50 пунктов, без
148
+ которых работу нельзя отдать агентам. Четырнадцать из них машина подтверждает по репозиторию —
149
+ файл-замок любой экосистемы, конфиг линтера любого языка, трекер ошибок в зависимостях,
150
+ конвейер, тесты. И называет, чем именно подтвердила. Оставшиеся 36 названы числом, а не спрятаны:
151
+ они — глазами.
152
+
153
+ Проверяется НАЛИЧИЕ признака, а не то, что он работает: «линтер настроен» и «линтер ловит» —
154
+ разные утверждения, и вывод говорит это вслух.
155
+
102
156
  ## Четыре ступени
103
157
 
104
158
  | Уровень | Требуется | Что доказано |
@@ -108,6 +162,18 @@ lessons: incidents # где копятся уроки
108
162
  | **AQK-2** | у гейтов красные и зелёные образцы, долг под храповиком | гейт ловит брак и молчит на исправном коде |
109
163
  | **AQK-3** | журнал уроков с выводами | шишка не набивается дважды |
110
164
 
165
+ ```mermaid
166
+ flowchart LR
167
+ L0["<b>AQK-0</b><br/>манифест<br/>и точка входа"]
168
+ L1["<b>AQK-1</b><br/>правила есть,<br/>гейты — команды"]
169
+ L2["<b>AQK-2</b><br/>красный и зелёный образцы,<br/>долг под храповиком"]
170
+ L3["<b>AQK-3</b><br/>журнал шишек<br/>с выводами"]
171
+ L0 --> L1 --> L2 --> L3
172
+ ```
173
+
174
+ Ступень — не приговор проекту: она мерит, насколько практика **машиночитаема**. Сто работающих
175
+ проверок без манифеста — это AQK-0, и это честно: их никто не может прочитать.
176
+
111
177
  ```bash
112
178
  aqk doctor --run --min 1 # в конвейере: ошибка, если ниже AQK-1 ИЛИ упал хоть один гейт
113
179
  ```
@@ -124,10 +190,34 @@ aqk badge --check # в конвейере: код 1 в тот день, ког
124
190
  роняет конвейер в тот день, когда README и репозиторий разошлись. Значок в начале этого файла
125
191
  проверяется так на каждом пуше.
126
192
 
193
+ ## Как хук pre-commit
194
+
195
+ Уже пользуешься [pre-commit](https://pre-commit.com)? Три строки в файл, который у тебя и так
196
+ лежит:
197
+
198
+ ```yaml
199
+ repos:
200
+ - repo: https://github.com/arsen-ask-lx/Agent_Quality_Kit
201
+ rev: v0.6.0
202
+ hooks:
203
+ - id: aqk # запускает объявленное; роняет коммит ниже AQK-1
204
+ # - id: aqk-doctor # только осмотр: уровень и чего не хватает, ничего не роняет
205
+ # - id: aqk-baseline # обязательный минимум проекта, подтверждённый прогоном
206
+ ```
207
+
208
+ `pre-commit` ставит пакет сам — настраивать больше нечего, зависимостей у пакета нет.
209
+
210
+ **Это не замена pre-commit, а слой над ним.** pre-commit запускает проверки, но ничего не
211
+ говорит о том, **какие** проверки в репозитории есть, работают ли они и на чём здесь уже
212
+ обжигались. Его собственная документация признаёт обе дыры прямо: нет ни уровней, ни оценки, ни
213
+ отчётности — и он не проверяет, что хук ловит то, что заявляет. Этот слой и добавляет AQK.
214
+
127
215
  ## В твоём конвейере
128
216
 
217
+ [![в GitHub Marketplace](https://img.shields.io/badge/GitHub%20Marketplace-Agent%20Quality%20Kit-2ea44f?logo=github)](https://github.com/marketplace/actions/agent-quality-kit-aqk)
218
+
129
219
  ```yaml
130
- - uses: arsen-ask-lx/Agent_Quality_Kit@v0.4.1
220
+ - uses: arsen-ask-lx/Agent_Quality_Kit@v0.5.0
131
221
  with:
132
222
  min: 1 # сборка падает ниже AQK-1 или если упал любой объявленный гейт
133
223
  ```
@@ -196,6 +286,24 @@ vendor/
196
286
  Запись принимается, только если её арбитр краснеет на красном образце, молчит на зелёном и
197
287
  назван реальный отказ, который она поймала. Проверяет это машина: `bash tool/selfcheck/gates.sh`.
198
288
 
289
+ ### Четыре записи, которые смотрят на агента, а не на код
290
+
291
+ Ruff, ESLint и gitleaks и так находят плохой код — AQK зовёт их, где может, вместо того чтобы
292
+ писать своё. Эти четыре смотрят в другое место: на момент, когда **сигнал** о плохом коде
293
+ выключают. Именно это делает агент, когда задача сформулирована как «сделай, чтобы прошло»:
294
+
295
+ | Запись | Что ловит |
296
+ |---|---|
297
+ | `gate-not-weakened` | починкой было подавление: голый `# noqa`, `eslint-disable` без имени правила, `@ts-ignore`, `--no-verify` |
298
+ | `ci-actually-fails` | шаг конвейера, который выносит вердикт, но не может провалиться — `run: pytest \|\| true`, `continue-on-error: true` |
299
+ | `test-has-assertion` | тест, который не может провалиться: пустое тело, `assert True`, пропуск без причины |
300
+ | `promise-has-gate` | правило в `AGENTS.md`, у которого не назван сторож — ни гейт, ни, честно, человек |
301
+
302
+ Каждая измерена на девятнадцати чужих репозиториях (~25 000 файлов) до внесения в каталог, и ещё
303
+ две записи этот же замер **отменил**: одну — потому что
304
+ [`agents-lint`](https://github.com/giacomo/agents-lint) делает это лучше, другую — потому что
305
+ 91 находка из 120 оказалась законным приёмом.
306
+
199
307
  ## Методички одним файлом
200
308
 
201
309
  ```bash
@@ -149,6 +149,46 @@ ruff check --select TRY400 --statistics . # сколько находок У
149
149
 
150
150
  ---
151
151
 
152
+ ## Заглушка вместо реализации: почти всё уже покрыто
153
+
154
+ Самый ожидаемый способ, которым агент «заканчивает» задачу, — подпись без работы. Мы собирались
155
+ писать на это запись каталога и не стали: замер по девятнадцати репозиториям (~25 000 файлов)
156
+ показал, что своей доли почти не остаётся.
157
+
158
+ | Что | Чем ловится | Не забыть |
159
+ |---|---|---|
160
+ | пустое тело функции в JS и TS | `eslint` [`no-empty-function`](https://eslint.org/docs/latest/rules/no-empty-function) | функция с комментарием внутри не считается пустой |
161
+ | абстрактный метод, не переопределённый в конкретном классе | `pylint` [`W0223`](https://pylint.readthedocs.io/en/latest/user_guide/messages/warning/abstract-method.html) | `--disable=all --enable=W0223` — остальное берёт ruff |
162
+ | `raise NotImplemented` вместо `NotImplementedError` | `ruff` [`F901`](https://docs.astral.sh/ruff/rules/raise-not-implemented/) | входит в группу `F` |
163
+ | лишний `pass` рядом с настоящим кодом | `ruff` `PIE790` | |
164
+
165
+ **Чего мы НЕ стали делать и почему.** Пустое тело (`pass`) — не признак недоделки: из 120 находок
166
+ первой версии 91 оказалась законной. Это null-объекты (`NoOpSpan` в sentry-python), безопасные
167
+ заглушки провайдера в pr-agent — там прямо стоит комментарий «safe no-op stubs», —
168
+ необязательные обработчики. А `raise NotImplementedError` в методе класса и есть питоновский
169
+ способ объявить абстракцию, даже без `abc`: так написаны `ContentDecoder` в httpx и интерфейс
170
+ плагина в pre-commit. За вычетом этих двух классов на 25 000 файлах не осталось ни одной находки.
171
+
172
+ ## Обвес самого агента: тоже есть готовое
173
+
174
+ К осени 2026 появился отдельный класс инструментов — линтеры не кода, а того, что читает агент:
175
+ `AGENTS.md`, `CLAUDE.md`, файлы навыков, конфиги хуков и MCP. Писать своё здесь незачем.
176
+
177
+ | Инструмент | Что проверяет | Состояние на 2026-09-06 |
178
+ |---|---|---|
179
+ | [`agnix`](https://github.com/agent-sh/agnix) | 455 правил: структура `CLAUDE.md`/`AGENTS.md`/`SKILL.md`, синтаксис конфигов MCP и хуков, соглашения об именах, **мёртвые ссылки на файлы**. Есть автопочинка и LSP | 404 ⭐, Rust, активен. `npm i -g agnix`, `brew`, `pip`, `cargo` |
180
+ | [`agents-lint`](https://github.com/giacomo/agents-lint) | мёртвые npm-скрипты, упомянутые в `AGENTS.md`, устаревшие рамки, деревья каталогов в контексте | 13 ⭐, TypeScript, последний коммит март 2026 |
181
+
182
+ ```bash
183
+ npm install -g agnix && agnix --strict .
184
+ ```
185
+
186
+ **Чего они НЕ делают — и почему у AQK остаётся своя половина.** Все они проверяют документ:
187
+ формат, существование путей, наличие скриптов. Ни один не спрашивает, **подкреплено ли обещание
188
+ командой с кодом возврата**. «Мы никогда не коммитим секреты» — грамматически безупречная
189
+ строка, на которую ни один из них ничего не скажет. Разделение простое: обвес агента проверяет
190
+ `agnix`, исполнимость обещаний — `promise-has-gate`.
191
+
152
192
  ## А если проект не на Python?
153
193
 
154
194
  Ничего не меняется. Запись каталога держит **одно намерение и несколько исполнителей**, и
@@ -70,6 +70,26 @@
70
70
  Плюс шестое, без которого запись не принимается: **доказательство** — реальный отказ, который
71
71
  она поймала. «Это хорошая практика» не принимается.
72
72
 
73
+ ## Зрелость записи не пишут руками
74
+
75
+ Седьмого поля нет: зрелость **считается** из доказательства. Ссылается `proof` на журнал шишек —
76
+ запись зрелая; не ссылается — условная, и это видно в приёмке каталога. Написать себе
77
+ `lifecycle: stable` нельзя, приёмка такую запись отклонит.
78
+
79
+ Это не придирка к форме. У всех трёх соседей, чей каталог мы разбирали, поле зрелости есть, и
80
+ у всех троих его заполняет автор: `lifecycle` у зондов Scorecard, `future`/`obsolete` у
81
+ критериев значка OpenSSF. Значение, написанное автором, означает доверие к автору. Каталог, где
82
+ зрелость объявляют, к сотне записей превращается в список, в котором нельзя выбрать.
83
+
84
+ Объявляется ровно одно состояние — **`deprecated`**, потому что «эту запись больше не ставят»
85
+ из её файлов не выводится никак. Вместе с ним обязателен `superseded_by` с именем существующей
86
+ записи, и `aqk add` тогда отказывает в установке, назвав преемника:
87
+
88
+ ```yaml
89
+ lifecycle: deprecated
90
+ superseded_by: no-print-in-prod
91
+ ```
92
+
73
93
  ## Запись без переносимого рецепта
74
94
 
75
95
  Иногда переносимой проверки быть не может: чтобы понять, вызывают ли функцию, нужен граф
@@ -113,6 +133,20 @@ samples_for: python
113
133
  если завтра поправят источник?» Правильный ответ — «результат изменится сам», а не «нужно
114
134
  поправить ещё и здесь».
115
135
 
136
+ Седьмой, с прогона по живому проекту: **гейт, тонущий в собственном шуме.** Родной инструмент
137
+ не получал общий список исключений и читал `.aqk/` — каталог, который положил сам комплект. Из
138
+ 1846 находок 435 были не про код вовсе. Гейт остаётся правильным и становится нечитаемым, а
139
+ нечитаемый выключают целиком. Мера здесь — доля находок не по делу, а не длина вывода.
140
+
141
+ Восьмой, оттуда же: **родной и переносимый рецепты одной записи мерят разное.** Переносимый
142
+ смотрел только в расширения кода, родной — во всё подряд. Один гейт означал разное на разных
143
+ машинах, и какое именно — зависело от того, что стоит в системе.
144
+
145
+ Девятый, найденный мутационной проверкой: **гейт держится за то, чего не заявляет.** Четыре
146
+ записи переставали работать на windows-переносах строк — жадный захват в `sed` проглатывал
147
+ `\r`. Красный образец зеленел, зелёный краснел. Пара образцов этого не видит: она доказывает
148
+ одно срабатывание, а не класс. Ловит `tool/selfcheck/mutation.sh`.
149
+
116
150
  Зелёный образец закрывает третий способ, и он важнее красного: что гейт ловит брак, проверяют
117
151
  при установке; что он молчит на исправном коде — не проверяют почти никогда.
118
152
 
@@ -159,6 +193,7 @@ samples_for: python
159
193
  | `has_deps: true` | есть файл зависимостей |
160
194
  | `has_tests: true` | есть каталог тестов или файлы вида `*_test.*` |
161
195
  | `has_env: true` | есть файл окружения |
196
+ | `has_ui: true` | есть стили или однофайловые компоненты (`.css`, `.scss`, `.vue`, `.svelte`, `.astro`) |
162
197
 
163
198
  Любое из `has_*` принимает и `false` — «показывать тем, у кого этого нет». Условие, которого
164
199
  программа не знает, отклоняется явно, а не пропускается молча.
@@ -22,8 +22,24 @@ shift || true
22
22
 
23
23
  . "$(dirname "$0")/_skip.sh" 2>/dev/null || { echo "не найден _skip.sh рядом с _native.sh"; exit 2; }
24
24
 
25
- OUT="$("$@" 2>&1)"; CODE=$?
26
- LEFT="$(printf '%s' "$OUT" | own_samples_filter "$DIR")"
25
+ # Два фильтра, а не один: own_samples_filter прячет образцы гейтов и то, что назвал проект
26
+ # в .aqkignore, а skip_paths_filter — общий список каталогов, который переносимые проверки
27
+ # получают при обходе. Родному инструменту он не доставался вовсе, и он читал `.aqk/`,
28
+ # `node_modules` и `.venv`. На живом проекте это дало 5597 строк вывода, где первой находкой
29
+ # были методички самого комплекта.
30
+ # Цвет снимается ДО фильтров. Родные инструменты печатают путь внутри escape-последовательности
31
+ # («\033[32m.aqk/docs/…»), и тогда имя каталога стоит не после «/» и не с начала строки — фильтр
32
+ # по границе пути его не видит. На живом проекте это выглядело как работающая правка: вывод
33
+ # сократился с 5597 строк до 5505, то есть не сократился. Заодно лог конвейера читается глазами.
34
+ # Код возврата берётся у САМОГО инструмента, до всякой обработки: в конвейере `$?` — это код
35
+ # последней команды, и снятие цвета молча делало бы любой прогон успешным.
36
+ OUT_RAW="$("$@" 2>&1)"; CODE=$?
37
+
38
+ # ESC подставляется через printf, а не пишется как \x1b: это расширение GNU sed, а гейты
39
+ # обязаны работать на любом sh.
40
+ ESC="$(printf '\033')"
41
+ OUT="$(printf '%s' "$OUT_RAW" | sed -e "s/${ESC}\[[0-9;]*[a-zA-Z]//g")"
42
+ LEFT="$(printf '%s' "$OUT" | skip_paths_filter | own_samples_filter "$DIR")"
27
43
 
28
44
  # Отказ не выдумываем: если инструмент завершился успешно, результат успешен, что бы ни
29
45
  # осталось в выводе. Красным делаем только то, что инструмент И счёл отказом, И что пережило
@@ -82,6 +82,24 @@ skip_grep() {
82
82
  for N in $SKIP_NAMES; do printf -- '--exclude-dir=%s ' "$N"; done
83
83
  }
84
84
 
85
+ # Для вывода родного инструмента: отбрасывает строки, чей путь лежит внутри пропускаемого
86
+ # каталога. Переносимые проверки получают этот список при обходе (skip_grep/skip_find), а
87
+ # родному инструменту он не доставался вовсе: на живом проекте первой находкой duplicate-code
88
+ # оказались методички самого комплекта в `.aqk/docs`, которые туда положил `init`.
89
+ #
90
+ # По сегменту пути, а не по подстроке — тот же урок, что с папкой `red`: имя «build» встречается
91
+ # и внутри слова, и файл `src/rebuild.py` прятать нельзя.
92
+ skip_paths_filter() {
93
+ RE_SKIP="$(printf '%s' "$SKIP_NAMES" | tr '\n' ' ' | sed -e 's/^ *//' -e 's/ *$//' \
94
+ -e 's/\./\\./g' -e 's/ */|/g')"
95
+ [ -n "$RE_SKIP" ] || { cat; return; }
96
+ # Граница слева — начало строки, «/» или любой символ, которого не бывает внутри имени пути.
97
+ # Родные инструменты печатают путь не с начала строки: у jscpd это « - путь». Пока требовалось
98
+ # начало строки или «/», такие строки не отсеивались вовсе. Буквы, цифры, «_», «.» и «-»
99
+ # границей не считаются — иначе «build/» отсеяло бы и «rebuild/».
100
+ grep -vE "(^|/|[^[:alnum:]_.-])($RE_SKIP)/"
101
+ }
102
+
85
103
  # Для find: -name X -prune -o …
86
104
  skip_find() {
87
105
  for N in $SKIP_NAMES; do printf -- '-name %s -prune -o ' "$N"; done
@@ -0,0 +1,42 @@
1
+ # Проверка в конвейере может провалиться
2
+
3
+ **Намерение.** Шаг выполнен, круг зелёный, проверка не сработала. `run: pytest || true` —
4
+ это строка в логе, а не проверка.
5
+
6
+ **Какой отказ это поймало.** Дыру нашли у себя. Запись `gates-run-in-ci` отвечает на вопрос
7
+ «упомянут ли гейт в конфиге конвейера» и на этом останавливается — то есть конфиг с
8
+ `run: pytest || true` проходил её зелёным. «Упомянут» и «работает» — разные утверждения, и весь
9
+ этот стандарт стоит на том, чтобы их не путать; у себя мы их спутали.
10
+
11
+ Замер по пятнадцати чужим репозиториям подтвердил, что класс живой: у `reviewdog` два шага с
12
+ его собственными линтерами идут под `continue-on-error: true`. Запись в журнале:
13
+ `incidents/README.md`, 2026-09-06.
14
+
15
+ **Что именно проверяется.** Конфиг разбирается по шагам. Шаг красный, если он **и** выносит
16
+ вердикт, **и** не может провалиться.
17
+
18
+ | Выносит вердикт, если | Не может провалиться, если |
19
+ |---|---|
20
+ | команда есть в списке запускалок (`pytest`, `eslint`, `go test`, `golangci-lint`, `mypy`, `cargo clippy`, …) | шаг помечен `continue-on-error: true` |
21
+ | команда объявлена гейтом в `.aqk.yml` **этого** проекта | задача помечена `allow_failure: true` (gitlab) |
22
+ | в **имени шага** стоит слово `lint`, `test`, `check`, `verify`, `audit`, `scan`, `coverage` | провал погашен в самой команде: `\|\| true`, `\|\| :`, `\|\| exit 0` |
23
+
24
+ Гашение прямо в команде красится только для закрытого списка запускалок: `docker network create … || true` — это идемпотентность, а не выключенная проверка.
25
+
26
+ **Готовый аналог.** Не нашли. [`actionlint`](https://github.com/rhysd/actionlint) разбирает
27
+ синтаксис workflow, [`zizmor`](https://github.com/woodruffw/zizmor) ищет в них дыры
28
+ безопасности — ни тот, ни другой не спрашивает, может ли шаг провалиться. `continue-on-error`
29
+ для них — законная настройка, каковой она и является: незаконной её делает то, ЧТО под ней
30
+ стоит, а это знает только проект.
31
+
32
+ **Чего НЕ ловит.**
33
+
34
+ - **Проверку, которую не по чему опознать.** Задача `mutation-diff` с именем «Mutation score on
35
+ changed files» под `continue-on-error` (нашлась в `kodus-ai`, автор сам пометил её «advisory —
36
+ does not block») не опознаётся: в имени нет слова-приметы, а команда не из списка. Объяви такую
37
+ проверку гейтом в `.aqk.yml` — тогда она станет видна точно, а не по догадке.
38
+ - **Провал, погашенный внутри скрипта.** `set +e`, `trap`, `exit 0` в конце `run: |` — это уже
39
+ логика скрипта, а не конфиг конвейера.
40
+ - **Шаг, который проходит по другой причине.** Тест, всегда возвращающий 0, — не эта запись,
41
+ а `test-has-assertion`.
42
+ - **`if: always()`** маскировкой не считается: он про порядок выполнения, а не про вердикт.