agent-quality-kit 0.6.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +61 -2
- package/README.ru.md +61 -2
- package/kit/docs/ai/agent-harness-playbook.md +1 -1
- package/kit/docs/ready-made-rules.md +29 -4
- package/kit/gates/README.md +40 -0
- package/kit/gates/_skip.sh +61 -1
- package/kit/gates/ci-actually-fails/README.md +12 -0
- package/kit/gates/ci-actually-fails/check.sh +26 -3
- package/kit/gates/ci-actually-fails/green/.github/workflows/ci.yml +16 -0
- package/kit/gates/ci-actually-fails/red/.github/workflows/soft.yml +15 -0
- package/kit/gates/ci-not-hijackable/README.md +56 -0
- package/kit/gates/ci-not-hijackable/check.sh +73 -0
- package/kit/gates/ci-not-hijackable/gate.yml +19 -0
- package/kit/gates/ci-not-hijackable/green/.github/workflows/triage.yml +19 -0
- package/kit/gates/ci-not-hijackable/red/.github/workflows/triage.yml +18 -0
- package/kit/gates/color-from-token/check.sh +19 -3
- package/kit/gates/color-from-token/green/Button.tsx +2 -0
- package/kit/gates/commit-explains-itself/README.md +13 -3
- package/kit/gates/commit-explains-itself/check.sh +8 -4
- package/kit/gates/complexity-limit/README.md +5 -0
- package/kit/gates/complexity-limit/check.sh +27 -9
- package/kit/gates/complexity-limit/green/test_fixtures.py +14 -0
- package/kit/gates/deps-are-pinned/README.md +14 -1
- package/kit/gates/deps-are-pinned/check.sh +6 -1
- package/kit/gates/deps-are-pinned/green/pyproject-with-requirements/pyproject.toml +12 -0
- package/kit/gates/deps-are-pinned/green/pyproject-with-requirements/requirements.txt +3 -0
- package/kit/gates/deps-are-pinned/red/pyproject-loose/pyproject.toml +12 -0
- package/kit/gates/deps-are-pinned/red/pyproject-loose/requirements.txt +3 -0
- package/kit/gates/duplicate-code/README.md +11 -2
- package/kit/gates/duplicate-code/check.sh +36 -5
- package/kit/gates/duplicate-code/gate.yml +8 -0
- package/kit/gates/duplicate-code/green/imports_a.go +20 -0
- package/kit/gates/duplicate-code/green/imports_b.go +19 -0
- package/kit/gates/entry-links-exist/README.md +5 -0
- package/kit/gates/entry-links-exist/check.sh +10 -1
- package/kit/gates/entry-links-exist/green/AGENTS.md +5 -0
- package/kit/gates/file-size-limit/README.md +9 -2
- package/kit/gates/file-size-limit/check.sh +14 -2
- package/kit/gates/gate-not-weakened/check.sh +13 -1
- package/kit/gates/hook-actually-fires/README.md +74 -0
- package/kit/gates/hook-actually-fires/check.sh +183 -0
- package/kit/gates/hook-actually-fires/gate.yml +15 -0
- package/kit/gates/hook-actually-fires/green/.claude/hooks/hooks.json +3 -0
- package/kit/gates/hook-actually-fires/green/.claude/settings.json +74 -0
- package/kit/gates/hook-actually-fires/green/.claude/settings.local.json +74 -0
- package/kit/gates/hook-actually-fires/red/.claude/hooks/hooks.json +4 -0
- package/kit/gates/hook-actually-fires/red/.claude/settings.json +53 -0
- package/kit/gates/no-phantom-package/README.md +84 -0
- package/kit/gates/no-phantom-package/check.sh +161 -0
- package/kit/gates/no-phantom-package/gate.yml +20 -0
- package/kit/gates/no-phantom-package/green/AGENTS.md +15 -0
- package/kit/gates/no-phantom-package/red/AGENTS.md +15 -0
- package/kit/gates/no-print-in-prod/README.md +33 -39
- package/kit/gates/no-print-in-prod/gate.yml +14 -6
- package/kit/gates/personal-config-not-shared/README.md +66 -0
- package/kit/gates/personal-config-not-shared/check.sh +103 -0
- package/kit/gates/personal-config-not-shared/gate.yml +16 -0
- package/kit/gates/personal-config-not-shared/green/.aqk-tracked +9 -0
- package/kit/gates/personal-config-not-shared/red/.aqk-tracked +6 -0
- package/kit/gates/secrets-not-in-code/check.sh +29 -4
- package/kit/gates/secrets-not-in-code/green/testdata/certificate/key.pem +3 -0
- package/kit/gates/swallowed-error/README.md +36 -18
- package/kit/gates/swallowed-error/gate.yml +13 -3
- package/kit/gates/test-has-assertion/check.sh +13 -1
- package/kit/gates/test-not-adjusted/README.md +79 -0
- package/kit/gates/test-not-adjusted/check.sh +136 -0
- package/kit/gates/test-not-adjusted/gate.yml +19 -0
- package/kit/gates/test-not-adjusted/green/after/calc.py +6 -0
- package/kit/gates/test-not-adjusted/green/after/tests/test_calc.py +9 -0
- package/kit/gates/test-not-adjusted/green/before/calc.py +2 -0
- package/kit/gates/test-not-adjusted/green/before/tests/test_calc.py +5 -0
- package/kit/gates/test-not-adjusted/red/after/calc.py +2 -0
- package/kit/gates/test-not-adjusted/red/after/tests/test_calc.py +5 -0
- package/kit/gates/test-not-adjusted/red/before/calc.py +2 -0
- package/kit/gates/test-not-adjusted/red/before/tests/test_calc.py +7 -0
- package/kit/gates/todo-without-task/README.md +6 -0
- package/kit/gates/todo-without-task/check.sh +14 -2
- package/kit/gates/todo-without-task/green/app.py +1 -0
- package/kit/ratchet/ratchet.sh +70 -2
- package/kit/rules/general.md +9 -0
- package/kit/rules-en/general.md +82 -0
- package/kit/rules-en/security.md +33 -0
- package/kit/rules-en/testing.md +48 -0
- package/llms.txt +22 -1
- package/package.json +6 -2
- package/tool/commands/badge.mjs +7 -1
- package/tool/commands/context.mjs +260 -0
- package/tool/commands/doctor.mjs +72 -25
- package/tool/commands/gates.mjs +10 -4
- package/tool/commands/learn.mjs +159 -0
- package/tool/commands/project.mjs +9 -1
- package/tool/commands/prove.mjs +67 -0
- package/tool/commands/report.mjs +37 -2
- package/tool/i18n/en-docs.mjs +154 -0
- package/tool/i18n/en.mjs +70 -90
- package/tool/i18n/ru-docs.mjs +156 -0
- package/tool/i18n/ru.mjs +69 -90
- package/tool/i18n/templates-en.mjs +1 -1
- package/tool/i18n/templates-ru.mjs +1 -1
- package/tool/lib/core.mjs +37 -2
- package/tool/lib/evidence.mjs +124 -0
- package/tool/lib/manifest.mjs +63 -5
- package/tool/lib/prove.mjs +172 -0
- package/tool/lib/repo.mjs +31 -2
- package/tool/lib/scope.mjs +46 -2
- package/tool/lib/templates.mjs +3 -0
- package/tool/program.mjs +23 -22
- package/tool/selfcheck/gates.sh +66 -0
- package/tool/selfcheck/mutation.sh +21 -1
- package/tool/selfcheck/smoke.sh +519 -39
- package/tool/selfcheck/units-context.mjs +186 -0
- package/tool/selfcheck/units-evidence.mjs +83 -0
- package/tool/selfcheck/units-learn.mjs +88 -0
- package/tool/selfcheck/units-level.mjs +122 -0
- package/tool/selfcheck/units.mjs +114 -2
- package/kit/gates/no-print-in-prod/check.sh +0 -38
- package/kit/gates/no-print-in-prod/green/docs.ts +0 -15
- package/kit/gates/no-print-in-prod/green/main.go +0 -8
- package/kit/gates/no-print-in-prod/green/main.rs +0 -4
- package/kit/gates/no-print-in-prod/red/main.go +0 -8
- package/kit/gates/no-print-in-prod/red/main.rs +0 -4
- package/kit/gates/swallowed-error/check.sh +0 -54
- package/kit/gates/swallowed-error/green/run.js +0 -8
- package/kit/gates/swallowed-error/red/run.js +0 -3
- /package/kit/gates/commit-explains-itself/green/{COMMIT_MSG → .aqk-commit-msg} +0 -0
- /package/kit/gates/commit-explains-itself/red/{COMMIT_MSG → .aqk-commit-msg} +0 -0
package/tool/i18n/ru.mjs
CHANGED
|
@@ -4,9 +4,24 @@
|
|
|
4
4
|
// проверка «оба каталога несут одни и те же ключи». Иначе один язык молча отстаёт, а
|
|
5
5
|
// обещание «два языка» превращается в слова.
|
|
6
6
|
|
|
7
|
+
import { ruDocs } from "./ru-docs.mjs";
|
|
8
|
+
|
|
7
9
|
import { templates } from "./templates-ru.mjs";
|
|
8
10
|
|
|
9
11
|
export const ru = {
|
|
12
|
+
learn: {
|
|
13
|
+
title: "Сказано вслух и не записано",
|
|
14
|
+
noLogs: (p) => `логов этого проекта нет: ${p}\n Команда читает переписку Claude Code на этой машине. Пусто — значит здесь не работали.`,
|
|
15
|
+
counted: (s, typed, said, fresh) =>
|
|
16
|
+
`сессий: ${s} · напечатано человеком: ${typed} · похоже на наставление: ${said} · нет в точке входа: ${fresh}`,
|
|
17
|
+
nothing: "всё, что похоже на правило, уже стоит в точке входа",
|
|
18
|
+
andMore: (n) => `… и ещё ${n}`,
|
|
19
|
+
warn:
|
|
20
|
+
"Это КАНДИДАТЫ, а не находки: на замере из 1619 реплик маркеры дали 79, и настоящих правил " +
|
|
21
|
+
"среди них примерно половина. Решает человек. Ничего не записано на диск — команда читает " +
|
|
22
|
+
"переписку и печатает только в терминал.",
|
|
23
|
+
},
|
|
24
|
+
...ruDocs,
|
|
10
25
|
templates,
|
|
11
26
|
help: {
|
|
12
27
|
tagline: "оснастка для разработки с агентами",
|
|
@@ -14,6 +29,7 @@ export const ru = {
|
|
|
14
29
|
init: "разложить правила и методички в текущий проект",
|
|
15
30
|
initForce: "перезаписать уже существующие файлы",
|
|
16
31
|
start: "кода ещё нет: сторожа дня 0 и порядок работы",
|
|
32
|
+
prove: "доказать, что гейты ловят брак: каждый по своему красному и зелёному образцу",
|
|
17
33
|
doctor: "проверить, что разложено и чего не хватает",
|
|
18
34
|
doctorRun: "ещё и запустить объявленные гейты",
|
|
19
35
|
doctorSince: "то же, но показать только то, что внёс диф относительно ссылки",
|
|
@@ -24,13 +40,20 @@ export const ru = {
|
|
|
24
40
|
new: "заготовка своего гейта для каталога",
|
|
25
41
|
note: "записать урок в общий журнал шишек",
|
|
26
42
|
blob: "собрать методички в один файл GOD_AI.md",
|
|
27
|
-
|
|
43
|
+
learn: "кандидаты в правила из локальной переписки: сказано вслух и не записано",
|
|
44
|
+
context: "состояние проекта одним блоком — для контекста агента, а не для чтения",
|
|
45
|
+
contextInstall: "то же самое, но целиком — карта и свод правил — и хуком в контекст",
|
|
46
|
+
report: "обязательная форма отчёта: что стоит, что нет, что не прочитано; --since <ссылка> — ещё и чем доказан диф",
|
|
28
47
|
badge: "значок уровня для README — и проверка, что он не врёт",
|
|
29
48
|
noInstall: "Без установки: npx agent-quality-kit init",
|
|
30
49
|
language: "Язык вывода: AQK_LANG=en (или ru), иначе по системной локали",
|
|
31
50
|
},
|
|
32
51
|
|
|
33
52
|
doctor: {
|
|
53
|
+
baselineClash: (f) =>
|
|
54
|
+
`«--baseline» — осмотр, а не прогон: он выходит с нулём всегда, и вместе с «${f}» даёт\n` +
|
|
55
|
+
" конвейер, который не может покраснеть.\n" +
|
|
56
|
+
" почини: раздели на две команды — «doctor --baseline» и «doctor --run --min 1».",
|
|
34
57
|
docsKit: "методички — здесь оригиналы, а не копия",
|
|
35
58
|
docs: "методички",
|
|
36
59
|
rulesKit: "стандарты — здесь оригиналы, а не копия",
|
|
@@ -42,6 +65,8 @@ export const ru = {
|
|
|
42
65
|
emptyCommands: (n) => `В AGENTS.md ${n} незаполненных команд.`,
|
|
43
66
|
emptyCommandsWhy: "Агент не может выполнить пустую строку.",
|
|
44
67
|
|
|
68
|
+
levelUnproven: (cmd) => `не доказано: ${cmd}`,
|
|
69
|
+
gatesDoNotCatch: (n, cmd) => `гейтов, не ловящих брак: ${n}. Подробности: ${cmd}`,
|
|
45
70
|
levelHeading: "Уровень соответствия AQK",
|
|
46
71
|
levelNone: "нет",
|
|
47
72
|
levelNotSet: "Уровень: стандарт в этом репозитории не заведён.",
|
|
@@ -72,6 +97,10 @@ export const ru = {
|
|
|
72
97
|
sinceBadRef: (ref) => `не могу сравнить с «${ref}»: такой ссылки нет или это не репозиторий git`,
|
|
73
98
|
notScopable: "вывод без путей — дифом не сужается, оставлен красным",
|
|
74
99
|
outsideDiff: (n) => `находки есть, но вне дифа (${n})`,
|
|
100
|
+
advisoryMark: "совещательный — показано, прогон не уронен",
|
|
101
|
+
advisorySummary: (names) =>
|
|
102
|
+
`совещательные и красные: ${names.join(", ")}. Это выключенные проверки: ` +
|
|
103
|
+
`либо почини и убери из advisory, либо признай, что правила нет.`,
|
|
75
104
|
runHeading: "Прогон объявленных гейтов",
|
|
76
105
|
timeout: "не уложился в 5 минут",
|
|
77
106
|
exitCode: (code) => `код ${code}`,
|
|
@@ -138,6 +167,8 @@ export const ru = {
|
|
|
138
167
|
has_deps: ["не видно файла зависимостей", "зависимости объявлены"],
|
|
139
168
|
has_tests: ["не видно тестов", "тесты есть"],
|
|
140
169
|
has_env: ["нет файла окружения", "файл окружения есть"],
|
|
170
|
+
has_agent_config: ["агента здесь не настраивали", "настройки агента есть"],
|
|
171
|
+
has_agent_entry: ["свода для агента здесь нет", "свод для агента есть"],
|
|
141
172
|
has_ui: ["не видно стилей и компонентов интерфейса", "интерфейс есть: стили или компоненты"],
|
|
142
173
|
},
|
|
143
174
|
},
|
|
@@ -167,7 +198,13 @@ export const ru = {
|
|
|
167
198
|
|
|
168
199
|
add: {
|
|
169
200
|
noSuchGate: (slug, cmd) => `Нет такого гейта: ${slug}\nСписок применимых — ${cmd}`,
|
|
170
|
-
|
|
201
|
+
toolMissing: (slug, missing) =>
|
|
202
|
+
`Записи ${slug} нужен ${missing.join(" и ")}, а его нет на этой машине.\n почини: поставь ${missing.join(" и ")} и повтори — запись делегирует готовому инструменту по устройству, своей проверки у неё нет.`,
|
|
203
|
+
noRecipe: (slug, stack, missing) =>
|
|
204
|
+
`У записи ${slug} нет команды ни под ${stack}, ни общей.` +
|
|
205
|
+
(Array.isArray(missing) && missing.length
|
|
206
|
+
? `\n почини: поставь ${missing.join(" или ")} и повтори — запись делегирует готовому инструменту по устройству, своей проверки у неё нет.`
|
|
207
|
+
: `\n почини: добавь рецепт под свой стек в gate.yml этой записи либо возьми другую.`),
|
|
171
208
|
thisStack: "этот стек",
|
|
172
209
|
needName: (cmd, doctor) => `Укажи имя гейта: ${cmd}. Список — ${doctor}`,
|
|
173
210
|
noManifest: (cmd) => `Нет .aqk.yml — сначала ${cmd}`,
|
|
@@ -220,7 +257,13 @@ export const ru = {
|
|
|
220
257
|
registryHead: (slug, stamp) =>
|
|
221
258
|
`# Реестр долга: ${slug}\n` +
|
|
222
259
|
`# Снят ${stamp}. Список разрешается ТОЛЬКО укорачивать.\n` +
|
|
223
|
-
`# Новое нарушение красит гейт; исправленное вычёркивается автоматически.\n
|
|
260
|
+
`# Новое нарушение красит гейт; исправленное вычёркивается автоматически.\n` +
|
|
261
|
+
`#\n` +
|
|
262
|
+
`# Долг без цели и срока не кончается. Цель — сколько нарушений считать погашенным долгом;\n` +
|
|
263
|
+
`# по достижении храповик скажет убрать обёртку. Срок необязателен, но если он есть,\n` +
|
|
264
|
+
`# после этой даты непогашенный долг красит гейт — срок без последствия не срок.\n` +
|
|
265
|
+
`# aqk-goal: 0\n` +
|
|
266
|
+
`# aqk-deadline:\n`,
|
|
224
267
|
recorded: (n) => `${n} нарушений записано долгом`,
|
|
225
268
|
libCopied: "обёртка скопирована в проект",
|
|
226
269
|
wrapped: "команда завёрнута в храповик",
|
|
@@ -302,6 +345,8 @@ export const ru = {
|
|
|
302
345
|
|
|
303
346
|
init: {
|
|
304
347
|
noDocs: (dir) => `Не найден корпус методичек: ${dir}\nПохоже, пакет установлен не полностью.`,
|
|
348
|
+
docsRu:
|
|
349
|
+
"методички в .aqk/docs/ остаются на русском — решение, а не недоделка.",
|
|
305
350
|
created: (n) => `создано (${n}):`,
|
|
306
351
|
andMore: (n) => `… и ещё ${n}`,
|
|
307
352
|
kept: (n) => `уже были на месте, не тронуты (${n}):`,
|
|
@@ -321,6 +366,7 @@ export const ru = {
|
|
|
321
366
|
n4c: ". Гейт стережёт существующий артефакт:",
|
|
322
367
|
n4d: "проверка на код, которого ещё нет, — мёртвое правило.",
|
|
323
368
|
burned: (cmd) => `Обжёгся на чём-то — запиши: ${cmd}`,
|
|
369
|
+
hookHint: (cmd) => `Claude Code: ${cmd} — состояние проекта будет попадать в контекст агента\n само, до первого действия. Для других агентов хука нет, и это не недоделка.`,
|
|
324
370
|
},
|
|
325
371
|
|
|
326
372
|
feedback: {
|
|
@@ -393,31 +439,27 @@ export const ru = {
|
|
|
393
439
|
nextWhy: "— прогнать всё, что объявлено",
|
|
394
440
|
},
|
|
395
441
|
|
|
396
|
-
manifestDoc: {
|
|
397
|
-
head: [
|
|
398
|
-
"# .aqk.yml — манифест Agent Quality Kit",
|
|
399
|
-
"# Что это: машиночитаемое описание того, как в этом репозитории живут агенты.",
|
|
400
|
-
"# Уровень соответствия считает `aqk doctor`. Пустое поле = ступень не пройдена,",
|
|
401
|
-
"# и это честно: заполнять заглушками бессмысленно, проверяются файлы, а не слова.",
|
|
402
|
-
],
|
|
403
|
-
entry: "# AQK-0 — что агент читает первым.",
|
|
404
|
-
rules: "# AQK-1 — где стандарты и какие проверки обязательны.",
|
|
405
|
-
gates: [
|
|
406
|
-
" # Имя: команда, возвращающая 0 или не 0. Пустое объявление защиты не даёт и бракуется",
|
|
407
|
-
" # проверкой «объявленный гейт запускается» — поэтому здесь примеры, а не заготовки.",
|
|
408
|
-
' # lint: "ruff check ."',
|
|
409
|
-
' # test: "pytest -q"',
|
|
410
|
-
" # Поставить готовую запись из каталога вместе с образцами: aqk add <имя>",
|
|
411
|
-
],
|
|
412
|
-
samples: [
|
|
413
|
-
"# AQK-2 — чем доказано, что гейты работают, и где реестры долга.",
|
|
414
|
-
"# samples: каталог с красными и зелёными образцами (гейт обязан краснеть на первом",
|
|
415
|
-
"# и молчать на втором). ratchets: списки известных нарушений, которые могут только",
|
|
416
|
-
"# укорачиваться.",
|
|
417
|
-
],
|
|
418
|
-
lessons: "# AQK-3 — где копятся уроки. Путь или адрес.",
|
|
419
|
-
},
|
|
420
442
|
|
|
443
|
+
prove: {
|
|
444
|
+
title: "aqk prove — доказательство гейтов",
|
|
445
|
+
running: "доказываю гейты по их образцам…",
|
|
446
|
+
proven: (n) => `доказано: ${n}`,
|
|
447
|
+
broken: (n) => `не ловят: ${n}`,
|
|
448
|
+
unprovable: (n) => `нечем доказать: ${n}`,
|
|
449
|
+
okRed: "краснеет на красном, молчит на зелёном",
|
|
450
|
+
redPassed: "промолчал на КРАСНОМ образце — гейт не ловит брак",
|
|
451
|
+
greenFailed: "покраснел на ЗЕЛЁНОМ образце — гейт ругается на исправный код",
|
|
452
|
+
empty: "команда пустая — объявление без команды защиты не даёт",
|
|
453
|
+
noSamples: "нет образцов — доказать нечем",
|
|
454
|
+
noTarget: "не видно, куда подставить каталог образца — команда написана руками",
|
|
455
|
+
otherRecipe: (lang) => `образцы написаны под рецепт «${lang}», а стоит другой — доказать нечем`,
|
|
456
|
+
noGates: "гейтов не объявлено — доказывать нечего",
|
|
457
|
+
noSamplesDir: "в .aqk.yml не заполнено поле samples — образцы искать негде",
|
|
458
|
+
nothingProven:
|
|
459
|
+
"ни один гейт не доказан. Уровень выше AQK-1 означал бы доверие к автору, а не факт:\n проект с гейтом «true» прошёл бы его так же, как проект с настоящей защитой.",
|
|
460
|
+
fix: (cmd) => `почини: поставь запись каталога вместе с образцами — ${cmd}`,
|
|
461
|
+
heading: "Доказательство гейтов",
|
|
462
|
+
},
|
|
421
463
|
badge: {
|
|
422
464
|
noManifest: (cmd) => `Нет .aqk.yml — уровня ещё нет. Начни с ${cmd}`,
|
|
423
465
|
notReached: (cmd) => `AQK-0 не достигнут — значок выдавать не за что. Чего не хватает: ${cmd}`,
|
|
@@ -430,69 +472,6 @@ export const ru = {
|
|
|
430
472
|
checkOk: (level, where) => `Значок совпал с прогоном: AQK-${level} — ${where}`,
|
|
431
473
|
},
|
|
432
474
|
|
|
433
|
-
report2: {
|
|
434
|
-
title: "Отчёт AQK",
|
|
435
|
-
noManifest: (cmd) => `Нет .aqk.yml — отчитываться не о чем. Начни с ${cmd}`,
|
|
436
|
-
level: "Уровень",
|
|
437
|
-
holdsTitle: "Что держит машина (прогон, а не манифест)",
|
|
438
|
-
nothingRuns: "⬜ ни один гейт не объявлен",
|
|
439
|
-
native: (prog) => `родной рецепт: ${prog}`,
|
|
440
|
-
portable: "переносимая проверка",
|
|
441
|
-
weakerTitle: "Стоит слабее возможного",
|
|
442
|
-
weaker: (progs) => `в системе есть ${progs}, но гейт стоит на переносимой проверке — она ловит меньше`,
|
|
443
|
-
missingTitle: "Чего нет",
|
|
444
|
-
nothingMissing: "✅ все применимые записи поставлены",
|
|
445
|
-
needsTool: (prog) => `нужен ${prog} — его нет в системе`,
|
|
446
|
-
notInstalled: "применимо, но не поставлено",
|
|
447
|
-
hiddenTitle: "Не применимо к этому репозиторию",
|
|
448
|
-
readTitle: "Что комплект велел прочитать",
|
|
449
|
-
readWarn:
|
|
450
|
-
"Значок означает только наличие файла на диске. Прочитан он или нет, машина не знает и " +
|
|
451
|
-
"не притворяется, что знает: это отвечает тот, кто отчитывается.",
|
|
452
|
-
ignoreTitle: "Что спрятано .aqkignore",
|
|
453
|
-
ignoreNone: "файла .aqkignore нет — ничего не спрятано",
|
|
454
|
-
ignoreWarn:
|
|
455
|
-
"Скрытое молча — тот же класс, что молчащий гейт: гейты по этим путям не смотрят вовсе. " +
|
|
456
|
-
"Строка здесь означает, что защиты там нет и не будет.",
|
|
457
|
-
whyTitle: "Зачем это нужно — коротко",
|
|
458
|
-
whyNothing: "нечего добавлять: применимое уже стоит",
|
|
459
|
-
saved: (path) => `Сохранено: ${path}`,
|
|
460
|
-
docs: {
|
|
461
|
-
baseline: "обязательный минимум проекта, без привязки к языку",
|
|
462
|
-
readyMade: "карта готовых правил: сперва ищи готовое, потом пиши своё",
|
|
463
|
-
rulesGeneral: "общие правила работы",
|
|
464
|
-
rulesTesting: "правила про тесты",
|
|
465
|
-
rulesSecurity: "правила про безопасность",
|
|
466
|
-
},
|
|
467
|
-
},
|
|
468
475
|
|
|
469
|
-
levels: [
|
|
470
|
-
{
|
|
471
|
-
title: "манифест и точка входа",
|
|
472
|
-
need: "создай .aqk.yml и укажи в entry файл, который агент читает первым (AGENTS.md)",
|
|
473
|
-
gives: "любой инструмент понимает, что читать в этом репозитории",
|
|
474
|
-
},
|
|
475
|
-
{
|
|
476
|
-
title: "правила и работающие гейты",
|
|
477
|
-
need: "укажи rules (каталог стандартов) и заполни хотя бы один гейт в gates реальной командой",
|
|
478
|
-
gives: "проверки объявлены командами, а не описаны словами",
|
|
479
|
-
},
|
|
480
|
-
{
|
|
481
|
-
title: "гейты доказаны, долг под храповиком",
|
|
482
|
-
need: "заведи samples (красные и зелёные образцы гейтов) и ratchets (реестры долга)",
|
|
483
|
-
gives: "гейт доказал, что ловит брак и молчит на исправном коде",
|
|
484
|
-
},
|
|
485
|
-
{
|
|
486
|
-
title: "уроки возвращаются в работу",
|
|
487
|
-
need: "укажи lessons — путь или адрес журнала, где каждый инцидент даёт вывод",
|
|
488
|
-
gives: "проект учится: одна и та же шишка не набивается дважды",
|
|
489
|
-
},
|
|
490
|
-
],
|
|
491
476
|
|
|
492
|
-
report: {
|
|
493
|
-
title: "aqk doctor --run",
|
|
494
|
-
version: "версия",
|
|
495
|
-
level: "уровень",
|
|
496
|
-
summary: (ok, all) => `итого: ${ok} из ${all} зелёных`,
|
|
497
|
-
},
|
|
498
477
|
};
|
|
@@ -18,7 +18,7 @@ const AGENTS_MD = `# AGENTS.md
|
|
|
18
18
|
- **Only the files the task is about.** No fixing things "while we are here". <!-- aqk: human -->
|
|
19
19
|
- **Done = proven.** Name the arbiter: a test, a live run, a check against the source. <!-- aqk: human -->
|
|
20
20
|
"Looks like it works" is not done.
|
|
21
|
-
- **Never swallow an error.** Either handled and logged, or re-raised. <!-- aqk:
|
|
21
|
+
- **Never swallow an error.** Either handled and logged, or re-raised. <!-- aqk: human -->
|
|
22
22
|
- **A fork in the road is a question for a human.** Departing from an agreed decision is not <!-- aqk: human -->
|
|
23
23
|
documented with a code comment.
|
|
24
24
|
- **Report on your work with the kit with a command, not with words.** When you are done, run <!-- aqk: human -->
|
|
@@ -23,7 +23,7 @@ const AGENTS_MD = `# AGENTS.md
|
|
|
23
23
|
- **Только файлы из задачи.** Заодно ничего не чиним. <!-- aqk: человек -->
|
|
24
24
|
- **Готово = доказано.** Назови арбитра: тест, живой прогон, сверка с источником. «Выглядит <!-- aqk: человек -->
|
|
25
25
|
рабочим» — не готово.
|
|
26
|
-
- **Ошибку не глотать.** Либо обработана и залогирована, либо проброшена. <!-- aqk:
|
|
26
|
+
- **Ошибку не глотать.** Либо обработана и залогирована, либо проброшена. <!-- aqk: человек -->
|
|
27
27
|
- **Развилка — вопрос человеку.** Отступление от принятого решения не оформляется комментарием <!-- aqk: человек -->
|
|
28
28
|
в коде.
|
|
29
29
|
- **Отчёт о работе с комплектом — командой, а не словами.** Закончил — выполни \`aqk report\`. <!-- aqk: человек -->
|
package/tool/lib/core.mjs
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
// пределе в 500. Разделена по назначению, а не пополам — так требует наше же правило про
|
|
5
5
|
// размер файла. Зависимостей по-прежнему нет ни одной: только встроенные модули Node.
|
|
6
6
|
|
|
7
|
+
import { LANG } from "../i18n/index.mjs";
|
|
7
8
|
import { access, readdir, mkdir, copyFile, writeFile } from "node:fs/promises";
|
|
8
9
|
import { constants } from "node:fs";
|
|
9
10
|
import { fileURLToPath } from "node:url";
|
|
@@ -17,7 +18,12 @@ const PKG_ROOT = resolve(HERE, "..", "..");
|
|
|
17
18
|
const CWD = process.cwd();
|
|
18
19
|
|
|
19
20
|
const DOCS_SRC = join(PKG_ROOT, "kit", "docs");
|
|
20
|
-
|
|
21
|
+
// Правила переносятся в проект НА ЯЗЫКЕ ВЫВОДА. Русский текст в англоязычном проекте — не
|
|
22
|
+
// мелочь: это первое, что там откроет человек, и первое, чего он не прочитает. Русская версия
|
|
23
|
+
// остаётся источником истины, английская — переводом; расходиться им нельзя, и совпадение
|
|
24
|
+
// НАБОРА ФАЙЛОВ сторожит модульная проверка. Совпадение содержания машина не сторожит — это
|
|
25
|
+
// названо в AGENTS.md, а не спрятано.
|
|
26
|
+
const RULES_SRC = join(PKG_ROOT, "kit", LANG === "en" ? "rules-en" : "rules");
|
|
21
27
|
const TARGET_DIR = ".aqk";
|
|
22
28
|
|
|
23
29
|
const c = {
|
|
@@ -51,6 +57,35 @@ function selfCmd() {
|
|
|
51
57
|
|
|
52
58
|
const SELF = selfCmd();
|
|
53
59
|
|
|
60
|
+
// КАРТА КОМАНД — ОДИН список на справку в терминале и на карту, уходящую в контекст агента.
|
|
61
|
+
// Два списка рядом через месяц врут по-разному, и непонятно, какой настоящий: это записано
|
|
62
|
+
// у нас в CLAUDE.md про своды правил и верно здесь буквально так же. Модульная проверка
|
|
63
|
+
// сторожит, что список не отстал от диспетчера в program.mjs.
|
|
64
|
+
function commandRows(L) {
|
|
65
|
+
const h = L.help;
|
|
66
|
+
return [
|
|
67
|
+
{ name: "init", args: "", text: h.init },
|
|
68
|
+
{ name: "init", args: "--force", text: h.initForce },
|
|
69
|
+
{ name: "start", args: "", text: h.start },
|
|
70
|
+
{ name: "doctor", args: "", text: h.doctor },
|
|
71
|
+
{ name: "doctor", args: "--run", text: h.doctorRun },
|
|
72
|
+
{ name: "doctor", args: "--run --since main", text: h.doctorSince },
|
|
73
|
+
{ name: "prove", args: "", text: h.prove },
|
|
74
|
+
{ name: "add", args: h.name, text: h.add },
|
|
75
|
+
{ name: "find", args: '"…"', text: h.find },
|
|
76
|
+
{ name: "why", args: '"…"', text: h.why },
|
|
77
|
+
{ name: "ratchet", args: h.name, text: h.ratchet },
|
|
78
|
+
{ name: "new", args: h.name, text: h.new },
|
|
79
|
+
{ name: "note", args: '"…"', text: h.note },
|
|
80
|
+
{ name: "blob", args: "", text: h.blob },
|
|
81
|
+
{ name: "report", args: "", text: h.report },
|
|
82
|
+
{ name: "learn", args: "", text: h.learn },
|
|
83
|
+
{ name: "context", args: "", text: h.context },
|
|
84
|
+
{ name: "context", args: "--full --install", text: h.contextInstall },
|
|
85
|
+
{ name: "badge", args: "", text: h.badge },
|
|
86
|
+
];
|
|
87
|
+
}
|
|
88
|
+
|
|
54
89
|
function die(msg) {
|
|
55
90
|
console.error(c.red(msg));
|
|
56
91
|
process.exit(1);
|
|
@@ -108,5 +143,5 @@ export {
|
|
|
108
143
|
copyDir, writeIfAbsent,
|
|
109
144
|
PKG_ROOT, CWD, DOCS_SRC, RULES_SRC, TARGET_DIR, docPath,
|
|
110
145
|
MANIFEST, GATES_SRC, PROJECT_GATES, RATCHET_DIR, RATCHET_LIB,
|
|
111
|
-
SELF, REPO_URL, c, exists, die, FEEDBACK_MARK,
|
|
146
|
+
SELF, REPO_URL, c, exists, die, FEEDBACK_MARK, commandRows,
|
|
112
147
|
};
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
// tool/lib/evidence.mjs — привязка доказательства к дифу.
|
|
2
|
+
//
|
|
3
|
+
// ЗАЧЕМ. «Готово = доказано» — центральное правило свода, и оно единственное из четырнадцати,
|
|
4
|
+
// за которым не следила машина: в `AGENTS.md` его сторожем честно записан человек. Цена этого
|
|
5
|
+
// измерена 2026-09-08 прогоном чужого инструмента по восьми нашим коммитам: `publish.yml`
|
|
6
|
+
// менялся и не был назван ни одной командой проверки — и именно он оказался сломан. Коммит при
|
|
7
|
+
// этом говорил «прогон: units 51, smoke 81». Утверждение было правдой и не относилось к делу.
|
|
8
|
+
//
|
|
9
|
+
// Механизм взят у [donecheck](https://github.com/AtharvaMaik/donecheck) (MIT, ноль
|
|
10
|
+
// зависимостей). Своего разбора команд не пишем — берём идею, а не код: donecheck САМ запускает
|
|
11
|
+
// команды проверки, а `doctor --run` их уже запустил, и обёртка означала бы двойной прогон
|
|
12
|
+
// всего набора гейтов. Здесь данные уже есть.
|
|
13
|
+
//
|
|
14
|
+
// ОДНО ОТЛИЧИЕ, И ОНО НАМЕРЕННОЕ. donecheck считает файл покрытым и по голому имени. В нашем
|
|
15
|
+
// каталоге имя `check.sh` носят два десятка разных файлов: мягкое сравнение объявило бы
|
|
16
|
+
// покрытым каждый из них, стоит любому гейту напечатать это слово. Ошибка в сторону «покрыто»
|
|
17
|
+
// — это тишина, а тишина здесь и есть предмет спора. Сравниваем строго, по полному пути.
|
|
18
|
+
|
|
19
|
+
import { createHash } from "node:crypto";
|
|
20
|
+
import { readFileSync } from "node:fs";
|
|
21
|
+
import { join } from "node:path";
|
|
22
|
+
import { changedFiles, pathsIn, normPath } from "./scope.mjs";
|
|
23
|
+
|
|
24
|
+
// Расширения, для которых «никто не проверил» — утверждение о деле. Документ не проверяется
|
|
25
|
+
// прогоном по своей природе, и требовать этого значило бы красить каждую правку README.
|
|
26
|
+
const CODE_EXT = new Set([
|
|
27
|
+
"c", "cjs", "cpp", "cs", "css", "go", "h", "java", "js", "json", "jsx", "kt", "mjs", "mts",
|
|
28
|
+
"php", "pl", "py", "rb", "rs", "scala", "sh", "sql", "swift", "toml", "ts", "tsx", "vue",
|
|
29
|
+
"yaml", "yml",
|
|
30
|
+
]);
|
|
31
|
+
|
|
32
|
+
// Образцы гейтов исключены по той же причине, по какой их исключает каждая сканирующая
|
|
33
|
+
// проверка каталога: они существуют, чтобы быть неправильными, и командой проверки покрыты
|
|
34
|
+
// быть не могут. Без этого замер дал восемь ложных находок из шестнадцати.
|
|
35
|
+
function isSample(p) {
|
|
36
|
+
return /(^|\/)gates\/[^/]+\/(red|green)(\/|$)/.test(p);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function ext(p) {
|
|
40
|
+
const m = /\.([A-Za-z0-9]+)$/.exec(p);
|
|
41
|
+
return m ? m[1].toLowerCase() : "";
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Файлы кода, которые внёс диф. null — если ссылка не разобрана: «сравнили не с тем» обязано
|
|
45
|
+
// отличаться от «изменений нет».
|
|
46
|
+
function changedCode(ref, cwd) {
|
|
47
|
+
const all = changedFiles(ref, cwd);
|
|
48
|
+
if (all === null) return null;
|
|
49
|
+
return [...all].map(normPath).filter((p) => CODE_EXT.has(ext(p)) && !isSample(p)).sort();
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Куда гейт был НАПРАВЛЕН: цели из его команды. Проверка, обошедшая каталог и не нашедшая
|
|
53
|
+
// ничего, файла не назовёт — и «просмотрен и чист» стало бы неотличимо от «никто не смотрел».
|
|
54
|
+
// Различить это по выводу нельзя, а по команде можно: она говорит, куда гейт направляли.
|
|
55
|
+
function targetsOf(cmd, isDir) {
|
|
56
|
+
const out = [];
|
|
57
|
+
for (const tok of String(cmd || "").split(/\s+/)) {
|
|
58
|
+
if (tok.startsWith("-")) continue;
|
|
59
|
+
const t = normPath(tok);
|
|
60
|
+
if (tok === "." || tok === "./") { out.push(""); continue; }
|
|
61
|
+
if (t && !t.includes("*") && isDir(t)) out.push(t.replace(/\/$/, ""));
|
|
62
|
+
}
|
|
63
|
+
return out;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Три состояния, а не два, и это главное в этой функции.
|
|
67
|
+
//
|
|
68
|
+
// named — гейт напечатал путь файла: он его точно видел и что-то о нём сказал;
|
|
69
|
+
// silent — гейт был направлен в каталог с этим файлом, но ничего не напечатал. Просмотрен и
|
|
70
|
+
// чист либо не просмотрен вовсе — по выводу это неразличимо, и выдавать одно за
|
|
71
|
+
// другое нельзя ни в ту, ни в другую сторону;
|
|
72
|
+
// none — ни одна команда даже не была направлена туда, где файл лежит.
|
|
73
|
+
//
|
|
74
|
+
// Двух состояний хватило ровно до первого прогона: `tool/commands/doctor.mjs` попал в «никем
|
|
75
|
+
// не проверен», хотя его обходят пять проверок — они просто промолчали, потому что нашли
|
|
76
|
+
// чисто. Замеряно 2026-09-08.
|
|
77
|
+
function coverage(files, results, isDir = () => false) {
|
|
78
|
+
const seen = results.map((r) => ({
|
|
79
|
+
name: r.name,
|
|
80
|
+
paths: pathsIn(`${r.cmd || ""}\n${r.out || ""}`),
|
|
81
|
+
targets: targetsOf(r.cmd, isDir),
|
|
82
|
+
}));
|
|
83
|
+
const covered = new Map();
|
|
84
|
+
const silent = new Map();
|
|
85
|
+
const uncovered = [];
|
|
86
|
+
for (const raw of files) {
|
|
87
|
+
const f = normPath(raw);
|
|
88
|
+
const by = seen.filter((n) => n.paths.has(f)).map((n) => n.name);
|
|
89
|
+
if (by.length) { covered.set(raw, by); continue; }
|
|
90
|
+
const aimed = seen
|
|
91
|
+
.filter((n) => n.targets.some((t) => t === "" || f === t || f.startsWith(`${t}/`)))
|
|
92
|
+
.map((n) => n.name);
|
|
93
|
+
if (aimed.length) silent.set(raw, aimed);
|
|
94
|
+
else uncovered.push(raw);
|
|
95
|
+
}
|
|
96
|
+
return { covered, silent, uncovered };
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Отпечаток того, о чём отчитывается расписка: базовый коммит, набор команд, содержимое файлов.
|
|
100
|
+
// Изменилось что угодно из этого — расписка устарела, и «прогнал, потом поправил ещё три файла»
|
|
101
|
+
// перестаёт быть неотличимым от «прогнал».
|
|
102
|
+
//
|
|
103
|
+
// files — пары [путь, содержимое]. Содержимое передаётся, а не читается здесь: вызывающий уже
|
|
104
|
+
// держит файлы в руках, а функция без ввода-вывода проверяется модульно.
|
|
105
|
+
function evidenceHash(base, commands, files) {
|
|
106
|
+
const h = createHash("sha256");
|
|
107
|
+
h.update(`base:${base || ""}\0`);
|
|
108
|
+
for (const c of commands) h.update(`cmd:${c?.cmd ?? c}\0`);
|
|
109
|
+
for (const [p, body] of [...files].sort((a, b) => String(a[0]).localeCompare(String(b[0])))) {
|
|
110
|
+
h.update(`path:${normPath(p)}\0`);
|
|
111
|
+
h.update(body === null || body === undefined ? "<missing>" : String(body));
|
|
112
|
+
h.update("\0");
|
|
113
|
+
}
|
|
114
|
+
return h.digest("hex");
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Читает содержимое для отпечатка. Отсутствующий файл — тоже факт: удаление меняет расписку.
|
|
118
|
+
function readForHash(files, cwd) {
|
|
119
|
+
return files.map((p) => {
|
|
120
|
+
try { return [p, readFileSync(join(cwd, p), "utf8")]; } catch { return [p, null]; }
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export { changedCode, coverage, evidenceHash, readForHash };
|
package/tool/lib/manifest.mjs
CHANGED
|
@@ -79,7 +79,34 @@ function parseManifest(text) {
|
|
|
79
79
|
// Список обязан совпадать с тем, что программа РЕАЛЬНО читает (`man?.<поле>` в tool/):
|
|
80
80
|
// лишнее имя здесь молча узаконивает поле, которое ни на что не влияет, — та же тишина,
|
|
81
81
|
// только с другой стороны. Сверено обходом: aqk, entry, rules, gates, samples, ratchets, lessons.
|
|
82
|
-
const KNOWN_KEYS = ["aqk", "entry", "rules", "gates", "samples", "ratchets", "lessons"];
|
|
82
|
+
const KNOWN_KEYS = ["aqk", "entry", "rules", "docs", "gates", "samples", "ratchets", "lessons", "advisory"];
|
|
83
|
+
|
|
84
|
+
// ГДЕ У ПРОЕКТА ЛЕЖИТ РАЗЛОЖЕННЫЙ КОМПЛЕКТ. Список для шапки `doctor`. До 2026-09-08 он был
|
|
85
|
+
// литеральным: `.aqk/rules`, `.aqk/docs`, `AGENTS.md` — независимо от того, что написано в
|
|
86
|
+
// манифесте. Второй пользователь прислал разбор: у него `rules: .temper/rules`, правила на
|
|
87
|
+
// месте, гейт entry-links-exist их видит, СТУПЕНЬ считается по манифесту и берётся — а шапка
|
|
88
|
+
// рисует два креста и советует сделать сделанное. Вывод расходился с собственным вердиктом
|
|
89
|
+
// программы; это хуже, чем просто неверный вывод, потому что оба напечатаны рядом.
|
|
90
|
+
// Поля `docs:` не существовало вовсе: методички было некуда перенести, и крест за них снять
|
|
91
|
+
// было нельзя ничем. Умолчания остаются для тех, кто полей не завёл, — это большинство.
|
|
92
|
+
function layoutChecks(man, inKit) {
|
|
93
|
+
const field = (name, dflt) => {
|
|
94
|
+
const v = man && typeof man === "object" && !Array.isArray(man) ? man[name] : null;
|
|
95
|
+
return typeof v === "string" && v.trim() ? v.trim() : dflt;
|
|
96
|
+
};
|
|
97
|
+
// Точка входа — тоже поле манифеста, и по той же причине: проект на `CLAUDE.md` получал крест
|
|
98
|
+
// за `AGENTS.md`, которого у него намеренно нет. Класс дефекта один, чинится он один раз.
|
|
99
|
+
const entries = (Array.isArray(man?.entry) ? man.entry : [])
|
|
100
|
+
.filter((e) => typeof e === "string" && e.trim())
|
|
101
|
+
.map((e) => e.trim());
|
|
102
|
+
return [
|
|
103
|
+
[field("docs", inKit ? "kit/docs" : ".aqk/docs"), inKit ? L.doctor.docsKit : L.doctor.docs],
|
|
104
|
+
[field("rules", inKit ? "kit/rules" : ".aqk/rules"), inKit ? L.doctor.rulesKit : L.doctor.rules],
|
|
105
|
+
...(entries.length ? entries : ["AGENTS.md"]).map((e) => [e, L.doctor.agents]),
|
|
106
|
+
[".gitignore", L.doctor.gitignore],
|
|
107
|
+
[".git", L.doctor.git],
|
|
108
|
+
];
|
|
109
|
+
}
|
|
83
110
|
|
|
84
111
|
function unknownKeys(man) {
|
|
85
112
|
if (!man || typeof man !== "object" || Array.isArray(man)) return [];
|
|
@@ -119,6 +146,22 @@ function entryLifecycle(rec) {
|
|
|
119
146
|
return { state, why, supersededBy: supersededBy || null, problem };
|
|
120
147
|
}
|
|
121
148
|
|
|
149
|
+
// СОВЕЩАТЕЛЬНЫЕ ГЕЙТЫ. Правило вводят в проект, где старый код ему не соответствует. Храповик
|
|
150
|
+
// отвечает на это одним способом: старое становится долгом, новое блокируется. Второй способ —
|
|
151
|
+
// показывать, не роняя, пока команда договаривается о правиле. Без него у человека остаётся
|
|
152
|
+
// выбор из двух крайностей: включить и сломать сборку либо не включать вовсе.
|
|
153
|
+
//
|
|
154
|
+
// ПОЧЕМУ СПИСКОМ В МАНИФЕСТЕ, А НЕ ФЛАГОМ ПРОГОНА. Флаг «не роняй ничего» — это тот самый
|
|
155
|
+
// `continue-on-error`, против которого написана наша запись ci-actually-fails: он понижает всё
|
|
156
|
+
// разом, не виден в дифе и не назван в сводке. Список виден в манифесте, называется поимённо и
|
|
157
|
+
// печатается КАЖДЫЙ прогон: совещательный гейт, о котором забыли, — это выключенная проверка,
|
|
158
|
+
// и молчать о нём нельзя.
|
|
159
|
+
function advisorySet(man) {
|
|
160
|
+
const v = man?.advisory;
|
|
161
|
+
if (Array.isArray(v)) return new Set(v.map((x) => String(x).trim()).filter(Boolean));
|
|
162
|
+
return new Set();
|
|
163
|
+
}
|
|
164
|
+
|
|
122
165
|
async function readManifest() {
|
|
123
166
|
const p = join(CWD, MANIFEST);
|
|
124
167
|
if (!(await exists(p))) return null;
|
|
@@ -130,7 +173,11 @@ async function readManifest() {
|
|
|
130
173
|
}
|
|
131
174
|
|
|
132
175
|
// Каждая ступень: что требуется, как проверяется, и что это даёт человеку.
|
|
133
|
-
|
|
176
|
+
// `proof` — результат `proveGates`: { ok } либо null, если доказательства не было. Ступень
|
|
177
|
+
// AQK-2 требует его прямо: до 2026-09-07 она проверяла, что папки образцов и храповиков
|
|
178
|
+
// СУЩЕСТВУЮТ, и проект с тремя гейтами `true` проходил порог AQK-3 с зелёным значком.
|
|
179
|
+
// Проверено прогоном на пустой папке. Наличие папки — не защита; уровень обязан означать факт.
|
|
180
|
+
async function assessLevel(man, proof = null) {
|
|
134
181
|
const has = async (rel) => Boolean(rel) && (await exists(join(CWD, String(rel))));
|
|
135
182
|
const isUrl = (v) => typeof v === "string" && /^https?:\/\//.test(v);
|
|
136
183
|
|
|
@@ -146,10 +193,21 @@ async function assessLevel(man) {
|
|
|
146
193
|
const conditions = [
|
|
147
194
|
Boolean(man?.aqk) && entriesExist,
|
|
148
195
|
(await has(man?.rules)) && filledGates.length > 0,
|
|
149
|
-
(await has(man?.samples)) && (await has(man?.ratchets)),
|
|
196
|
+
(await has(man?.samples)) && (await has(man?.ratchets)) && proof?.ok === true,
|
|
150
197
|
isUrl(man?.lessons) || (await has(man?.lessons)),
|
|
151
198
|
];
|
|
152
|
-
|
|
199
|
+
// Ступени, которым нужно доказательство, помечаются отдельно: «не выполнено» и «не проверяли»
|
|
200
|
+
// — разные состояния, и печатать их одинаково значит врать ровно тем способом, против
|
|
201
|
+
// которого весь комплект.
|
|
202
|
+
const NEEDS_PROOF = 2;
|
|
203
|
+
const steps = conditions.map((ok, level) => ({
|
|
204
|
+
level,
|
|
205
|
+
ok,
|
|
206
|
+
// Ровно вторая: третья ступень проверяет журнал, и посылать за доказательством там
|
|
207
|
+
// значит указать не на ту недостачу. Найдено код-ревью 2026-09-07.
|
|
208
|
+
needsProof: level === NEEDS_PROOF && proof === null,
|
|
209
|
+
...L.levels[level],
|
|
210
|
+
}));
|
|
153
211
|
|
|
154
212
|
let reached = -1;
|
|
155
213
|
for (const s of steps) {
|
|
@@ -184,5 +242,5 @@ function manifestWithGate(text, slug, cmd) {
|
|
|
184
242
|
|
|
185
243
|
export {
|
|
186
244
|
parseManifest, readManifest, assessLevel, manifestWithGate, unknownKeys, KNOWN_KEYS,
|
|
187
|
-
entryLifecycle,
|
|
245
|
+
entryLifecycle, advisorySet, layoutChecks,
|
|
188
246
|
};
|