agent-quality-kit 0.12.0 → 0.14.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 (74) hide show
  1. package/README.md +43 -7
  2. package/README.ru.md +44 -7
  3. package/kit/gates/api-contract-has-arbiter/README.md +16 -1
  4. package/kit/gates/api-contract-has-arbiter/check.sh +66 -23
  5. package/kit/gates/complexity-limit/gate.yml +4 -0
  6. package/kit/gates/dead-code/gate.yml +4 -0
  7. package/kit/gates/entry-commands-exist/README.md +64 -0
  8. package/kit/gates/entry-commands-exist/check.sh +110 -0
  9. package/kit/gates/entry-commands-exist/gate.yml +19 -0
  10. package/kit/gates/entry-commands-exist/green/AGENTS.md +13 -0
  11. package/kit/gates/entry-commands-exist/green/Makefile +6 -0
  12. package/kit/gates/entry-commands-exist/green/justfile +2 -0
  13. package/kit/gates/entry-commands-exist/green/package.json +10 -0
  14. package/kit/gates/entry-commands-exist/red/AGENTS.md +9 -0
  15. package/kit/gates/entry-commands-exist/red/Makefile +2 -0
  16. package/kit/gates/entry-commands-exist/red/package.json +9 -0
  17. package/kit/gates/env-secrets-not-committed/README.md +73 -0
  18. package/kit/gates/env-secrets-not-committed/check.sh +139 -0
  19. package/kit/gates/env-secrets-not-committed/gate.yml +21 -0
  20. package/kit/gates/env-secrets-not-committed/green/.aqk-tracked +10 -0
  21. package/kit/gates/env-secrets-not-committed/green/.env +10 -0
  22. package/kit/gates/env-secrets-not-committed/green/.env.production +5 -0
  23. package/kit/gates/env-secrets-not-committed/green/.env.test +2 -0
  24. package/kit/gates/env-secrets-not-committed/red/.aqk-tracked +5 -0
  25. package/kit/gates/env-secrets-not-committed/red/.env +7 -0
  26. package/kit/gates/no-print-in-prod/gate.yml +4 -0
  27. package/kit/gates/swallowed-error/gate.yml +4 -0
  28. package/kit/gates/todo-without-task/gate.yml +4 -0
  29. package/llms.txt +8 -2
  30. package/package.json +1 -1
  31. package/tool/commands/badge.mjs +1 -1
  32. package/tool/commands/context.mjs +81 -9
  33. package/tool/commands/doctor-catalog.mjs +222 -0
  34. package/tool/commands/doctor.mjs +81 -209
  35. package/tool/commands/learn.mjs +119 -19
  36. package/tool/commands/probe.mjs +50 -68
  37. package/tool/commands/project.mjs +6 -1
  38. package/tool/commands/prompt.mjs +69 -0
  39. package/tool/commands/report.mjs +1 -1
  40. package/tool/commands/vitals.mjs +15 -11
  41. package/tool/i18n/en-docs.mjs +22 -1
  42. package/tool/i18n/en-gates.mjs +6 -1
  43. package/tool/i18n/en.mjs +78 -4
  44. package/tool/i18n/index.mjs +42 -3
  45. package/tool/i18n/ru-docs.mjs +24 -1
  46. package/tool/i18n/ru-gates.mjs +6 -1
  47. package/tool/i18n/ru.mjs +87 -4
  48. package/tool/lib/adopt.mjs +15 -1
  49. package/tool/lib/advice.mjs +115 -0
  50. package/tool/lib/annotate.mjs +66 -0
  51. package/tool/lib/brief.mjs +3 -1
  52. package/tool/lib/cadence.mjs +40 -1
  53. package/tool/lib/core.mjs +40 -1
  54. package/tool/lib/gate-worker.mjs +18 -0
  55. package/tool/lib/history.mjs +34 -5
  56. package/tool/lib/manifest.mjs +65 -21
  57. package/tool/lib/repo.mjs +47 -35
  58. package/tool/lib/run.mjs +98 -9
  59. package/tool/program.mjs +4 -0
  60. package/tool/selfcheck/smoke/_fixture.mjs +8 -3
  61. package/tool/selfcheck/smoke/api-contract.test.mjs +37 -0
  62. package/tool/selfcheck/smoke/corpus.test.mjs +151 -0
  63. package/tool/selfcheck/smoke/first-run.test.mjs +144 -0
  64. package/tool/selfcheck/smoke/verdict.test.mjs +91 -3
  65. package/tool/selfcheck/smoke.sh +10 -2
  66. package/tool/selfcheck/units-annotate.mjs +67 -0
  67. package/tool/selfcheck/units-cadence.mjs +40 -1
  68. package/tool/selfcheck/units-context.mjs +64 -1
  69. package/tool/selfcheck/units-learn.mjs +32 -0
  70. package/tool/selfcheck/units-level.mjs +97 -3
  71. package/tool/selfcheck/units-probe.mjs +2 -1
  72. package/tool/selfcheck/units-prompt.mjs +106 -0
  73. package/tool/selfcheck/units-repo.mjs +44 -1
  74. package/tool/selfcheck/units-verdict.mjs +76 -0
package/README.md CHANGED
@@ -102,16 +102,20 @@ aqk badge write the level badge into the README
102
102
  aqk badge --check fail if the badge disagrees with a run
103
103
 
104
104
  aqk context the repository state in one block, for an agent's context:
105
- level, what is red now, rules nobody enforces, ratchets
105
+ level, what is red now, rules nobody enforces, ratchets,
106
+ the next three steps with commands, and when to run what
107
+ aqk prompt one task to paste into an agent: what to fix, in order, and the
108
+ command that proves each item done
106
109
  aqk vitals is what the kit runs on wired up: gate tools, hooks, freshness
107
110
  aqk doctor --run --brief one line on success, the whole run on failure — for hooks
108
111
  aqk context --full the same plus the command map and the rulebook verbatim (~7000
109
- tokens against ~375: the price of an agent that does not guess)
112
+ tokens against ~500: the price of an agent that does not guess)
110
113
  aqk context --install put a SessionStart hook into .claude/settings.json
