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
package/README.md
CHANGED
|
@@ -7,24 +7,61 @@
|
|
|
7
7
|
[](LICENSE)
|
|
8
8
|
[](https://github.com/arsen-ask-lx/Agent_Quality_Kit)
|
|
9
9
|
|
|
10
|
-
**
|
|
11
|
-
|
|
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
|
-
|
|
15
|
-
|
|
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
|
-
|
|
23
|
-
|
|
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
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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
|
+
[](https://github.com/marketplace/actions/agent-quality-kit-aqk)
|
|
218
|
+
|
|
130
219
|
```yaml
|
|
131
|
-
- uses: arsen-ask-lx/Agent_Quality_Kit@v0.
|
|
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
|
[](LICENSE)
|
|
8
8
|
[](https://github.com/arsen-ask-lx/Agent_Quality_Kit)
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
**Проверить, готов ли репозиторий к тому, что код в нём пишет ИИ-агент — и превратить правила,
|
|
11
|
+
которые проект обещает соблюдать, в команды с кодом возврата.**
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|
|
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
|
-
|
|
25
|
-
|
|
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
|
+
[](https://github.com/marketplace/actions/agent-quality-kit-aqk)
|
|
218
|
+
|
|
129
219
|
```yaml
|
|
130
|
-
- uses: arsen-ask-lx/Agent_Quality_Kit@v0.
|
|
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
|
Ничего не меняется. Запись каталога держит **одно намерение и несколько исполнителей**, и
|
package/kit/gates/README.md
CHANGED
|
@@ -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
|
программа не знает, отклоняется явно, а не пропускается молча.
|
package/kit/gates/_native.sh
CHANGED
|
@@ -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
|
-
|
|
26
|
-
|
|
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
|
# осталось в выводе. Красным делаем только то, что инструмент И счёл отказом, И что пережило
|
package/kit/gates/_skip.sh
CHANGED
|
@@ -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()`** маскировкой не считается: он про порядок выполнения, а не про вердикт.
|