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/tool/selfcheck/syntax.sh
CHANGED
|
@@ -15,8 +15,16 @@ BAD=0
|
|
|
15
15
|
for F in $(git ls-files 'tool/*.mjs' 'tool/**/*.mjs' 2>/dev/null); do
|
|
16
16
|
node --check "$F" || BAD=1
|
|
17
17
|
done
|
|
18
|
+
# Разбираем тем интерпретатором, который объявлен в шебанге, а не всегда bash. `bash -n`
|
|
19
|
+
# принимает bash-измы («<<<», массивы, «local») в файле, который объявлен как `sh`, — и
|
|
20
|
+
# переносимость ломается молча, на чужой машине с dash. Так и вышло: четыре гейта, объявленные
|
|
21
|
+
# `#!/usr/bin/env sh`, получили «<<<» и прошли проверку синтаксиса зелёными.
|
|
18
22
|
for F in $(git ls-files '*.sh' 2>/dev/null); do
|
|
19
|
-
|
|
23
|
+
case "$(head -1 "$F")" in
|
|
24
|
+
*bash*) SH=bash ;;
|
|
25
|
+
*) SH=sh ;;
|
|
26
|
+
esac
|
|
27
|
+
"$SH" -n "$F" || BAD=1
|
|
20
28
|
done
|
|
21
29
|
|
|
22
30
|
[ "$BAD" -eq 0 ] || echo " почини: файл не разбирается — до него не доедет ни одна другая проверка."
|
package/tool/selfcheck/units.mjs
CHANGED
|
@@ -11,8 +11,9 @@
|
|
|
11
11
|
|
|
12
12
|
import test from "node:test";
|
|
13
13
|
import assert from "node:assert/strict";
|
|
14
|
-
import { parseManifest, manifestWithGate } from "../lib/manifest.mjs";
|
|
14
|
+
import { parseManifest, manifestWithGate, unknownKeys } from "../lib/manifest.mjs";
|
|
15
15
|
import { triggerVerdict, recipeFor, stems, overlap, EXT_LANG, whichSync } from "../lib/repo.mjs";
|
|
16
|
+
import { assessBaseline, ITEMS, BASELINE_TOTAL } from "../lib/baseline.mjs";
|
|
16
17
|
import { CATALOGS, pickLang, L } from "../i18n/index.mjs";
|
|
17
18
|
import { badgeMarkdown, BADGE_RE, placesToCheck } from "../commands/badge.mjs";
|
|
18
19
|
import { dirname } from "node:path";
|
|
@@ -199,3 +200,77 @@ test("значок ищется в точке входа и в README, без п
|
|
|
199
200
|
assert.equal(places.filter((p) => p === "README.md").length, 1);
|
|
200
201
|
assert.ok(placesToCheck({}).includes("README.md"), "без entry README всё равно проверяется");
|
|
201
202
|
});
|
|
203
|
+
|
|
204
|
+
// ЗАЧЕМ. Опечатка в имени поля молча означала «поля нет»: `gate:` вместо `gates:` давало
|
|
205
|
+
// вердикт «гейтов не объявлено», а не «в манифесте опечатка». Тишина неотличима от успеха —
|
|
206
|
+
// тот самый дефект, ради которого весь стандарт существует, только внутри нас самих.
|
|
207
|
+
test("опечатка в поле манифеста называется, а не молчит", () => {
|
|
208
|
+
assert.deepEqual(unknownKeys(parseManifest("aqk: 1\ngate:\n smoke: \"bash x.sh\"\n")), ["gate"]);
|
|
209
|
+
assert.deepEqual(unknownKeys(parseManifest("aqk: 1\nrules: kit/rules\nlessons: incidents\n")), []);
|
|
210
|
+
// Пустой и отсутствующий манифест — не повод ругаться на поля.
|
|
211
|
+
assert.deepEqual(unknownKeys(null), []);
|
|
212
|
+
assert.deepEqual(unknownKeys({}), []);
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
// ЗАЧЕМ. Разбор резал строку по «#» безусловно, в том числе внутри кавычек. Команда с решёткой
|
|
216
|
+
// — `--format "...,c#,..."`, `grep '#!'`, любой цвет `#fff` — молча обрезалась, и гейт запускал
|
|
217
|
+
// НЕ ТУ команду, которая объявлена. Объявленное и исполняемое разошлись бы беззвучно: ровно
|
|
218
|
+
// тот класс, ради которого стандарт существует. Найдено при правке рецепта duplicate-code.
|
|
219
|
+
test("решётка внутри кавычек не считается комментарием", () => {
|
|
220
|
+
const m = parseManifest('gates:\n dup: "npx jscpd --format \"java,c#,php\" ."\n');
|
|
221
|
+
assert.equal(m.gates.dup, 'npx jscpd --format "java,c#,php" .');
|
|
222
|
+
// Настоящий комментарий после команды по-прежнему срезается.
|
|
223
|
+
const c = parseManifest('gates:\n x: "bash a.sh" # пояснение\n');
|
|
224
|
+
assert.equal(c.gates.x, "bash a.sh");
|
|
225
|
+
// И комментарий на отдельной строке.
|
|
226
|
+
assert.deepEqual(Object.keys(parseManifest("# только комментарий\naqk: 1\n")), ["aqk"]);
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
// ЗАЧЕМ. Пункты baseline проверяются НАЛИЧИЕМ признака, и признак обязан быть семейством, а не
|
|
230
|
+
// одним именем: список, знающий только про npm, объявил бы половину мира несоответствующей.
|
|
231
|
+
// Проверяем именно нейтральность — что пункт засчитывается по маркеру любой экосистемы.
|
|
232
|
+
test("baseline: признак засчитывается по любой экосистеме", () => {
|
|
233
|
+
const by = (files) => Object.fromEntries(assessBaseline({ files }).map((r) => [r.key, r]));
|
|
234
|
+
for (const lock of ["package-lock.json", "poetry.lock", "go.sum", "Cargo.lock", "Gemfile.lock", "composer.lock", "mix.lock"]) {
|
|
235
|
+
assert.equal(by([lock]).lockfile.ok, true, lock);
|
|
236
|
+
assert.deepEqual(by([lock]).lockfile.by, { kind: "file", value: lock.toLowerCase() });
|
|
237
|
+
}
|
|
238
|
+
for (const lint of [".eslintrc.json", "ruff.toml", ".golangci.yml", "clippy.toml", ".rubocop.yml", "phpstan.neon", ".swiftlint.yml"]) {
|
|
239
|
+
assert.equal(by([lint]).linter.ok, true, lint);
|
|
240
|
+
}
|
|
241
|
+
// Пустой репозиторий: ни одного признака, и ни одной ложной галочки.
|
|
242
|
+
assert.equal(assessBaseline({}).every((r) => r.ok === false), true);
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
test("baseline: гейт, факт и поле манифеста засчитываются наравне с файлом", () => {
|
|
246
|
+
const one = (arg) => Object.fromEntries(assessBaseline(arg).map((r) => [r.key, r]));
|
|
247
|
+
assert.equal(one({ gateKeys: ["secrets-not-in-code"] }).secretScan.ok, true);
|
|
248
|
+
assert.equal(one({ facts: { has_ci: true } }).pipeline.ok, true);
|
|
249
|
+
assert.equal(one({ manifest: { entry: ["AGENTS.md"] } }).machineReadable.ok, true);
|
|
250
|
+
assert.equal(one({ manifest: { entry: [] } }).machineReadable.ok, false);
|
|
251
|
+
assert.equal(one({ depsText: '"@sentry/node": "^7"' }).errorTracker.ok, true);
|
|
252
|
+
assert.equal(one({ depsText: "sentry-sdk==2.0" }).errorTracker.ok, true);
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
// Число пунктов в методичке — не выдумка кода: если методичка вырастет, а число останется,
|
|
256
|
+
// отчёт начнёт врать о том, сколько осталось человеку.
|
|
257
|
+
test("baseline: заявленное число пунктов совпадает с методичкой", async () => {
|
|
258
|
+
const { readFile } = await import("node:fs/promises");
|
|
259
|
+
const { fileURLToPath } = await import("node:url");
|
|
260
|
+
const doc = await readFile(fileURLToPath(new URL("../../kit/docs/ai/project-baseline.md", import.meta.url)), "utf8");
|
|
261
|
+
const nums = [...doc.matchAll(/^(\d+)\. \*\*/gm)].map((m) => Number(m[1]));
|
|
262
|
+
assert.equal(Math.max(...nums), BASELINE_TOTAL);
|
|
263
|
+
assert.equal(ITEMS.every((i) => i.n <= BASELINE_TOTAL), true);
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
// ЗАЧЕМ. Запись про внешний вид, показанная бэкенду или утилите командной строки, — это совет
|
|
267
|
+
// не по адресу; таким записям перестают верить, и вместе с ними всему каталогу. Признак
|
|
268
|
+
// интерфейса отличает проект со стилями от проекта на тех же языках без них.
|
|
269
|
+
test("триггер по интерфейсу отделяет фронтенд от бэкенда на том же языке", () => {
|
|
270
|
+
const rec = { trigger: { has_ui: true } };
|
|
271
|
+
const base = { langs: new Set(["typescript"]), files: 100, gateKeys: [] };
|
|
272
|
+
assert.equal(triggerVerdict(rec, { ...base, has_ui: true }).applies, true);
|
|
273
|
+
assert.equal(triggerVerdict(rec, { ...base, has_ui: false }).applies, false);
|
|
274
|
+
// Причина сокрытия называется, а не молчит: иначе «не показано» неотличимо от «нечего показать».
|
|
275
|
+
assert.equal(typeof triggerVerdict(rec, { ...base, has_ui: false }).why, "string");
|
|
276
|
+
});
|