111
114
  (add --full to install the full block)
112
115
 
113
- aqk learn rule candidates from local transcripts:
114
- said out loud, never written down
116
+ aqk learn rule candidates from local transcripts: said out loud, never
117
+ written down and what you had to repeat ("I told you",
118
+ "again"), first of all a written rule the agent still breaks
115
119
  aqk note "..." write a bruise into the journal
116
120
  aqk ratchet <name> a debt registry for a declared gate: may only get shorter
117
121
  aqk blob every guide as a single file
@@ -183,7 +187,7 @@ aqk: 1
183
187
  entry: [AGENTS.md] # what the agent reads first
184
188
  rules: .aqk/rules # where the standards live
185
189
  docs: .aqk/docs # where the guides live (optional; this is the default)
186
- lang: en # output language for THIS repo, over the machine locale
190
+ lang: en # output language; without it the language of AGENTS.md/README
187
191
  gates: # what must pass — as commands, not as prose
188
192
  lint: "npm run lint"
189
193
  secrets-not-in-code: "bash gates/secrets-not-in-code/check.sh ."
@@ -347,7 +351,7 @@ Already using [pre-commit](https://pre-commit.com)? Three lines in the file you
347
351
  ```yaml
348
352
  repos:
349
353
  - repo: https://github.com/arsen-ask-lx/Agent_Quality_Kit
350
- rev: v0.12.0
354
+ rev: v0.14.0
351
355
  hooks:
352
356
  - id: aqk # runs what the repository declares; blocks below AQK-1
353
357
  # - id: aqk-doctor # read-only: the level and what is missing, blocks nothing
@@ -368,7 +372,7 @@ layer AQK adds.
368
372
  [![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)
369
373
 
370
374
  ```yaml
371
- - uses: arsen-ask-lx/Agent_Quality_Kit@v0.12.0
375
+ - uses: arsen-ask-lx/Agent_Quality_Kit@v0.14.0
372
376
  with:
373
377
  min: 1 # the build fails below AQK-1, or if any declared gate failed
374
378
  ```
@@ -380,6 +384,12 @@ the same thing in a single line:
380
384
  - run: npx agent-quality-kit doctor --run --min 1
381
385
  ```
382
386
 
387
+ **Findings show up in the pull request itself.** Inside GitHub Actions a failed gate's finding
388
+ that names a file — `src/a.py:12: …` — becomes a red annotation on that line of the diff, with the
389
+ gate's "fix:" advice attached; an advisory gate gives a yellow one. At most ten of each per run
390
+ (the limit GitHub is reported to take per step); the rest are counted in the log. The verdict
391
+ does not change — annotations print what the run already decided.
392
+
383
393
  ## Without Node at all
384
394
 
385
395
  A Python, Go or Rust project where nobody installed Node and nobody will:
@@ -456,6 +466,32 @@ Otherwise a gate that cannot fail the build looks exactly like one that can, and
456
466
  is in `advisory:` only on the day it goes red. Red ones are additionally named by name in the
457
467
  summary: an advisory gate everyone forgot about is a switched-off check.
458
468
 
469
+ **A gate that needs a live stack** (a cost check against a seeded database, e2e) goes into a group,
470
+ and a fast run skips the group:
471
+
472
+ ```yaml
473
+ groups:
474
+ stack: [cost, e2e]
475
+ ```
476
+
477
+ ```bash
478
+ aqk doctor --run --skip stack # everything except the stack
479
+ aqk doctor --run --only stack # only the stack, on the stand
480
+ ```
481
+
482
+ Skipped gates are named in the output and written to the run report as "not run" — not as green.
483
+ An unknown name after `--skip` is a refusal: a typo must not quietly mean "skipped nothing".
484
+
485
+ **Independent gates can run in parallel:** `aqk doctor --run --jobs 4`. Off by default, on
486
+ purpose: two gates writing into the same folder (`npm run build` twice into `dist/`) would fail
487
+ at random when run together, and a flaky red is worse than a slow one. Output keeps the declared
488
+ order. On this repository, 30 gates: about 110 s one by one, 68 s with `--jobs 3`.
489
+
490
+ **Output is short by default.** A passed gate, an entry the machine already holds and an entry that
491
+ does not apply fold into one counted line each; a failed gate, an advisory one, advice and "what to
492
+ add" always print in full. `aqk doctor --verbose` lists everything by name; in a pipeline whose log
493
+ is read later, set `AQK_VERBOSE=1`.
494
+
459
495
  ## When a bug slips past the guards
460
496
 
461
497
  ```bash
package/README.ru.md CHANGED
@@ -104,16 +104,21 @@ aqk badge вписать значок уровня в README
104
104
  aqk badge --check упасть, если значок расходится с прогоном
105
105
 
106
106
  aqk context состояние репозитория одним блоком, для контекста агента:
107
- уровень, что красное сейчас, правила без арбитра, храповики
107
+ уровень, что красное сейчас, правила без арбитра, храповики,
108
+ три следующих шага с командами и когда что запускать
109
+ aqk prompt одно задание для агента: что починить, по порядку, и у каждого
110
+ пункта команда, которая докажет «готово»
108
111
  aqk vitals подключено ли то, чем комплект работает: инструменты, хуки, свежесть
109
112
  aqk doctor --run --brief одна строка на успехе, весь прогон при провале — для хуков
110
113
  aqk context --full то же плюс карта команд и свод правил дословно (≈7000 токенов
111
- против ≈375 — плата за то, чтобы агент не догадывался)
114
+ против ≈500 — плата за то, чтобы агент не догадывался)
112
115
  aqk context --install поставить хук SessionStart в .claude/settings.json
113
116
  (с --full ставится полный блок)
114
117
 
115
- aqk learn кандидаты в правила из локальной переписки:
116
- что сказано вслух и не записано
118
+ aqk learn кандидаты в правила из локальной переписки: что сказано
119
+ вслух и не записано — и что пришлось повторять («я же
120
+ говорил», «опять»), прежде всего записанное правило,
121
+ которое агент всё равно нарушает
117
122
  aqk note "..." записать шишку в журнал
118
123
  aqk ratchet <имя> реестр долга для объявленного гейта: может только укорачиваться
119
124
  aqk blob все методички одним файлом
@@ -185,7 +190,7 @@ aqk: 1
185
190
  entry: [AGENTS.md] # что агент читает первым
186
191
  rules: .aqk/rules # где стандарты
187
192
  docs: .aqk/docs # где методички (необязательно, это и есть умолчание)
188
- lang: ru # язык вывода для ЭТОГО репозитория, поверх локали машины
193
+ lang: ru # язык вывода; без поля язык AGENTS.md/README, потом локаль
189
194
  gates: # что обязано пройти — командами, не словами
190
195
  lint: "npm run lint"
191
196
  secrets-not-in-code: "bash gates/secrets-not-in-code/check.sh ."
@@ -350,7 +355,7 @@ aqk badge --check # в конвейере: код 1 в тот день, ког
350
355
  ```yaml
351
356
  repos:
352
357
  - repo: https://github.com/arsen-ask-lx/Agent_Quality_Kit
353
- rev: v0.12.0
358
+ rev: v0.14.0
354
359
  hooks:
355
360
  - id: aqk # запускает объявленное; роняет коммит ниже AQK-1
356
361
  # - id: aqk-doctor # только осмотр: уровень и чего не хватает, ничего не роняет
@@ -369,7 +374,7 @@ repos:
369
374
  [![в GitHub Marketplace](https://img.shields.io/badge/GitHub%20Marketplace-Agent%20Quality%20Kit-2ea44f?logo=github)](https://github.com/marketplace/actions/agent-quality-kit-aqk)
370
375
 
371
376
  ```yaml
372
- - uses: arsen-ask-lx/Agent_Quality_Kit@v0.12.0
377
+ - uses: arsen-ask-lx/Agent_Quality_Kit@v0.14.0
373
378
  with:
374
379
  min: 1 # сборка падает ниже AQK-1 или если упал любой объявленный гейт
375
380
  ```
@@ -381,6 +386,12 @@ repos:
381
386
  - run: npx agent-quality-kit doctor --run --min 1
382
387
  ```
383
388
 
389
+ **Находки видны прямо в pull request.** В GitHub Actions находка упавшего гейта, которая называет
390
+ файл, — `src/a.py:12: …` — становится красной пометкой у этой строки дифа, вместе с советом
391
+ «почини» того же гейта; совещательный гейт даёт жёлтую. Не больше десяти каждого вида за прогон
392
+ (столько GitHub, по имеющимся данным, принимает за шаг), остальное — числом в логе. Вердикт от
393
+ пометок не меняется: они печатают то, что прогон уже решил.
394
+
384
395
  ## Без Node вовсе
385
396
 
386
397
  Проект на Python, Go или Rust, где Node никто не ставил и не поставит:
@@ -455,6 +466,32 @@ advisory:
455
466
  списке `advisory:` человек узнаёт только в день, когда гейт покраснел. Покрасневшие вдобавок
456
467
  названы в сводке поимённо: совещательный гейт, о котором забыли, — это выключенная проверка.
457
468
 
469
+ **Гейт, которому нужен живой стенд** (цена на засеянной базе, e2e), кладётся в группу, и быстрый
470
+ прогон группу пропускает:
471
+
472
+ ```yaml
473
+ groups:
474
+ stack: [cost, e2e]
475
+ ```
476
+
477
+ ```bash
478
+ aqk doctor --run --skip stack # всё, кроме стенда
479
+ aqk doctor --run --only stack # только стенд, на стенде
480
+ ```
481
+
482
+ Пропущенные названы в выводе и записаны в отчёт прогона как «не запускались» — не как зелёные.
483
+ Неизвестное имя после `--skip` — отказ: опечатка не должна тихо означать «пропустили ничего».
484
+
485
+ **Независимые гейты можно гнать параллельно:** `aqk doctor --run --jobs 4`. По умолчанию
486
+ выключено намеренно: два гейта, пишущие в одну папку (`npm run build` дважды в `dist/`), вместе
487
+ падали бы через раз, а плавающее красное хуже медленного. Вывод — в порядке объявления. На этом
488
+ репозитории, 30 гейтов: около 110 с по одному, 68 с с `--jobs 3`.
489
+
490
+ **Вывод короткий по умолчанию.** Прошедший гейт, запись, которую уже держит машина, и
491
+ неприменимая запись сворачиваются в одну строку со счётом; упавший гейт, совещательный, совет и
492
+ «что поставить» печатаются всегда. `aqk doctor --verbose` — всё поимённо; в конвейере, где лог
493
+ читают потом, — `AQK_VERBOSE=1`.
494
+
458
495
 
459
496
  ## Поймал ошибку, которую не поймал сторож
460
497
 
@@ -16,7 +16,7 @@
16
16
 
17
17
  Отсюда обе красные ветки. Замер целиком — `incidents/README.md`, 2026-09-09.
18
18
 
19
- **Что именно проверяется.** Записи две, и вторая тоньше первой.
19
+ **Что именно проверяется.** Записи три; вторая тоньше первой, третья — тот же договор без файла.
20
20
 
21
21
  1. **Спецификацию не держит ни одна команда.** Файл `openapi*.{yaml,yml,json}` (и `swagger*`,
22
22
  `asyncapi*`) в репозитории есть, а ни в конвейере, ни в сборочных файлах, ни в скриптах, ни
@@ -24,6 +24,14 @@
24
24
  2. **Арбитр сужен до «не пятисотка».** `schemathesis --checks not_a_server_error` (или `-c`).
25
25
  У `schemathesis` по умолчанию включены **все** проверки, включая
26
26
  `response_schema_conformance`; сужение до одной — не настройка, а отключение.
27
+ 3. **Договор в коде, а проверку типов не запускает никто.** В `package.json` стоит `@trpc/server`,
28
+ `@ts-rest/core`, `@hono/zod-openapi` или провайдер типов Fastify (`@fastify/type-provider-*`,
29
+ `fastify-type-provider-zod`), а `tsc`, `vue-tsc` или `tsgo` не зовёт ни одна команда. У такого
30
+ договора арбитр — типы: разошлись сервер и клиент, краснеет сборка типов. Отзыв с живого
31
+ проекта 2026-09-11: схемы zod в общем пакете, сервер на `@fastify/type-provider-zod` — а
32
+ запись писала «спецификации не видно». Один `zod` договором не считается: им разбирают и
33
+ формы, и конфиги. Файлы блокировки (`package-lock.json`) запуском не считаются — там
34
+ `"bin": { "tsc": … }` у любого проекта на TypeScript.
27
35
 
28
36
  Четыре семьи держателей отвечают на **разные** вопросы, и подменять один другим нельзя:
29
37
 
@@ -61,3 +69,10 @@
61
69
  запись. Красным сделано только то, у чего законного применения нет.
62
70
  - **Что спецификация вообще описывает этот сервер.** Файл может описывать чужой API; сверять
63
71
  их машина не умеет.
72
+ - **Что договор в коде проверяется ВО ВРЕМЯ РАБОТЫ.** Типы сходятся — а ответ сервер отдаёт
73
+ непроверенным: у tRPC без `.output()`, у ts-rest без `responseValidation` на сервере, у Fastify без
74
+ сериализатора схемы ответа. Это расхождение `tsc` не видит по построению: типы описывают
75
+ намерение, а не то, что ушло по сети. Сверять настройку каждой библиотеки — десять правил
76
+ ради одной записи; запись держит то, что одинаково у всех: проверку типов кто-то запускает.
77
+ - **Договор в коде у других библиотек.** `zod` плюс рукописный клиент, GraphQL-схема в коде,
78
+ gRPC — не опознаются. Список взят с живого проекта и с реестра npm, а не для полноты.
@@ -40,23 +40,36 @@ SPECS=$(find "$DIR" $(skip_find) -type f \
40
40
  -o -iname 'swagger*.yaml' -o -iname 'swagger*.yml' -o -iname 'swagger*.json' \
41
41
  -o -iname 'asyncapi*.yaml' -o -iname 'asyncapi*.yml' -o -iname 'asyncapi*.json' \) \
42
42
  -print 2>/dev/null | own_samples_filter "$DIR" | grep -v '^$')
43
- [ -z "$SPECS" ] && { echo "спецификации API здесь нет — эта проверка не про тебя"; exit 0; }
44
43
 
45
- # --- кто её держит ------------------------------------------------------------
46
- # Четыре семьи держателей, и они отвечают на РАЗНЫЕ вопросы подробности в README:
47
- # сверка с сервером schemathesis, dredd, portman, newman, pact «документ не врёт»
48
- # линт документа spectral, redocly, vacuum, openapi-spec-validator, swagger-cli
49
- # ломающие правки oasdiff «вчерашний клиент переживёт сегодняшний выпуск»
50
- # потребитель openapi-typescript, orval, oapi-codegen, openapi-generator, kubb
51
- # типы порождены договором, и расхождение ломает сборку
52
- HOLDERS='schemathesis|dredd|portman|newman[[:space:]]+run|pact-broker|pact-verifier|can-i-deploy|spectral[[:space:]]+lint|redocly[[:space:]]+(lint|bundle)|vacuum[[:space:]]+(lint|report|html-report)|openapi-spec-validator|swagger-cli|oasdiff|openapi-typescript|orval|oapi-codegen|openapi-generator|kubb'
53
- # Ищем в том, что ЗАПУСКАЮТ: конвейер, оболочечные скрипты, сборочные файлы, объявления пакета
54
- # и манифест самого комплекта. Не в коде: упоминание инструмента в исходнике — не его запуск.
55
- FOUND=$(grep -rnE "$HOLDERS" $(skip_grep) \
56
- --include=*.yml --include=*.yaml --include=*.sh --include=*.json --include=*.toml \
57
- --include=*.ini --include=*.cfg --include=*.mk --include=Makefile --include=Justfile \
58
- --include=justfile --include=Taskfile.yml --include=*.gradle --include=Jenkinsfile \
59
- "$DIR" 2>/dev/null | own_samples_filter "$DIR" | grep -v '^$')
44
+ # ДОГОВОР В КОДЕ тот же договор без файла. Отзыв с живого проекта 2026-09-11: схемы zod
45
+ # запросов и ответов в общем пакете, сервер на `@fastify/type-provider-zod`а проверка писала
46
+ # «спецификации нет», потому что знала только имя `openapi*`. Опознаём по зависимости в
47
+ # `package.json`: tRPC, ts-rest, Hono с zod-openapi, провайдеры типов Fastify (официальные
48
+ # `@fastify/type-provider-*` и `fastify-type-provider-zod`). Один `zod` НЕ договор: им
49
+ # разбирают формы и конфиги. Имена сверены с реестром npm 2026-09-11, а не по памяти: по
50
+ # памяти был назван `fastify-type-provider-zod`, а на живом проекте стоял `@fastify/...`.
51
+ CODE_DEPS='@trpc/server|@ts-rest/core|@hono/zod-openapi|@fastify/type-provider-[a-z0-9-]+|fastify-type-provider-zod'
52
+ CODE=$(find "$DIR" $(skip_find) -type f -name package.json -print 2>/dev/null \
53
+ | own_samples_filter "$DIR" | grep -v '^$' \
54
+ | while IFS= read -r F; do grep -qE "\"($CODE_DEPS)\"[[:space:]]*:" "$F" && printf '%s\n' "$F"; done)
55
+
56
+ [ -z "$SPECS$CODE" ] && {
57
+ echo "договора API здесь нет — ни файла OpenAPI, ни tRPC, ts-rest или провайдера типов"
58
+ echo "Fastify; эта проверка не про тебя"
59
+ exit 0
60
+ }
61
+
62
+ # Где искать запуск: конвейер, оболочечные скрипты, сборочные файлы, объявления пакета и
63
+ # манифест самого комплекта. Не в коде: упоминание инструмента в исходнике — не его запуск.
64
+ # Файлы блокировки — не запуск тоже: `package-lock.json` держит `"bin": { "tsc": … }` у
65
+ # каждого проекта на TypeScript, и без исключения арбитр находился бы всегда.
66
+ run_grep() {
67
+ grep -rnE "$1" $(skip_grep) --exclude=package-lock.json --exclude=npm-shrinkwrap.json \
68
+ --include=*.yml --include=*.yaml --include=*.sh --include=*.json --include=*.toml \
69
+ --include=*.ini --include=*.cfg --include=*.mk --include=Makefile --include=Justfile \
70
+ --include=justfile --include=Taskfile.yml --include=*.gradle --include=Jenkinsfile \
71
+ "$DIR" 2>/dev/null | own_samples_filter "$DIR" | grep -v '^$' | drop_definitions
72
+ }
60
73
 
61
74
  # ОПРЕДЕЛЕНИЕ ЗАПИСИ КАТАЛОГА — НЕ НАХОДКА, и это касается не только своей записи. Соседняя
62
75
  # запись `ci-actually-fails` держит в своём `check.sh` строку со списком запускалок, где
@@ -66,12 +79,42 @@ FOUND=$(grep -rnE "$HOLDERS" $(skip_grep) \
66
79
  # Найдено аудитом фич 2026-09-09 — прогоном на настоящем проекте, а не образцами: красный и
67
80
  # зелёный образцы лежат по одному, а в проекте записи стоят рядом. Признак определения взят
68
81
  # самый надёжный: в той же папке лежит `gate.yml`.
69
- FOUND=$(printf '%s\n' "$FOUND" | while IFS= read -r L; do
70
- F="${L%%:*}"
71
- [ -n "$F" ] || continue
72
- [ -f "$(dirname "$F")/gate.yml" ] && continue
73
- printf '%s\n' "$L"
74
- done | grep -v '^$')
82
+ drop_definitions() {
83
+ while IFS= read -r L; do
84
+ F="${L%%:*}"
85
+ [ -n "$F" ] || continue
86
+ [ -f "$(dirname "$F")/gate.yml" ] && continue
87
+ printf '%s\n' "$L"
88
+ done | grep -v '^$'
89
+ }
90
+
91
+ # Арбитр договора в коде — проверка типов: разошлись сервер и клиент, `tsc` краснеет. Слово
92
+ # целиком: `tsc-alias` и `typescript` запуском проверки типов не являются.
93
+ RC=0
94
+ if [ -n "$CODE" ]; then
95
+ TYPED=$(run_grep '(^|[^a-zA-Z0-9_-])(vue-tsc|tsc|tsgo)([[:space:]]|"|$)')
96
+ if [ -z "$TYPED" ]; then
97
+ printf '%s\n' "$CODE" | while IFS= read -r F; do
98
+ D=$(grep -oE "\"($CODE_DEPS)\"" "$F" | head -1 | tr -d '"')
99
+ echo "$F: договор API в коде ($D) — проверку типов не запускает ни одна команда"
100
+ done
101
+ echo " почини: заведи «tsc --noEmit» (или «tsc --build») в конвейере или в гейтах"
102
+ echo " манифеста. Договор в коде держат типы: разошлись сервер и клиент — краснеет сборка"
103
+ echo " типов. Никто её не запускает — договор расходится молча, как файл OpenAPI без сверки."
104
+ RC=1
105
+ fi
106
+ fi
107
+ [ -z "$SPECS" ] && exit "$RC"
108
+
109
+ # --- кто её держит ------------------------------------------------------------
110
+ # Четыре семьи держателей, и они отвечают на РАЗНЫЕ вопросы — подробности в README:
111
+ # сверка с сервером schemathesis, dredd, portman, newman, pact — «документ не врёт»
112
+ # линт документа spectral, redocly, vacuum, openapi-spec-validator, swagger-cli
113
+ # ломающие правки oasdiff — «вчерашний клиент переживёт сегодняшний выпуск»
114
+ # потребитель openapi-typescript, orval, oapi-codegen, openapi-generator, kubb —
115
+ # типы порождены договором, и расхождение ломает сборку
116
+ HOLDERS='schemathesis|dredd|portman|newman[[:space:]]+run|pact-broker|pact-verifier|can-i-deploy|spectral[[:space:]]+lint|redocly[[:space:]]+(lint|bundle)|vacuum[[:space:]]+(lint|report|html-report)|openapi-spec-validator|swagger-cli|oasdiff|openapi-typescript|orval|oapi-codegen|openapi-generator|kubb'
117
+ FOUND=$(run_grep "$HOLDERS")
75
118
 
76
119
  if [ -z "$FOUND" ]; then
77
120
  printf '%s\n' "$SPECS" | sed 's/$/: спецификацию не держит ни одна команда/'
@@ -121,4 +164,4 @@ if [ -n "$NARROW$LOUD" ]; then
121
164
  echo " провал, погашенный «|| true» или «continue-on-error», — это ci-actually-fails."
122
165
  exit 1
123
166
  fi
124
- exit 0
167
+ exit "$RC"
@@ -17,3 +17,7 @@ recipes:
17
17
  typescript: eslint --no-config-lookup --ignore-pattern 'gates/*/red/**' --ignore-pattern 'gates/*/green/**' --rule '{"complexity":["error",10],"max-depth":["error",4]}' {dir}
18
18
 
19
19
  proof: incidents/README.md — «2026-08-27 ревизия гейтов», п. 6: 75 функций сложнее нормы, худшая с 29 ветвлениями
20
+
21
+ # Правило Biome под эту запись — для сверки `covers:` (рецептов под Biome нет). Имя сверено по
22
+ # схеме конфигурации Biome 2.5.12, а не по памяти.
23
+ biome_rules: complexity/noExcessiveCognitiveComplexity
@@ -27,3 +27,7 @@ recipes:
27
27
  samples_for: python
28
28
 
29
29
  proof: incidents/README.md — «2026-08-27 ревизия гейтов», п. 5: мёртвые функции бэкенда не искал никто, реестр на 148 записей
30
+
31
+ # Правило Biome под эту запись — для сверки `covers:` (рецептов под Biome нет). Имя сверено по
32
+ # схеме конфигурации Biome 2.5.12, а не по памяти.
33
+ biome_rules: correctness/noUnusedVariables, correctness/noUnusedImports
@@ -0,0 +1,64 @@
1
+ # Команды из свода существуют
2
+
3
+ **Намерение.** Агент берёт команды из свода дословно. Свод, который велит `npm run lint:cli`,
4
+ когда такого скрипта нет, отправляет агента в «Missing script» — и дальше тот либо чинит не то,
5
+ либо докладывает «проверил» про проверку, которой нет. Соседняя запись `entry-links-exist` ловит
6
+ в своде несуществующие ФАЙЛЫ; эта — КОМАНДЫ.
7
+
8
+ **Какой отказ это поймало.** Выборка 2026-09-11: 99 публичных репозиториев с `AGENTS.md`, где
9
+ упомянут `npm run`. В 9 свод называет скрипт, которого нет ни в одном `package.json`
10
+ репозитория. В двух история показывает механизм:
11
+
12
+ | Репозиторий | Что было | Что осталось |
13
+ |---|---|---|
14
+ | `notefig/notefig` | `lint:cli` добавлен в `cbb21a4c` вместе со строкой в своде, удалён в тот же день в `ffaf7d8c` | `npm run lint:cli` в `AGENTS.md` |
15
+ | `JonnyKreng/pebble-navi` | `debug` добавлен в `1630de8` вместе со строкой в своде, удалён через два дня в `757c563` | `npm run debug # build + install + logs` |
16
+
17
+ Замер целиком — `incidents/README.md`, 2026-09-11.
18
+
19
+ **Почему машина, а не внимательность.** Скрипт удаляют в одном файле, свод живёт в другом, и
20
+ правка «fix(ci): resolve GitHub Actions failures» о своде не думает. Ровно как у
21
+ `entry-links-exist`: расхождение появляется не при написании свода, а через дни, в другой задаче.
22
+
23
+ **Что именно сверяется.**
24
+
25
+ | В своде | С чем |
26
+ |---|---|
27
+ | `npm run X`, `pnpm run X`, `yarn run X`, `bun run X` | ключи блока `"scripts"` всех `package.json` репозитория, кроме `node_modules` |
28
+ | `make X` (и `make -j4 X`) | цели всех `Makefile`, `makefile`, `GNUmakefile`, `*.mk` |
29
+ | `just X` | рецепты и псевдонимы `justfile` |
30
+
31
+ Скрипты берутся только из блока `"scripts"`: в `pebble-navi` `debug` — ещё и имя зависимости, и
32
+ поиск любого `"debug":` в файле сказал бы «есть». Все `package.json` репозитория, а не только
33
+ корневой: команда из свода часто живёт в пакете рабочего пространства.
34
+
35
+ **Какие файлы.** Только своды агента: `AGENTS.md`, `CLAUDE.md`, `GEMINI.md` в корне (регистр
36
+ имени не важен), `.claude/CLAUDE.md`, `.github/copilot-instructions.md`. README не берётся: там
37
+ пишут и команды для ЧУЖОГО проекта — «в своём проекте запусти `npm run aqk`».
38
+
39
+ **Готовый аналог.** agentlint, правило `cmd-cross-reference`, — только npm-скрипты и только
40
+ корневой `package.json`. На живом проекте, где свод называет шестнадцать целей `make` и ни
41
+ одного `npm run`, оно молчит по построению.
42
+
43
+ **Чего НЕ ловит.**
44
+
45
+ - **`make` и `just` в прозе.** Ищутся только внутри кода — в обратных кавычках и блоках: «make
46
+ sure the tree is clean» — не команда. Свод, где команда написана прозой без кавычек, сверен не
47
+ будет. `npm run` однозначен и ищется везде.
48
+ - **Шаблоны и присваивания.** `npm run build:*`, `npm run cms:<имя>`, `make <цель>`, `make test
49
+ V=1` не называют одну команду и пропускаются. Отсюда же: свод, говорящий «скрипты `dev:local*`
50
+ сняты», находкой не считается.
51
+ - **Команды с каталогом.** `make -C docs html`, `make -f other.mk x`, `npm --prefix web run x`,
52
+ `pnpm --filter web run x` — цель живёт в чужом файле, по одной строке его не определить.
53
+ Пропускаются целиком.
54
+ - **Сокращения и прочие запускалки.** `yarn build` без `run`, `tox -e lint`, `task lint`,
55
+ `cargo xtask`, `uv run x` — не сверяются. Список взят из случаев, а не для полноты.
56
+ - **Цели из шаблонных правил make.** `%.o: %.c` покрывает `make foo.o`; такая цель будет названа
57
+ несуществующей. В своде агента такое встречается редко.
58
+ - **Что команда делает то, что обещает свод.** Скрипт `lint` может ничего не проверять — это
59
+ вопрос уже к нему, а не к своду.
60
+
61
+ **Образцы.** `red/` — свод велит `npm run debug`, а `debug` в `package.json` есть только в
62
+ зависимостях; и `make deploy` при `Makefile` с одной целью `test`: гейт обязан краснеть. `green/`
63
+ — те же имена существуют, `make -j4 lint` с флагом, `just fmt`, «make sure» в прозе, шаблоны
64
+ `npm run gen:*` и `make <target>`, `VERSION := 1.0` в `Makefile`: гейт обязан молчать.
@@ -0,0 +1,110 @@
1
+ #!/usr/bin/env sh
2
+ # Команда, которую свод велит запускать, существует в проекте.
3
+ #
4
+ # ЗАЧЕМ ИМЕННО ЭТО. Агент берёт команды из свода дословно. Скрипт переименовали или удалили —
5
+ # свод продолжает велеть «npm run lint:cli», агент зовёт несуществующее, получает «Missing
6
+ # script» и дальше либо чинит не то, либо сообщает «проверил» про проверку, которой нет.
7
+ # Соседняя запись `entry-links-exist` ловит в своде несуществующие ФАЙЛЫ; эта — КОМАНДЫ.
8
+ #
9
+ # СЛУЧАИ, ИЗ КОТОРЫХ ВЗЯЛАСЬ ЗАПИСЬ (2026-09-11, выборка из 99 публичных репозиториев с
10
+ # AGENTS.md, где упомянут «npm run»; подробности — incidents/README.md):
11
+ # notefig/notefig скрипт lint:cli появился в cbb21a4c вместе со строкой в своде и в тот
12
+ # же день удалён в ffaf7d8c; свод всё ещё велит «npm run lint:cli»
13
+ # JonnyKreng/pebble-navi скрипт debug добавлен в 1630de8 вместе со строкой в своде, удалён
14
+ # через два дня в 757c563; строка «npm run debug» в своде осталась
15
+ # Во втором случае `debug` есть в зависимостях — поэтому скрипты берутся из блока "scripts",
16
+ # а не любым «"debug":» в файле: иначе ложное «есть».
17
+ DIR="${1:-.}"
18
+ SKIP_LIB="$(dirname "$0")/../_skip.sh"
19
+ if [ ! -f "$SKIP_LIB" ]; then
20
+ echo "рядом с проверкой нет _skip.sh — обход не собран, проверка не состоялась"
21
+ echo " почини: скопируй гейт вместе с файлом kit/gates/_skip.sh, он общий на весь каталог"
22
+ exit 2
23
+ fi
24
+ . "$SKIP_LIB"
25
+ # Файл на месте — этого мало: подмена содержимого давала код 0. Метка стоит в КОНЦЕ _skip.sh,
26
+ # поэтому проверка ловит и обрыв файла на середине.
27
+ if [ "${AQK_SKIP_READY:-}" != 1 ]; then
28
+ echo "_skip.sh есть, но обход не собрался — проверка НЕ СОСТОЯЛАСЬ, а не прошла"
29
+ echo " почини: замени kit/gates/_skip.sh целым файлом из каталога"
30
+ exit 2
31
+ fi
32
+
33
+ # --- своды агента ---------------------------------------------------------------
34
+ # То, что агент читает при запуске. README не берём: там пишут и команды для ЧУЖОГО проекта
35
+ # («в своём проекте запусти npm run aqk»). Регистр имени не важен: `agents.md` встречается.
36
+ ENTRIES=$( { find "$DIR" -maxdepth 1 -type f \( -iname 'agents.md' -o -iname 'claude.md' -o -iname 'gemini.md' \) -print
37
+ for F in .claude/CLAUDE.md .github/copilot-instructions.md; do [ -f "$DIR/$F" ] && printf '%s\n' "$DIR/$F"; done
38
+ } 2>/dev/null | own_samples_filter "$DIR" | grep -v '^$')
39
+ [ -z "$ENTRIES" ] && { echo "свода агента здесь нет — сверять нечего"; exit 0; }
40
+
41
+ # --- что в проекте есть ---------------------------------------------------------
42
+ # Скрипты — только из блока "scripts" каждого package.json (рабочие пространства тоже: команда
43
+ # из свода часто живёт в пакете, а не в корне). Файл склеивается в строку: блок бывает и в одну.
44
+ SCRIPTS=$(find "$DIR" $(skip_find) -type f -name package.json -print 2>/dev/null | own_samples_filter "$DIR" | grep -v '^$' \
45
+ | while IFS= read -r P; do
46
+ tr '\r\n' ' ' < "$P" | sed -n 's/.*"scripts"[[:space:]]*:[[:space:]]*{\([^}]*\)}.*/\1/p' \
47
+ | grep -oE '"[^"]+"[[:space:]]*:' | sed 's/^"//; s/"[[:space:]]*:$//'
48
+ done)
49
+ HAS_PKG=$(find "$DIR" $(skip_find) -type f -name package.json -print 2>/dev/null | own_samples_filter "$DIR" | grep -v '^$' | head -1)
50
+
51
+ # Цели make: строка вне рецепта, слова до двоеточия; «VAR := x» — присваивание, не цель.
52
+ TARGETS=$(find "$DIR" $(skip_find) -type f \( -name Makefile -o -name makefile -o -name GNUmakefile -o -name '*.mk' \) -print 2>/dev/null \
53
+ | own_samples_filter "$DIR" | grep -v '^$' | while IFS= read -r M; do
54
+ awk '/^[^\t#][^=]*:([^=]|$)/ { sub(/:.*/, ""); n = split($0, a, /[ \t]+/); for (i = 1; i <= n; i++) if (a[i] != "") print a[i] }' "$M"
55
+ done)
56
+
57
+ # Рецепты just: «имя:», «@имя арг:», «alias имя := …».
58
+ RECIPES=$(find "$DIR" $(skip_find) -type f \( -name justfile -o -name Justfile -o -name .justfile \) -print 2>/dev/null \
59
+ | own_samples_filter "$DIR" | grep -v '^$' | while IFS= read -r J; do
60
+ awk '/^alias[ \t]+/ { print $2; next }
61
+ /^@?[A-Za-z_][A-Za-z0-9_-]*([ \t][^:=]*)?:([^=]|$)/ { sub(/^@/, ""); sub(/[ \t:].*/, ""); print }' "$J"
62
+ done)
63
+
64
+ has() { printf '%s\n' "$2" | grep -qxF -- "$1"; }
65
+
66
+ # --- что свод велит запускать ---------------------------------------------------
67
+ # «npm run X» однозначен где угодно. «make X» и «just X» — только внутри кода (обратные кавычки,
68
+ # блоки): в прозе «make sure» — не команда. Шаблоны («npm run build:*», «make <цель>») и
69
+ # присваивания («make test V=1») пропускаются: они не называют одну команду.
70
+ MISS=0
71
+ # Список команд — через файл, а не через трубу: цикл в трубе идёт в подоболочке, и отметка
72
+ # о находке из него не возвращается.
73
+ TMP=$(mktemp) || exit 2
74
+ trap 'rm -f "$TMP"' EXIT
75
+ report() {
76
+ echo "$1: «$2» — $3"
77
+ MISS=1
78
+ }
79
+ while IFS= read -r E; do
80
+ [ -n "$E" ] || continue
81
+ # Код свода: строки блоков целиком, вне блоков — только вставки в обратных кавычках.
82
+ CODE=$(awk '/^[ \t]*(```|~~~)/ { f = !f; next }
83
+ f { print; next }
84
+ { while (match($0, /`[^`]+`/)) { print substr($0, RSTART + 1, RLENGTH - 2); $0 = substr($0, RSTART + RLENGTH) } }' "$E")
85
+ grep -oE '(npm|pnpm|yarn|bun)[[:space:]]+run[[:space:]]+[A-Za-z][A-Za-z0-9_:.-]*[*<{]?' "$E" \
86
+ | grep -v '[*<{:]$' | sed 's/\.$//; s/[[:space:]][[:space:]]*/ /g' | sort -u > "$TMP"
87
+ while IFS= read -r CMD; do
88
+ N=${CMD##* }
89
+ has "$N" "$SCRIPTS" && continue
90
+ if [ -n "$HAS_PKG" ]; then report "$E" "$CMD" "такого скрипта нет ни в одном package.json"
91
+ else report "$E" "$CMD" "package.json в репозитории нет вовсе"; fi
92
+ done < "$TMP"
93
+ for N in $(printf '%s\n' "$CODE" | grep -E '(^|[^A-Za-z0-9_-])make[[:space:]]' | grep -vE -- '-C|--directory|-f[[:space:]]|--file' \
94
+ | grep -oE '(^|[^A-Za-z0-9_-])make([[:space:]]+-[A-Za-z0-9]+)*[[:space:]]+[A-Za-z][A-Za-z0-9_.-]*=?' \
95
+ | grep -v '=$' | sed 's/.*[[:space:]]//' | sort -u); do
96
+ has "$N" "$TARGETS" || report "$E" "make $N" "такой цели нет ни в одном Makefile"
97
+ done
98
+ for N in $(printf '%s\n' "$CODE" | grep -oE '(^|[^A-Za-z0-9_-])just[[:space:]]+[A-Za-z][A-Za-z0-9_-]*' \
99
+ | sed 's/.*[[:space:]]//' | sort -u); do
100
+ has "$N" "$RECIPES" || report "$E" "just $N" "такого рецепта нет в justfile"
101
+ done
102
+ done <<EOF
103
+ $ENTRIES
104
+ EOF
105
+
106
+ if [ "$MISS" = 1 ]; then
107
+ echo " почини: верни команду в проект или исправь свод. Агент берёт команды из свода дословно:"
108
+ echo " несуществующая команда — это «Missing script» у агента и «проверил» про проверку, которой нет."
109
+ fi
110
+ exit "$MISS"
@@ -0,0 +1,19 @@
1
+ # Запись каталога AQK. Норма и все поля — kit/gates/README.md.
2
+ # Читается программой; всё, что нельзя выполнить, живёт в README.md рядом.
3
+
4
+ intent: команды, которые свод велит запускать, существуют в проекте
5
+ intent_en: commands the agent rules tell it to run actually exist in the project
6
+
7
+ # Только там, где агенту вообще пишут свод. Проекту без AGENTS.md/CLAUDE.md сверять нечего.
8
+ trigger:
9
+ has_agent_entry: true
10
+
11
+ # Переносимая проверка — единственный рецепт. Готовое правило есть у agentlint
12
+ # (`cmd-cross-reference`), но оно знает только npm-скрипты; на живом проекте, где свод называет
13
+ # шестнадцать целей make, оно молчит по построению. Разбор — research/competitors/agentlint.md.
14
+ recipes:
15
+ any: bash {gate}/check.sh {dir}
16
+
17
+ proof: incidents/README.md, 2026-09-11 «свод велит команду, которой в проекте больше нет» —
18
+ 9 из 99 публичных репозиториев с AGENTS.md называют скрипт, которого нет ни в одном
19
+ package.json; в двух история показывает, как: скрипт удалён, строка в своде осталась
@@ -0,0 +1,13 @@
1
+ # Rules
2
+
3
+ Make sure the tree is clean, then build and check before every commit:
4
+
5
+ ```sh
6
+ npm run build
7
+ npm run debug # build + install + logs
8
+ make test
9
+ make -j4 lint
10
+ just fmt
11
+ ```
12
+
13
+ Scripts named like `npm run gen:*` are generated; run `make <target>` for anything else.
@@ -0,0 +1,6 @@
1
+ .PHONY: test lint
2
+ VERSION := 1.0
3
+ test:
4
+ node --test
5
+ lint: test
6
+ echo ok
@@ -0,0 +1,2 @@
1
+ fmt:
2
+ echo fmt
@@ -0,0 +1,10 @@
1
+ {
2
+ "name": "sample",
3
+ "scripts": {
4
+ "build": "tsc",
5
+ "debug": "npm run build && node dist/index.js"
6
+ },
7
+ "dependencies": {
8
+ "debug": "4.3.4"
9
+ }
10
+ }
@@ -0,0 +1,9 @@
1
+ # Rules
2
+
3
+ Build and check before every commit:
4
+
5
+ ```sh
6
+ npm run build
7
+ npm run debug # build + install + logs
8
+ make deploy
9
+ ```
@@ -0,0 +1,2 @@
1
+ test:
2
+ node --test