agent-quality-kit 0.11.0 → 0.12.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 +4 -2
- package/README.ru.md +4 -2
- package/kit/gates/complexity-limit/gate.yml +5 -0
- package/kit/gates/dead-code/gate.yml +5 -0
- package/kit/gates/duplicate-code/gate.yml +5 -0
- package/kit/gates/no-print-in-prod/gate.yml +5 -0
- package/kit/gates/secrets-not-in-code/gate.yml +20 -0
- package/kit/gates/swallowed-error/gate.yml +5 -0
- package/kit/gates/todo-without-task/gate.yml +5 -0
- package/llms.txt +1 -1
- package/package.json +1 -1
- package/tool/commands/context.mjs +3 -1
- package/tool/commands/doctor.mjs +77 -5
- package/tool/commands/gates.mjs +3 -2
- package/tool/commands/probe.mjs +89 -5
- package/tool/commands/vitals.mjs +6 -1
- package/tool/i18n/en-docs.mjs +2 -1
- package/tool/i18n/en-gates.mjs +4 -0
- package/tool/i18n/en.mjs +13 -0
- package/tool/i18n/ru-docs.mjs +2 -1
- package/tool/i18n/ru-gates.mjs +4 -0
- package/tool/i18n/ru.mjs +13 -0
- package/tool/lib/adopt.mjs +98 -0
- package/tool/lib/cadence.mjs +36 -1
- package/tool/lib/execution.mjs +50 -1
- package/tool/lib/history.mjs +14 -4
- package/tool/lib/prove.mjs +2 -2
- package/tool/lib/repo.mjs +47 -0
- package/tool/lib/run.mjs +32 -3
- package/tool/selfcheck/gates.sh +12 -2
- package/tool/selfcheck/units-cadence.mjs +37 -1
- package/tool/selfcheck/units-execution.mjs +78 -1
- package/tool/selfcheck/units-level.mjs +24 -0
- package/tool/selfcheck/units-probe.mjs +104 -1
- package/tool/selfcheck/units-repo.mjs +123 -1
- package/tool/selfcheck/units.mjs +34 -1
package/README.md
CHANGED
|
@@ -46,6 +46,8 @@ Go, Rust, Java, Ruby, PHP, C#, Kotlin, Swift, Scala. Where the project already h
|
|
|
46
46
|
says so out loud when it falls back.
|
|
47
47
|
|
|
48
48
|
**Requirements:** Node 18+ and an `sh` shell. Present on macOS, Linux and WSL; Git Bash on Windows.
|
|
49
|
+
On Windows the gates run through Git Bash even when `bash` on your PATH is the WSL stub in
|
|
50
|
+
`System32` — it is found next to `git.exe`; set `AQK_BASH` to point elsewhere.
|
|
49
51
|
|
|
50
52
|
### One movement, and everything follows from it
|
|
51
53
|
|
|
@@ -345,7 +347,7 @@ Already using [pre-commit](https://pre-commit.com)? Three lines in the file you
|
|
|
345
347
|
```yaml
|
|
346
348
|
repos:
|
|
347
349
|
- repo: https://github.com/arsen-ask-lx/Agent_Quality_Kit
|
|
348
|
-
rev: v0.
|
|
350
|
+
rev: v0.12.0
|
|
349
351
|
hooks:
|
|
350
352
|
- id: aqk # runs what the repository declares; blocks below AQK-1
|
|
351
353
|
# - id: aqk-doctor # read-only: the level and what is missing, blocks nothing
|
|
@@ -366,7 +368,7 @@ layer AQK adds.
|
|
|
366
368
|
[](https://github.com/marketplace/actions/agent-quality-kit-aqk)
|
|
367
369
|
|
|
368
370
|
```yaml
|
|
369
|
-
- uses: arsen-ask-lx/Agent_Quality_Kit@v0.
|
|
371
|
+
- uses: arsen-ask-lx/Agent_Quality_Kit@v0.12.0
|
|
370
372
|
with:
|
|
371
373
|
min: 1 # the build fails below AQK-1, or if any declared gate failed
|
|
372
374
|
```
|
package/README.ru.md
CHANGED
|
@@ -48,6 +48,8 @@ TypeScript, Go, Rust, Java, Ruby, PHP, C#, Kotlin, Swift, Scala. Там, где
|
|
|
48
48
|
сообщает, когда откатилась на переносимый.
|
|
49
49
|
|
|
50
50
|
**Требуется:** Node 18+ и оболочка `sh`. Есть на macOS, Linux и в WSL; на Windows — Git Bash.
|
|
51
|
+
На Windows гейты идут через Git Bash, даже если `bash` в PATH — заглушка WSL из `System32`:
|
|
52
|
+
он ищется рядом с `git.exe`; другой путь задаётся `AQK_BASH`.
|
|
51
53
|
|
|
52
54
|
### Одно движение, из которого следует всё остальное
|
|
53
55
|
|
|
@@ -348,7 +350,7 @@ aqk badge --check # в конвейере: код 1 в тот день, ког
|
|
|
348
350
|
```yaml
|
|
349
351
|
repos:
|
|
350
352
|
- repo: https://github.com/arsen-ask-lx/Agent_Quality_Kit
|
|
351
|
-
rev: v0.
|
|
353
|
+
rev: v0.12.0
|
|
352
354
|
hooks:
|
|
353
355
|
- id: aqk # запускает объявленное; роняет коммит ниже AQK-1
|
|
354
356
|
# - id: aqk-doctor # только осмотр: уровень и чего не хватает, ничего не роняет
|
|
@@ -367,7 +369,7 @@ repos:
|
|
|
367
369
|
[](https://github.com/marketplace/actions/agent-quality-kit-aqk)
|
|
368
370
|
|
|
369
371
|
```yaml
|
|
370
|
-
- uses: arsen-ask-lx/Agent_Quality_Kit@v0.
|
|
372
|
+
- uses: arsen-ask-lx/Agent_Quality_Kit@v0.12.0
|
|
371
373
|
with:
|
|
372
374
|
min: 1 # сборка падает ниже AQK-1 или если упал любой объявленный гейт
|
|
373
375
|
```
|
|
@@ -4,6 +4,11 @@ intent_en: a function does not grow past the complexity you can hold in your hea
|
|
|
4
4
|
trigger:
|
|
5
5
|
always: true
|
|
6
6
|
|
|
7
|
+
# Домашние страницы готовых инструментов, которые зовут рецепты ниже. Адреса сверены
|
|
8
|
+
# по реестру github 2026-09-10, а не написаны по памяти: три выдуманных адреса чужих
|
|
9
|
+
# репозиториев — записанная шишка комплекта.
|
|
10
|
+
tool: https://github.com/astral-sh/ruff · https://github.com/eslint/eslint
|
|
11
|
+
|
|
7
12
|
recipes:
|
|
8
13
|
# Переносимая мера грубая — глубина вложенности. Готовые правила считают ветвления и
|
|
9
14
|
# инструкции, это точнее; см. kit/docs/ready-made-rules.md
|
|
@@ -10,6 +10,11 @@ trigger:
|
|
|
10
10
|
# Переносимого рецепта здесь нет намеренно: чтобы понять, вызывают ли функцию, нужен граф
|
|
11
11
|
# вызовов, а не поиск по тексту. Это тот случай, когда без готового инструмента не обойтись —
|
|
12
12
|
# и он есть под каждый распространённый стек.
|
|
13
|
+
# Домашние страницы готовых инструментов, которые зовут рецепты ниже. Адреса сверены
|
|
14
|
+
# по реестру github 2026-09-10, а не написаны по памяти: три выдуманных адреса чужих
|
|
15
|
+
# репозиториев — записанная шишка комплекта.
|
|
16
|
+
tool: https://github.com/jendrikseipp/vulture · https://github.com/webpro-nl/knip
|
|
17
|
+
|
|
13
18
|
recipes:
|
|
14
19
|
python: vulture --min-confidence 60 {dir}
|
|
15
20
|
typescript: npx --yes knip@6 --directory {dir}
|
|
@@ -4,6 +4,11 @@ intent_en: the same code does not multiply into copies across the project
|
|
|
4
4
|
trigger:
|
|
5
5
|
files_gt: 20
|
|
6
6
|
|
|
7
|
+
# Домашние страницы готовых инструментов, которые зовут рецепты ниже. Адреса сверены
|
|
8
|
+
# по реестру github 2026-09-10, а не написаны по памяти: три выдуманных адреса чужих
|
|
9
|
+
# репозиториев — записанная шишка комплекта.
|
|
10
|
+
tool: https://github.com/kucherenko/jscpd · https://github.com/pylint-dev/pylint
|
|
11
|
+
|
|
7
12
|
recipes:
|
|
8
13
|
any: bash {gate}/check.sh {dir}
|
|
9
14
|
# jscpd умеет и Python, и TypeScript одним прогоном и считает похожесть, а не совпадение.
|
|
@@ -11,6 +11,11 @@ trigger:
|
|
|
11
11
|
# печать в примерах документации, в тестах, в бенчмарках и в выводе командной строки.
|
|
12
12
|
# Отличить их от отладки можно только разбором кода, а не поиском по тексту, — и это
|
|
13
13
|
# ровно то, что уже делают ruff и eslint.
|
|
14
|
+
# Домашние страницы готовых инструментов, которые зовут рецепты ниже. Адреса сверены
|
|
15
|
+
# по реестру github 2026-09-10, а не написаны по памяти: три выдуманных адреса чужих
|
|
16
|
+
# репозиториев — записанная шишка комплекта.
|
|
17
|
+
tool: https://github.com/astral-sh/ruff · https://github.com/eslint/eslint
|
|
18
|
+
|
|
14
19
|
recipes:
|
|
15
20
|
python: ruff check -q --output-format=concise --select T20 {dir}
|
|
16
21
|
typescript: eslint --no-config-lookup --ignore-pattern 'gates/*/red/**' --ignore-pattern 'gates/*/green/**' --rule '{"no-console":"error"}' {dir}
|
|
@@ -4,7 +4,27 @@ intent_en: keys, passwords and private keys do not end up in the code
|
|
|
4
4
|
trigger:
|
|
5
5
|
always: true
|
|
6
6
|
|
|
7
|
+
# Домашние страницы готовых инструментов, которые зовут рецепты ниже. Адреса сверены
|
|
8
|
+
# по реестру github 2026-09-10, а не написаны по памяти: три выдуманных адреса чужих
|
|
9
|
+
# репозиториев — записанная шишка комплекта.
|
|
10
|
+
tool: https://github.com/gitleaks/gitleaks
|
|
11
|
+
|
|
7
12
|
recipes:
|
|
13
|
+
# Готовый аналог сильнее нашего, и об этом прямо сказано в README записи: `gitleaks` знает
|
|
14
|
+
# сотни форматов токенов и умеет читать историю, а не только рабочее дерево. Наша проверка —
|
|
15
|
+
# запасная, для тех, кто не хочет ставить лишний бинарь.
|
|
16
|
+
#
|
|
17
|
+
# Ключ `native`, а не язык: секреты ищутся в любом файле, и раскладывать одну команду по
|
|
18
|
+
# восьми языкам значило бы завести ровно тот повтор, против которого у нас есть гейт.
|
|
19
|
+
#
|
|
20
|
+
# `dir` — рабочее дерево; чтение истории (`gitleaks git`) сюда не берём: гейт обязан отвечать
|
|
21
|
+
# про ТЕКУЩЕЕ состояние, а прошлое лечится не проверкой, а ротацией ключа.
|
|
22
|
+
#
|
|
23
|
+
# ОСТОРОЖНО С КОДОМ ВОЗВРАТА: у `gitleaks` 1 означает «нашёл ЛИБО сломался» — по документации,
|
|
24
|
+
# проверено 2026-09-10. То есть для него «находка» и «сбой инструмента» неразличимы, и это
|
|
25
|
+
# ровно тот класс, который мы разделяем у себя. Пока принимаем как есть и говорим об этом
|
|
26
|
+
# вслух, а не делаем вид, что кодов три.
|
|
27
|
+
native: gitleaks dir --no-banner {dir}
|
|
8
28
|
any: bash {gate}/check.sh {dir}
|
|
9
29
|
|
|
10
30
|
proof: kit/docs/ai/project-baseline.md, пункт 5 — «секреты не хранятся в коде вообще: ни в истории, ни в примерах, ни в тестах»
|
|
@@ -7,6 +7,11 @@ intent_en: an error is not silently swallowed — it is handled and logged, or r
|
|
|
7
7
|
trigger:
|
|
8
8
|
langs: python, javascript, typescript, go
|
|
9
9
|
|
|
10
|
+
# Домашние страницы готовых инструментов, которые зовут рецепты ниже. Адреса сверены
|
|
11
|
+
# по реестру github 2026-09-10, а не написаны по памяти: три выдуманных адреса чужих
|
|
12
|
+
# репозиториев — записанная шишка комплекта.
|
|
13
|
+
tool: https://github.com/astral-sh/ruff · https://github.com/eslint/eslint · https://github.com/kisielk/errcheck
|
|
14
|
+
|
|
10
15
|
recipes:
|
|
11
16
|
# BLE — ловля голого исключения, TRY400 — запись без трейса, SIM105 — перехват ради тишины.
|
|
12
17
|
python: ruff check -q --output-format=concise --select BLE,TRY400,SIM105 {dir}
|
|
@@ -4,6 +4,11 @@ intent_en: "fix later" markers are absent from finished code — a filed task re
|
|
|
4
4
|
trigger:
|
|
5
5
|
always: true
|
|
6
6
|
|
|
7
|
+
# Домашние страницы готовых инструментов, которые зовут рецепты ниже. Адреса сверены
|
|
8
|
+
# по реестру github 2026-09-10, а не написаны по памяти: три выдуманных адреса чужих
|
|
9
|
+
# репозиториев — записанная шишка комплекта.
|
|
10
|
+
tool: https://github.com/astral-sh/ruff · https://github.com/eslint/eslint
|
|
11
|
+
|
|
7
12
|
recipes:
|
|
8
13
|
any: bash {gate}/check.sh {dir}
|
|
9
14
|
# Готовое правило точнее самописного и не требует поддержки.
|
package/llms.txt
CHANGED
|
@@ -70,7 +70,7 @@ Zero runtime dependencies. Node 18+ and an `sh` shell. MIT.
|
|
|
70
70
|
the files `init` writes are owned by root, so you cannot edit your own manifest. Debian-based
|
|
71
71
|
on purpose: the gates are `sh`, `grep`, `awk`, `find` — under alpine's busybox they behave
|
|
72
72
|
differently, and an image where the gates behave differently is worse than no image
|
|
73
|
-
- As a GitHub Action: `uses: arsen-ask-lx/Agent_Quality_Kit@v0.
|
|
73
|
+
- As a GitHub Action: `uses: arsen-ask-lx/Agent_Quality_Kit@v0.12.0` with `min: 1`
|
|
74
74
|
(https://github.com/marketplace/actions/agent-quality-kit-aqk)
|
|
75
75
|
|
|
76
76
|
## What makes it different
|
package/package.json
CHANGED
|
@@ -80,7 +80,9 @@ function contextBlock(state, T = L.context) {
|
|
|
80
80
|
if (pr.state === "never") out.push(T.probeNever);
|
|
81
81
|
else if (pr.state === "off") out.push(T.probeOff);
|
|
82
82
|
else if (pr.state === "unknown") out.push(T.probeUnknown);
|
|
83
|
-
|
|
83
|
+
// Имена — агенту они нужнее числа: «один класс» не говорит, какой файл трогать осторожно.
|
|
84
|
+
else if (pr.blind > 0) out.push(T.probeBlind(pr.blind, pr.state === "stale" ? pr.behind : 0,
|
|
85
|
+
(pr.classes || []).map((b) => `${b.slug} (${b.file})`).join(", ")));
|
|
84
86
|
else out.push(T.probeClean(pr.state === "stale" ? pr.behind : 0));
|
|
85
87
|
}
|
|
86
88
|
|
package/tool/commands/doctor.mjs
CHANGED
|
@@ -5,15 +5,16 @@ import { join, resolve } from "node:path";
|
|
|
5
5
|
import { spawnSync } from "node:child_process";
|
|
6
6
|
import { scopeOutput, splitAdvice, changedFiles } from "../lib/scope.mjs";
|
|
7
7
|
import { CWD, PKG_ROOT, TARGET_DIR, MANIFEST, SELF, c, exists, die } from "../lib/core.mjs";
|
|
8
|
-
import { cmdProbe, probeStatus } from "./probe.mjs";
|
|
8
|
+
import { cmdProbe, probeStatus, blindAdvice } from "./probe.mjs";
|
|
9
9
|
import { readManifest, assessLevel, unknownKeys, KNOWN_KEYS, advisorySet, layoutChecks, coversOf, coversUnproven, unparsedLines } from "../lib/manifest.mjs";
|
|
10
10
|
import { proveGates } from "../lib/prove.mjs";
|
|
11
|
-
import { detectFacts, readCatalog, triggerVerdict, browserServerAdvice } from "../lib/repo.mjs";
|
|
11
|
+
import { detectFacts, readCatalog, triggerVerdict, browserServerAdvice, startWith } from "../lib/repo.mjs";
|
|
12
|
+
import { proposeGates, ADOPT_FILES, ADOPT_SCRIPTS } from "../lib/adopt.mjs";
|
|
12
13
|
import { assessBaseline, DEP_FILES, BASELINE_TOTAL } from "../lib/baseline.mjs";
|
|
13
14
|
import { L } from "../i18n/index.mjs";
|
|
14
15
|
import { countArbiters } from "./context.mjs";
|
|
15
16
|
import { beginBrief, finishBrief } from "../lib/brief.mjs";
|
|
16
|
-
import { declaredGates, sinceRef, runGates } from "../lib/run.mjs";
|
|
17
|
+
import { declaredGates, sinceRef, runGates, progress } from "../lib/run.mjs";
|
|
17
18
|
|
|
18
19
|
// Обязательный минимум проекта — прогоном, а не по памяти. До сих пор это было единственное
|
|
19
20
|
// место, где комплект просил верить на слово, что человек прочитал методичку и сверился.
|
|
@@ -50,7 +51,7 @@ async function reportBaseline(man, facts) {
|
|
|
50
51
|
);
|
|
51
52
|
}
|
|
52
53
|
|
|
53
|
-
async function reportCatalog(man, facts) {
|
|
54
|
+
async function reportCatalog(man, facts, probe = null) {
|
|
54
55
|
const catalog = await readCatalog();
|
|
55
56
|
if (!catalog.length) return;
|
|
56
57
|
|
|
@@ -117,6 +118,70 @@ async function reportCatalog(man, facts) {
|
|
|
117
118
|
console.log(c.dim(`\n ${L.doctor.notApplicable(skip.length)}`));
|
|
118
119
|
for (const [rec, why] of skip) console.log(c.dim(` · ${rec.slug.padEnd(22)} ${why}`));
|
|
119
120
|
}
|
|
121
|
+
// ЧТО У ВАС УЖЕ ЕСТЬ — до итога и до списка крестов. Комплект, поставленный в проект с
|
|
122
|
+
// eslint, mocha и конвейером, показывал двадцать крестов и «держит машина 0»: мы считали
|
|
123
|
+
// только СВОИ записи, а чужие проверки не читали вовсе. С точки зрения владельца это
|
|
124
|
+
// неправда, и первое, что он видел, было обвинением. Предлагаем, а не вписываем: гейт в
|
|
125
|
+
// чужом манифесте без спроса — наше решение в чужом файле.
|
|
126
|
+
if (!declaredGates(man).length) {
|
|
127
|
+
const files = {};
|
|
128
|
+
for (const n of ADOPT_FILES) {
|
|
129
|
+
try { files[n] = await readFile(join(CWD, n), "utf8"); } catch { /* нет — и ладно */ }
|
|
130
|
+
}
|
|
131
|
+
for (const n of ADOPT_SCRIPTS) if (await exists(join(CWD, n))) files[n] = "";
|
|
132
|
+
const found = proposeGates(files);
|
|
133
|
+
if (found.length) {
|
|
134
|
+
console.log(`\n ${c.bold(L.doctor.haveAlready(found.length))}`);
|
|
135
|
+
for (const g of found) {
|
|
136
|
+
console.log(` ${c.green("✔")} ${g.name.padEnd(12)} ${c.dim(`${g.cmd} ← ${g.source}`)}`);
|
|
137
|
+
}
|
|
138
|
+
console.log(c.dim(` ${L.doctor.haveAlreadyHow(found.map((g) => `${g.name}: "${g.cmd}"`).join(" "))}`));
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// ЧТО ВАШИ ПРОВЕРКИ ПРОПУСТИЛИ. Проба знала имена непойманных классов и писала в отметку одно
|
|
143
|
+
// число; человек в `doctor` не видел ничего. Это самое конкретное, что мы знаем о проекте, —
|
|
144
|
+
// не «хорошая практика», а брак, подсаженный в ЕГО файл и ЕГО проверками не замеченный, —
|
|
145
|
+
// поэтому стоит выше списка «с чего начать». Читается из файла: ничего не запускает.
|
|
146
|
+
const blindOnes = (probe?.classes || []).map((b) => [b, catalog.find((r) => r.slug === b.slug)]).filter(([, r]) => r);
|
|
147
|
+
if (blindOnes.length) {
|
|
148
|
+
console.log(`\n ${c.yellow("⚠")} ${c.bold(L.doctor.blindHeading(probe.behind))}`);
|
|
149
|
+
for (const [b, rec] of blindOnes) {
|
|
150
|
+
// Три случая, и сливать их нельзя. Гейт стоял и проба его ГОНЯЛА — «стоит, но здесь не
|
|
151
|
+
// ловит», самое ценное. Гейт объявлен, но проба его не гоняла (поставлен позже или
|
|
152
|
+
// медленный) — «поймает ли, покажет следующая», а не «пойман». Гейта нет — совет.
|
|
153
|
+
const ranIt = probe.ran?.has(rec.slug);
|
|
154
|
+
const now = facts.gateKeys.includes(rec.slug);
|
|
155
|
+
console.log(` ${now && !ranIt ? c.dim("~") : c.red("✘")} ${rec.slug.padEnd(22)} ${c.dim(`${rec.intent || ""} ← ${b.file}`)}`);
|
|
156
|
+
if (ranIt) { console.log(c.dim(` ${L.doctor.blindRan(rec.slug)}`)); continue; }
|
|
157
|
+
if (now) { console.log(c.dim(` ${L.doctor.blindInstalled}`)); continue; }
|
|
158
|
+
const adv = blindAdvice(rec, facts, {});
|
|
159
|
+
if (adv.command) console.log(c.dim(` ${L.doctor.startCmd(adv.command)}`));
|
|
160
|
+
else console.log(c.dim(` ${L.doctor.install(`${SELF} add ${rec.slug}`)}`));
|
|
161
|
+
}
|
|
162
|
+
console.log(c.dim(` ${L.doctor.blindMore(`${SELF} probe`)}`));
|
|
163
|
+
} else if (probe?.state === "never" && declaredGates(man).length) {
|
|
164
|
+
console.log(c.dim(`\n ${L.doctor.probeNever(`${SELF} probe`)}`));
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// С ЧЕГО НАЧАТЬ. Двадцать одинаковых крестов — это ноль требований: закрывают первое
|
|
168
|
+
// попавшееся или не закрывают ничего. Порядок не по нашему вкусу: сперва то, что родилось из
|
|
169
|
+
// настоящего отказа И закрывается одной готовой командой.
|
|
170
|
+
if (todo.length > 3) {
|
|
171
|
+
const first = startWith(todo, facts, 3);
|
|
172
|
+
console.log(`\n ${c.bold(L.doctor.startWith)}`);
|
|
173
|
+
for (const rec of first) {
|
|
174
|
+
const adv = blindAdvice(rec, facts, {});
|
|
175
|
+
console.log(` ${c.yellow("→")} ${rec.slug.padEnd(22)} ${c.dim(rec.intent || "")}`);
|
|
176
|
+
if (adv.command) console.log(c.dim(` ${L.doctor.startCmd(adv.command)}`));
|
|
177
|
+
if (adv.tool) console.log(c.dim(` ${L.doctor.startTool(adv.tool)}`));
|
|
178
|
+
}
|
|
179
|
+
// Одна проверка руками — это разовый героизм. Сказать про хук здесь, а не в конце: человек
|
|
180
|
+
// читает первые строки и закрывает, а именно сейчас у него в руках список того, что стоит
|
|
181
|
+
// повесить перед пушем.
|
|
182
|
+
console.log(c.dim(`\n ${L.doctor.startHook}`));
|
|
183
|
+
}
|
|
184
|
+
|
|
120
185
|
console.log(
|
|
121
186
|
`\n ${c.bold(L.doctor.total)} ${L.doctor.totalHeld(held.length)}, ${L.doctor.totalTodo(c.yellow(todo.length))}, ` +
|
|
122
187
|
(byOther.length ? `${L.doctor.totalCovered(byOther.length)}, ` : "") +
|
|
@@ -227,7 +292,11 @@ async function cmdDoctor() {
|
|
|
227
292
|
// Доказательство считается только при прогоне: узнать, ловит ли гейт брак, нельзя иначе как
|
|
228
293
|
// запустив его по образцу. Без прогона ступени со второй помечаются «не доказано» — это
|
|
229
294
|
// честнее, чем показывать их выполненными по наличию папок.
|
|
295
|
+
// Доказательство — секунды тишины до первой строки уровня; строка «идёт» их называет.
|
|
296
|
+
const bar = progress();
|
|
297
|
+
if (process.argv.includes("--run")) bar.show(c.dim(` ⋯ ${L.doctor.proving}`));
|
|
230
298
|
const proof = process.argv.includes("--run") ? await proveGates(man) : null;
|
|
299
|
+
bar.clear();
|
|
231
300
|
const { reached, steps } = await assessLevel(man, proof);
|
|
232
301
|
|
|
233
302
|
console.log(c.bold(`\n ${L.doctor.levelHeading}\n`));
|
|
@@ -274,7 +343,10 @@ async function cmdDoctor() {
|
|
|
274
343
|
await reportBaseline(man, facts);
|
|
275
344
|
process.exit(0);
|
|
276
345
|
}
|
|
277
|
-
|
|
346
|
+
// Состояние пробы — из файла отметки, миллисекунды. Нет его — блок про пробу просто молчит.
|
|
347
|
+
let probe = null;
|
|
348
|
+
try { probe = await probeStatus(); } catch { /* пробы нет — и ладно */ }
|
|
349
|
+
const cat = (await reportCatalog(man, facts, probe)) || { held: 0, todo: 0, todoRecs: [] };
|
|
278
350
|
|
|
279
351
|
// «Объявлен» ≠ «работает». Без --run говорим это вслух, а не молчим.
|
|
280
352
|
const wantRun = process.argv.includes("--run");
|
package/tool/commands/gates.mjs
CHANGED
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
} from "../lib/repo.mjs";
|
|
16
16
|
import { GATE_YML_TEMPLATE, CHECK_SH_TEMPLATE, README_TEMPLATE } from "../lib/templates.mjs";
|
|
17
17
|
import { L } from "../i18n/index.mjs";
|
|
18
|
+
import { gateCommand } from "../lib/execution.mjs";
|
|
18
19
|
|
|
19
20
|
// Ставит гейт из каталога в проект. Проверка КОПИРУЕТСЯ в репозиторий, а не остаётся
|
|
20
21
|
// ссылкой в пакет: при установке через npx пакет временный, и завтра команда в манифесте
|
|
@@ -230,7 +231,7 @@ async function cmdRatchet(args) {
|
|
|
230
231
|
|
|
231
232
|
// Снимок текущих нарушений — это и есть долг. Ключ без номера строки: правка соседней
|
|
232
233
|
// строки не должна читаться как новое нарушение.
|
|
233
|
-
const r = spawnSync(inner, { shell: true, cwd: CWD, encoding: "utf8", timeout: 300000 });
|
|
234
|
+
const r = spawnSync(gateCommand(inner), { shell: true, cwd: CWD, encoding: "utf8", timeout: 300000 });
|
|
234
235
|
if (r.status === 127 || (r.error && r.error.code === "ENOENT")) {
|
|
235
236
|
die(L.ratchet.notRunnable(slug, inner));
|
|
236
237
|
}
|
|
@@ -438,7 +439,7 @@ async function cmdWhy(args) {
|
|
|
438
439
|
|
|
439
440
|
// --- 3. объявлен: спрашиваем у него самого ---------------------------------
|
|
440
441
|
console.log(c.dim(` ${L.why.declaredAs(cmd)}`));
|
|
441
|
-
const r = spawnSync(String(cmd), { shell: true, cwd: CWD, encoding: "utf8", timeout: 300000 });
|
|
442
|
+
const r = spawnSync(gateCommand(String(cmd)), { shell: true, cwd: CWD, encoding: "utf8", timeout: 300000 });
|
|
442
443
|
const ci = await runsInCi(slug, String(cmd));
|
|
443
444
|
|
|
444
445
|
if (r.status === 127 || (r.error && r.error.code === "ENOENT")) {
|
package/tool/commands/probe.mjs
CHANGED
|
@@ -22,14 +22,16 @@
|
|
|
22
22
|
// не порог. Порог — у `doctor --run --min`.
|
|
23
23
|
import { spawnSync } from "node:child_process";
|
|
24
24
|
import { mkdtemp, mkdir, copyFile, rm, readdir, writeFile, readFile, symlink } from "node:fs/promises";
|
|
25
|
+
import { readFileSync } from "node:fs";
|
|
25
26
|
import { tmpdir } from "node:os";
|
|
26
27
|
import { join, dirname, extname } from "node:path";
|
|
27
28
|
import { readManifest } from "../lib/manifest.mjs";
|
|
28
29
|
import { fixHotspots, probeSummary, probeVerdictPaired, countProbe } from "../lib/history.mjs";
|
|
29
30
|
import { detectFacts, readCatalog, triggerVerdict } from "../lib/repo.mjs";
|
|
30
31
|
import { CWD, GATES_SRC, TARGET_DIR, c, SELF, exists } from "../lib/core.mjs";
|
|
31
|
-
import { probeState, probeEvery, PROBE_EVERY } from "../lib/cadence.mjs";
|
|
32
|
+
import { probeState, probeEvery, PROBE_EVERY, blindLines, parseBlind, parseRan } from "../lib/cadence.mjs";
|
|
32
33
|
import { L } from "../i18n/index.mjs";
|
|
34
|
+
import { gateCommand } from "../lib/execution.mjs";
|
|
33
35
|
|
|
34
36
|
// Тот же набор расширений, что у привязки доказательства к дифу. Список один на программу:
|
|
35
37
|
// второй через месяц разошёлся бы с первым.
|
|
@@ -137,6 +139,70 @@ function extAlternatives(ext) {
|
|
|
137
139
|
return fam ? [ext, ...fam.filter((e) => e !== ext)] : [ext];
|
|
138
140
|
}
|
|
139
141
|
|
|
142
|
+
// Показать САМ ОБРАЗЕЦ, а не пересказ. «Класс не прикрыт» остаётся словами, пока человек не
|
|
143
|
+
// увидел, что именно мы подсадили в его файл.
|
|
144
|
+
//
|
|
145
|
+
// Первая версия печатала одну «показательную» строку — и угадывала плохо: у мёртвого кода дефект
|
|
146
|
+
// во ВТОРОЙ функции, у отладочной печати во второй строке тела. Угадывать не надо: образцы
|
|
147
|
+
// каталога маленькие по норме, и четырёх строк хватает, чтобы стало видно. Комментарии
|
|
148
|
+
// выброшены: в наших образцах они объясняют замысел коллеге, а не показывают дефект.
|
|
149
|
+
function sampleLines(path, max = 4) {
|
|
150
|
+
let text = "";
|
|
151
|
+
try { text = readFileSync(path, "utf8"); } catch { return []; }
|
|
152
|
+
return text.split("\n")
|
|
153
|
+
.map((l) => l.replace(/\s+$/, ""))
|
|
154
|
+
.filter((l) => l.trim() && !/^\s*(#|\/\/|\/\*|\*|--|<!--)/.test(l))
|
|
155
|
+
.slice(0, max)
|
|
156
|
+
.map((l) => l.slice(0, 88));
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Совет по НЕПОКРЫТОМУ классу: команда, которую можно вставить прямо сейчас.
|
|
160
|
+
//
|
|
161
|
+
// Проба находит настоящие дыры и печатала про них «close it: aqk add <имя>» — то есть «поставь
|
|
162
|
+
// нашу штуку». Человек, впервые увидевший комплект, закрывает окно. А готовая однострочная
|
|
163
|
+
// команда под его стек У НАС УЖЕ ЛЕЖИТ в `recipes` записи каталога; мы её не показывали.
|
|
164
|
+
//
|
|
165
|
+
// Замер руками на `requests` (самый скачиваемый python-пакет) 2026-09-10: в
|
|
166
|
+
// `src/requests/utils.py` — 75 коммитов-починок; дописана функция с `except Exception: pass`;
|
|
167
|
+
// их собственные `ruff` и `pytest` дали 0 и на чистой копии, и на подсаженной. Строка, которая
|
|
168
|
+
// поймала бы это, лежала в нашем каталоге всё это время.
|
|
169
|
+
//
|
|
170
|
+
// Переносимый рецепт (`any`) в совет НЕ идёт: он зовёт файл из комплекта, и человеку без
|
|
171
|
+
// комплекта вставить его некуда. Нет родного рецепта под стек — команды нет, и это честнее
|
|
172
|
+
// выдуманной.
|
|
173
|
+
function blindAdvice(entry, facts, hot = {}) {
|
|
174
|
+
const recipes = entry?.recipes && typeof entry.recipes === "object" ? entry.recipes : {};
|
|
175
|
+
// `langs` приходит МНОЖЕСТВОМ, а не массивом — `Array.isArray` тихо давал пустой список, и
|
|
176
|
+
// совет не печатался вовсе. Поймано на живом `requests`: langs = Set(1) { python }.
|
|
177
|
+
const langs = facts?.langs ? [...facts.langs] : [];
|
|
178
|
+
// Тот же порядок, что у `pickRecipe`: свой язык → безъязыковой родной → ничего. Переносимый
|
|
179
|
+
// (`any`) сюда не идёт никогда: он зовёт файл из комплекта, и человеку без комплекта вставить
|
|
180
|
+
// его некуда.
|
|
181
|
+
let cmd = null;
|
|
182
|
+
for (const key of [...langs, "native"]) {
|
|
183
|
+
const r = recipes[key];
|
|
184
|
+
if (!r || /\{gate\}/.test(r)) continue;
|
|
185
|
+
cmd = String(r).replace(/\{dir\}/g, ".")
|
|
186
|
+
// Вычистить то, что относится к НАМ, а не к его проекту. Исключение наших красных
|
|
187
|
+
// образцов нужно УСТАНОВЛЕННОМУ гейту — рядом с ним лежат образцы. Человеку, который
|
|
188
|
+
// команду только копирует, этих каталогов не существует, и флаги про них подрывают
|
|
189
|
+
// доверие: инструмент говорит про чужое хозяйство вместо его кода.
|
|
190
|
+
.replace(/\s--ignore-pattern\s+'[^']*gates\/[^']*'/g, "")
|
|
191
|
+
.replace(/\s--ignore-paths=?\s*'[^']*gates\/[^']*'/g, "")
|
|
192
|
+
.replace(/\s+/g, " ")
|
|
193
|
+
.trim();
|
|
194
|
+
break;
|
|
195
|
+
}
|
|
196
|
+
// Адрес — того инструмента, которым команда начинается: поле `tool` общее на все языки, и
|
|
197
|
+
// python-проекту показывалась ссылка на eslint. Первые три слова, а не одно: `npx knip`,
|
|
198
|
+
// `python -m vulture`. Не совпало — весь список: лишняя ссылка лучше, чем ни одной.
|
|
199
|
+
const urls = entry?.tool ? String(entry.tool).split(/\s+·\s+/) : [];
|
|
200
|
+
const head = cmd ? cmd.split(" ").slice(0, 3) : [];
|
|
201
|
+
const own = urls.find((u) => head.includes(u.replace(/\/+$/, "").split("/").pop()));
|
|
202
|
+
const tool = own ?? (entry?.tool ? String(entry.tool) : null);
|
|
203
|
+
return { command: cmd, tool, file: hot.file ?? null, fixes: hot.fixes ?? null, slug: entry?.slug ?? null };
|
|
204
|
+
}
|
|
205
|
+
|
|
140
206
|
// Красный образец записи, подходящий по расширению горячего файла. Расширение обязано
|
|
141
207
|
// совпадать: питоновский образец в проекте на TypeScript не проверит ничего, а покажет
|
|
142
208
|
// «не прикрыто» — ложная тревога того же класса, что молчащий гейт, только наоборот.
|
|
@@ -224,7 +290,7 @@ function runGates(gates, sandbox, { stopOnRed = false } = {}) {
|
|
|
224
290
|
const out = [];
|
|
225
291
|
for (const [name, cmd] of gates) {
|
|
226
292
|
const t0 = Date.now();
|
|
227
|
-
const r = spawnSync(cmd, { shell: true, cwd: sandbox, encoding: "utf8", timeout: 120000 });
|
|
293
|
+
const r = spawnSync(gateCommand(cmd), { shell: true, cwd: sandbox, encoding: "utf8", timeout: 120000 });
|
|
228
294
|
const code = r.status === null ? 2 : r.status;
|
|
229
295
|
out.push({ name, code, ms: Date.now() - t0 });
|
|
230
296
|
if (stopOnRed && code === 1) break;
|
|
@@ -338,6 +404,19 @@ async function cmdProbe(args, { auto = false } = {}) {
|
|
|
338
404
|
console.log(` ${c.green("✔")} ${e.intent.padEnd(48)} ${c.dim(P.caught(caught.join(", ")))}`);
|
|
339
405
|
} else if (verdict === "blind") {
|
|
340
406
|
console.log(` ${c.red("✘")} ${e.intent.padEnd(48)} ${c.red(P.blind)}`);
|
|
407
|
+
// Объяснить, а не назвать. Три строки, каждая отвечает на свой вопрос человека:
|
|
408
|
+
// «почему именно здесь», «что вы вообще подсадили» и «что мне сделать ПРЯМО СЕЙЧАС».
|
|
409
|
+
// Последняя обязана работать БЕЗ комплекта: польза до установки — единственный
|
|
410
|
+
// способ заслужить установку.
|
|
411
|
+
const adv = blindAdvice(e, facts, { file: rel, fixes });
|
|
412
|
+
console.log(c.dim(` ${P.blindWhere(rel, fixes)}`));
|
|
413
|
+
const lines = sampleLines(sample);
|
|
414
|
+
if (lines.length) {
|
|
415
|
+
console.log(c.dim(` ${P.blindWhat}`));
|
|
416
|
+
for (const l of lines) console.log(c.dim(` ${l}`));
|
|
417
|
+
}
|
|
418
|
+
if (adv.command) console.log(` ${c.yellow(P.blindFix(adv.command))}`);
|
|
419
|
+
if (adv.tool) console.log(c.dim(` ${P.blindTool(adv.tool)}`));
|
|
341
420
|
console.log(c.dim(` ${P.install(`${SELF} add ${e.slug}`)}`));
|
|
342
421
|
} else {
|
|
343
422
|
console.log(` ${c.dim("~")} ${c.dim(e.intent.padEnd(48))} ${c.dim(P.unknown)}`);
|
|
@@ -362,7 +441,11 @@ async function cmdProbe(args, { auto = false } = {}) {
|
|
|
362
441
|
|
|
363
442
|
// Отметка нужна не для отчёта, а для КАДЕНЦИИ: по ней следующий прогон поймёт, что пора.
|
|
364
443
|
// Без неё команда снова становится тем, о чём надо вспомнить.
|
|
365
|
-
await writeMark(commitCount(), blind,
|
|
444
|
+
await writeMark(commitCount(), blind, [
|
|
445
|
+
`ran: ${probeGates.map(([name]) => name).join(" ")}`, "",
|
|
446
|
+
...blindLines(records), "",
|
|
447
|
+
...hot.map(({ path: p2, fixes }) => `- ${p2} (${P.fixes(fixes)})`),
|
|
448
|
+
]);
|
|
366
449
|
} finally {
|
|
367
450
|
await rm(sandbox, { recursive: true, force: true });
|
|
368
451
|
}
|
|
@@ -378,7 +461,8 @@ async function probeStatus() {
|
|
|
378
461
|
const every = probeEvery(man);
|
|
379
462
|
if (every === null) return { state: "unknown", behind: null, badEvery: String(man?.probe) };
|
|
380
463
|
if (every === 0) return { state: "off", behind: null };
|
|
381
|
-
|
|
464
|
+
const mark = await readMark();
|
|
465
|
+
return { ...probeState(mark, commitCount(), every), classes: parseBlind(mark?.text), ran: parseRan(mark?.text) };
|
|
382
466
|
}
|
|
383
467
|
|
|
384
|
-
export { cmdProbe, probeStatus, probeableGates, gatesState, extAlternatives, planProbeGates, isCode };
|
|
468
|
+
export { cmdProbe, probeStatus, probeableGates, gatesState, extAlternatives, planProbeGates, blindAdvice, isCode };
|
package/tool/commands/vitals.mjs
CHANGED
|
@@ -21,6 +21,7 @@ import { join } from "node:path";
|
|
|
21
21
|
import { CWD, MANIFEST, SELF, c, exists } from "../lib/core.mjs";
|
|
22
22
|
import { readManifest, unparsedLines, gateRequires } from "../lib/manifest.mjs";
|
|
23
23
|
import { whichSync } from "../lib/repo.mjs";
|
|
24
|
+
import { gitBash } from "../lib/execution.mjs";
|
|
24
25
|
import { updateWanted } from "../lib/brief.mjs";
|
|
25
26
|
import { L } from "../i18n/index.mjs";
|
|
26
27
|
|
|
@@ -94,7 +95,11 @@ async function cmdVitals() {
|
|
|
94
95
|
for (const [gate, cmd] of Object.entries(gates)) {
|
|
95
96
|
const prog = progOf(cmd);
|
|
96
97
|
if (!prog || seen.has(prog)) continue;
|
|
97
|
-
|
|
98
|
+
// На Windows слово `bash` в PATH — часто заглушка WSL, и «найден» было бы неправдой: гейт
|
|
99
|
+
// запустится через Git Bash (gateCommand) или не запустится вовсе. Спрашиваем того же, кого
|
|
100
|
+
// спросит прогон, — иначе vitals и doctor --run снова разойдутся.
|
|
101
|
+
const found = prog === "bash" && process.platform === "win32" ? Boolean(gitBash()) : Boolean(whichSync(prog));
|
|
102
|
+
seen.set(prog, { gate, prog, found });
|
|
98
103
|
}
|
|
99
104
|
|
|
100
105
|
// Первого слова мало. Запись каталога бывает обёрткой: команда начинается с `bash`, который
|
package/tool/i18n/en-docs.mjs
CHANGED
|
@@ -70,8 +70,9 @@ const enDocs = {
|
|
|
70
70
|
probeNever: "No coverage probe has run — what is covered by nothing here is UNKNOWN. That is not \"covered\": `aqk probe`.",
|
|
71
71
|
probeOff: "The coverage probe is switched off in the manifest (`probe: 0`) — nobody counts what is covered by nothing here.",
|
|
72
72
|
probeUnknown: "The coverage probe could not run — what is covered by nothing here is UNKNOWN. That is not \"covered\".",
|
|
73
|
-
probeBlind: (n, behind) =>
|
|
73
|
+
probeBlind: (n, behind, names = "") =>
|
|
74
74
|
`Covered by nothing: ${n} defect classes in the places people most often come back to fix` +
|
|
75
|
+
(names ? ` — ${names}` : "") +
|
|
75
76
|
(behind ? ` (the probe is ${behind} commits behind)` : "") + ". Details: `aqk probe`.",
|
|
76
77
|
probeClean: (behind) =>
|
|
77
78
|
"Coverage probe: in the places probed, every applicable class is caught by something" +
|
package/tool/i18n/en-gates.mjs
CHANGED
|
@@ -265,6 +265,10 @@ export const enGates = {
|
|
|
265
265
|
caught: (names) => `caught by: ${names}`,
|
|
266
266
|
blind: "NOTHING CATCHES IT",
|
|
267
267
|
unknown: "nothing to check with — the gate did not run (delegated tool missing)",
|
|
268
|
+
blindWhere: (file, fixes) => `where: ${file} — ${fixes} fix commits in its history`,
|
|
269
|
+
blindWhat: "what we planted into your file:",
|
|
270
|
+
blindFix: (cmd) => `catch it right now, no kit needed: ${cmd}`,
|
|
271
|
+
blindTool: (url) => `the tool: ${url}`,
|
|
268
272
|
install: (cmd) => `close it: ${cmd}`,
|
|
269
273
|
noSampleFor: (ext) => `the catalogue has no red sample for "${ext}" — nothing to check with`,
|
|
270
274
|
noGates: (cmd) => `no gates declared — nothing to probe with. First: ${cmd}`,
|
package/tool/i18n/en.mjs
CHANGED
|
@@ -104,6 +104,17 @@ export const en = {
|
|
|
104
104
|
`claim unverified: "${e}" is declared held by gate "${g}", but neither its command nor the\n linter config names rules ${codes} — the entry may be held by nothing`,
|
|
105
105
|
coversUnprovenHow: (cmd) => `settle it: add those rules to the linter, or install the entry — ${cmd}`,
|
|
106
106
|
totalCovered: (n) => `held by another arbiter ${n}`,
|
|
107
|
+
blindHeading: (behind) => `The probe${behind ? ` (${behind} commits ago)` : ""} planted defects in your files — your checks did NOT catch them:`,
|
|
108
|
+
blindRan: (g) => `gate ${g} is declared and was run — and still missed the defect in this file`,
|
|
109
|
+
blindInstalled: "declared, but the probe did not run this gate (added later or too slow) — the next probe will show whether it catches",
|
|
110
|
+
blindMore: (cmd) => `what was planted and where — ${cmd}`,
|
|
111
|
+
probeNever: (cmd) => `Whether your checks catch a real defect has not been tested yet: ${cmd} plants one in a copy of the project and shows (a minute or two, your files are not touched).`,
|
|
112
|
+
startWith: "Start with these three — born from a real failure, and each closes with one ready command:",
|
|
113
|
+
startCmd: (cmd) => `one line, no kit needed: ${cmd}`,
|
|
114
|
+
startTool: (url) => `the tool: ${url}`,
|
|
115
|
+
startHook: "Running these by hand is a one-off. To have them run before every push: pre-commit (repo: https://github.com/arsen-ask-lx/Agent_Quality_Kit, hooks aqk / aqk-doctor), or a plain .git/hooks/pre-push.",
|
|
116
|
+
haveAlready: (n) => `Checks you ALREADY have (${n}) — found in your own files, not invented:`,
|
|
117
|
+
haveAlreadyHow: (line) => `declare them and a machine holds them, not your attention. In .aqk.yml, under gates: ${line}`,
|
|
107
118
|
total: "Total:",
|
|
108
119
|
totalHeld: (n) => `held by a machine ${n}`,
|
|
109
120
|
totalTodo: (n) => `applicable but not installed ${n}`,
|
|
@@ -120,6 +131,8 @@ export const en = {
|
|
|
120
131
|
`either fix them and drop them from advisory, or admit the rule does not exist.`,
|
|
121
132
|
runHeading: "Running the declared gates",
|
|
122
133
|
timeout: "did not finish within 5 minutes",
|
|
134
|
+
running: (i, n) => `[${i}/${n}] running…`,
|
|
135
|
+
proving: "checking that the gates catch defects on their own samples…",
|
|
123
136
|
exitCode: (code) => `exit ${code}`,
|
|
124
137
|
moreLines: (n) => `… and ${n} more lines`,
|
|
125
138
|
declaredNotRun: (n) => `${n} gates declared, but never run.`,
|
package/tool/i18n/ru-docs.mjs
CHANGED
|
@@ -72,8 +72,9 @@ const ruDocs = {
|
|
|
72
72
|
probeNever: "Проба покрытия не делалась — что здесь не прикрыто ничем, НЕИЗВЕСТНО. Это не «прикрыто»: `aqk probe`.",
|
|
73
73
|
probeOff: "Проба покрытия выключена в манифесте (`probe: 0`) — что здесь не прикрыто ничем, никто не считает.",
|
|
74
74
|
probeUnknown: "Пробу покрытия провести не удалось — что здесь не прикрыто ничем, НЕИЗВЕСТНО. Это не «прикрыто».",
|
|
75
|
-
probeBlind: (n, behind) =>
|
|
75
|
+
probeBlind: (n, behind, names = "") =>
|
|
76
76
|
`Не прикрыто ничем: ${n} классов брака в местах, куда чаще всего возвращаются с починкой` +
|
|
77
|
+
(names ? ` — ${names}` : "") +
|
|
77
78
|
(behind ? ` (проба отстала на ${behind} коммитов)` : "") + ". Подробно: `aqk probe`.",
|
|
78
79
|
probeClean: (behind) =>
|
|
79
80
|
"Проба покрытия: в проверенных местах каждый применимый класс кто-то ловит" +
|
package/tool/i18n/ru-gates.mjs
CHANGED
|
@@ -267,6 +267,10 @@ export const ruGates = {
|
|
|
267
267
|
caught: (names) => `ловит: ${names}`,
|
|
268
268
|
blind: "НЕ ЛОВИТ НИКТО",
|
|
269
269
|
unknown: "проверить нечем — гейт не состоялся (нет делегированной программы)",
|
|
270
|
+
blindWhere: (file, fixes) => `где: ${file} — починок в истории: ${fixes}`,
|
|
271
|
+
blindWhat: "что подсадили в ваш файл:",
|
|
272
|
+
blindFix: (cmd) => `поймать прямо сейчас, без комплекта: ${cmd}`,
|
|
273
|
+
blindTool: (url) => `инструмент: ${url}`,
|
|
270
274
|
install: (cmd) => `закрыть: ${cmd}`,
|
|
271
275
|
noSampleFor: (ext) => `в каталоге нет красного образца под «${ext}» — проверить нечем`,
|
|
272
276
|
noGates: (cmd) => `гейтов не объявлено — пробовать нечем. Сначала: ${cmd}`,
|
package/tool/i18n/ru.mjs
CHANGED
|
@@ -105,6 +105,17 @@ export const ru = {
|
|
|
105
105
|
`заявка не подтверждена: «${e}» объявлена закрытой гейтом «${g}», но ни его команда, ни\n конфиг линтера не называют правил ${codes} — запись может быть не закрыта ничем`,
|
|
106
106
|
coversUnprovenHow: (cmd) => `подтверди: добавь эти правила в линтер, либо поставь запись — ${cmd}`,
|
|
107
107
|
totalCovered: (n) => `закрыто другим арбитром ${n}`,
|
|
108
|
+
blindHeading: (behind) => `Проба${behind ? ` (коммитов с тех пор: ${behind})` : ""} подсадила брак в ваши файлы — ваши проверки его НЕ ПОЙМАЛИ:`,
|
|
109
|
+
blindRan: (g) => `гейт ${g} стоит и прогонялся — а брак в этом файле пропустил`,
|
|
110
|
+
blindInstalled: "объявлено, но проба этот гейт не гоняла (поставлен позже или медленный) — поймает ли, покажет следующая",
|
|
111
|
+
blindMore: (cmd) => `что именно подсадили и куда — ${cmd}`,
|
|
112
|
+
probeNever: (cmd) => `Ловят ли ваши проверки настоящий брак, ещё не проверялось: ${cmd} подсадит его в копию проекта и покажет (минута-две, рабочие файлы не трогает).`,
|
|
113
|
+
startWith: "Начните с этих трёх — они родились из настоящего отказа и закрываются одной готовой командой:",
|
|
114
|
+
startCmd: (cmd) => `одной строкой, без комплекта: ${cmd}`,
|
|
115
|
+
startTool: (url) => `инструмент: ${url}`,
|
|
116
|
+
startHook: "Прогнать руками — разовый героизм. Чтобы это случалось перед каждым пушем: pre-commit (репозиторий https://github.com/arsen-ask-lx/Agent_Quality_Kit, хуки aqk / aqk-doctor) либо обычный .git/hooks/pre-push.",
|
|
117
|
+
haveAlready: (n) => `Проверки, которые у вас УЖЕ ЕСТЬ (${n}) — прочитаны в ваших файлах, не выдуманы:`,
|
|
118
|
+
haveAlreadyHow: (line) => `объявите их — и держать будет машина, а не ваше внимание. В .aqk.yml, в gates: ${line}`,
|
|
108
119
|
total: "Итого:",
|
|
109
120
|
totalHeld: (n) => `держит машина ${n}`,
|
|
110
121
|
totalTodo: (n) => `применимо но не поставлено ${n}`,
|
|
@@ -121,6 +132,8 @@ export const ru = {
|
|
|
121
132
|
`либо почини и убери из advisory, либо признай, что правила нет.`,
|
|
122
133
|
runHeading: "Прогон объявленных гейтов",
|
|
123
134
|
timeout: "не уложился в 5 минут",
|
|
135
|
+
running: (i, n) => `[${i}/${n}] идёт…`,
|
|
136
|
+
proving: "проверяю, что гейты ловят брак на своих образцах…",
|
|
124
137
|
exitCode: (code) => `код ${code}`,
|
|
125
138
|
moreLines: (n) => `… и ещё ${n} строк`,
|
|
126
139
|
declaredNotRun: (n) => `${n} гейтов объявлено, но не запускалось.`,
|