agent-quality-kit 0.2.2
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/LICENSE +21 -0
- package/README.md +155 -0
- package/kit/docs/ai/agent-harness-playbook.md +596 -0
- package/kit/docs/ai/ai-native-development.md +371 -0
- package/kit/docs/ai/ai-sdlc.md +221 -0
- package/kit/docs/ai/anthropic-ai-native-sdlc-2026-08.md +294 -0
- package/kit/docs/ai/app-owner-strategy.md +921 -0
- package/kit/docs/ai/deep-research-2026-07.md +161 -0
- package/kit/docs/ai/harness-best-practices.md +385 -0
- package/kit/docs/ai/index.md +64 -0
- package/kit/docs/ai/project-baseline.md +261 -0
- package/kit/docs/ai/quality-gates-checklist.md +322 -0
- package/kit/docs/ai/sources-building-with-agents.md +111 -0
- package/kit/docs/ai/stream-2026-08-ai-coding-panel.md +304 -0
- package/kit/docs/ready-made-rules.md +170 -0
- package/kit/gates/README.md +231 -0
- package/kit/gates/_skip.sh +75 -0
- package/kit/gates/commit-explains-itself/README.md +45 -0
- package/kit/gates/commit-explains-itself/check.sh +63 -0
- package/kit/gates/commit-explains-itself/gate.yml +10 -0
- package/kit/gates/commit-explains-itself/green/COMMIT_MSG +6 -0
- package/kit/gates/commit-explains-itself/red/COMMIT_MSG +3 -0
- package/kit/gates/complexity-limit/README.md +37 -0
- package/kit/gates/complexity-limit/check.sh +44 -0
- package/kit/gates/complexity-limit/gate.yml +13 -0
- package/kit/gates/complexity-limit/green/flat.py +10 -0
- package/kit/gates/complexity-limit/red/deep.py +9 -0
- package/kit/gates/dead-code/README.md +30 -0
- package/kit/gates/dead-code/gate.yml +23 -0
- package/kit/gates/dead-code/green/mod.py +9 -0
- package/kit/gates/dead-code/red/mod.py +9 -0
- package/kit/gates/deps-are-pinned/README.md +29 -0
- package/kit/gates/deps-are-pinned/check.sh +49 -0
- package/kit/gates/deps-are-pinned/gate.yml +9 -0
- package/kit/gates/deps-are-pinned/green/nodep-go/go.mod +3 -0
- package/kit/gates/deps-are-pinned/green/package-lock.json +3 -0
- package/kit/gates/deps-are-pinned/green/package.json +4 -0
- package/kit/gates/deps-are-pinned/green/requirements.txt +2 -0
- package/kit/gates/deps-are-pinned/red/package.json +4 -0
- package/kit/gates/deps-are-pinned/red/requirements.txt +2 -0
- package/kit/gates/deps-are-pinned/red/withdep-go/go.mod +5 -0
- package/kit/gates/duplicate-code/README.md +40 -0
- package/kit/gates/duplicate-code/check.sh +58 -0
- package/kit/gates/duplicate-code/gate.yml +12 -0
- package/kit/gates/duplicate-code/green/common.py +9 -0
- package/kit/gates/duplicate-code/green/use.py +9 -0
- package/kit/gates/duplicate-code/red/a.py +12 -0
- package/kit/gates/duplicate-code/red/b.py +12 -0
- package/kit/gates/entry-links-exist/README.md +22 -0
- package/kit/gates/entry-links-exist/check.sh +24 -0
- package/kit/gates/entry-links-exist/gate.yml +16 -0
- package/kit/gates/entry-links-exist/green/AGENTS.md +5 -0
- package/kit/gates/entry-links-exist/green/rules/general.md +3 -0
- package/kit/gates/entry-links-exist/red/AGENTS.md +3 -0
- package/kit/gates/file-size-limit/README.md +22 -0
- package/kit/gates/file-size-limit/check.sh +34 -0
- package/kit/gates/file-size-limit/gate.yml +9 -0
- package/kit/gates/file-size-limit/green/a.py +251 -0
- package/kit/gates/file-size-limit/green/b.py +251 -0
- package/kit/gates/file-size-limit/red/big.py +601 -0
- package/kit/gates/gate-has-samples/README.md +29 -0
- package/kit/gates/gate-has-samples/check.sh +48 -0
- package/kit/gates/gate-has-samples/gate.yml +9 -0
- package/kit/gates/gate-has-samples/green/.aqk.yml +10 -0
- package/kit/gates/gate-has-samples/green/gates/no-print-in-prod/check.sh +2 -0
- package/kit/gates/gate-has-samples/green/gates/no-print-in-prod/green/good.py +2 -0
- package/kit/gates/gate-has-samples/green/gates/no-print-in-prod/red/bad.py +1 -0
- package/kit/gates/gate-has-samples/red/.aqk.yml +10 -0
- package/kit/gates/gate-has-samples/red/gates/no-print-in-prod/check.sh +2 -0
- package/kit/gates/gates-are-runnable/README.md +23 -0
- package/kit/gates/gates-are-runnable/check.sh +35 -0
- package/kit/gates/gates-are-runnable/gate.yml +9 -0
- package/kit/gates/gates-are-runnable/green/.aqk.yml +9 -0
- package/kit/gates/gates-are-runnable/green/checks/lint.sh +2 -0
- package/kit/gates/gates-are-runnable/red/.aqk.yml +6 -0
- package/kit/gates/gates-run-in-ci/README.md +29 -0
- package/kit/gates/gates-run-in-ci/check.sh +42 -0
- package/kit/gates/gates-run-in-ci/gate.yml +12 -0
- package/kit/gates/gates-run-in-ci/green/.aqk.yml +6 -0
- package/kit/gates/gates-run-in-ci/green/.github/workflows/ci.yml +7 -0
- package/kit/gates/gates-run-in-ci/green/checks/lint.sh +2 -0
- package/kit/gates/gates-run-in-ci/red/.aqk.yml +6 -0
- package/kit/gates/gates-run-in-ci/red/.github/workflows/ci.yml +7 -0
- package/kit/gates/gates-run-in-ci/red/checks/lint.sh +2 -0
- package/kit/gates/lesson-has-outcome/README.md +37 -0
- package/kit/gates/lesson-has-outcome/check.sh +50 -0
- package/kit/gates/lesson-has-outcome/gate.yml +11 -0
- package/kit/gates/lesson-has-outcome/green/.aqk.yml +2 -0
- package/kit/gates/lesson-has-outcome/green/incidents/README.md +32 -0
- package/kit/gates/lesson-has-outcome/red/.aqk.yml +2 -0
- package/kit/gates/lesson-has-outcome/red/incidents/README.md +13 -0
- package/kit/gates/no-print-in-prod/README.md +44 -0
- package/kit/gates/no-print-in-prod/check.sh +36 -0
- package/kit/gates/no-print-in-prod/gate.yml +15 -0
- package/kit/gates/no-print-in-prod/green/docs.ts +15 -0
- package/kit/gates/no-print-in-prod/green/legacy.py +9 -0
- package/kit/gates/no-print-in-prod/green/main.go +8 -0
- package/kit/gates/no-print-in-prod/green/main.rs +4 -0
- package/kit/gates/no-print-in-prod/green/service.py +8 -0
- package/kit/gates/no-print-in-prod/red/main.go +8 -0
- package/kit/gates/no-print-in-prod/red/main.rs +4 -0
- package/kit/gates/no-print-in-prod/red/service.py +3 -0
- package/kit/gates/secrets-not-in-code/README.md +29 -0
- package/kit/gates/secrets-not-in-code/check.sh +18 -0
- package/kit/gates/secrets-not-in-code/gate.yml +9 -0
- package/kit/gates/secrets-not-in-code/green/settings.py +4 -0
- package/kit/gates/secrets-not-in-code/red/settings.py +2 -0
- package/kit/gates/swallowed-error/README.md +26 -0
- package/kit/gates/swallowed-error/check.sh +54 -0
- package/kit/gates/swallowed-error/gate.yml +12 -0
- package/kit/gates/swallowed-error/green/loader.py +11 -0
- package/kit/gates/swallowed-error/green/run.js +8 -0
- package/kit/gates/swallowed-error/red/loader.py +5 -0
- package/kit/gates/swallowed-error/red/run.js +3 -0
- package/kit/gates/todo-without-task/README.md +26 -0
- package/kit/gates/todo-without-task/check.sh +19 -0
- package/kit/gates/todo-without-task/gate.yml +12 -0
- package/kit/gates/todo-without-task/green/order.py +9 -0
- package/kit/gates/todo-without-task/red/order.py +8 -0
- package/kit/ratchet/ratchet.sh +62 -0
- package/kit/rules/general.md +55 -0
- package/kit/rules/security.md +33 -0
- package/kit/rules/testing.md +46 -0
- package/package.json +41 -0
- package/tool/commands/doctor.mjs +230 -0
- package/tool/commands/gates.mjs +445 -0
- package/tool/commands/project.mjs +316 -0
- package/tool/lib/core.mjs +98 -0
- package/tool/lib/manifest.mjs +140 -0
- package/tool/lib/repo.mjs +270 -0
- package/tool/lib/templates.mjs +187 -0
- package/tool/program.mjs +81 -0
- package/tool/selfcheck/conditional.sh +24 -0
- package/tool/selfcheck/gates.sh +127 -0
- package/tool/selfcheck/smoke.sh +539 -0
- package/tool/selfcheck/syntax.sh +23 -0
- package/tool/selfcheck/units.mjs +105 -0
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
// tool/commands/doctor.mjs — что разложено, какая ступень, какие гейты применимы и работают.
|
|
2
|
+
|
|
3
|
+
import { readFile, mkdir, writeFile } from "node:fs/promises";
|
|
4
|
+
import { join, resolve } from "node:path";
|
|
5
|
+
import { spawnSync } from "node:child_process";
|
|
6
|
+
import { CWD, PKG_ROOT, TARGET_DIR, SELF, c, exists } from "../lib/core.mjs";
|
|
7
|
+
import { readManifest, assessLevel } from "../lib/manifest.mjs";
|
|
8
|
+
import { detectFacts, readCatalog, triggerVerdict, recipeFor } from "../lib/repo.mjs";
|
|
9
|
+
|
|
10
|
+
async function reportCatalog(man, facts) {
|
|
11
|
+
const catalog = await readCatalog();
|
|
12
|
+
if (!catalog.length) return;
|
|
13
|
+
|
|
14
|
+
const held = [], todo = [], skip = [];
|
|
15
|
+
for (const rec of catalog) {
|
|
16
|
+
const v = triggerVerdict(rec, facts);
|
|
17
|
+
if (!v.applies) skip.push([rec, v.why]);
|
|
18
|
+
else if (facts.gateKeys.includes(rec.slug)) held.push(rec);
|
|
19
|
+
else todo.push(rec);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
console.log(c.bold(`\n Гейты\n`));
|
|
23
|
+
const marks = ["has_ci", "has_db", "has_docker", "has_tests", "has_deps"]
|
|
24
|
+
.filter((k) => facts[k])
|
|
25
|
+
.map((k) => k.replace("has_", ""));
|
|
26
|
+
console.log(
|
|
27
|
+
c.dim(` языки: ${[...facts.langs].join(", ") || "не определены"} · файлов: ${facts.files}` +
|
|
28
|
+
(marks.length ? ` · есть: ${marks.join(", ")}` : "") + "\n")
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
for (const rec of held) console.log(` ${c.green("✔")} ${rec.slug.padEnd(22)} ${c.dim(rec.intent || "")}`);
|
|
32
|
+
for (const rec of todo) {
|
|
33
|
+
console.log(` ${c.yellow("✘")} ${rec.slug.padEnd(22)} ${rec.intent || ""}`);
|
|
34
|
+
console.log(c.dim(` поставить: ${SELF} add ${rec.slug}`));
|
|
35
|
+
}
|
|
36
|
+
if (skip.length) {
|
|
37
|
+
console.log(c.dim(`\n Не применимо к этому репозиторию (${skip.length}):`));
|
|
38
|
+
for (const [rec, why] of skip) console.log(c.dim(` · ${rec.slug.padEnd(22)} ${why}`));
|
|
39
|
+
}
|
|
40
|
+
console.log(
|
|
41
|
+
`\n ${c.bold("Итого:")} держит машина ${held.length}, применимо но не поставлено ${c.yellow(todo.length)}, ` +
|
|
42
|
+
c.dim(`скрыто ${skip.length}`) + "\n"
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// «Гейт объявлен» и «гейт работает» — разные утверждения. Первое читается из манифеста,
|
|
47
|
+
// второе узнаётся только запуском. Пока doctor верил манифесту на слово, уровень означал
|
|
48
|
+
// добросовестность автора, а не факт — ровно то, от чего мы защищаемся.
|
|
49
|
+
//
|
|
50
|
+
// Запуск чужих команд — по явной просьбе (--run), а не втихую: гейт бывает долгим и с
|
|
51
|
+
// побочными действиями. Без флага doctor честно говорит, что не проверял.
|
|
52
|
+
|
|
53
|
+
function declaredGates(man) {
|
|
54
|
+
const g = man?.gates && typeof man.gates === "object" && !Array.isArray(man.gates) ? man.gates : {};
|
|
55
|
+
return Object.entries(g)
|
|
56
|
+
.map(([name, cmd]) => [name, String(cmd || "").trim()])
|
|
57
|
+
.filter(([, cmd]) => cmd);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function runGates(man) {
|
|
61
|
+
const gates = declaredGates(man);
|
|
62
|
+
if (!gates.length) return { failed: 0, ran: 0, results: [] };
|
|
63
|
+
|
|
64
|
+
console.log(c.bold("\n Прогон объявленных гейтов\n"));
|
|
65
|
+
let failed = 0;
|
|
66
|
+
const results = [];
|
|
67
|
+
|
|
68
|
+
for (const [name, cmd] of gates) {
|
|
69
|
+
const t0 = Date.now();
|
|
70
|
+
const r = spawnSync(cmd, { shell: true, cwd: CWD, encoding: "utf8", timeout: 300000 });
|
|
71
|
+
const secs = (Math.max(0, Date.now() - t0) / 1000).toFixed(1);
|
|
72
|
+
|
|
73
|
+
if (r.error && r.error.code === "ETIMEDOUT") {
|
|
74
|
+
console.log(` ${c.red("✘")} ${name.padEnd(14)} ${c.red("не уложился в 5 минут")}`);
|
|
75
|
+
failed++;
|
|
76
|
+
results.push({ name, cmd, ok: false, secs, note: "не уложился в 5 минут" });
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
const code = r.status;
|
|
80
|
+
if (code === 0) {
|
|
81
|
+
console.log(` ${c.green("✔")} ${name.padEnd(14)} ${c.dim(`${secs}s · ${cmd}`)}`);
|
|
82
|
+
results.push({ name, cmd, ok: true, secs });
|
|
83
|
+
} else {
|
|
84
|
+
failed++;
|
|
85
|
+
const out = `${r.stdout || ""}${r.stderr || ""}`.trim().split("\n").filter(Boolean);
|
|
86
|
+
console.log(` ${c.red("✘")} ${name.padEnd(14)} ${c.red(`код ${code}`)} ${c.dim(`· ${secs}s · ${cmd}`)}`);
|
|
87
|
+
for (const line of out.slice(0, 3)) console.log(c.dim(` ${line.slice(0, 100)}`));
|
|
88
|
+
if (out.length > 3) console.log(c.dim(` … и ещё ${out.length - 3} строк`));
|
|
89
|
+
results.push({ name, cmd, ok: false, secs, code });
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return { failed, ran: gates.length, results };
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Короткий отчёт «что из этого реально брали» — не для человека, а для агента в следующей
|
|
96
|
+
// сессии и для самого владельца: список объявленных гейтов молчит о том, сколько из них
|
|
97
|
+
// действительно стоят и работают именно СЕЙЧАС. Перезаписывается каждым прогоном, не копится:
|
|
98
|
+
// история — дело git-лога коммитов с этим отчётом, если владелец решит его коммитить.
|
|
99
|
+
async function writeRunReport({ version, reached, results }) {
|
|
100
|
+
const stamp = new Date().toISOString().replace("T", " ").slice(0, 16);
|
|
101
|
+
const ok = results.filter((r) => r.ok).length;
|
|
102
|
+
const lines = [
|
|
103
|
+
`# aqk doctor --run — ${stamp}`,
|
|
104
|
+
version ? `версия: ${version}` : null,
|
|
105
|
+
`уровень: AQK-${reached < 0 ? "нет" : reached}`,
|
|
106
|
+
"",
|
|
107
|
+
...results.map((r) => `${r.ok ? "✔" : "✘"} ${r.name} — ${r.secs}s${r.ok ? "" : ` (${r.note || `код ${r.code}`})`}`),
|
|
108
|
+
"",
|
|
109
|
+
`итого: ${ok} из ${results.length} зелёных`,
|
|
110
|
+
].filter((l) => l !== null);
|
|
111
|
+
|
|
112
|
+
const dst = join(CWD, TARGET_DIR, "last-run.md");
|
|
113
|
+
await mkdir(join(CWD, TARGET_DIR), { recursive: true });
|
|
114
|
+
await writeFile(dst, lines.join("\n") + "\n", "utf8");
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
async function cmdDoctor() {
|
|
118
|
+
// Версия в шапке — единственное, что привязывает баг-репорт к коммиту, если ставили не из
|
|
119
|
+
// релиза: без неё "у меня не работает" ничем не отличается от любой другой версии за год.
|
|
120
|
+
let version = "";
|
|
121
|
+
try {
|
|
122
|
+
const pkg = JSON.parse(await readFile(join(PKG_ROOT, "package.json"), "utf8"));
|
|
123
|
+
version = pkg.version || "";
|
|
124
|
+
} catch { /* пакет без package.json — версия просто не покажется */ }
|
|
125
|
+
console.log(c.bold(`\naqk doctor${version ? ` v${version}` : ""}\n`));
|
|
126
|
+
|
|
127
|
+
// В самом комплекте разложенной копии `.aqk/` нет и быть не должно: здесь лежат оригиналы,
|
|
128
|
+
// а копия завтра разошлась бы с ними. Без этого различия `doctor` краснел на собственном
|
|
129
|
+
// репозитории и требовал разложить комплект в комплект.
|
|
130
|
+
const inKit = resolve(CWD) === resolve(PKG_ROOT);
|
|
131
|
+
const checks = [
|
|
132
|
+
inKit ? ["kit/docs", "методички — здесь оригиналы, а не копия"] : [".aqk/docs", "методички"],
|
|
133
|
+
inKit ? ["kit/rules", "стандарты — здесь оригиналы, а не копия"] : [".aqk/rules", "стандарты"],
|
|
134
|
+
["AGENTS.md", "точка входа для агентов"],
|
|
135
|
+
[".gitignore", "гигиена репозитория"],
|
|
136
|
+
[".git", "проект под контролем версий"],
|
|
137
|
+
];
|
|
138
|
+
|
|
139
|
+
let missing = 0;
|
|
140
|
+
for (const [path, what] of checks) {
|
|
141
|
+
const ok = await exists(join(CWD, path));
|
|
142
|
+
if (!ok) missing++;
|
|
143
|
+
console.log(` ${ok ? c.green("✔") : c.red("✘")} ${path.padEnd(22)} ${c.dim(what)}`);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Команды в AGENTS.md заполнены или остались пустыми заготовками?
|
|
147
|
+
const agents = join(CWD, "AGENTS.md");
|
|
148
|
+
if (await exists(agents)) {
|
|
149
|
+
const text = await readFile(agents, "utf8");
|
|
150
|
+
const emptyCommands = (text.match(/^- [^:]+: ``$/gm) || []).length;
|
|
151
|
+
if (emptyCommands) {
|
|
152
|
+
console.log(
|
|
153
|
+
`\n ${c.yellow("!")} В AGENTS.md ${emptyCommands} незаполненных команд. ` +
|
|
154
|
+
c.dim("Агент не может выполнить пустую строку.")
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const man = await readManifest();
|
|
160
|
+
const { reached, steps } = await assessLevel(man);
|
|
161
|
+
|
|
162
|
+
console.log(c.bold("\n Уровень соответствия AQK\n"));
|
|
163
|
+
for (const s of steps) {
|
|
164
|
+
const mark = s.ok ? c.green("✔") : reached + 1 === s.level ? c.yellow("→") : c.dim("·");
|
|
165
|
+
console.log(` ${mark} AQK-${s.level} ${s.title}`);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const next = steps.find((s) => !s.ok);
|
|
169
|
+
// «Уровень не достигнут» на зрелом проекте читается как приговор проекту, а он им не
|
|
170
|
+
// является: уровень мерит машиночитаемость практики, а не саму практику. Проект с сотней
|
|
171
|
+
// работающих проверок и без манифеста стоит на нуле — и это сообщение обязано это объяснить,
|
|
172
|
+
// иначе человек услышит «у тебя плохо» и закроет.
|
|
173
|
+
if (reached < 0 && !man) {
|
|
174
|
+
console.log(c.yellow("\n Уровень: стандарт в этом репозитории не заведён.\n"));
|
|
175
|
+
console.log(
|
|
176
|
+
c.dim(" Это не оценка проекта. Уровень мерит не зрелость практики, а то, можно ли\n") +
|
|
177
|
+
c.dim(" прочитать её машиной. Проверки могут стоять и работать — но пока они не\n") +
|
|
178
|
+
c.dim(" объявлены в .aqk.yml, ни агент, ни конвейер, ни новый человек о них не знают.\n")
|
|
179
|
+
);
|
|
180
|
+
} else {
|
|
181
|
+
console.log(
|
|
182
|
+
reached < 0
|
|
183
|
+
? c.yellow(`\n Уровень: манифест есть, но AQK-0 не пройден.\n`)
|
|
184
|
+
: c.green(`\n Уровень: AQK-${reached}.\n`)
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
if (next) {
|
|
189
|
+
console.log(` ${c.bold(`Чтобы достичь AQK-${next.level}:`)} ${next.need}`);
|
|
190
|
+
console.log(c.dim(` Что это даст: ${next.gives}\n`));
|
|
191
|
+
} else {
|
|
192
|
+
console.log(c.green(" Все ступени пройдены.\n"));
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const facts = await detectFacts(man);
|
|
196
|
+
await reportCatalog(man, facts);
|
|
197
|
+
|
|
198
|
+
// «Объявлен» ≠ «работает». Без --run говорим это вслух, а не молчим.
|
|
199
|
+
const wantRun = process.argv.includes("--run");
|
|
200
|
+
const gates = declaredGates(man);
|
|
201
|
+
let gateFailed = 0;
|
|
202
|
+
if (wantRun) {
|
|
203
|
+
const run = runGates(man);
|
|
204
|
+
gateFailed = run.failed;
|
|
205
|
+
await writeRunReport({ version, reached, results: run.results });
|
|
206
|
+
} else if (gates.length) {
|
|
207
|
+
console.log(
|
|
208
|
+
c.yellow(` ${gates.length} гейтов объявлено, но не запускалось.`) +
|
|
209
|
+
c.dim(` «Объявлен» и «работает» — разные утверждения: ${SELF} doctor --run\n`)
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Код возврата — для конвейера. Порог задаётся так: aqk doctor --min 1
|
|
214
|
+
const minIdx = process.argv.indexOf("--min");
|
|
215
|
+
const min = minIdx > -1 ? Number(process.argv[minIdx + 1]) : null;
|
|
216
|
+
if (min !== null) {
|
|
217
|
+
const pass = reached >= min && gateFailed === 0;
|
|
218
|
+
console.log(
|
|
219
|
+
pass
|
|
220
|
+
? c.green(` Порог AQK-${min} пройден.\n`)
|
|
221
|
+
: c.red(` Порог AQK-${min} НЕ пройден: сейчас AQK-${reached < 0 ? "нет" : reached}.\n`)
|
|
222
|
+
);
|
|
223
|
+
process.exit(pass ? 0 : 1);
|
|
224
|
+
}
|
|
225
|
+
process.exit(missing || reached < 0 || gateFailed ? 1 : 0);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Наружу — только команда. Остальное здесь же и используется: экспорт, который никто не
|
|
229
|
+
// импортирует, читается как «это часть договора» и мешает менять внутренности.
|
|
230
|
+
export { cmdDoctor };
|