agent-quality-kit 0.10.1 → 0.11.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 +8 -3
- package/README.ru.md +8 -3
- package/kit/gates/_native.sh +4 -0
- package/kit/gates/_skip.sh +7 -0
- package/kit/gates/api-contract-has-arbiter/check.sh +7 -0
- package/kit/gates/color-from-token/check.sh +7 -0
- package/kit/gates/commit-explains-itself/check.sh +8 -2
- package/kit/gates/complexity-limit/check.sh +22 -4
- package/kit/gates/complexity-limit/gate.yml +2 -2
- package/kit/gates/complexity-limit/red/deep.js +15 -0
- package/kit/gates/duplicate-code/check.sh +7 -0
- package/kit/gates/duplicate-code/gate.yml +5 -1
- package/kit/gates/file-size-limit/check.sh +7 -0
- package/kit/gates/file-size-limit/red/big.js +600 -0
- package/kit/gates/gate-not-weakened/check.sh +7 -0
- package/kit/gates/gate-not-weakened/green/suppress.js +4 -0
- package/kit/gates/gate-not-weakened/red/suppress.js +5 -0
- package/kit/gates/mcp-server-resolves/check.sh +7 -0
- package/kit/gates/no-phantom-package/check.sh +7 -0
- package/kit/gates/no-print-in-prod/gate.yml +3 -3
- package/kit/gates/personal-config-not-shared/check.sh +7 -0
- package/kit/gates/secrets-not-in-code/check.sh +7 -0
- package/kit/gates/secrets-not-in-code/green/config.js +4 -0
- package/kit/gates/secrets-not-in-code/red/leak.js +6 -0
- package/kit/gates/swallowed-error/gate.yml +3 -3
- package/kit/gates/test-has-assertion/check.sh +7 -0
- package/kit/gates/test-has-assertion/green/checkout.test.js +5 -0
- package/kit/gates/test-has-assertion/red/checkout.test.js +8 -0
- package/kit/gates/test-not-adjusted/check.sh +8 -2
- package/kit/gates/todo-without-task/check.sh +7 -0
- package/kit/gates/todo-without-task/gate.yml +2 -2
- package/kit/gates/todo-without-task/green/app.js +2 -0
- package/kit/gates/todo-without-task/red/later.js +4 -0
- package/llms.txt +4 -2
- package/package.json +1 -1
- package/tool/commands/badge.mjs +1 -1
- package/tool/commands/doctor.mjs +18 -148
- package/tool/commands/probe.mjs +197 -41
- package/tool/commands/report.mjs +1 -1
- package/tool/i18n/en-gates.mjs +19 -5
- package/tool/i18n/en.mjs +3 -0
- package/tool/i18n/ru-gates.mjs +20 -6
- package/tool/i18n/ru.mjs +3 -0
- package/tool/lib/history.mjs +79 -8
- package/tool/lib/run.mjs +165 -0
- package/tool/selfcheck/gates.sh +32 -4
- package/tool/selfcheck/smoke/_fixture.mjs +24 -2
- package/tool/selfcheck/smoke/fail-closed.test.mjs +112 -0
- package/tool/selfcheck/smoke/own-samples.test.mjs +131 -0
- package/tool/selfcheck/units-probe.mjs +282 -22
|
@@ -9,9 +9,9 @@ trigger:
|
|
|
9
9
|
|
|
10
10
|
recipes:
|
|
11
11
|
# BLE — ловля голого исключения, TRY400 — запись без трейса, SIM105 — перехват ради тишины.
|
|
12
|
-
python: ruff check --select BLE,TRY400,SIM105 {dir}
|
|
13
|
-
typescript: eslint --rule '{"no-empty":["error",{"allowEmptyCatch":false}]}' {dir}
|
|
14
|
-
javascript: eslint --rule '{"no-empty":["error",{"allowEmptyCatch":false}]}' {dir}
|
|
12
|
+
python: ruff check -q --output-format=concise --select BLE,TRY400,SIM105 {dir}
|
|
13
|
+
typescript: eslint --no-config-lookup --ignore-pattern 'gates/*/red/**' --ignore-pattern 'gates/*/green/**' --rule '{"no-empty":["error",{"allowEmptyCatch":false}]}' {dir}
|
|
14
|
+
javascript: eslint --no-config-lookup --ignore-pattern 'gates/*/red/**' --ignore-pattern 'gates/*/green/**' --rule '{"no-empty":["error",{"allowEmptyCatch":false}]}' {dir}
|
|
15
15
|
# errcheck — канонический инструмент Go под этот же предмет: ошибка присвоена и не проверена.
|
|
16
16
|
go: errcheck -blank {dir}/...
|
|
17
17
|
|
|
@@ -25,6 +25,13 @@ if [ ! -f "$SKIP_LIB" ]; then
|
|
|
25
25
|
exit 2
|
|
26
26
|
fi
|
|
27
27
|
. "$SKIP_LIB"
|
|
28
|
+
# Файл на месте — этого мало: подмена содержимого давала код 0. Метка стоит в КОНЦЕ _skip.sh,
|
|
29
|
+
# поэтому проверка ловит и обрыв файла на середине.
|
|
30
|
+
if [ "${AQK_SKIP_READY:-}" != 1 ]; then
|
|
31
|
+
echo "_skip.sh есть, но обход не собрался — проверка НЕ СОСТОЯЛАСЬ, а не прошла"
|
|
32
|
+
echo " почини: замени kit/gates/_skip.sh целым файлом из каталога"
|
|
33
|
+
exit 2
|
|
34
|
+
fi
|
|
28
35
|
|
|
29
36
|
# Только файлы тестов. Слово «assert» в обычном коде — не тест, а проверка входа.
|
|
30
37
|
FILES=$(find "$DIR" $(skip_find) -type f \( \
|
|
@@ -56,8 +56,14 @@ if [ -d "$DIR/before" ] && [ -d "$DIR/after" ]; then
|
|
|
56
56
|
REPO="$T"; RANGE="HEAD~1..HEAD"
|
|
57
57
|
else
|
|
58
58
|
if ! (cd "$DIR" 2>/dev/null && git rev-parse --git-dir >/dev/null 2>&1); then
|
|
59
|
-
|
|
60
|
-
|
|
59
|
+
# НЕ «проверять нечего», а «проверить нечем»: без истории свидетель слеп, и код 0 здесь
|
|
60
|
+
# неотличим от «проверено и чисто». Договор комплекта: 0 — чисто, 1 — находка, остальное —
|
|
61
|
+
# не смогли. Тот же довод уже записан ниже про мелкий клон и в protection-not-removed:
|
|
62
|
+
# когда свидетель слеп, зеленеть нельзя.
|
|
63
|
+
echo "не git-репозиторий — историю смотреть нечем, проверка НЕ СОСТОЯЛАСЬ"
|
|
64
|
+
echo " почини: запусти проверку в репозитории, либо убери запись из манифеста —"
|
|
65
|
+
echo " почини: гейт, который не может посмотреть, не защищает ничего."
|
|
66
|
+
exit 2
|
|
61
67
|
fi
|
|
62
68
|
# Диапазон: по умолчанию последний коммит. Проект может назвать свой — так же, как это
|
|
63
69
|
# делает `doctor --since`.
|
|
@@ -15,6 +15,13 @@ if [ ! -f "$SKIP_LIB" ]; then
|
|
|
15
15
|
exit 2
|
|
16
16
|
fi
|
|
17
17
|
. "$SKIP_LIB"
|
|
18
|
+
# Файл на месте — этого мало: подмена содержимого давала код 0. Метка стоит в КОНЦЕ _skip.sh,
|
|
19
|
+
# поэтому проверка ловит и обрыв файла на середине.
|
|
20
|
+
if [ "${AQK_SKIP_READY:-}" != 1 ]; then
|
|
21
|
+
echo "_skip.sh есть, но обход не собрался — проверка НЕ СОСТОЯЛАСЬ, а не прошла"
|
|
22
|
+
echo " почини: замени kit/gates/_skip.sh целым файлом из каталога"
|
|
23
|
+
exit 2
|
|
24
|
+
fi
|
|
18
25
|
|
|
19
26
|
# shellcheck disable=SC2086
|
|
20
27
|
# Маркер обязан стоять В КОММЕНТАРИИ. Иначе гейт краснеет на имени переменной с таким же
|
|
@@ -7,7 +7,7 @@ trigger:
|
|
|
7
7
|
recipes:
|
|
8
8
|
any: bash {gate}/check.sh {dir}
|
|
9
9
|
# Готовое правило точнее самописного и не требует поддержки.
|
|
10
|
-
python: ruff check --select FIX,TD {dir}
|
|
11
|
-
typescript: eslint --rule '{"no-warning-comments":["error",{"terms":["todo","fixme","hack","xxx"]}]}' {dir}
|
|
10
|
+
python: ruff check -q --output-format=concise --select FIX,TD {dir}
|
|
11
|
+
typescript: eslint --no-config-lookup --ignore-pattern 'gates/*/red/**' --ignore-pattern 'gates/*/green/**' --rule '{"no-warning-comments":["error",{"terms":["todo","fixme","hack","xxx"]}]}' {dir}
|
|
12
12
|
|
|
13
13
|
proof: incidents/README.md — «2026-08-25 разбор 1069 коммитов»: 416 коммитов из 1069 оказались стабилизацией уже выкаченного
|
package/llms.txt
CHANGED
|
@@ -3,7 +3,9 @@
|
|
|
3
3
|
> A standard and a CLI that check whether a repository is ready to have its code written by AI
|
|
4
4
|
> coding agents. Every rule the project promises to follow becomes a command with an exit code,
|
|
5
5
|
> so a machine holds the promise instead of somebody's attention. Reports a level from AQK-0 to
|
|
6
|
-
> AQK-3, computed by a run — never by a questionnaire and never by a model's opinion.
|
|
6
|
+
> AQK-3, computed by a run — never by a questionnaire and never by a model's opinion. One step
|
|
7
|
+
> further than readiness scores: `probe` plants a known defect into a copy of the project and
|
|
8
|
+
> checks whether the DECLARED guards go red. "Tests exist" and "tests catch" are different claims.
|
|
7
9
|
|
|
8
10
|
Vendor-neutral: works with any coding agent (Claude Code, Codex, Cursor, Gemini CLI, GitHub
|
|
9
11
|
Copilot, Windsurf, Aider, OpenCode) and with no AI at all. It calls no vendor API and needs no
|
|
@@ -68,7 +70,7 @@ Zero runtime dependencies. Node 18+ and an `sh` shell. MIT.
|
|
|
68
70
|
the files `init` writes are owned by root, so you cannot edit your own manifest. Debian-based
|
|
69
71
|
on purpose: the gates are `sh`, `grep`, `awk`, `find` — under alpine's busybox they behave
|
|
70
72
|
differently, and an image where the gates behave differently is worse than no image
|
|
71
|
-
- As a GitHub Action: `uses: arsen-ask-lx/Agent_Quality_Kit@v0.
|
|
73
|
+
- As a GitHub Action: `uses: arsen-ask-lx/Agent_Quality_Kit@v0.11.0` with `min: 1`
|
|
72
74
|
(https://github.com/marketplace/actions/agent-quality-kit-aqk)
|
|
73
75
|
|
|
74
76
|
## What makes it different
|
package/package.json
CHANGED
package/tool/commands/badge.mjs
CHANGED
|
@@ -10,7 +10,7 @@ import { join } from "node:path";
|
|
|
10
10
|
import { CWD, SELF, REPO_URL, c, exists, die } from "../lib/core.mjs";
|
|
11
11
|
import { readManifest, assessLevel } from "../lib/manifest.mjs";
|
|
12
12
|
import { proveGates } from "../lib/prove.mjs";
|
|
13
|
-
import { runGates, declaredGates } from "
|
|
13
|
+
import { runGates, declaredGates } from "../lib/run.mjs";
|
|
14
14
|
import { L } from "../i18n/index.mjs";
|
|
15
15
|
|
|
16
16
|
// Один разбор на запись и на чтение: значок, который мы печатаем, обязан читаться нами же.
|
package/tool/commands/doctor.mjs
CHANGED
|
@@ -11,7 +11,9 @@ import { proveGates } from "../lib/prove.mjs";
|
|
|
11
11
|
import { detectFacts, readCatalog, triggerVerdict, browserServerAdvice } from "../lib/repo.mjs";
|
|
12
12
|
import { assessBaseline, DEP_FILES, BASELINE_TOTAL } from "../lib/baseline.mjs";
|
|
13
13
|
import { L } from "../i18n/index.mjs";
|
|
14
|
+
import { countArbiters } from "./context.mjs";
|
|
14
15
|
import { beginBrief, finishBrief } from "../lib/brief.mjs";
|
|
16
|
+
import { declaredGates, sinceRef, runGates } from "../lib/run.mjs";
|
|
15
17
|
|
|
16
18
|
// Обязательный минимум проекта — прогоном, а не по памяти. До сих пор это было единственное
|
|
17
19
|
// место, где комплект просил верить на слово, что человек прочитал методичку и сверился.
|
|
@@ -125,153 +127,6 @@ async function reportCatalog(man, facts) {
|
|
|
125
127
|
return { held: held.length, todo: todo.length, todoRecs: todo };
|
|
126
128
|
}
|
|
127
129
|
|
|
128
|
-
// «Гейт объявлен» и «гейт работает» — разные утверждения. Первое читается из манифеста,
|
|
129
|
-
// второе узнаётся только запуском. Пока doctor верил манифесту на слово, уровень означал
|
|
130
|
-
// добросовестность автора, а не факт — ровно то, от чего мы защищаемся.
|
|
131
|
-
//
|
|
132
|
-
// Запуск чужих команд — по явной просьбе (--run), а не втихую: гейт бывает долгим и с
|
|
133
|
-
// побочными действиями. Без флага doctor честно говорит, что не проверял.
|
|
134
|
-
|
|
135
|
-
function declaredGates(man) {
|
|
136
|
-
const g = man?.gates && typeof man.gates === "object" && !Array.isArray(man.gates) ? man.gates : {};
|
|
137
|
-
return Object.entries(g)
|
|
138
|
-
.map(([name, cmd]) => [name, String(cmd || "").trim()])
|
|
139
|
-
.filter(([, cmd]) => cmd);
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
// Ссылка, относительно которой сужается вывод: `--since main`, `--since HEAD~5`.
|
|
143
|
-
// Без значения флаг бессмыслен — молча взять умолчание нельзя: «сужено не тем» неотличимо
|
|
144
|
-
// от «не сужено».
|
|
145
|
-
function sinceRef(argv = process.argv) {
|
|
146
|
-
const i = argv.indexOf("--since");
|
|
147
|
-
if (i === -1) return null;
|
|
148
|
-
const v = argv[i + 1];
|
|
149
|
-
return v && !v.startsWith("-") ? v : null;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
function runGates(man, opts = {}) {
|
|
153
|
-
const gates = declaredGates(man);
|
|
154
|
-
if (!gates.length) return { failed: 0, ran: 0, results: [] };
|
|
155
|
-
const advisory = advisorySet(man);
|
|
156
|
-
|
|
157
|
-
// Сужение по дифу — договор с человеком, и он должен видеть, ЧТО именно сужено. Пустой диф
|
|
158
|
-
// называется вслух: иначе «все гейты зелёные» означало бы «сравнили не с тем» и читалось бы
|
|
159
|
-
// как успех. Это тот же класс, что и весь стандарт, только внутри нашего флага.
|
|
160
|
-
const scoped = opts.since ? changedFiles(opts.since, CWD) : null;
|
|
161
|
-
if (opts.since && scoped === null) die(L.doctor.sinceBadRef(opts.since));
|
|
162
|
-
if (scoped) console.log(c.dim(`\n ${L.doctor.sinceHeading(opts.since, scoped.size)}`));
|
|
163
|
-
|
|
164
|
-
console.log(c.bold(`\n ${L.doctor.runHeading}\n`));
|
|
165
|
-
let failed = 0;
|
|
166
|
-
const results = [];
|
|
167
|
-
|
|
168
|
-
for (const [name, cmd] of gates) {
|
|
169
|
-
const t0 = Date.now();
|
|
170
|
-
const r = spawnSync(cmd, { shell: true, cwd: CWD, encoding: "utf8", timeout: 300000 });
|
|
171
|
-
const secs = (Math.max(0, Date.now() - t0) / 1000).toFixed(1);
|
|
172
|
-
// Вывод гейта запоминается целиком (с потолком, чтобы болтливый инструмент не съел память):
|
|
173
|
-
// по нему считается покрытие дифа — какой файл вообще был назван хоть одной проверкой.
|
|
174
|
-
// Без этого «готово = доказано» остаётся правилом, за которым следит только человек.
|
|
175
|
-
const outAll = `${r.stdout || ""}${r.stderr || ""}`.slice(0, 200000);
|
|
176
|
-
|
|
177
|
-
if (r.error && r.error.code === "ETIMEDOUT") {
|
|
178
|
-
console.log(` ${c.red("✘")} ${name.padEnd(14)} ${c.red(L.doctor.timeout)}`);
|
|
179
|
-
failed++;
|
|
180
|
-
results.push({ name, cmd, ok: false, secs, note: L.doctor.timeout, out: outAll });
|
|
181
|
-
continue;
|
|
182
|
-
}
|
|
183
|
-
const code = r.status;
|
|
184
|
-
if (code === 0) {
|
|
185
|
-
// Совещательный называется и когда он зелёный. Иначе гейт, который уронить прогон НЕ
|
|
186
|
-
// МОЖЕТ, по выводу неотличим от того, который может, — и список `advisory:` в манифесте
|
|
187
|
-
// виден только в тот день, когда он покраснел. Измерено 2026-09-09: зелёный
|
|
188
|
-
// совещательный печатался обычной галочкой, а README обещал, что список назван каждый
|
|
189
|
-
// прогон. Тот же класс, что молчащий гейт, только про сам прибор.
|
|
190
|
-
const quiet = advisory.has(name) ? ` ${c.yellow(L.doctor.advisoryQuiet)}` : "";
|
|
191
|
-
console.log(` ${c.green("✔")} ${name.padEnd(14)}${quiet} ${c.dim(`${secs}s · ${cmd}`)}`);
|
|
192
|
-
// Зелёный гейт иногда всё-таки говорит человеку что-то важное: храповик, дошедший до цели,
|
|
193
|
-
// просит убрать обёртку. Вывод успешного гейта не показывался вовсе, и это сообщение
|
|
194
|
-
// уходило в никуда — тот же класс, что обрезанный совет у красного, только тише.
|
|
195
|
-
// Показываем ровно строки с меткой совета: остальной вывод успешной проверки — шум.
|
|
196
|
-
const okAdvice = splitAdvice(`${r.stdout || ""}${r.stderr || ""}`.trim().split("\n").filter(Boolean)).advice;
|
|
197
|
-
for (const line of okAdvice.slice(0, 6)) console.log(c.yellow(` ${line.trim().slice(0, 110)}`));
|
|
198
|
-
results.push({ name, cmd, ok: true, secs, advisory: advisory.has(name), out: outAll });
|
|
199
|
-
} else {
|
|
200
|
-
const raw = `${r.stdout || ""}${r.stderr || ""}`.trim().split("\n").filter(Boolean);
|
|
201
|
-
// Совет отделяется ДО сужения. Иначе он сам попадает под фильтр по путям: сообщение
|
|
202
|
-
// храповика про вышедший срок называет путь к реестру, реестра в дифе нет, и гейт,
|
|
203
|
-
// обязанный краснеть по сроку, печатался зелёным с пометкой «находки вне дифа».
|
|
204
|
-
// Ровно то, что стандарт запрещает: срок без последствия. Найдено ревью 2026-09-06.
|
|
205
|
-
const parted = splitAdvice(raw);
|
|
206
|
-
let out = parted.findings;
|
|
207
|
-
const alwaysAdvice = parted.advice;
|
|
208
|
-
|
|
209
|
-
// Сужение до дифа. Три исхода, и все три называются вслух.
|
|
210
|
-
if (scoped) {
|
|
211
|
-
const s = scopeOutput(out, scoped);
|
|
212
|
-
// Гейт, у которого находок нет вовсе, а есть только совет, сузить нечем: его вердикт
|
|
213
|
-
// не про файлы. Признать такой успешным — вернуть ту же тишину другим путём.
|
|
214
|
-
if (!s.scopable || out.length === 0) {
|
|
215
|
-
// Гейт печатает вердикт без путей — сузить нечем. Признать его успешным значило бы
|
|
216
|
-
// выдать провал за тишину; остаётся красным, и причина названа.
|
|
217
|
-
// Совещательный не роняет прогон НИКОГДА — в том числе здесь. Раньше failed++ стоял
|
|
218
|
-
// безусловно, и гейт, объявленный совещательным, валил сборку с `--since` только
|
|
219
|
-
// потому, что в его выводе нет путей. Измерено 2026-09-09.
|
|
220
|
-
const nsAdv = advisory.has(name);
|
|
221
|
-
const nsMark = nsAdv ? c.yellow("!") : c.red("✘");
|
|
222
|
-
const nsVerdict = nsAdv ? c.yellow(L.doctor.advisoryMark) : c.red(L.doctor.exitCode(code));
|
|
223
|
-
console.log(` ${nsMark} ${name.padEnd(14)} ${nsVerdict} ${c.dim(`· ${L.doctor.notScopable}`)}`);
|
|
224
|
-
if (!nsAdv) failed++;
|
|
225
|
-
results.push({ name, cmd, ok: false, secs, code, advisory: nsAdv, note: L.doctor.notScopable, out: outAll });
|
|
226
|
-
continue;
|
|
227
|
-
}
|
|
228
|
-
if (s.findings === 0) {
|
|
229
|
-
// Долг есть, но не в том, что внёс диф. Зелёный — но с числом спрятанного: молчаливое
|
|
230
|
-
// «всё хорошо» здесь было бы неправдой.
|
|
231
|
-
const sQuiet = advisory.has(name) ? ` ${c.yellow(L.doctor.advisoryQuiet)}` : "";
|
|
232
|
-
console.log(` ${c.green("✔")} ${name.padEnd(14)}${sQuiet} ${c.dim(`${secs}s · ${L.doctor.outsideDiff(out.length)}`)}`);
|
|
233
|
-
results.push({ name, cmd, ok: true, secs, advisory: advisory.has(name), scopedAway: out.length, out: outAll });
|
|
234
|
-
continue;
|
|
235
|
-
}
|
|
236
|
-
out = s.kept;
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
failed++;
|
|
240
|
-
// Находки обрезаются, совет — никогда. Все записи каталога печатают «почини: …» последней
|
|
241
|
-
// строкой, и при обрезке до трёх строк человек не видел именно её: находка без действия
|
|
242
|
-
// закрывает окно, а не дефект.
|
|
243
|
-
// Совещательный гейт показывает находки и не роняет прогон. Знак другой, чтобы «показано»
|
|
244
|
-
// и «провалено» не читались одинаково; в сводке ниже он назван поимённо.
|
|
245
|
-
const isAdvisory = advisory.has(name);
|
|
246
|
-
if (isAdvisory) failed--;
|
|
247
|
-
const mark = isAdvisory ? c.yellow("!") : c.red("✘");
|
|
248
|
-
const verdict = isAdvisory ? c.yellow(L.doctor.advisoryMark) : c.red(L.doctor.exitCode(code));
|
|
249
|
-
console.log(` ${mark} ${name.padEnd(14)} ${verdict} ${c.dim(`· ${secs}s · ${cmd}`)}`);
|
|
250
|
-
// ГОЛОВА И ХВОСТ, А НЕ ТОЛЬКО ГОЛОВА. Гейт, который сам является прогоном (наш `smoke`),
|
|
251
|
-
// печатает сотни строк, и вердикт у него в конце — при обрезке до первых трёх человек
|
|
252
|
-
// видел «программа разбирается» и ни слова о том, что упало. Час поисков в конвейере
|
|
253
|
-
// 2026-09-09 стоил ровно этого. Голова нужна тоже: у сканирующих записей находки идут
|
|
254
|
-
// с первой строки.
|
|
255
|
-
const HEAD = 3, TAIL = 2;
|
|
256
|
-
for (const line of out.slice(0, HEAD)) console.log(c.dim(` ${line.slice(0, 100)}`));
|
|
257
|
-
if (out.length > HEAD + TAIL) {
|
|
258
|
-
console.log(c.dim(` ${L.doctor.moreLines(out.length - HEAD - TAIL)}`));
|
|
259
|
-
for (const line of out.slice(-TAIL)) console.log(c.dim(` ${line.slice(0, 100)}`));
|
|
260
|
-
} else {
|
|
261
|
-
for (const line of out.slice(HEAD)) console.log(c.dim(` ${line.slice(0, 100)}`));
|
|
262
|
-
}
|
|
263
|
-
// Совет тоже не бесконечен: гейт, зовущий помощник шесть раз, печатает его шесть раз.
|
|
264
|
-
for (const line of alwaysAdvice.slice(0, 6)) console.log(c.yellow(` ${line.trim().slice(0, 110)}`));
|
|
265
|
-
results.push({ name, cmd, ok: false, secs, code, advisory: isAdvisory, out: outAll });
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
// Совещательные, которые покраснели, называются вслух ВСЕГДА. Молчание о них — ровно та
|
|
269
|
-
// тишина, против которой построен стандарт: проверка выключена, а выглядит как её отсутствие.
|
|
270
|
-
const advisoryFailed = results.filter((x) => x.advisory && !x.ok).map((x) => x.name);
|
|
271
|
-
if (advisoryFailed.length) console.log(`\n ${c.yellow(L.doctor.advisorySummary(advisoryFailed))}`);
|
|
272
|
-
return { failed, ran: gates.length, results, advisoryFailed };
|
|
273
|
-
}
|
|
274
|
-
|
|
275
130
|
// Короткий отчёт «что из этого реально брали» — не для человека, а для агента в следующей
|
|
276
131
|
// сессии и для самого владельца: список объявленных гейтов молчит о том, сколько из них
|
|
277
132
|
// действительно стоят и работают именно СЕЙЧАС. Перезаписывается каждым прогоном, не копится:
|
|
@@ -328,6 +183,21 @@ async function cmdDoctor() {
|
|
|
328
183
|
const agents = join(CWD, entryFile);
|
|
329
184
|
if (await exists(agents)) {
|
|
330
185
|
const text = await readFile(agents, "utf8");
|
|
186
|
+
|
|
187
|
+
// Сколько обещаний НЕ сторожит машина. Считалось и печаталось это давно — но только в
|
|
188
|
+
// блоке `context`, который читает АГЕНТ. Человеку, который и назначен сторожем, `doctor`
|
|
189
|
+
// не говорил ни слова: единственный, кто обязан помнить о непроверяемом обещании, был
|
|
190
|
+
// единственным, кому о нём не сообщали.
|
|
191
|
+
//
|
|
192
|
+
// Найдено не нами: отчёт живого проекта 2026-09-10 — «всё, что касается масштаба, помечено
|
|
193
|
+
// aqk: человек. AQK отработал честно: потребовал назвать сторожа, мы назвали — и сторож не
|
|
194
|
+
// проверил». В нашем собственном своде так помечены 12 правил из 14.
|
|
195
|
+
const arb = countArbiters(text, ["человек", "human", "nobody"]);
|
|
196
|
+
if (arb.total && arb.human) {
|
|
197
|
+
console.log(`\n ${c.yellow("!")} ${L.doctor.rulesByHuman(arb.total, arb.machine, arb.human)}`);
|
|
198
|
+
console.log(c.dim(` ${L.doctor.rulesByHumanWhy}`));
|
|
199
|
+
}
|
|
200
|
+
|
|
331
201
|
const emptyCommands = (text.match(/^- [^:]+: ``$/gm) || []).length;
|
|
332
202
|
if (emptyCommands) {
|
|
333
203
|
console.log(
|
|
@@ -489,4 +359,4 @@ async function cmdDoctor() {
|
|
|
489
359
|
|
|
490
360
|
// Наружу — только команда. Остальное здесь же и используется: экспорт, который никто не
|
|
491
361
|
// импортирует, читается как «это часть договора» и мешает менять внутренности.
|
|
492
|
-
export { cmdDoctor
|
|
362
|
+
export { cmdDoctor };
|