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