agent-quality-kit 0.11.0 → 0.13.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 +28 -4
- package/README.ru.md +28 -4
- package/kit/gates/complexity-limit/gate.yml +9 -0
- package/kit/gates/dead-code/gate.yml +9 -0
- package/kit/gates/duplicate-code/gate.yml +5 -0
- package/kit/gates/env-secrets-not-committed/README.md +73 -0
- package/kit/gates/env-secrets-not-committed/check.sh +139 -0
- package/kit/gates/env-secrets-not-committed/gate.yml +21 -0
- package/kit/gates/env-secrets-not-committed/green/.aqk-tracked +10 -0
- package/kit/gates/env-secrets-not-committed/green/.env +10 -0
- package/kit/gates/env-secrets-not-committed/green/.env.production +5 -0
- package/kit/gates/env-secrets-not-committed/green/.env.test +2 -0
- package/kit/gates/env-secrets-not-committed/red/.aqk-tracked +5 -0
- package/kit/gates/env-secrets-not-committed/red/.env +7 -0
- package/kit/gates/no-print-in-prod/gate.yml +9 -0
- package/kit/gates/secrets-not-in-code/gate.yml +20 -0
- package/kit/gates/swallowed-error/gate.yml +9 -0
- package/kit/gates/todo-without-task/gate.yml +9 -0
- package/llms.txt +4 -2
- package/package.json +1 -1
- package/tool/commands/badge.mjs +1 -1
- package/tool/commands/context.mjs +59 -4
- package/tool/commands/doctor.mjs +171 -44
- package/tool/commands/gates.mjs +3 -2
- package/tool/commands/probe.mjs +86 -22
- package/tool/commands/project.mjs +6 -1
- package/tool/commands/report.mjs +1 -1
- package/tool/commands/vitals.mjs +21 -12
- package/tool/i18n/en-docs.mjs +24 -2
- package/tool/i18n/en-gates.mjs +10 -1
- package/tool/i18n/en.mjs +25 -0
- package/tool/i18n/ru-docs.mjs +26 -2
- package/tool/i18n/ru-gates.mjs +10 -1
- package/tool/i18n/ru.mjs +25 -0
- package/tool/lib/adopt.mjs +112 -0
- package/tool/lib/advice.mjs +115 -0
- package/tool/lib/brief.mjs +3 -1
- package/tool/lib/cadence.mjs +46 -1
- package/tool/lib/core.mjs +39 -1
- package/tool/lib/execution.mjs +50 -1
- package/tool/lib/gate-worker.mjs +18 -0
- package/tool/lib/history.mjs +45 -6
- package/tool/lib/manifest.mjs +65 -21
- package/tool/lib/prove.mjs +2 -2
- package/tool/lib/repo.mjs +18 -0
- package/tool/lib/run.mjs +108 -8
- package/tool/selfcheck/gates.sh +12 -2
- package/tool/selfcheck/smoke/first-run.test.mjs +105 -0
- package/tool/selfcheck/smoke/verdict.test.mjs +51 -2
- package/tool/selfcheck/smoke.sh +6 -2
- package/tool/selfcheck/units-cadence.mjs +51 -1
- package/tool/selfcheck/units-context.mjs +64 -1
- package/tool/selfcheck/units-execution.mjs +78 -1
- package/tool/selfcheck/units-level.mjs +99 -1
- package/tool/selfcheck/units-probe.mjs +104 -0
- package/tool/selfcheck/units-repo.mjs +146 -0
- package/tool/selfcheck/units-verdict.mjs +76 -0
- package/tool/selfcheck/units.mjs +34 -1
package/tool/commands/probe.mjs
CHANGED
|
@@ -22,14 +22,17 @@
|
|
|
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
|
-
import { fixHotspots, probeSummary, probeVerdictPaired, countProbe } from "../lib/history.mjs";
|
|
29
|
+
import { fixHotspots, probeSummary, probeVerdictPaired, countProbe, namesPlant, catchVerdict } from "../lib/history.mjs";
|
|
29
30
|
import { detectFacts, readCatalog, triggerVerdict } from "../lib/repo.mjs";
|
|
31
|
+
import { blindAdvice } from "../lib/advice.mjs";
|
|
30
32
|
import { CWD, GATES_SRC, TARGET_DIR, c, SELF, exists } from "../lib/core.mjs";
|
|
31
|
-
import { probeState, probeEvery, PROBE_EVERY } from "../lib/cadence.mjs";
|
|
33
|
+
import { probeState, probeEvery, PROBE_EVERY, blindLines, parseBlind, parseRan } from "../lib/cadence.mjs";
|
|
32
34
|
import { L } from "../i18n/index.mjs";
|
|
35
|
+
import { gateCommand } from "../lib/execution.mjs";
|
|
33
36
|
|
|
34
37
|
// Тот же набор расширений, что у привязки доказательства к дифу. Список один на программу:
|
|
35
38
|
// второй через месяц разошёлся бы с первым.
|
|
@@ -93,7 +96,7 @@ async function writeMark(now, blind, lines) {
|
|
|
93
96
|
"",
|
|
94
97
|
...lines,
|
|
95
98
|
"",
|
|
96
|
-
"Файл эфемерный: его переписывает каждая проба.
|
|
99
|
+
"Файл эфемерный: его переписывает каждая проба. `aqk init` кладёт его в .gitignore.",
|
|
97
100
|
].join("\n");
|
|
98
101
|
await writeFile(MARK(), body + "\n", "utf8");
|
|
99
102
|
}
|
|
@@ -137,11 +140,28 @@ function extAlternatives(ext) {
|
|
|
137
140
|
return fam ? [ext, ...fam.filter((e) => e !== ext)] : [ext];
|
|
138
141
|
}
|
|
139
142
|
|
|
143
|
+
// Показать САМ ОБРАЗЕЦ, а не пересказ. «Класс не прикрыт» остаётся словами, пока человек не
|
|
144
|
+
// увидел, что именно мы подсадили в его файл.
|
|
145
|
+
//
|
|
146
|
+
// Первая версия печатала одну «показательную» строку — и угадывала плохо: у мёртвого кода дефект
|
|
147
|
+
// во ВТОРОЙ функции, у отладочной печати во второй строке тела. Угадывать не надо: образцы
|
|
148
|
+
// каталога маленькие по норме, и четырёх строк хватает, чтобы стало видно. Комментарии
|
|
149
|
+
// выброшены: в наших образцах они объясняют замысел коллеге, а не показывают дефект.
|
|
150
|
+
function sampleLines(path, max = 4) {
|
|
151
|
+
let text = "";
|
|
152
|
+
try { text = readFileSync(path, "utf8"); } catch { return []; }
|
|
153
|
+
return text.split("\n")
|
|
154
|
+
.map((l) => l.replace(/\s+$/, ""))
|
|
155
|
+
.filter((l) => l.trim() && !/^\s*(#|\/\/|\/\*|\*|--|<!--)/.test(l))
|
|
156
|
+
.slice(0, max)
|
|
157
|
+
.map((l) => l.slice(0, 88));
|
|
158
|
+
}
|
|
159
|
+
|
|
140
160
|
// Красный образец записи, подходящий по расширению горячего файла. Расширение обязано
|
|
141
161
|
// совпадать: питоновский образец в проекте на TypeScript не проверит ничего, а покажет
|
|
142
162
|
// «не прикрыто» — ложная тревога того же класса, что молчащий гейт, только наоборот.
|
|
143
|
-
async function
|
|
144
|
-
const dir = join(GATES_SRC, entry,
|
|
163
|
+
async function sampleFor(entry, ext, kind = "red") {
|
|
164
|
+
const dir = join(GATES_SRC, entry, kind);
|
|
145
165
|
if (!(await exists(dir))) return null;
|
|
146
166
|
let names = [];
|
|
147
167
|
try { names = await readdir(dir); } catch { return null; }
|
|
@@ -160,7 +180,8 @@ async function redSampleFor(entry, ext) {
|
|
|
160
180
|
//
|
|
161
181
|
// Способ взят из мутационного тестирования, где та же задача решена двадцать лет назад: Stryker
|
|
162
182
|
// копирует проект во временный каталог, СИМЛИНКУЕТ `node_modules` и гоняет там родную команду.
|
|
163
|
-
// Копируются
|
|
183
|
+
// Копируются отслеживаемые и неигнорируемые файлы (`git ls-files --cached --others
|
|
184
|
+
// --exclude-standard`) — файлы проекта не меняются (итог пишется в .aqk/last-probe.md), а
|
|
164
185
|
// мусор сборки не тащится; тяжёлые каталоги зависимостей симлинкуются, иначе `npm test` в
|
|
165
186
|
// песочнице падал бы с «модуль не найден», и это читалось бы как сбой инструмента.
|
|
166
187
|
const DEP_DIRS = ["node_modules", ".venv", "venv", "vendor", "target", ".tox", ".bundle"];
|
|
@@ -218,20 +239,44 @@ async function plant(root, relPath, sample) {
|
|
|
218
239
|
// Гейт запускается В ПЕСОЧНИЦЕ и командой КАК ЕСТЬ — ничего в неё не подставляется. Именно это
|
|
219
240
|
// и делает пробу независимой от формы команды.
|
|
220
241
|
//
|
|
221
|
-
//
|
|
222
|
-
|
|
223
|
-
function runGates(gates, sandbox, { stopOnRed = false } = {}) {
|
|
242
|
+
// Вывод гейта сохраняется: по нему видно, ИЗ-ЗА ЧЕГО он покраснел (см. catchVerdict).
|
|
243
|
+
function runGates(gates, sandbox) {
|
|
224
244
|
const out = [];
|
|
225
245
|
for (const [name, cmd] of gates) {
|
|
226
246
|
const t0 = Date.now();
|
|
227
|
-
const r = spawnSync(cmd, { shell: true, cwd: sandbox, encoding: "utf8", timeout: 120000 });
|
|
247
|
+
const r = spawnSync(gateCommand(cmd), { shell: true, cwd: sandbox, encoding: "utf8", timeout: 120000 });
|
|
228
248
|
const code = r.status === null ? 2 : r.status;
|
|
229
|
-
out.push({ name, code, ms: Date.now() - t0 });
|
|
230
|
-
if (stopOnRed && code === 1) break;
|
|
249
|
+
out.push({ name, code, ms: Date.now() - t0, out: `${r.stdout || ""}${r.stderr || ""}`.slice(0, 200000) });
|
|
231
250
|
}
|
|
232
251
|
return out;
|
|
233
252
|
}
|
|
234
253
|
|
|
254
|
+
// Один класс брака на одном файле: гейты по одному, в порядке плана. Покраснел и назвал файл —
|
|
255
|
+
// контроль ЗЕЛЁНЫМ образцом той же записи в том же месте (catchVerdict). Ранний выход — только на
|
|
256
|
+
// ПОДТВЕРЖДЁННОЙ поимке: первым мог покраснеть форматтер, и остановка на нём скрыла бы линтер,
|
|
257
|
+
// который брак действительно поймал.
|
|
258
|
+
async function probeOne({ sandbox, rel, red, green, gates, baseOut }) {
|
|
259
|
+
const after = [];
|
|
260
|
+
let restore = await plant(sandbox, rel, red);
|
|
261
|
+
try {
|
|
262
|
+
for (const g of gates) {
|
|
263
|
+
const [res] = runGates([g], sandbox);
|
|
264
|
+
after.push(res);
|
|
265
|
+
if (res.code !== 1) continue;
|
|
266
|
+
let ctl = null;
|
|
267
|
+
if (green && namesPlant(baseOut(res.name), res.out, rel)) {
|
|
268
|
+
await restore(); restore = await plant(sandbox, rel, green);
|
|
269
|
+
[ctl] = runGates([g], sandbox);
|
|
270
|
+
await restore(); restore = await plant(sandbox, rel, red);
|
|
271
|
+
}
|
|
272
|
+
res.why = catchVerdict(baseOut(res.name), res, ctl, rel);
|
|
273
|
+
res.named = res.why === "caught";
|
|
274
|
+
if (res.named) break;
|
|
275
|
+
}
|
|
276
|
+
} finally { await restore(); }
|
|
277
|
+
return after;
|
|
278
|
+
}
|
|
279
|
+
|
|
235
280
|
// Каким гейтом пробовать и в каком порядке.
|
|
236
281
|
//
|
|
237
282
|
// Цена пробы = (файлы × записи) × сумма длительностей гейтов. На самом комплекте после перехода
|
|
@@ -302,7 +347,8 @@ async function cmdProbe(args, { auto = false } = {}) {
|
|
|
302
347
|
// Пробуем только запланированными, в порядке плана.
|
|
303
348
|
const byName = new Map(gates);
|
|
304
349
|
const probeGates = plan.use.map((g) => [g.name, byName.get(g.name)]);
|
|
305
|
-
const baseline = plan.use.map((g) => ({ name: g.name, code: g.code }));
|
|
350
|
+
const baseline = plan.use.map((g) => ({ name: g.name, code: g.code, out: g.out }));
|
|
351
|
+
const baseOut = (name) => baseline.find((b) => b.name === name)?.out || "";
|
|
306
352
|
|
|
307
353
|
console.log(c.dim(` ${P.method(hot.length, entries.length, plan.use.length)}\n`));
|
|
308
354
|
if (plan.tooSlow.length) {
|
|
@@ -319,28 +365,41 @@ async function cmdProbe(args, { auto = false } = {}) {
|
|
|
319
365
|
let probed = 0;
|
|
320
366
|
|
|
321
367
|
for (const e of entries) {
|
|
322
|
-
const sample = await
|
|
368
|
+
const sample = await sampleFor(e.slug, ext, "red");
|
|
323
369
|
if (!sample) continue;
|
|
324
370
|
probed++;
|
|
325
|
-
const
|
|
326
|
-
|
|
327
|
-
try { after = runGates(probeGates, sandbox, { stopOnRed: true }); } finally { await restore(); }
|
|
371
|
+
const green = await sampleFor(e.slug, ext, "green");
|
|
372
|
+
const after = await probeOne({ sandbox, rel, red: sample, green, gates: probeGates, baseOut });
|
|
328
373
|
// Ранний выход обрывает список: гейты, до которых не дошли, считаются такими же, как на
|
|
329
374
|
// сухом прогоне. Иначе их отсутствие прочиталось бы как сбой запуска.
|
|
330
375
|
const seen = new Set(after.map((a) => a.name));
|
|
331
376
|
const full = after.concat(baseline.filter((b) => !seen.has(b.name)));
|
|
332
377
|
const r = probeVerdictPaired(baseline, full);
|
|
333
378
|
const verdict = r.verdict;
|
|
334
|
-
const
|
|
335
|
-
|
|
379
|
+
const redNow = full.filter((a) => a.code === 1 && baseline.find((b) => b.name === a.name)?.code === 0);
|
|
380
|
+
const caught = redNow.filter((a) => a.named !== false).map((a) => a.name);
|
|
381
|
+
const nameless = redNow.filter((a) => a.named === false).map((a) => `${a.name} (${a.why === "planting" ? P.planting : P.nameless})`);
|
|
336
382
|
records.push({ entry: e.slug, file: rel, verdict });
|
|
337
383
|
if (verdict === "caught") {
|
|
338
384
|
console.log(` ${c.green("✔")} ${e.intent.padEnd(48)} ${c.dim(P.caught(caught.join(", ")))}`);
|
|
339
385
|
} else if (verdict === "blind") {
|
|
340
386
|
console.log(` ${c.red("✘")} ${e.intent.padEnd(48)} ${c.red(P.blind)}`);
|
|
387
|
+
// Объяснить, а не назвать. Три строки, каждая отвечает на свой вопрос человека:
|
|
388
|
+
// «почему именно здесь», «что вы вообще подсадили» и «что мне сделать ПРЯМО СЕЙЧАС».
|
|
389
|
+
// Последняя обязана работать БЕЗ комплекта: польза до установки — единственный
|
|
390
|
+
// способ заслужить установку.
|
|
391
|
+
const adv = blindAdvice(e, facts, { file: rel, fixes });
|
|
392
|
+
console.log(c.dim(` ${P.blindWhere(rel, fixes)}`));
|
|
393
|
+
const lines = sampleLines(sample);
|
|
394
|
+
if (lines.length) {
|
|
395
|
+
console.log(c.dim(` ${P.blindWhat}`));
|
|
396
|
+
for (const l of lines) console.log(c.dim(` ${l}`));
|
|
397
|
+
}
|
|
398
|
+
if (adv.command) console.log(` ${c.yellow(P.blindFix(adv.command))}`);
|
|
399
|
+
if (adv.tool) console.log(c.dim(` ${P.blindTool(adv.tool)}`));
|
|
341
400
|
console.log(c.dim(` ${P.install(`${SELF} add ${e.slug}`)}`));
|
|
342
401
|
} else {
|
|
343
|
-
console.log(` ${c.dim("~")} ${c.dim(e.intent.padEnd(48))} ${c.dim(P.unknown)}`);
|
|
402
|
+
console.log(` ${c.dim("~")} ${c.dim(e.intent.padEnd(48))} ${c.dim(nameless.length ? P.unattributed(nameless.join(", ")) : P.unknown)}`);
|
|
344
403
|
}
|
|
345
404
|
}
|
|
346
405
|
if (!probed) { unprobedN++; console.log(c.dim(` ${P.noSampleFor(ext || "—")}`)); }
|
|
@@ -362,7 +421,11 @@ async function cmdProbe(args, { auto = false } = {}) {
|
|
|
362
421
|
|
|
363
422
|
// Отметка нужна не для отчёта, а для КАДЕНЦИИ: по ней следующий прогон поймёт, что пора.
|
|
364
423
|
// Без неё команда снова становится тем, о чём надо вспомнить.
|
|
365
|
-
await writeMark(commitCount(), blind,
|
|
424
|
+
await writeMark(commitCount(), blind, [
|
|
425
|
+
`ran: ${probeGates.map(([name]) => name).join(" ")}`, "",
|
|
426
|
+
...blindLines(records), "",
|
|
427
|
+
...hot.map(({ path: p2, fixes }) => `- ${p2} (${P.fixes(fixes)})`),
|
|
428
|
+
]);
|
|
366
429
|
} finally {
|
|
367
430
|
await rm(sandbox, { recursive: true, force: true });
|
|
368
431
|
}
|
|
@@ -378,7 +441,8 @@ async function probeStatus() {
|
|
|
378
441
|
const every = probeEvery(man);
|
|
379
442
|
if (every === null) return { state: "unknown", behind: null, badEvery: String(man?.probe) };
|
|
380
443
|
if (every === 0) return { state: "off", behind: null };
|
|
381
|
-
|
|
444
|
+
const mark = await readMark();
|
|
445
|
+
return { ...probeState(mark, commitCount(), every), classes: parseBlind(mark?.text), ran: parseRan(mark?.text) };
|
|
382
446
|
}
|
|
383
447
|
|
|
384
448
|
export { cmdProbe, probeStatus, probeableGates, gatesState, extAlternatives, planProbeGates, isCode };
|
|
@@ -6,7 +6,7 @@ import { spawnSync } from "node:child_process";
|
|
|
6
6
|
import { join, dirname, relative } from "node:path";
|
|
7
7
|
import {
|
|
8
8
|
CWD, PKG_ROOT, DOCS_SRC, RULES_SRC, TARGET_DIR, MANIFEST, SELF, REPO_URL, c, exists, die,
|
|
9
|
-
copyDir, writeIfAbsent, FEEDBACK_MARK, docPath } from "../lib/core.mjs";
|
|
9
|
+
copyDir, writeIfAbsent, FEEDBACK_MARK, docPath, ensureIgnored } from "../lib/core.mjs";
|
|
10
10
|
import { AGENTS_MD, CLAUDE_MD, MANIFEST_YML } from "../lib/templates.mjs";
|
|
11
11
|
import { banner } from "../lib/banner.mjs";
|
|
12
12
|
import { readManifest } from "../lib/manifest.mjs";
|
|
@@ -41,6 +41,10 @@ async function cmdInit(args) {
|
|
|
41
41
|
const claude = join(CWD, "CLAUDE.md");
|
|
42
42
|
track(await writeIfAbsent(claude, CLAUDE_MD, { force }), claude);
|
|
43
43
|
|
|
44
|
+
// Служебные файлы — в .gitignore сразу, до первого прогона: иначе первый же `doctor --run`
|
|
45
|
+
// оставит в дереве файл, который попадёт в коммит (так и случилось на живом проекте).
|
|
46
|
+
const ignored = await ensureIgnored(CWD);
|
|
47
|
+
|
|
44
48
|
// Заставка в начале init — первая встреча человека с комплектом. Второй раз он увидит её
|
|
45
49
|
// только если сам спросит `--version`: то, что видишь тридцатый раз, перестаёт читаться.
|
|
46
50
|
console.log(`\n${banner()}\n`);
|
|
@@ -56,6 +60,7 @@ async function cmdInit(args) {
|
|
|
56
60
|
if (LANG === "en" && created.some((f) => f.includes(`${TARGET_DIR}/docs/`) || f.includes(`${TARGET_DIR}\\docs\\`))) {
|
|
57
61
|
console.log(c.dim(`\n ${L.init.docsRu}`));
|
|
58
62
|
}
|
|
63
|
+
if (ignored.length) console.log(c.dim(`\n ${L.init.ignored(ignored.join(", "))}`));
|
|
59
64
|
if (skipped.length) {
|
|
60
65
|
console.log(c.yellow(`\n ${L.init.kept(skipped.length)}`));
|
|
61
66
|
for (const f of skipped) console.log(` ${f}`);
|
package/tool/commands/report.mjs
CHANGED
|
@@ -86,7 +86,7 @@ async function cmdReport() {
|
|
|
86
86
|
|
|
87
87
|
// Прогон, а не чтение манифеста: «объявлен» и «работает» — разные утверждения, и весь
|
|
88
88
|
// смысл этой команды в том, чтобы в отчёт попало второе.
|
|
89
|
-
const run = declared.length ? runGates(man) : { results: [], failed: 0 };
|
|
89
|
+
const run = declared.length ? await runGates(man) : { results: [], failed: 0 };
|
|
90
90
|
|
|
91
91
|
const held = [], broken = [], todo = [], skip = [];
|
|
92
92
|
for (const res of run.results) {
|
package/tool/commands/vitals.mjs
CHANGED
|
@@ -16,11 +16,13 @@
|
|
|
16
16
|
// сознательное решение: pre-commit локально мы не ставим, проверки идут в CI. Команда, которая
|
|
17
17
|
// кричит «сломано» про выбор, — ровно та, которую выключают в первый день, и вместе с ней
|
|
18
18
|
// перестают читать настоящие отказы. Кода возврата касается только `✘`.
|
|
19
|
+
import { spawnSync } from "node:child_process";
|
|
19
20
|
import { readFile } from "node:fs/promises";
|
|
20
21
|
import { join } from "node:path";
|
|
21
|
-
import { CWD, MANIFEST, SELF, c, exists } from "../lib/core.mjs";
|
|
22
|
+
import { CWD, MANIFEST, SELF, c, exists, preCommitHook } from "../lib/core.mjs";
|
|
22
23
|
import { readManifest, unparsedLines, gateRequires } from "../lib/manifest.mjs";
|
|
23
24
|
import { whichSync } from "../lib/repo.mjs";
|
|
25
|
+
import { gitBash } from "../lib/execution.mjs";
|
|
24
26
|
import { updateWanted } from "../lib/brief.mjs";
|
|
25
27
|
import { L } from "../i18n/index.mjs";
|
|
26
28
|
|
|
@@ -94,7 +96,11 @@ async function cmdVitals() {
|
|
|
94
96
|
for (const [gate, cmd] of Object.entries(gates)) {
|
|
95
97
|
const prog = progOf(cmd);
|
|
96
98
|
if (!prog || seen.has(prog)) continue;
|
|
97
|
-
|
|
99
|
+
// На Windows слово `bash` в PATH — часто заглушка WSL, и «найден» было бы неправдой: гейт
|
|
100
|
+
// запустится через Git Bash (gateCommand) или не запустится вовсе. Спрашиваем того же, кого
|
|
101
|
+
// спросит прогон, — иначе vitals и doctor --run снова разойдутся.
|
|
102
|
+
const found = prog === "bash" && process.platform === "win32" ? Boolean(gitBash()) : Boolean(whichSync(prog));
|
|
103
|
+
seen.set(prog, { gate, prog, found });
|
|
98
104
|
}
|
|
99
105
|
|
|
100
106
|
// Первого слова мало. Запись каталога бывает обёрткой: команда начинается с `bash`, который
|
|
@@ -117,14 +123,7 @@ async function cmdVitals() {
|
|
|
117
123
|
// Хук pre-commit проверяется в `.git/hooks`, а НЕ в `.pre-commit-config.yaml`. Запись в
|
|
118
124
|
// конфиге — это намерение; сработает только то, что лежит в самом гите. Ровно та разница,
|
|
119
125
|
// ради которой весь комплект: объявлено и работает — разные утверждения.
|
|
120
|
-
|
|
121
|
-
const hook = join(CWD, ".git", "hooks", "pre-commit");
|
|
122
|
-
if (await exists(join(CWD, ".git"))) {
|
|
123
|
-
preCommit = false;
|
|
124
|
-
if (await exists(hook)) {
|
|
125
|
-
try { preCommit = /pre-commit|aqk/i.test(await readFile(hook, "utf8")); } catch { preCommit = null; }
|
|
126
|
-
}
|
|
127
|
-
}
|
|
126
|
+
const preCommit = await preCommitHook(CWD);
|
|
128
127
|
|
|
129
128
|
let sessionHook = null;
|
|
130
129
|
const settings = join(CWD, ".claude", "settings.json");
|
|
@@ -140,15 +139,25 @@ async function cmdVitals() {
|
|
|
140
139
|
|
|
141
140
|
let version = null;
|
|
142
141
|
if (updateWanted()) {
|
|
142
|
+
let current = "";
|
|
143
143
|
try {
|
|
144
144
|
const { PKG_ROOT } = await import("../lib/core.mjs");
|
|
145
|
-
|
|
145
|
+
current = JSON.parse(await readFile(join(PKG_ROOT, "package.json"), "utf8")).version || "";
|
|
146
146
|
const r = await fetch("https://registry.npmjs.org/agent-quality-kit/latest", {
|
|
147
147
|
signal: AbortSignal.timeout(3000),
|
|
148
148
|
headers: { accept: "application/vnd.npm.install-v1+json" },
|
|
149
149
|
});
|
|
150
150
|
version = { current, latest: r.ok ? String((await r.json()).version || "") : "" };
|
|
151
|
-
} catch { /*
|
|
151
|
+
} catch { /* прямой запрос не прошёл — ниже спросим npm */ }
|
|
152
|
+
// ПРЯМОЙ ЗАПРОС НЕ ПРОШЁЛ — СПРОСИТЬ NPM. Отзыв с живого проекта 2026-09-11: vitals писал «не
|
|
153
|
+
// достучался до реестра», а `npm view` на той же машине работал. `fetch` в Node не знает
|
|
154
|
+
// прокси и зеркала из .npmrc, а npm знает. Только здесь, не в хуке: там секунды дороже.
|
|
155
|
+
if (current && !version?.latest) {
|
|
156
|
+
const r = spawnSync("npm", ["view", "agent-quality-kit", "version"],
|
|
157
|
+
{ encoding: "utf8", timeout: 10000, shell: process.platform === "win32" });
|
|
158
|
+
const latest = r.status === 0 ? String(r.stdout || "").trim().split("\n").pop() : "";
|
|
159
|
+
version = { current, latest };
|
|
160
|
+
}
|
|
152
161
|
}
|
|
153
162
|
|
|
154
163
|
const rows = vitalsRows({ tools: [...seen.values()], unparsed, preCommit, sessionHook, version });
|
package/tool/i18n/en-docs.mjs
CHANGED
|
@@ -66,18 +66,39 @@ const enDocs = {
|
|
|
66
66
|
runClean: (when) => `Last run ${when} — nothing red.`,
|
|
67
67
|
runRed: (when, names) => `Last run ${when} — RED: ${names}.`,
|
|
68
68
|
andMore: (n) => `and ${n} more`,
|
|
69
|
-
skipped: (n) => `Not run: ${n} — the tool is absent on this machine
|
|
69
|
+
skipped: (n) => `Not run: ${n} — skipped by --only/--skip or the tool is absent on this machine; their state is unknown.`,
|
|
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" +
|
|
78
79
|
(behind ? ` (${behind} commits behind)` : "") + ".",
|
|
79
80
|
ratchets: (list) => `Ratchets: ${list}. The list may only get shorter, never longer.`,
|
|
80
81
|
where: (entry) => `The rulebook: ${entry}. What proves a diff: \`aqk report --since main\`.`,
|
|
82
|
+
nextTitle: "Next, in order — computed from this repository, not generic advice:",
|
|
83
|
+
nextStep: {
|
|
84
|
+
init: () => "Set up the standard: `aqk init` — without .aqk.yml `aqk add` refuses.",
|
|
85
|
+
adopt: (s) => `Declare the checks this project already has: in .aqk.yml, under gates: ${s.gates.map((g) => `${g.name}: "${g.cmd}"`).join(", ")}. Then \`aqk doctor --run\`.`,
|
|
86
|
+
blind: (s) => `The "${s.slug}" defect the probe planted in ${s.file} was NOT caught by your checks.` +
|
|
87
|
+
(s.command ? ` Catch it now: \`${s.command}\`.` : "") + ` Keep it caught: \`aqk add ${s.slug}\`.`,
|
|
88
|
+
start: (s) => `Install ${s.slug}: \`aqk add ${s.slug}\`` + (s.command ? ` (one line, no kit needed: \`${s.command}\`)` : "") +
|
|
89
|
+
", then `aqk prove` — the gate must go red on its own red sample.",
|
|
90
|
+
},
|
|
91
|
+
nextMore: (n) => `And ${n} more — the full list: \`aqk doctor\`.`,
|
|
92
|
+
whenTitle: "When to do what:",
|
|
93
|
+
whenCommit: (hook) => hook === true
|
|
94
|
+
? "Before a commit → the `.git/hooks/pre-commit` hook runs the checks itself; do not bypass it (`--no-verify`)."
|
|
95
|
+
: "Before a commit → `aqk doctor --run --since main`: there is no pre-commit hook, it will not happen by itself.",
|
|
96
|
+
whenRules: [
|
|
97
|
+
"Added or changed a check → `aqk prove`: the gate must go red on its own red sample, otherwise it checks nothing.",
|
|
98
|
+
"Writing a rule into the rulebook → put the arbiter mark next to it, `<!-- aqk: gate-name -->`; no gate — `<!-- aqk: human -->`, which is an admission, not a check.",
|
|
99
|
+
"A check is in your way → do not weaken it (`|| true`, `--exit-zero`, a suppression without a rule code): stop and ask the owner.",
|
|
100
|
+
"You think it is done → `aqk report --since main`: every changed file must be named by a check.",
|
|
101
|
+
],
|
|
81
102
|
mapTitle: "WHAT THIS TOOL CAN DO. The full list of commands — not a retelling, the same list\nthe help is built from:",
|
|
82
103
|
rulesTitle: (e) => `THE RULEBOOK OF THIS PROJECT (${e}) — verbatim, in full. This is not an invitation\nto read it: it is already here.`,
|
|
83
104
|
hookAlready: (p) => `the hook is already in ${p} — changing nothing.`,
|
|
@@ -195,6 +216,7 @@ const enDocs = {
|
|
|
195
216
|
},
|
|
196
217
|
],
|
|
197
218
|
report: {
|
|
219
|
+
skippedBySelect: "not run (by --only/--skip), state unknown",
|
|
198
220
|
title: "aqk doctor --run",
|
|
199
221
|
version: "version",
|
|
200
222
|
level: "level",
|
package/tool/i18n/en-gates.mjs
CHANGED
|
@@ -158,6 +158,7 @@ export const enGates = {
|
|
|
158
158
|
"the guides in .aqk/docs/ are in Russian — a deliberate decision, not a broken install.\n The rules in .aqk/rules/ are in English; the guides are prose an agent may ignore anyway,\n and what a machine holds lives in .aqk.yml and the gates. Translation waits for someone who needs it.",
|
|
159
159
|
created: (n) => `created (${n}):`,
|
|
160
160
|
andMore: (n) => `… and ${n} more`,
|
|
161
|
+
ignored: (list) => `the kit's runtime files were added to .gitignore — machine state, it does not belong in git: ${list}`,
|
|
161
162
|
kept: (n) => `already there, left untouched (${n}):`,
|
|
162
163
|
overwrite: (cmd) => `overwrite: ${cmd}`,
|
|
163
164
|
nextTitle: "What to do next, in order:",
|
|
@@ -255,16 +256,24 @@ export const enGates = {
|
|
|
255
256
|
badEvery: (v) => `the manifest says probe: "${v}", which is not a commit count. The probe does NOT run: silently using the default would mean doing something other than what is written.`,
|
|
256
257
|
autoFirst: "no coverage probe has ever run here — running it myself. Turn off: AQK_PROBE=0",
|
|
257
258
|
auto: (n) => `${n} commits since the last probe — running it myself. Turn off: AQK_PROBE=0`,
|
|
259
|
+
autoNotInCi: (cmd) => `in CI the probe does not run by itself — it would be minutes of surprise in a fast check; run it as a separate job: ${cmd} (or AQK_PROBE=1)`,
|
|
258
260
|
title: "aqk probe — what the declared checks cannot see",
|
|
259
261
|
method: (files, entries, gates) =>
|
|
260
262
|
`method: a red sample from a catalogue entry is planted into a COPY of the project, then the ` +
|
|
261
263
|
`DECLARED gates are run there — the command is used as written, nothing is substituted into it. ` +
|
|
262
264
|
`Files: ${files}, applicable entries: ${entries}, gates green on a clean checkout: ${gates}. ` +
|
|
263
|
-
`
|
|
265
|
+
`Project files are not changed; the probe result goes to .aqk/last-probe.md (in .gitignore after init).`,
|
|
264
266
|
fixes: (n) => `fixes in history: ${n}`,
|
|
265
267
|
caught: (names) => `caught by: ${names}`,
|
|
266
268
|
blind: "NOTHING CATCHES IT",
|
|
267
269
|
unknown: "nothing to check with — the gate did not run (delegated tool missing)",
|
|
270
|
+
nameless: "did not name the planted file",
|
|
271
|
+
planting: "goes red on the green sample too — fails from the planting itself",
|
|
272
|
+
unattributed: (g) => `went red: ${g} — the catch is not proven`,
|
|
273
|
+
blindWhere: (file, fixes) => `where: ${file} — ${fixes} fix commits in its history`,
|
|
274
|
+
blindWhat: "what we planted into your file:",
|
|
275
|
+
blindFix: (cmd) => `catch it right now, no kit needed: ${cmd}`,
|
|
276
|
+
blindTool: (url) => `the tool: ${url}`,
|
|
268
277
|
install: (cmd) => `close it: ${cmd}`,
|
|
269
278
|
noSampleFor: (ext) => `the catalogue has no red sample for "${ext}" — nothing to check with`,
|
|
270
279
|
noGates: (cmd) => `no gates declared — nothing to probe with. First: ${cmd}`,
|
package/tool/i18n/en.mjs
CHANGED
|
@@ -44,6 +44,7 @@ export const en = {
|
|
|
44
44
|
blob: "assemble the guides into a single GOD_AI.md",
|
|
45
45
|
learn: "rule candidates from local transcripts: said out loud, never written down",
|
|
46
46
|
context: "the project state in one block — for an agent's context, not for reading",
|
|
47
|
+
vitals: "is what the kit runs on wired up: gate tools, hooks, version freshness",
|
|
47
48
|
contextInstall: "the same in full — the map and the rulebook — installed as a hook",
|
|
48
49
|
report: "the mandatory report form: what is in place, what is not, what was not read; --since <ref> adds what proves the diff",
|
|
49
50
|
badge: "a level badge for your README — and a check that it does not lie",
|
|
@@ -65,6 +66,14 @@ export const en = {
|
|
|
65
66
|
gitignore: "repository hygiene",
|
|
66
67
|
git: "project under version control",
|
|
67
68
|
|
|
69
|
+
runtimeTracked: (f, cmd) => `${f} is tracked by git — every run rewrites it, and the tree always shows a modified file. Take it out: ${cmd}`,
|
|
70
|
+
runtimeNotIgnored: (f, cmd) => `${f} is this machine's state, yet git sees it: one \`git add .\` and it is in a commit. Hide it: ${cmd}`,
|
|
71
|
+
layoutAdvice: "missing — advice, it does not fail the run",
|
|
72
|
+
coversImpossible: (entry, gate, linter) => `the claim "${gate} holds ${entry}" is wrong: ${linter} has no rule for this class — there is nothing to hold it with`,
|
|
73
|
+
coversCantCheck: (entry, gate) => `cannot check the claim "${gate} holds ${entry}": the gate's linter is not recognised or the entry has no rules for it — taken on trust`,
|
|
74
|
+
selectUnknown: (names, groups) => `--only/--skip: "${names}" is neither a gate from gates: nor a group from groups:${groups ? ` (groups: ${groups})` : ""}. Running everything instead of skipping would be a lie, so stopping.`,
|
|
75
|
+
selectSkipped: (names) => `not run (by --only/--skip): ${names} — their state is unknown, they are not "green"`,
|
|
76
|
+
jobsBad: (v) => `--jobs expects a whole number from 1: "${v}" will not do. A one-by-one run passed off as parallel would be a lie, so stopping.`,
|
|
68
77
|
rulesByHuman: (total, machine, human) =>
|
|
69
78
|
`${human} of ${total} rules in the entry point are guarded by a HUMAN, ${machine} by a machine.`,
|
|
70
79
|
rulesByHumanWhy: "A rule guarded by a human is guarded by nobody the day the human is busy. That is the hole this kit exists to close — and the reminder belongs to the human, not only to the agent. Counted in the ENTRY POINT only: a promise kept in any other file is checked by nothing at all, and this kit will not tell you it exists.",
|
|
@@ -104,6 +113,19 @@ export const en = {
|
|
|
104
113
|
`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
114
|
coversUnprovenHow: (cmd) => `settle it: add those rules to the linter, or install the entry — ${cmd}`,
|
|
106
115
|
totalCovered: (n) => `held by another arbiter ${n}`,
|
|
116
|
+
blindHeading: (behind) => `The probe${behind ? ` (${behind} commits ago)` : ""} planted defects in your files — your checks did NOT catch them:`,
|
|
117
|
+
blindRan: (g) => `gate ${g} is declared and was run — and still missed the defect in this file`,
|
|
118
|
+
blindInstalled: "declared, but the probe did not run this gate (added later or too slow) — the next probe will show whether it catches",
|
|
119
|
+
blindMore: (cmd) => `what was planted and where — ${cmd}`,
|
|
120
|
+
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 project files are not changed).`,
|
|
121
|
+
todoRest: (n) => `The other entries that apply here (${n}):`,
|
|
122
|
+
todoRestHow: (self) => `install any: ${self} add <name> · what it catches and why: ${self} why <name>`,
|
|
123
|
+
startWith: "Start with these three — born from a real failure, and each closes with one ready command:",
|
|
124
|
+
startCmd: (cmd) => `one line, no kit needed: ${cmd}`,
|
|
125
|
+
startTool: (url) => `the tool: ${url}`,
|
|
126
|
+
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.",
|
|
127
|
+
haveAlready: (n) => `Checks you ALREADY have (${n}) — found in your own files, not invented:`,
|
|
128
|
+
haveAlreadyHow: (line) => `declare them and a machine holds them, not your attention. In .aqk.yml, under gates: ${line}`,
|
|
107
129
|
total: "Total:",
|
|
108
130
|
totalHeld: (n) => `held by a machine ${n}`,
|
|
109
131
|
totalTodo: (n) => `applicable but not installed ${n}`,
|
|
@@ -120,6 +142,8 @@ export const en = {
|
|
|
120
142
|
`either fix them and drop them from advisory, or admit the rule does not exist.`,
|
|
121
143
|
runHeading: "Running the declared gates",
|
|
122
144
|
timeout: "did not finish within 5 minutes",
|
|
145
|
+
running: (i, n) => `[${i}/${n}] running…`,
|
|
146
|
+
proving: "checking that the gates catch defects on their own samples…",
|
|
123
147
|
exitCode: (code) => `exit ${code}`,
|
|
124
148
|
moreLines: (n) => `… and ${n} more lines`,
|
|
125
149
|
declaredNotRun: (n) => `${n} gates declared, but never run.`,
|
|
@@ -186,6 +210,7 @@ export const en = {
|
|
|
186
210
|
has_docker: ["no Dockerfile or compose", "docker is already here"],
|
|
187
211
|
has_deps: ["no dependency file in sight", "dependencies are declared"],
|
|
188
212
|
has_tests: ["no tests in sight", "tests exist"],
|
|
213
|
+
has_biome: ["the linter is not Biome", "the project's linter is Biome"],
|
|
189
214
|
has_env: ["no environment file", "an environment file exists"],
|
|
190
215
|
has_agent_config: ["the agent was never configured here", "agent settings exist"],
|
|
191
216
|
has_agent_entry: ["no entry point for an agent here", "an entry point for an agent exists"],
|
package/tool/i18n/ru-docs.mjs
CHANGED
|
@@ -68,18 +68,41 @@ const ruDocs = {
|
|
|
68
68
|
runClean: (when) => `Последний прогон ${when} — красных нет.`,
|
|
69
69
|
runRed: (when, names) => `Последний прогон ${when} — КРАСНЫЕ: ${names}.`,
|
|
70
70
|
andMore: (n) => `и ещё ${n}`,
|
|
71
|
-
skipped: (n) => `Не запускались: ${n} — инструмента нет на этой
|
|
71
|
+
skipped: (n) => `Не запускались: ${n} — пропущены по --only/--skip или инструмента нет на этой машине; их состояние неизвестно.`,
|
|
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
|
"Проба покрытия: в проверенных местах каждый применимый класс кто-то ловит" +
|
|
80
81
|
(behind ? ` (отстала на ${behind} коммитов)` : "") + ".",
|
|
81
82
|
ratchets: (list) => `Храповики: ${list}. Список может только укорачиваться, увеличивать нельзя.`,
|
|
82
83
|
where: (entry) => `Свод правил: ${entry}. Чем доказан диф: \`aqk report --since main\`.`,
|
|
84
|
+
nextTitle: "Дальше, по порядку — вычислено из этого репозитория, а не общий совет:",
|
|
85
|
+
// По виду шага — таблица, а не лесенка тернарников: её не держат в голове, и наш же
|
|
86
|
+
// complexity-limit её поймал.
|
|
87
|
+
nextStep: {
|
|
88
|
+
init: () => "Заведи стандарт: `aqk init` — без .aqk.yml `aqk add` откажет.",
|
|
89
|
+
adopt: (s) => `Объяви проверки, которые у проекта уже есть: в .aqk.yml, в gates: ${s.gates.map((g) => `${g.name}: "${g.cmd}"`).join(", ")}. Затем \`aqk doctor --run\`.`,
|
|
90
|
+
blind: (s) => `Брак «${s.slug}», подсаженный пробой в ${s.file}, ваши проверки НЕ поймали.` +
|
|
91
|
+
(s.command ? ` Поймать сейчас: \`${s.command}\`.` : "") + ` Держать всегда: \`aqk add ${s.slug}\`.`,
|
|
92
|
+
start: (s) => `Поставь ${s.slug}: \`aqk add ${s.slug}\`` + (s.command ? ` (одной строкой, без комплекта: \`${s.command}\`)` : "") +
|
|
93
|
+
", затем `aqk prove` — гейт обязан покраснеть на своём красном образце.",
|
|
94
|
+
},
|
|
95
|
+
nextMore: (n) => `И ещё ${n} — весь список: \`aqk doctor\`.`,
|
|
96
|
+
whenTitle: "Когда что делать:",
|
|
97
|
+
whenCommit: (hook) => hook === true
|
|
98
|
+
? "Перед коммитом → хук `.git/hooks/pre-commit` прогонит проверки сам; не обходи его (`--no-verify`)."
|
|
99
|
+
: "Перед коммитом → `aqk doctor --run --since main`: хука перед коммитом нет, само это не случится.",
|
|
100
|
+
whenRules: [
|
|
101
|
+
"Добавил или поменял проверку → `aqk prove`: гейт обязан покраснеть на своём красном образце, иначе он не проверяет ничего.",
|
|
102
|
+
"Пишешь правило в свод → рядом метка арбитра `<!-- aqk: имя-гейта -->`; гейта нет — `<!-- aqk: человек -->`, и это признание, а не проверка.",
|
|
103
|
+
"Проверка мешает → не ослабляй её (`|| true`, `--exit-zero`, подавление без кода правила): остановись и спроси владельца.",
|
|
104
|
+
"Считаешь, что готово → `aqk report --since main`: каждый изменённый файл должен быть назван проверкой.",
|
|
105
|
+
],
|
|
83
106
|
mapTitle: "ЧТО УМЕЕТ ЭТОТ ИНСТРУМЕНТ. Полный список команд — не пересказ, а тот же список,\nиз которого собрана справка:",
|
|
84
107
|
rulesTitle: (e) => `СВОД ПРАВИЛ ЭТОГО ПРОЕКТА (${e}) — дословно, целиком. Это не приглашение\nпрочитать: он уже здесь.`,
|
|
85
108
|
hookAlready: (p) => `хук уже стоит в ${p} — ничего не меняю.`,
|
|
@@ -197,6 +220,7 @@ const ruDocs = {
|
|
|
197
220
|
},
|
|
198
221
|
],
|
|
199
222
|
report: {
|
|
223
|
+
skippedBySelect: "не запускался (по --only/--skip), состояние неизвестно",
|
|
200
224
|
title: "aqk doctor --run",
|
|
201
225
|
version: "версия",
|
|
202
226
|
level: "уровень",
|
package/tool/i18n/ru-gates.mjs
CHANGED
|
@@ -160,6 +160,7 @@ export const ruGates = {
|
|
|
160
160
|
"методички в .aqk/docs/ остаются на русском — решение, а не недоделка.",
|
|
161
161
|
created: (n) => `создано (${n}):`,
|
|
162
162
|
andMore: (n) => `… и ещё ${n}`,
|
|
163
|
+
ignored: (list) => `служебные файлы комплекта дописаны в .gitignore — это состояние машины, в git ему не место: ${list}`,
|
|
163
164
|
kept: (n) => `уже были на месте, не тронуты (${n}):`,
|
|
164
165
|
overwrite: (cmd) => `перезаписать: ${cmd}`,
|
|
165
166
|
nextTitle: "Что дальше — по порядку:",
|
|
@@ -257,16 +258,24 @@ export const ruGates = {
|
|
|
257
258
|
badEvery: (v) => `в манифесте probe: «${v}» — это не число коммитов. Проба НЕ делается: подставить умолчание значило бы делать не то, что написано.`,
|
|
258
259
|
autoFirst: "пробы покрытия здесь ещё не делали — делаю её сам. Выключить: AQK_PROBE=0",
|
|
259
260
|
auto: (n) => `прошло ${n} коммитов с прошлой пробы — делаю её сам. Выключить: AQK_PROBE=0`,
|
|
261
|
+
autoNotInCi: (cmd) => `в конвейере проба сама не запускается — это минуты сюрпризом в быстрой проверке; поставьте её отдельной задачей: ${cmd} (или AQK_PROBE=1)`,
|
|
260
262
|
title: "aqk probe — чего объявленные проверки не видят",
|
|
261
263
|
method: (files, entries, gates) =>
|
|
262
264
|
`способ: красный образец записи каталога подсаживается в КОПИЮ проекта, и там гоняются ` +
|
|
263
265
|
`ОБЪЯВЛЕННЫЕ гейты — команда берётся как написана, в неё ничего не подставляется. ` +
|
|
264
266
|
`Файлов: ${files}, применимых записей: ${entries}, гейтов зелёных на чистом дереве: ${gates}. ` +
|
|
265
|
-
|
|
267
|
+
`Файлы проекта не меняются; итог пробы пишется в .aqk/last-probe.md (он в .gitignore после init).`,
|
|
266
268
|
fixes: (n) => `починок в истории: ${n}`,
|
|
267
269
|
caught: (names) => `ловит: ${names}`,
|
|
268
270
|
blind: "НЕ ЛОВИТ НИКТО",
|
|
269
271
|
unknown: "проверить нечем — гейт не состоялся (нет делегированной программы)",
|
|
272
|
+
nameless: "не назвал подсаженный файл",
|
|
273
|
+
planting: "краснеет и на зелёном образце — падает от самой подсадки",
|
|
274
|
+
unattributed: (g) => `покраснело: ${g} — поимка не доказана`,
|
|
275
|
+
blindWhere: (file, fixes) => `где: ${file} — починок в истории: ${fixes}`,
|
|
276
|
+
blindWhat: "что подсадили в ваш файл:",
|
|
277
|
+
blindFix: (cmd) => `поймать прямо сейчас, без комплекта: ${cmd}`,
|
|
278
|
+
blindTool: (url) => `инструмент: ${url}`,
|
|
270
279
|
install: (cmd) => `закрыть: ${cmd}`,
|
|
271
280
|
noSampleFor: (ext) => `в каталоге нет красного образца под «${ext}» — проверить нечем`,
|
|
272
281
|
noGates: (cmd) => `гейтов не объявлено — пробовать нечем. Сначала: ${cmd}`,
|
package/tool/i18n/ru.mjs
CHANGED
|
@@ -45,6 +45,7 @@ export const ru = {
|
|
|
45
45
|
blob: "собрать методички в один файл GOD_AI.md",
|
|
46
46
|
learn: "кандидаты в правила из локальной переписки: сказано вслух и не записано",
|
|
47
47
|
context: "состояние проекта одним блоком — для контекста агента, а не для чтения",
|
|
48
|
+
vitals: "подключено ли то, чем комплект работает: инструменты гейтов, хуки, свежесть версии",
|
|
48
49
|
contextInstall: "то же самое, но целиком — карта и свод правил — и хуком в контекст",
|
|
49
50
|
report: "обязательная форма отчёта: что стоит, что нет, что не прочитано; --since <ссылка> — ещё и чем доказан диф",
|
|
50
51
|
badge: "значок уровня для README — и проверка, что он не врёт",
|
|
@@ -66,6 +67,14 @@ export const ru = {
|
|
|
66
67
|
gitignore: "гигиена репозитория",
|
|
67
68
|
git: "проект под контролем версий",
|
|
68
69
|
|
|
70
|
+
runtimeTracked: (f, cmd) => `${f} отслеживается git — каждый прогон его переписывает, и в дереве вечно висит изменённый файл. Вынуть: ${cmd}`,
|
|
71
|
+
runtimeNotIgnored: (f, cmd) => `${f} — состояние этой машины, а git его видит: одно \`git add .\`, и он в коммите. Спрятать: ${cmd}`,
|
|
72
|
+
layoutAdvice: "нет — это совет, прогон не роняет",
|
|
73
|
+
coversImpossible: (entry, gate, linter) => `заявка «${gate} держит ${entry}» неверна: у ${linter} нет правила под этот класс — закрывать его нечем`,
|
|
74
|
+
coversCantCheck: (entry, gate) => `заявку «${gate} держит ${entry}» проверить не умею: линтер гейта не распознан или правил записи для него нет — принято на слово`,
|
|
75
|
+
selectUnknown: (names, groups) => `--only/--skip: не знаю «${names}» — это не гейт из gates: и не группа из groups:${groups ? ` (группы: ${groups})` : ""}. Прогон всего подряд вместо пропуска был бы неправдой, поэтому стоп.`,
|
|
76
|
+
selectSkipped: (names) => `не запускались (по --only/--skip): ${names} — их состояние неизвестно, это не «зелёные»`,
|
|
77
|
+
jobsBad: (v) => `--jobs ждёт целое число от 1: «${v}» не подходит. Прогон по одному под видом параллельного был бы неправдой, поэтому стоп.`,
|
|
69
78
|
rulesByHuman: (total, machine, human) =>
|
|
70
79
|
`правил в точке входа: ${total}. Сторож — ЧЕЛОВЕК у ${human}, машина у ${machine}.`,
|
|
71
80
|
rulesByHumanWhy: "Правило со сторожем-человеком не сторожит никто в тот день, когда человек занят. Это та самая дыра, ради которой комплект и написан, — и напоминание про неё нужно человеку, а не только агенту. Считаны правила ТОЧКИ ВХОДА: обещание, живущее в любом другом файле, не проверяет вообще ничто, и комплект даже не скажет вам, что оно есть.",
|
|
@@ -105,6 +114,19 @@ export const ru = {
|
|
|
105
114
|
`заявка не подтверждена: «${e}» объявлена закрытой гейтом «${g}», но ни его команда, ни\n конфиг линтера не называют правил ${codes} — запись может быть не закрыта ничем`,
|
|
106
115
|
coversUnprovenHow: (cmd) => `подтверди: добавь эти правила в линтер, либо поставь запись — ${cmd}`,
|
|
107
116
|
totalCovered: (n) => `закрыто другим арбитром ${n}`,
|
|
117
|
+
blindHeading: (behind) => `Проба${behind ? ` (коммитов с тех пор: ${behind})` : ""} подсадила брак в ваши файлы — ваши проверки его НЕ ПОЙМАЛИ:`,
|
|
118
|
+
blindRan: (g) => `гейт ${g} стоит и прогонялся — а брак в этом файле пропустил`,
|
|
119
|
+
blindInstalled: "объявлено, но проба этот гейт не гоняла (поставлен позже или медленный) — поймает ли, покажет следующая",
|
|
120
|
+
blindMore: (cmd) => `что именно подсадили и куда — ${cmd}`,
|
|
121
|
+
probeNever: (cmd) => `Ловят ли ваши проверки настоящий брак, ещё не проверялось: ${cmd} подсадит его в копию проекта и покажет (минута-две, файлы проекта не меняет).`,
|
|
122
|
+
todoRest: (n) => `Остальные записи, применимые у вас (${n}):`,
|
|
123
|
+
todoRestHow: (self) => `поставить любую: ${self} add <имя> · что она ловит и зачем: ${self} why <имя>`,
|
|
124
|
+
startWith: "Начните с этих трёх — они родились из настоящего отказа и закрываются одной готовой командой:",
|
|
125
|
+
startCmd: (cmd) => `одной строкой, без комплекта: ${cmd}`,
|
|
126
|
+
startTool: (url) => `инструмент: ${url}`,
|
|
127
|
+
startHook: "Прогнать руками — разовый героизм. Чтобы это случалось перед каждым пушем: pre-commit (репозиторий https://github.com/arsen-ask-lx/Agent_Quality_Kit, хуки aqk / aqk-doctor) либо обычный .git/hooks/pre-push.",
|
|
128
|
+
haveAlready: (n) => `Проверки, которые у вас УЖЕ ЕСТЬ (${n}) — прочитаны в ваших файлах, не выдуманы:`,
|
|
129
|
+
haveAlreadyHow: (line) => `объявите их — и держать будет машина, а не ваше внимание. В .aqk.yml, в gates: ${line}`,
|
|
108
130
|
total: "Итого:",
|
|
109
131
|
totalHeld: (n) => `держит машина ${n}`,
|
|
110
132
|
totalTodo: (n) => `применимо но не поставлено ${n}`,
|
|
@@ -121,6 +143,8 @@ export const ru = {
|
|
|
121
143
|
`либо почини и убери из advisory, либо признай, что правила нет.`,
|
|
122
144
|
runHeading: "Прогон объявленных гейтов",
|
|
123
145
|
timeout: "не уложился в 5 минут",
|
|
146
|
+
running: (i, n) => `[${i}/${n}] идёт…`,
|
|
147
|
+
proving: "проверяю, что гейты ловят брак на своих образцах…",
|
|
124
148
|
exitCode: (code) => `код ${code}`,
|
|
125
149
|
moreLines: (n) => `… и ещё ${n} строк`,
|
|
126
150
|
declaredNotRun: (n) => `${n} гейтов объявлено, но не запускалось.`,
|
|
@@ -192,6 +216,7 @@ export const ru = {
|
|
|
192
216
|
has_docker: ["нет Dockerfile или compose", "docker уже есть"],
|
|
193
217
|
has_deps: ["не видно файла зависимостей", "зависимости объявлены"],
|
|
194
218
|
has_tests: ["не видно тестов", "тесты есть"],
|
|
219
|
+
has_biome: ["линтер не Biome", "линтер проекта — Biome"],
|
|
195
220
|
has_env: ["нет файла окружения", "файл окружения есть"],
|
|
196
221
|
has_agent_config: ["агента здесь не настраивали", "настройки агента есть"],
|
|
197
222
|
has_agent_entry: ["свода для агента здесь нет", "свод для агента есть"],
|