agent-quality-kit 0.4.2 → 0.5.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 +101 -12
- package/README.ru.md +101 -11
- package/kit/gates/README.md +15 -0
- package/kit/gates/_native.sh +18 -2
- package/kit/gates/_skip.sh +18 -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/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/ratchet/ratchet.sh +9 -2
- package/llms.txt +57 -0
- package/package.json +2 -1
- package/tool/commands/doctor.mjs +62 -7
- package/tool/commands/gates.mjs +9 -2
- package/tool/commands/project.mjs +7 -4
- package/tool/commands/report.mjs +2 -2
- package/tool/i18n/en.mjs +38 -0
- package/tool/i18n/ru.mjs +43 -0
- package/tool/lib/baseline.mjs +87 -0
- package/tool/lib/core.mjs +10 -2
- package/tool/lib/manifest.mjs +33 -2
- package/tool/lib/repo.mjs +7 -0
- package/tool/selfcheck/gates.sh +9 -2
- package/tool/selfcheck/mutation.sh +95 -0
- package/tool/selfcheck/smoke.sh +216 -8
- package/tool/selfcheck/syntax.sh +9 -1
- package/tool/selfcheck/units.mjs +76 -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
20
|
```
|
|
21
21
|
|
|
22
|
-
`doctor` only reads
|
|
23
|
-
|
|
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
|
|
24
40
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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
|
|
48
|
+
```
|
|
49
|
+
|
|
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).
|
|
55
|
+
|
|
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.5.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
|
```
|
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
|
+
### Работает с любым агентом и любым языком
|
|
27
|
+
|
|
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
|
+
### Одно движение, из которого следует всё остальное
|
|
23
41
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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.5.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
|
```
|
package/kit/gates/README.md
CHANGED
|
@@ -113,6 +113,20 @@ samples_for: python
|
|
|
113
113
|
если завтра поправят источник?» Правильный ответ — «результат изменится сам», а не «нужно
|
|
114
114
|
поправить ещё и здесь».
|
|
115
115
|
|
|
116
|
+
Седьмой, с прогона по живому проекту: **гейт, тонущий в собственном шуме.** Родной инструмент
|
|
117
|
+
не получал общий список исключений и читал `.aqk/` — каталог, который положил сам комплект. Из
|
|
118
|
+
1846 находок 435 были не про код вовсе. Гейт остаётся правильным и становится нечитаемым, а
|
|
119
|
+
нечитаемый выключают целиком. Мера здесь — доля находок не по делу, а не длина вывода.
|
|
120
|
+
|
|
121
|
+
Восьмой, оттуда же: **родной и переносимый рецепты одной записи мерят разное.** Переносимый
|
|
122
|
+
смотрел только в расширения кода, родной — во всё подряд. Один гейт означал разное на разных
|
|
123
|
+
машинах, и какое именно — зависело от того, что стоит в системе.
|
|
124
|
+
|
|
125
|
+
Девятый, найденный мутационной проверкой: **гейт держится за то, чего не заявляет.** Четыре
|
|
126
|
+
записи переставали работать на windows-переносах строк — жадный захват в `sed` проглатывал
|
|
127
|
+
`\r`. Красный образец зеленел, зелёный краснел. Пара образцов этого не видит: она доказывает
|
|
128
|
+
одно срабатывание, а не класс. Ловит `tool/selfcheck/mutation.sh`.
|
|
129
|
+
|
|
116
130
|
Зелёный образец закрывает третий способ, и он важнее красного: что гейт ловит брак, проверяют
|
|
117
131
|
при установке; что он молчит на исправном коде — не проверяют почти никогда.
|
|
118
132
|
|
|
@@ -159,6 +173,7 @@ samples_for: python
|
|
|
159
173
|
| `has_deps: true` | есть файл зависимостей |
|
|
160
174
|
| `has_tests: true` | есть каталог тестов или файлы вида `*_test.*` |
|
|
161
175
|
| `has_env: true` | есть файл окружения |
|
|
176
|
+
| `has_ui: true` | есть стили или однофайловые компоненты (`.css`, `.scss`, `.vue`, `.svelte`, `.astro`) |
|
|
162
177
|
|
|
163
178
|
Любое из `has_*` принимает и `false` — «показывать тем, у кого этого нет». Условие, которого
|
|
164
179
|
программа не знает, отклоняется явно, а не пропускается молча.
|
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,52 @@
|
|
|
1
|
+
# Цвет приходит из токена темы, а не литералом в компоненте
|
|
2
|
+
|
|
3
|
+
**Намерение.** Литеральный цвет тема не перекрашивает. В проекте с несколькими темами экран,
|
|
4
|
+
свёрстанный литералами, в тёмной теме остаётся светлым пятном — и видит это не автор, а
|
|
5
|
+
пользователь, который тему переключил. Дефект тихий по построению: в теме автора всё правильно.
|
|
6
|
+
|
|
7
|
+
**Какой отказ это поймало.** Живой проект на React с двенадцатью темами. Запрет на сырой цвет
|
|
8
|
+
там оказался проверкой №8 из семнадцати в собственной оснастке — и завели его не из вкуса, а
|
|
9
|
+
после экранов, не переживших переключение темы. Запись в журнале: `incidents/README.md`,
|
|
10
|
+
2026-09-06, там же измерено соотношение, ради которого запись переносили в каталог: из тридцати
|
|
11
|
+
с лишним запретов интерфейса машина ловила одиннадцать, остальные держались на глазах человека.
|
|
12
|
+
|
|
13
|
+
**Файл-источник значений называет проект.** Где-то литерал обязан быть — иначе токену неоткуда
|
|
14
|
+
взяться. По умолчанию проверка пропускает файлы, чьё имя содержит `tokens`, `theme`, `palette`,
|
|
15
|
+
`colors`, `variables`, `design-system`. Своё имя проект называет сам, рядом с командой в
|
|
16
|
+
манифесте — тем же приёмом, что каталоги с законной печатью в `no-print-in-prod`:
|
|
17
|
+
|
|
18
|
+
```yaml
|
|
19
|
+
color-from-token: "AQK_COLOR_SOURCE='tokens|brand' bash .aqk/gates/color-from-token/check.sh ."
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
**Готовый аналог есть, но он покрывает меньше, и поэтому его здесь нет.** У `stylelint` есть
|
|
23
|
+
правило `color-no-hex`, и для файлов стилей оно точнее нашего поиска. Но оно видит только стили,
|
|
24
|
+
а литерал в компоненте — `style={{ background: "#2563EB" }}`, `className="bg-[#2563EB]"` —
|
|
25
|
+
остаётся вне его. Родной рецепт покрывал бы половину предмета, и запись означала бы разное в
|
|
26
|
+
зависимости от того, что стоит в системе. Урок этого же дня: родной и переносимый рецепты одной
|
|
27
|
+
записи обязаны мерить одно и то же. Поэтому рецепт один, переносимый; кому нужна точность по
|
|
28
|
+
стилям — ставит `stylelint` отдельно, это не отменяет запись.
|
|
29
|
+
|
|
30
|
+
**Замер на живом проекте.** React, двенадцать тем, 2750 файлов кода. Первый прогон дал 20
|
|
31
|
+
находок, и **все 20 были чужими** — литералы в отдельных html-документах внутри `.claude/`:
|
|
32
|
+
отчёты и справочники оснастки агента. Отдельный html-документ темы не имеет и иметь не может,
|
|
33
|
+
литерал там законен; `.html` убран из списка расширений — не на вкус, а по этому замеру.
|
|
34
|
+
Второй прогон: одна находка во фронтенде — палитра диаграммы загрузки, где цвета треков вписаны
|
|
35
|
+
литералами Tailwind-500 и потому не меняются вместе с темой. Ложных срабатываний: **одно**, и
|
|
36
|
+
оно тоже исправлено — цвет, упомянутый внутри многострочного комментария, читался как находка.
|
|
37
|
+
|
|
38
|
+
**Чего НЕ ловит.**
|
|
39
|
+
|
|
40
|
+
- Цвет, собранный по частям: `"#" + hex`, `rgb(37, 99, 235)`, `hsl(...)`, именованные `red` и
|
|
41
|
+
`white`. Проверка ищет литерал в форме `#rgb`, `#rrggbb`, `#rrggbbaa`, а не «цвет вообще».
|
|
42
|
+
- Селектор по идентификатору, записанный шестнадцатеричными буквами (`#abcdef { … }`), и якорь
|
|
43
|
+
в ссылке той же формы читаются как цвет. Это цена простоты; лечится именем в allowlist.
|
|
44
|
+
- Литерал в разметке и в документации: расширений `.md` и `.html` в списке нет намеренно.
|
|
45
|
+
В разметке цвет упоминают, а не применяют; отдельный html-документ не имеет темы, и литерал
|
|
46
|
+
в нём законен — как печать в скрипте для `no-print-in-prod`.
|
|
47
|
+
- Цвет, вычисленный в рантайме: `lighten(base, 0.2)`, значение из ответа сервера, инлайн-стиль,
|
|
48
|
+
собранный из переменной. Проверка читает исходник, а не то, что окажется в браузере.
|
|
49
|
+
|
|
50
|
+
**Образцы.** `red/` — три стека, где цвет вписан литералом: React со `style`, обычный CSS,
|
|
51
|
+
однофайловый компонент Vue. `green/` — то же самое через токены, плюс `tokens.css`, где литерал
|
|
52
|
+
законен, и заметка в разметке, на которой проверка обязана молчать.
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
#!/usr/bin/env sh
|
|
2
|
+
# Цвет приходит из токена темы, а не из литерала в компоненте.
|
|
3
|
+
#
|
|
4
|
+
# ЗАЧЕМ. Литеральный цвет тема не перекрашивает. Проект с несколькими темами получает экран,
|
|
5
|
+
# который в тёмной теме остаётся светлым пятном, — и это видно не автору, а пользователю,
|
|
6
|
+
# который переключил тему. Дефект тихий: в теме автора всё выглядит правильно.
|
|
7
|
+
DIR="${1:-.}"
|
|
8
|
+
. "$(dirname "$0")/../_skip.sh" 2>/dev/null || SKIP_NAMES=".git .aqk node_modules .venv"
|
|
9
|
+
|
|
10
|
+
# Расширения, где живёт ТЕМИЗИРУЕМЫЙ интерфейс. Своё, а не общий CODE_EXT: там нет ни css, ни
|
|
11
|
+
# vue — они не код в смысле «отладочная печать», но именно в них живёт цвет.
|
|
12
|
+
#
|
|
13
|
+
# `.html` в списке НЕТ, и это измерено, а не выбрано на вкус. Первый прогон по живому проекту
|
|
14
|
+
# (React, 12 тем) дал 20 находок, и ВСЕ 20 — в отдельных html-документах внутри `.claude/`:
|
|
15
|
+
# отчёты и справочники оснастки агента. Во фронтенде — ноль. Отдельный html-документ не имеет
|
|
16
|
+
# темы и не может её иметь: литерал там законен, как печать в скрипте. Гейт, на 100%
|
|
17
|
+
# состоящий из чужих находок, выключают в первый день — см. шапку `_skip.sh`.
|
|
18
|
+
COLOR_EXT="css scss sass less styl ts tsx js jsx mjs cjs vue svelte astro"
|
|
19
|
+
|
|
20
|
+
# Файлы-ИСТОЧНИКИ значений: там литерал законен, иначе токену неоткуда взяться. Имя источника
|
|
21
|
+
# проект называет сам — как и каталоги с законной печатью в no-print-in-prod. По умолчанию
|
|
22
|
+
# общепринятые имена; свои задаются переменной, рядом с командой в манифесте.
|
|
23
|
+
SOURCE_RE="${AQK_COLOR_SOURCE:-tokens|theme|themes|palette|colors|colours|variables|design-system}"
|
|
24
|
+
|
|
25
|
+
EXT_RE="$(printf '%s' "$COLOR_EXT" | tr ' ' '|')"
|
|
26
|
+
|
|
27
|
+
# shellcheck disable=SC2086
|
|
28
|
+
HITS=$(find "$DIR" $(skip_find "$DIR") -type f -print 2>/dev/null \
|
|
29
|
+
| grep -E "\.($EXT_RE)$" \
|
|
30
|
+
| grep -viE "(^|/)[^/]*($SOURCE_RE)[^/]*\.($EXT_RE)$" \
|
|
31
|
+
| while IFS= read -r F; do is_generated "$F" || printf '%s\n' "$F"; done \
|
|
32
|
+
| LC_ALL=C sort \
|
|
33
|
+
| xargs -r awk '
|
|
34
|
+
# Блочные комментарии вырезаются ПО СОСТОЯНИЮ, а не построчно. Однострочный фильтр
|
|
35
|
+
# смотрит только на начало строки, и цвет, упомянутый в середине многострочного
|
|
36
|
+
# пояснения, читался как находка: на живом проекте так и вышло — единственное
|
|
37
|
+
# ложное срабатывание пришло из строки внутри /* … */. Тот же класс, что запись
|
|
38
|
+
# журнала «печать внутри комментария считалась печатью».
|
|
39
|
+
FNR == 1 { inblock = 0 }
|
|
40
|
+
{
|
|
41
|
+
line = $0
|
|
42
|
+
if (inblock) {
|
|
43
|
+
i = index(line, "*/")
|
|
44
|
+
if (i == 0) next
|
|
45
|
+
line = substr(line, i + 2); inblock = 0
|
|
46
|
+
}
|
|
47
|
+
# Вырезаем закрытые /* … */ внутри строки, затем открытый хвост.
|
|
48
|
+
while ((s = index(line, "/*")) > 0) {
|
|
49
|
+
rest = substr(line, s + 2)
|
|
50
|
+
e = index(rest, "*/")
|
|
51
|
+
if (e == 0) { line = substr(line, 1, s - 1); inblock = 1; break }
|
|
52
|
+
line = substr(line, 1, s - 1) substr(rest, e + 2)
|
|
53
|
+
}
|
|
54
|
+
if (line ~ /#[0-9a-fA-F]{8}([^0-9a-fA-F]|$)|#[0-9a-fA-F]{6}([^0-9a-fA-F]|$)|#[0-9a-fA-F]{3}([^0-9a-fA-F]|$)/)
|
|
55
|
+
print FILENAME ":" FNR ":" $0
|
|
56
|
+
}
|
|
57
|
+
' 2>/dev/null \
|
|
58
|
+
| drop_comments \
|
|
59
|
+
| own_samples_filter "$DIR")
|
|
60
|
+
|
|
61
|
+
if [ -n "$HITS" ]; then
|
|
62
|
+
printf '%s\n' "$HITS" | head -20
|
|
63
|
+
N=$(printf '%s\n' "$HITS" | grep -c .); [ "$N" -gt 20 ] && echo " … и ещё $((N - 20))"
|
|
64
|
+
echo " почини: замени литерал на токен темы — var(--color-…), theme('colors.…'), \$color-…"
|
|
65
|
+
echo " литеральный цвет тема не перекрашивает: в другой теме экран останется чужим пятном."
|
|
66
|
+
echo " файл-источник значений называется сам: AQK_COLOR_SOURCE='tokens|brand' перед командой."
|
|
67
|
+
exit 1
|
|
68
|
+
fi
|
|
69
|
+
exit 0
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
intent: цвет приходит из токена темы, а не литералом в компоненте
|
|
2
|
+
intent_en: colour comes from a theme token, not a literal in the component
|
|
3
|
+
|
|
4
|
+
# Не по языку, а по наличию интерфейса. Триггер `langs: javascript, typescript` показывал бы
|
|
5
|
+
# запись каждому бэкенду, библиотеке и утилите командной строки на JS — а их большинство, и
|
|
6
|
+
# интерфейса там нет. Записи, показанной не тому, не верят, и это стоит доверия всему каталогу.
|
|
7
|
+
trigger:
|
|
8
|
+
has_ui: true
|
|
9
|
+
|
|
10
|
+
recipes:
|
|
11
|
+
any: bash {gate}/check.sh {dir}
|
|
12
|
+
|
|
13
|
+
proof: incidents/README.md, 2026-09-06 «из тридцати с лишним запретов интерфейса гейт ловил
|
|
14
|
+
одиннадцать» — прогон по audit_project (Django + React, 12 тем): проверка №8 в собственной
|
|
15
|
+
оснастке проекта родилась после экранов, остававшихся светлыми в тёмной теме
|
|
@@ -26,7 +26,30 @@ else
|
|
|
26
26
|
exit 0
|
|
27
27
|
fi
|
|
28
28
|
|
|
29
|
-
|
|
29
|
+
# Сообщение слияния сочиняет не автор, а инструмент. GitHub делает это дважды и по-разному:
|
|
30
|
+
# при разборе предложения изменений выкладывает синтетический коммит «Merge <sha> into <sha>»,
|
|
31
|
+
# а кнопка Merge на сайте пишет «Merge pull request #N from …». Первую форму гейт научился
|
|
32
|
+
# различать вчера по словам — и на второй покраснел бы снова.
|
|
33
|
+
#
|
|
34
|
+
# Поэтому смотрим не на слова, а на форму: у слияния два родителя. Если при этом в сообщении
|
|
35
|
+
# нет разделов отчёта — его писал инструмент, и спрашивать надо второго родителя, последний
|
|
36
|
+
# коммит автора. Слияние, которое автор описал сам, проходит по HEAD и ничего не теряет.
|
|
37
|
+
REF=HEAD
|
|
38
|
+
PARENTS=$(cd "$DIR" && git rev-list --parents -n 1 HEAD 2>/dev/null | wc -w)
|
|
39
|
+
HEADMSG=$(cd "$DIR" && git log -1 --format=%B 2>/dev/null)
|
|
40
|
+
if [ "${PARENTS:-0}" -ge 3 ] &&
|
|
41
|
+
! printf '%s\n' "$HEADMSG" | grep -q "^Сделано:" &&
|
|
42
|
+
! printf '%s\n' "$HEADMSG" | grep -q "^Не уверен:"; then
|
|
43
|
+
if (cd "$DIR" && git rev-parse -q --verify HEAD^2 >/dev/null 2>&1); then
|
|
44
|
+
REF=HEAD^2
|
|
45
|
+
else
|
|
46
|
+
echo "слияние без отчёта, второго родителя не видно — проверка пропущена"
|
|
47
|
+
echo " дай конвейеру два коммита истории: actions/checkout@v4 с fetch-depth: 2"
|
|
48
|
+
exit 0
|
|
49
|
+
fi
|
|
50
|
+
fi
|
|
51
|
+
|
|
52
|
+
MSG=$(cd "$DIR" && git log -1 --format=%B "$REF" 2>/dev/null)
|
|
30
53
|
|
|
31
54
|
# Коммит, который трогает только журнал, отчёта в теле не требует: сама запись и есть отчёт,
|
|
32
55
|
# причём подробнее — и её сторожит `lesson-has-outcome`. Иначе гейт воюет с командой `note`,
|
|
@@ -38,7 +61,7 @@ else
|
|
|
38
61
|
[ -z "$LESSONS" ] && LESSONS="incidents"
|
|
39
62
|
case "$LESSONS" in http*) LESSONS="" ;; esac # journal по адресу, а не путём — не применимо
|
|
40
63
|
if [ -n "$LESSONS" ]; then
|
|
41
|
-
FILES=$(cd "$DIR" && git show --pretty=format: --name-only
|
|
64
|
+
FILES=$(cd "$DIR" && git show --pretty=format: --name-only "$REF" 2>/dev/null | grep -v '^$')
|
|
42
65
|
if [ -n "$FILES" ]; then
|
|
43
66
|
OUTSIDE=$(printf '%s\n' "$FILES" | grep -v "^$LESSONS/" | grep -v "^$LESSONS\$")
|
|
44
67
|
if [ -z "$OUTSIDE" ]; then
|