agent-quality-kit 0.7.0 → 0.9.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 +135 -9
- package/README.ru.md +167 -23
- package/kit/docs/ai/project-baseline.md +14 -0
- package/kit/docs/ready-made-rules.md +103 -0
- package/kit/gates/_skip.sh +61 -1
- 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 +10 -2
- package/kit/gates/color-from-token/green/Button.tsx +2 -0
- package/kit/gates/complexity-limit/check.sh +6 -7
- package/kit/gates/duplicate-code/check.sh +5 -1
- package/kit/gates/entry-links-exist/check.sh +4 -1
- package/kit/gates/entry-links-exist/green/AGENTS.md +2 -0
- package/kit/gates/file-size-limit/check.sh +1 -1
- package/kit/gates/lesson-has-outcome/check.sh +5 -1
- package/kit/gates/mcp-server-resolves/README.md +62 -0
- package/kit/gates/mcp-server-resolves/check.sh +110 -0
- package/kit/gates/mcp-server-resolves/gate.yml +18 -0
- package/kit/gates/mcp-server-resolves/green/.mcp.json +20 -0
- package/kit/gates/mcp-server-resolves/red/.mcp.json +16 -0
- package/kit/gates/secrets-not-in-code/check.sh +16 -3
- package/kit/gates/secrets-not-in-code/green/testdata/certificate/key.pem +3 -0
- package/kit/gates/todo-without-task/check.sh +1 -1
- package/kit/gates/todo-without-task/green/app.py +1 -0
- package/llms.txt +38 -2
- package/package.json +2 -3
- package/tool/commands/context.mjs +264 -0
- package/tool/commands/doctor.mjs +104 -28
- package/tool/commands/learn.mjs +159 -0
- package/tool/commands/project.mjs +19 -2
- package/tool/commands/prove.mjs +1 -0
- package/tool/commands/report.mjs +33 -1
- package/tool/commands/vitals.mjs +159 -0
- package/tool/i18n/en-docs.mjs +125 -1
- package/tool/i18n/en.mjs +39 -36
- package/tool/i18n/index.mjs +36 -3
- package/tool/i18n/ru-docs.mjs +127 -1
- package/tool/i18n/ru.mjs +39 -36
- package/tool/lib/banner.mjs +59 -0
- package/tool/lib/brief.mjs +192 -0
- package/tool/lib/core.mjs +32 -1
- package/tool/lib/evidence.mjs +124 -0
- package/tool/lib/manifest.mjs +173 -15
- package/tool/lib/prove.mjs +24 -2
- package/tool/lib/repo.mjs +31 -1
- package/tool/lib/scope.mjs +10 -1
- package/tool/lib/templates.mjs +1 -0
- package/tool/program.mjs +45 -23
- package/tool/selfcheck/smoke.sh +592 -3
- package/tool/selfcheck/units-banner.mjs +65 -0
- package/tool/selfcheck/units-brief.mjs +97 -0
- package/tool/selfcheck/units-context.mjs +188 -0
- package/tool/selfcheck/units-evidence.mjs +83 -0
- package/tool/selfcheck/units-learn.mjs +88 -0
- package/tool/selfcheck/units-level.mjs +211 -3
- package/tool/selfcheck/units-repo.mjs +134 -0
- package/tool/selfcheck/units-vitals.mjs +62 -0
- package/tool/selfcheck/units.mjs +4 -75
package/tool/i18n/ru-docs.mjs
CHANGED
|
@@ -7,6 +7,132 @@
|
|
|
7
7
|
// вторые — он же в терминале, одну секунду.
|
|
8
8
|
|
|
9
9
|
const ruDocs = {
|
|
10
|
+
// Блок vitals переехал сюда 2026-09-09 по той же причине, что и brief: терминальный
|
|
11
|
+
// каталог снова перерос 500 строк, и поймал это наш же file-size-limit.
|
|
12
|
+
vitals: {
|
|
13
|
+
title: "Подключено ли то, чем комплект работает",
|
|
14
|
+
names: {
|
|
15
|
+
tools: "инструменты гейтов",
|
|
16
|
+
manifest: "манифест разобран",
|
|
17
|
+
preCommit: "хук pre-commit",
|
|
18
|
+
sessionHook: "состояние агенту",
|
|
19
|
+
version: "версия",
|
|
20
|
+
vitals: "подключено ли то, чем комплект работает: инструменты, хуки, свежесть",
|
|
21
|
+
},
|
|
22
|
+
toolsOk: (n) => `все ${n} на месте`,
|
|
23
|
+
toolsMissing: (l) => `НЕ НАЙДЕНЫ: ${l} — эти гейты не состоятся`,
|
|
24
|
+
manifestOk: "разобран целиком",
|
|
25
|
+
manifestBad: (n) => `${n} строк не разобраны и НЕ ДЕЙСТВУЮТ — подробности в doctor`,
|
|
26
|
+
unknownHook: "посмотреть не смогли — неизвестно, а не «нет»",
|
|
27
|
+
preCommitOk: "прописан в .git/hooks",
|
|
28
|
+
preCommitNo: "в .git/hooks его нет: запись в конфиге — намерение, а не защита",
|
|
29
|
+
sessionOk: "хук SessionStart отдаёт состояние агенту",
|
|
30
|
+
sessionNo: (cmd) => `агент не получает состояние: ${cmd}`,
|
|
31
|
+
versionOk: (v) => `${v}, свежая`,
|
|
32
|
+
versionOld: (l, cur) => `есть ${l}, у вас ${cur}`,
|
|
33
|
+
},
|
|
34
|
+
// Блок краткого вывода переехал сюда 2026-09-09: терминальный каталог снова перерос
|
|
35
|
+
// 500 строк, и поймал это наш же file-size-limit. Шов по смыслу условен — это всё-таки
|
|
36
|
+
// терминал, — но предел настоящий, а делить пополам хуже, чем делить по соседству.
|
|
37
|
+
brief: {
|
|
38
|
+
name: "AQK",
|
|
39
|
+
held: (n) => `держит ${n}`,
|
|
40
|
+
todo: (n) => `не поставлено ${n}`,
|
|
41
|
+
levelUnknown: "уровень неизвестен",
|
|
42
|
+
red: (l) => ` КРАСНЫЕ: ${l}`,
|
|
43
|
+
advise: (slug, why) => ` ↑ поставить: ${slug} — ${why}`,
|
|
44
|
+
adviseOff: (cmd, env) => ` не нужно: ${cmd}, либо ${env}`,
|
|
45
|
+
update: (l, c, how) => `новая версия ${l}, у вас ${c} — обновить: ${how}`,
|
|
46
|
+
updateHow: "npx agent-quality-kit@latest, либо npm i -g agent-quality-kit",
|
|
47
|
+
updateHookHow: "pre-commit autoupdate",
|
|
48
|
+
updateOff: (env) => ` не нужно: ${env}`,
|
|
49
|
+
},
|
|
50
|
+
// БЛОК СОСТОЯНИЯ — единственный наш текст, у которого читатель машина, а не человек.
|
|
51
|
+
// Уходит в контекст агента хуком SessionStart, поэтому пишется как утверждения о факте:
|
|
52
|
+
// без вежливости, без вводных, каждая строка либо факт, либо честное «неизвестно».
|
|
53
|
+
// Замер 2026-09-08: блок ≈147 токенов против ≈3348 у AGENTS.md — и несёт то, чего в файле
|
|
54
|
+
// нет и быть не может, потому что меняется каждый день.
|
|
55
|
+
context: {
|
|
56
|
+
title: "AQK — состояние этого репозитория на сейчас. Ниже только вычисленное машиной; где\nмашина не знает — сказано «неизвестно», и это не «всё хорошо».",
|
|
57
|
+
level: (r, top, missing) =>
|
|
58
|
+
`Уровень: AQK-${r} из ${top}.` + (missing ? ` До AQK-${r + 1} не хватает: ${missing}.` : ""),
|
|
59
|
+
levelUnknown: "Уровень: не вычислен — в репозитории нет .aqk.yml. Стандарт здесь не заведён.",
|
|
60
|
+
rules: (total, machine, human) =>
|
|
61
|
+
`Правил в точке входа: ${total}. Держит машина ${machine}, держит человек ${human}.`,
|
|
62
|
+
rulesNobody: "Правило, которое держит человек, не держит никто: машина его не проверяет.",
|
|
63
|
+
runNone:
|
|
64
|
+
"Прогон не делался — какие проверки красные, НЕИЗВЕСТНО. Это не «чисто»: `aqk doctor --run`.",
|
|
65
|
+
runStale: (when) =>
|
|
66
|
+
`Последний прогон ${when} СТАРЕЕ последнего коммита — он описывает не тот код, что здесь.`,
|
|
67
|
+
runClean: (when) => `Последний прогон ${when} — красных нет.`,
|
|
68
|
+
runRed: (when, names) => `Последний прогон ${when} — КРАСНЫЕ: ${names}.`,
|
|
69
|
+
andMore: (n) => `и ещё ${n}`,
|
|
70
|
+
skipped: (n) => `Не запускались: ${n} — инструмента нет на этой машине, их состояние неизвестно.`,
|
|
71
|
+
ratchets: (list) => `Храповики: ${list}. Список может только укорачиваться, увеличивать нельзя.`,
|
|
72
|
+
where: (entry) => `Свод правил: ${entry}. Чем доказан диф: \`aqk report --since main\`.`,
|
|
73
|
+
mapTitle: "ЧТО УМЕЕТ ЭТОТ ИНСТРУМЕНТ. Полный список команд — не пересказ, а тот же список,\nиз которого собрана справка:",
|
|
74
|
+
rulesTitle: (e) => `СВОД ПРАВИЛ ЭТОГО ПРОЕКТА (${e}) — дословно, целиком. Это не приглашение\nпрочитать: он уже здесь.`,
|
|
75
|
+
hookAlready: (p) => `хук уже стоит в ${p} — ничего не меняю.`,
|
|
76
|
+
hookAdded: (p) => `хук SessionStart дописан в ${p}:`,
|
|
77
|
+
hookCreated: (p) => `создан ${p} с хуком SessionStart:`,
|
|
78
|
+
hookBadJson: (p) => `${p} не разбирается как JSON — не трогаю его. Почини файл и повтори.`,
|
|
79
|
+
hookWhat:
|
|
80
|
+
"теперь состояние проекта попадает в контекст агента до первого действия, а не по его желанию.",
|
|
81
|
+
},
|
|
82
|
+
// Форма отчёта переехала сюда из терминального каталога 2026-09-08: это текст, который
|
|
83
|
+
// программа ПИШЕТ в .aqk/report.md, а не говорит в терминал. Повод — тот же гейт
|
|
84
|
+
// file-size-limit, что развёл эти файлы в первый раз: терминальный каталог снова перерос
|
|
85
|
+
// 500 строк. Шов по смыслу, а не пополам.
|
|
86
|
+
report2: {
|
|
87
|
+
evidenceNamed: (who) => `назван проверками: ${who}`,
|
|
88
|
+
evidenceSilent: (n) => `${n} ${n === 1 ? "проверка прошла" : "проверок прошли"} мимо и промолчали`,
|
|
89
|
+
evidenceTitle: "Чем доказан этот диф",
|
|
90
|
+
evidenceUncovered: "ни одна проверка не назвала этот файл",
|
|
91
|
+
evidenceBase: "база сравнения",
|
|
92
|
+
evidenceHash: "отпечаток доказательства",
|
|
93
|
+
evidenceBadRef: (r) => `ссылка «${r}» не разобрана — покрытие не считано`,
|
|
94
|
+
evidenceNoFiles: (r) => `файлов кода в дифе с «${r}» нет — доказывать нечего`,
|
|
95
|
+
evidenceWarn:
|
|
96
|
+
"◻️ означает: проверка обошла каталог и ничего не сказала. «Просмотрен и чист» и «не " +
|
|
97
|
+
"просмотрен вовсе» по выводу неразличимы — и выдавать одно за другое нельзя ни в ту, ни " +
|
|
98
|
+
"в другую сторону. " +
|
|
99
|
+
"Отпечаток считается по базе, набору команд и содержимому файлов. Изменилось что-то из " +
|
|
100
|
+
"этого — отчёт устарел, и «прогнал, потом поправил ещё три файла» перестаёт быть " +
|
|
101
|
+
"неотличимым от «прогнал». Механизм взят у donecheck (MIT).",
|
|
102
|
+
title: "Отчёт AQK",
|
|
103
|
+
noManifest: (cmd) => `Нет .aqk.yml — отчитываться не о чем. Начни с ${cmd}`,
|
|
104
|
+
level: "Уровень",
|
|
105
|
+
holdsTitle: "Что держит машина (прогон, а не манифест)",
|
|
106
|
+
nothingRuns: "⬜ ни один гейт не объявлен",
|
|
107
|
+
native: (prog) => `родной рецепт: ${prog}`,
|
|
108
|
+
portable: "переносимая проверка",
|
|
109
|
+
weakerTitle: "Стоит слабее возможного",
|
|
110
|
+
weaker: (progs) => `в системе есть ${progs}, но гейт стоит на переносимой проверке — она ловит меньше`,
|
|
111
|
+
missingTitle: "Чего нет",
|
|
112
|
+
nothingMissing: "✅ все применимые записи поставлены",
|
|
113
|
+
needsTool: (prog) => `нужен ${prog} — его нет в системе`,
|
|
114
|
+
notInstalled: "применимо, но не поставлено",
|
|
115
|
+
hiddenTitle: "Не применимо к этому репозиторию",
|
|
116
|
+
readTitle: "Что комплект велел прочитать",
|
|
117
|
+
readWarn:
|
|
118
|
+
"Значок означает только наличие файла на диске. Прочитан он или нет, машина не знает и " +
|
|
119
|
+
"не притворяется, что знает: это отвечает тот, кто отчитывается.",
|
|
120
|
+
ignoreTitle: "Что спрятано .aqkignore",
|
|
121
|
+
ignoreNone: "файла .aqkignore нет — ничего не спрятано",
|
|
122
|
+
ignoreWarn:
|
|
123
|
+
"Скрытое молча — тот же класс, что молчащий гейт: гейты по этим путям не смотрят вовсе. " +
|
|
124
|
+
"Строка здесь означает, что защиты там нет и не будет.",
|
|
125
|
+
whyTitle: "Зачем это нужно — коротко",
|
|
126
|
+
whyNothing: "нечего добавлять: применимое уже стоит",
|
|
127
|
+
saved: (path) => `Сохранено: ${path}`,
|
|
128
|
+
docs: {
|
|
129
|
+
baseline: "обязательный минимум проекта, без привязки к языку",
|
|
130
|
+
readyMade: "карта готовых правил: сперва ищи готовое, потом пиши своё",
|
|
131
|
+
rulesGeneral: "общие правила работы",
|
|
132
|
+
rulesTesting: "правила про тесты",
|
|
133
|
+
rulesSecurity: "правила про безопасность",
|
|
134
|
+
},
|
|
135
|
+
},
|
|
10
136
|
manifestDoc: {
|
|
11
137
|
head: [
|
|
12
138
|
"# .aqk.yml — манифест Agent Quality Kit",
|
|
@@ -15,7 +141,7 @@ const ruDocs = {
|
|
|
15
141
|
"# и это честно: заполнять заглушками бессмысленно, проверяются файлы, а не слова.",
|
|
16
142
|
],
|
|
17
143
|
entry: "# AQK-0 — что агент читает первым.",
|
|
18
|
-
rules: "# AQK-1 — где стандарты и какие проверки обязательны.",
|
|
144
|
+
rules: "# AQK-1 — где стандарты и какие проверки обязательны. docs — где методички:\n# оба пути читает doctor, так что комплект можно разложить куда угодно.",
|
|
19
145
|
gates: [
|
|
20
146
|
" # Имя: команда, возвращающая 0 или не 0. Пустое объявление защиты не даёт и бракуется",
|
|
21
147
|
" # проверкой «объявленный гейт запускается» — поэтому здесь примеры, а не заготовки.",
|
package/tool/i18n/ru.mjs
CHANGED
|
@@ -9,6 +9,18 @@ import { ruDocs } from "./ru-docs.mjs";
|
|
|
9
9
|
import { templates } from "./templates-ru.mjs";
|
|
10
10
|
|
|
11
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
|
+
},
|
|
12
24
|
...ruDocs,
|
|
13
25
|
templates,
|
|
14
26
|
help: {
|
|
@@ -28,13 +40,21 @@ export const ru = {
|
|
|
28
40
|
new: "заготовка своего гейта для каталога",
|
|
29
41
|
note: "записать урок в общий журнал шишек",
|
|
30
42
|
blob: "собрать методички в один файл GOD_AI.md",
|
|
31
|
-
|
|
43
|
+
learn: "кандидаты в правила из локальной переписки: сказано вслух и не записано",
|
|
44
|
+
context: "состояние проекта одним блоком — для контекста агента, а не для чтения",
|
|
45
|
+
contextInstall: "то же самое, но целиком — карта и свод правил — и хуком в контекст",
|
|
46
|
+
report: "обязательная форма отчёта: что стоит, что нет, что не прочитано; --since <ссылка> — ещё и чем доказан диф",
|
|
32
47
|
badge: "значок уровня для README — и проверка, что он не врёт",
|
|
48
|
+
version: "заставка и номер версии — то же по --version и -v",
|
|
33
49
|
noInstall: "Без установки: npx agent-quality-kit init",
|
|
34
50
|
language: "Язык вывода: AQK_LANG=en (или ru), иначе по системной локали",
|
|
35
51
|
},
|
|
36
52
|
|
|
37
53
|
doctor: {
|
|
54
|
+
baselineClash: (f) =>
|
|
55
|
+
`«--baseline» — осмотр, а не прогон: он выходит с нулём всегда, и вместе с «${f}» даёт\n` +
|
|
56
|
+
" конвейер, который не может покраснеть.\n" +
|
|
57
|
+
" почини: раздели на две команды — «doctor --baseline» и «doctor --run --min 1».",
|
|
38
58
|
docsKit: "методички — здесь оригиналы, а не копия",
|
|
39
59
|
docs: "методички",
|
|
40
60
|
rulesKit: "стандарты — здесь оригиналы, а не копия",
|
|
@@ -69,6 +89,16 @@ export const ru = {
|
|
|
69
89
|
hasThings: "есть",
|
|
70
90
|
install: (cmd) => `поставить: ${cmd}`,
|
|
71
91
|
notApplicable: (n) => `Не применимо к этому репозиторию (${n}):`,
|
|
92
|
+
noBrowserServer:
|
|
93
|
+
"у проекта есть интерфейс, а у агента нет браузера: посмотреть на своё изменение глазами\n он не может и судит о работе по тому, что собралось. Это разные утверждения.",
|
|
94
|
+
noBrowserServerHow: (l) => `подключается MCP-сервером, любым агентом: ${l}`,
|
|
95
|
+
coveredBy: (n) => `закрыто другим арбитром: ${n} — переносимую запись ставить незачем`,
|
|
96
|
+
coveredByGate: (g) => `держит «${g}», объявленный в манифесте`,
|
|
97
|
+
coversUnknown: (l) => `covers называет гейт, которого нет в gates: ${l} — эти записи не закрыты ничем`,
|
|
98
|
+
coversUnproven: (e, g, codes) =>
|
|
99
|
+
`заявка не подтверждена: «${e}» объявлена закрытой гейтом «${g}», но ни его команда, ни\n конфиг линтера не называют правил ${codes} — запись может быть не закрыта ничем`,
|
|
100
|
+
coversUnprovenHow: (cmd) => `подтверди: добавь эти правила в линтер, либо поставь запись — ${cmd}`,
|
|
101
|
+
totalCovered: (n) => `закрыто другим арбитром ${n}`,
|
|
72
102
|
total: "Итого:",
|
|
73
103
|
totalHeld: (n) => `держит машина ${n}`,
|
|
74
104
|
totalTodo: (n) => `применимо но не поставлено ${n}`,
|
|
@@ -78,6 +108,7 @@ export const ru = {
|
|
|
78
108
|
sinceBadRef: (ref) => `не могу сравнить с «${ref}»: такой ссылки нет или это не репозиторий git`,
|
|
79
109
|
notScopable: "вывод без путей — дифом не сужается, оставлен красным",
|
|
80
110
|
outsideDiff: (n) => `находки есть, но вне дифа (${n})`,
|
|
111
|
+
advisoryQuiet: "(совещательный — уронить прогон не может)",
|
|
81
112
|
advisoryMark: "совещательный — показано, прогон не уронен",
|
|
82
113
|
advisorySummary: (names) =>
|
|
83
114
|
`совещательные и красные: ${names.join(", ")}. Это выключенные проверки: ` +
|
|
@@ -92,6 +123,9 @@ export const ru = {
|
|
|
92
123
|
manifestUnknown: (keys) =>
|
|
93
124
|
`В манифесте поля, которых стандарт не знает: ${keys.join(", ")}. Похоже на опечатку — ` +
|
|
94
125
|
`такое поле молча читается как отсутствующее, и вердикт выходит неверным.`,
|
|
126
|
+
manifestUnparsed: (n, t) => `строка ${n} манифеста не разобрана и НЕ ДЕЙСТВУЕТ: ${t}`,
|
|
127
|
+
manifestUnparsedWhy:
|
|
128
|
+
"имена полей и гейтов пишутся латиницей, цифрами, дефисом и подчёркиванием. Объявленное\n здесь не выполняется вовсе — а выглядит так, будто выполняется.",
|
|
95
129
|
manifestKnown: (keys) => `Поля манифеста: ${keys.join(", ")}`,
|
|
96
130
|
thresholdPass: (min) => `Порог AQK-${min} пройден.`,
|
|
97
131
|
thresholdFail: (min, now) => `Порог AQK-${min} НЕ пройден: сейчас AQK-${now}.`,
|
|
@@ -151,6 +185,7 @@ export const ru = {
|
|
|
151
185
|
has_agent_config: ["агента здесь не настраивали", "настройки агента есть"],
|
|
152
186
|
has_agent_entry: ["свода для агента здесь нет", "свод для агента есть"],
|
|
153
187
|
has_ui: ["не видно стилей и компонентов интерфейса", "интерфейс есть: стили или компоненты"],
|
|
188
|
+
has_mcp: ["агенту здесь не подключали внешних инструментов через MCP", "MCP-серверы объявлены"],
|
|
154
189
|
},
|
|
155
190
|
},
|
|
156
191
|
|
|
@@ -347,12 +382,14 @@ export const ru = {
|
|
|
347
382
|
n4c: ". Гейт стережёт существующий артефакт:",
|
|
348
383
|
n4d: "проверка на код, которого ещё нет, — мёртвое правило.",
|
|
349
384
|
burned: (cmd) => `Обжёгся на чём-то — запиши: ${cmd}`,
|
|
385
|
+
hookHint: (cmd) => `Claude Code: ${cmd} — состояние проекта будет попадать в контекст агента\n само, до первого действия. Для других агентов хука нет, и это не недоделка.`,
|
|
350
386
|
},
|
|
351
387
|
|
|
352
388
|
feedback: {
|
|
353
389
|
title: "Если пригодилось:",
|
|
354
390
|
star: (url) => `Поставь звезду — ${url}`,
|
|
355
391
|
issue: "Нашёл баг или не подошло — заведи Issue, самая полезная обратная связь: и то и другое.",
|
|
392
|
+
notRemembered: "запомнить не удалось (дом недоступен для записи) — сообщение появится снова",
|
|
356
393
|
once: "Это разовое сообщение: больше не покажется на этой машине.",
|
|
357
394
|
},
|
|
358
395
|
|
|
@@ -434,6 +471,7 @@ export const ru = {
|
|
|
434
471
|
noTarget: "не видно, куда подставить каталог образца — команда написана руками",
|
|
435
472
|
otherRecipe: (lang) => `образцы написаны под рецепт «${lang}», а стоит другой — доказать нечем`,
|
|
436
473
|
noGates: "гейтов не объявлено — доказывать нечего",
|
|
474
|
+
needsProgram: (progs) => `НЕ ПРОВЕРЕНА здесь — нужен «${progs}»`,
|
|
437
475
|
noSamplesDir: "в .aqk.yml не заполнено поле samples — образцы искать негде",
|
|
438
476
|
nothingProven:
|
|
439
477
|
"ни один гейт не доказан. Уровень выше AQK-1 означал бы доверие к автору, а не факт:\n проект с гейтом «true» прошёл бы его так же, как проект с настоящей защитой.",
|
|
@@ -452,41 +490,6 @@ export const ru = {
|
|
|
452
490
|
checkOk: (level, where) => `Значок совпал с прогоном: AQK-${level} — ${where}`,
|
|
453
491
|
},
|
|
454
492
|
|
|
455
|
-
report2: {
|
|
456
|
-
title: "Отчёт AQK",
|
|
457
|
-
noManifest: (cmd) => `Нет .aqk.yml — отчитываться не о чем. Начни с ${cmd}`,
|
|
458
|
-
level: "Уровень",
|
|
459
|
-
holdsTitle: "Что держит машина (прогон, а не манифест)",
|
|
460
|
-
nothingRuns: "⬜ ни один гейт не объявлен",
|
|
461
|
-
native: (prog) => `родной рецепт: ${prog}`,
|
|
462
|
-
portable: "переносимая проверка",
|
|
463
|
-
weakerTitle: "Стоит слабее возможного",
|
|
464
|
-
weaker: (progs) => `в системе есть ${progs}, но гейт стоит на переносимой проверке — она ловит меньше`,
|
|
465
|
-
missingTitle: "Чего нет",
|
|
466
|
-
nothingMissing: "✅ все применимые записи поставлены",
|
|
467
|
-
needsTool: (prog) => `нужен ${prog} — его нет в системе`,
|
|
468
|
-
notInstalled: "применимо, но не поставлено",
|
|
469
|
-
hiddenTitle: "Не применимо к этому репозиторию",
|
|
470
|
-
readTitle: "Что комплект велел прочитать",
|
|
471
|
-
readWarn:
|
|
472
|
-
"Значок означает только наличие файла на диске. Прочитан он или нет, машина не знает и " +
|
|
473
|
-
"не притворяется, что знает: это отвечает тот, кто отчитывается.",
|
|
474
|
-
ignoreTitle: "Что спрятано .aqkignore",
|
|
475
|
-
ignoreNone: "файла .aqkignore нет — ничего не спрятано",
|
|
476
|
-
ignoreWarn:
|
|
477
|
-
"Скрытое молча — тот же класс, что молчащий гейт: гейты по этим путям не смотрят вовсе. " +
|
|
478
|
-
"Строка здесь означает, что защиты там нет и не будет.",
|
|
479
|
-
whyTitle: "Зачем это нужно — коротко",
|
|
480
|
-
whyNothing: "нечего добавлять: применимое уже стоит",
|
|
481
|
-
saved: (path) => `Сохранено: ${path}`,
|
|
482
|
-
docs: {
|
|
483
|
-
baseline: "обязательный минимум проекта, без привязки к языку",
|
|
484
|
-
readyMade: "карта готовых правил: сперва ищи готовое, потом пиши своё",
|
|
485
|
-
rulesGeneral: "общие правила работы",
|
|
486
|
-
rulesTesting: "правила про тесты",
|
|
487
|
-
rulesSecurity: "правила про безопасность",
|
|
488
|
-
},
|
|
489
|
-
},
|
|
490
493
|
|
|
491
494
|
|
|
492
495
|
};
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
// tool/lib/banner.mjs — заставка. Первая встреча человека с комплектом.
|
|
2
|
+
//
|
|
3
|
+
// ЗАЧЕМ ОНА ЕСТЬ. Жалоба владельца: «скачал и че дальше, не понятно, работает он вообще или
|
|
4
|
+
// нет». Строка присутствия в прогоне отвечает на «работает ли», а заставка — на «что это
|
|
5
|
+
// вообще такое»: она показывается там, где человек встречается с инструментом впервые.
|
|
6
|
+
//
|
|
7
|
+
// ПОКАЗЫВАЕТСЯ ДВАЖДЫ ЗА ВСЮ ЖИЗНЬ: при `init` и по `--version`. На каждой команде — нет:
|
|
8
|
+
// то, что видишь тридцатый раз, перестаёт читаться, а рядом стоят находки, которые читать надо.
|
|
9
|
+
//
|
|
10
|
+
// РИСУНОК — СВОЙ. Готовые работы из чужих подборок красивы, но у них нет ни автора, ни
|
|
11
|
+
// лицензии, а пакет уезжает в npm под MIT. Вставить чужое молча — сделать ровно то, за что мы
|
|
12
|
+
// ругаем других. Этот прислан владельцем; надпись собрана шрифтом ANSI Shadow, к шрифтам
|
|
13
|
+
// вопросов о правах нет.
|
|
14
|
+
// Отступ у кошки ОДИН пробел, у букв — два, и это не опечатка. Пустой Брайль `⠀` занимает
|
|
15
|
+
// место, но ничего не рисует: у рисунка видимая часть начинается со второго знака, и при
|
|
16
|
+
// одинаковом отступе кошка выглядела сдвинутой вправо на целый знак. Центры сведены по
|
|
17
|
+
// ВИДИМОЙ части, а не по началу строки.
|
|
18
|
+
const ART = [
|
|
19
|
+
" ⠀⠀⠀⠀⢀⣾⢷⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣼⢧⡀",
|
|
20
|
+
" ⠀⠀⠀⠀⠀⢀⣾⠃⢘⣷⣀⣀⣤⣤⣤⣄⣀⢸⡏⠈⢷⡀",
|
|
21
|
+
" ⠀⠀⠀⠀⠀⣼⣷⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣶⣬⣷",
|
|
22
|
+
" ⠀⠀⠀⠀⢰⣿⣿⣿⣿⠁⠸⠗⢹⣿⡟⠰⠷⠈⢻⣿⣿⣿⡇",
|
|
23
|
+
" ⠀⠀⢐⣒⣺⣿⣿⣿⣿⣀⠀⣀⣼⠛⢷⣄⠀⢀⣼⣿⣿⣿⣿⣉⡁",
|
|
24
|
+
" ⠀⠀⠀⠤⢺⣿⣿⣿⣿⣿⣿⣿⣿⡇⣿⣿⣿⣿⣿⣿⣿⣿⣿⠢",
|
|
25
|
+
"",
|
|
26
|
+
" █████╗ ██████╗ ██╗ ██╗",
|
|
27
|
+
" ██╔══██╗██╔═══██╗██║ ██╔╝",
|
|
28
|
+
" ███████║██║ ██║█████╔╝",
|
|
29
|
+
" ██╔══██║██║▄▄ ██║██╔═██╗",
|
|
30
|
+
" ██║ ██║╚██████╔╝██║ ██╗",
|
|
31
|
+
" ╚═╝ ╚═╝ ╚══▀▀═╝ ╚═╝ ╚═╝",
|
|
32
|
+
];
|
|
33
|
+
|
|
34
|
+
// Объявленная ширина сверяется с настоящей модульной проверкой: правка рисунка, разъехавшаяся
|
|
35
|
+
// с этим числом, роняет её. Иначе число здесь через месяц врёт, как всякая цифра в комментарии.
|
|
36
|
+
const BANNER_WIDTH = 27;
|
|
37
|
+
|
|
38
|
+
// Терминал без UTF-8 превратит Брайль в вопросительные знаки, и человек решит, что инструмент
|
|
39
|
+
// сломан. Тогда честнее короткая строка: она несёт то же самое, только буквами.
|
|
40
|
+
function canDrawArt(env) {
|
|
41
|
+
if (String(env.AQK_NO_ART || "") === "1") return false;
|
|
42
|
+
const loc = `${env.LC_ALL || ""}${env.LC_CTYPE || ""}${env.LANG || ""}`.toLowerCase();
|
|
43
|
+
// Windows-терминалы локаль обычно не выставляют вовсе, а UTF-8 там с 2019 года есть.
|
|
44
|
+
// Поэтому пусто — рисуем; явно не-UTF-8 — не рисуем.
|
|
45
|
+
if (!loc) return true;
|
|
46
|
+
return loc.includes("utf-8") || loc.includes("utf8");
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// ВЕРСИИ В ЗАСТАВКЕ НЕТ — решение владельца. Выпуски идут часто, и номер в картинке устаревает
|
|
50
|
+
// быстрее всего остального; за версией есть `--version`, где она и печатается отдельной строкой.
|
|
51
|
+
function banner(env = process.env) {
|
|
52
|
+
if (!canDrawArt(env)) return " aqk — agent quality kit";
|
|
53
|
+
// Отступ подписи — шесть, а не три: он посчитан от ВИДИМОГО центра букв (со 2-го знака по
|
|
54
|
+
// 26-й, центр 14), а не подобран на глаз. Подобранный на глаз держался до первой правки
|
|
55
|
+
// рисунка; посчитанный переживёт её, а проверка ниже покажет, если разъедется.
|
|
56
|
+
return `${ART.join("\n")}\n agent quality kit`;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export { banner, BANNER_WIDTH, canDrawArt };
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import { readFile, writeFile, mkdir } from "node:fs/promises";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { CWD, PKG_ROOT, TARGET_DIR, SELF, c } from "./core.mjs";
|
|
4
|
+
import { L } from "../i18n/index.mjs";
|
|
5
|
+
import { canDrawArt } from "./banner.mjs";
|
|
6
|
+
// tool/lib/brief.mjs — короткая строка присутствия для прогона в хуке.
|
|
7
|
+
//
|
|
8
|
+
// ЗАЧЕМ ЭТО СУЩЕСТВУЕТ. Хук pre-commit молчит на успехе: вывод показывается только при провале
|
|
9
|
+
// — это его умолчание, проверено по документации 2026-09-08. То есть комплект, который всё
|
|
10
|
+
// держит, для человека НЕОТЛИЧИМ от невставленного: он поставил, поработал неделю и не увидел
|
|
11
|
+
// ни строки. «Скачал и че дальше» — дословная жалоба владельца.
|
|
12
|
+
//
|
|
13
|
+
// Это ровно тот порок, против которого написан весь комплект, только у нас самих: тишина
|
|
14
|
+
// неотличима от успеха. Поэтому строка печатается ВСЕГДА, в том числе — и особенно — когда
|
|
15
|
+
// всё хорошо. Одна строка: присутствие видно, читать нечего.
|
|
16
|
+
//
|
|
17
|
+
// СОВЕТ ОТДЕЛЬНО И РЕДКО. Вторая строка называет ОДНУ непоставленную запись и способ отказаться.
|
|
18
|
+
// Не список: список читается как «у вас всё плохо» и не помогает выбрать. Не каждый коммит:
|
|
19
|
+
// то, что видишь тридцатый раз, перестаёт читаться — и пролистывается вместе с настоящими
|
|
20
|
+
// находками, стоящими рядом.
|
|
21
|
+
|
|
22
|
+
// Сутки. Не «раз в прогон» и не «раз в неделю»: за сутки человек успевает забыть, но не успевает
|
|
23
|
+
// устать. Число здесь спорное — важно, что ограничитель есть и он машинный.
|
|
24
|
+
const ADVICE_EVERY_MS = 24 * 60 * 60 * 1000;
|
|
25
|
+
|
|
26
|
+
// ЗНАЧОК ПРИСУТСТВИЯ — здесь, а не в каталогах строк. Символ один на оба языка, и держать его
|
|
27
|
+
// в двух местах значит однажды получить разные значки в ru и en: то же правило, по которому у
|
|
28
|
+
// нас один свод правил на две точки входа. Выбран владельцем из пятидесяти семи вариантов.
|
|
29
|
+
// В терминале без UTF-8 он превратится в мусор — там правило то же, что у заставки, и оно
|
|
30
|
+
// одно на двоих: разойдись эти два условия, и значок рисовался бы там, где картинка уже нет.
|
|
31
|
+
const MARK = "❖";
|
|
32
|
+
|
|
33
|
+
function briefLine(state, L, env = process.env) {
|
|
34
|
+
const t = L.brief;
|
|
35
|
+
const mark = canDrawArt(env) ? `${MARK} ` : "";
|
|
36
|
+
const parts = [t.held(state.held), t.todo(state.todo)];
|
|
37
|
+
if (state.level >= 0) parts.push(`AQK-${state.level}`);
|
|
38
|
+
else parts.push(t.levelUnknown);
|
|
39
|
+
const head = `${mark}${t.name} ${parts.join(" · ")}`;
|
|
40
|
+
if (!state.red || !state.red.length) return head;
|
|
41
|
+
return `${head}\n${t.red(state.red.join(", "))}`;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// «Не знаем, когда показывали» и «показывали давно» — одно и то же решение: показать.
|
|
45
|
+
// Испорченная отметка попадает сюда же намеренно: молчать из-за нечитаемого файла состояния
|
|
46
|
+
// значит потерять совет навсегда и не сказать почему.
|
|
47
|
+
function adviceDue(lastIso, now = Date.now()) {
|
|
48
|
+
if (!lastIso) return true;
|
|
49
|
+
const t = Date.parse(String(lastIso));
|
|
50
|
+
if (!Number.isFinite(t)) return true;
|
|
51
|
+
return now - t >= ADVICE_EVERY_MS;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Первая из непоставленных, а не «самая важная»: важность мы не считаем, а порядок каталога
|
|
55
|
+
// осмыслен — записи в нём лежат от общего к частному. Выдавать порядок за приоритет нельзя.
|
|
56
|
+
function pickAdvice(todo = []) {
|
|
57
|
+
return todo.length ? todo[0] : null;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// УВЕДОМЛЕНИЕ ОБ ОБНОВЛЕНИИ — и почему НЕ автообновление.
|
|
61
|
+
//
|
|
62
|
+
// Выпуски идут часто, а у половины способов установки версия закреплена и сама не двигается:
|
|
63
|
+
// `rev:` у pre-commit, тег у GitHub Action. Человек ставит комплект, получает версию с уже
|
|
64
|
+
// исправленной ошибкой и не узнаёт об этом никогда. У `npx` без версии проблемы нет — он берёт
|
|
65
|
+
// свежее при каждом запуске.
|
|
66
|
+
//
|
|
67
|
+
// АВТООБНОВЛЕНИЯ НЕТ, И ЭТО РЕШЕНИЕ, А НЕ НЕДОДЕЛКА. В тот же день выпущена запись каталога,
|
|
68
|
+
// краснеющая на `@latest`: «версия не закреплена — завтра приедет другая». Инструмент, который
|
|
69
|
+
// молча подменяет себя, стоя на воротах коммита, делал бы ровно то, что мы запрещаем другим.
|
|
70
|
+
// Первый же внешний читатель это заметит, и будет прав.
|
|
71
|
+
//
|
|
72
|
+
// СРАВНЕНИЕ ПО ЧИСЛАМ. Строкой «0.10.0» меньше «0.9.0», и уведомление пропало бы ровно на
|
|
73
|
+
// десятом выпуске — тихо и надолго. Такие поломки не замечают месяцами.
|
|
74
|
+
function cmpVer(a, b) {
|
|
75
|
+
const pa = String(a).split(".").map((n) => parseInt(n, 10) || 0);
|
|
76
|
+
const pb = String(b).split(".").map((n) => parseInt(n, 10) || 0);
|
|
77
|
+
for (let i = 0; i < Math.max(pa.length, pb.length); i++) {
|
|
78
|
+
const d = (pa[i] || 0) - (pb[i] || 0);
|
|
79
|
+
if (d) return d;
|
|
80
|
+
}
|
|
81
|
+
return 0;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Совет зависит от способа установки: у pre-commit это `autoupdate`, а не npm. Совет мимо
|
|
85
|
+
// способа человек не выполнит — и перестанет читать следующие.
|
|
86
|
+
function updateNotice(current, latest, env = process.env, L) {
|
|
87
|
+
if (!current || !latest || cmpVer(latest, current) <= 0) return null;
|
|
88
|
+
const how = env.PRE_COMMIT ? L.brief.updateHookHow : L.brief.updateHow;
|
|
89
|
+
return `${L.brief.update(latest, current, how)}\n${L.brief.updateOff("AQK_UPDATE=0")}`;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// В конвейере не спрашиваем вовсе: там версия закреплена сознательно, читать уведомление
|
|
93
|
+
// некому, а лишний исходящий запрос из инструмента, который иначе не делает ни одного, —
|
|
94
|
+
// плохой размен. Отказ человека уважается тем же способом.
|
|
95
|
+
function updateWanted(env = process.env) {
|
|
96
|
+
if (String(env.AQK_UPDATE || "") === "0") return false;
|
|
97
|
+
if (env.CI || env.GITHUB_ACTIONS || env.GITLAB_CI) return false;
|
|
98
|
+
return true;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// КРАТКИЙ РЕЖИМ для хука. Вывод целиком БУФЕРИЗУЕТСЯ, а печатается одна строка присутствия —
|
|
102
|
+
// и, при провале, весь буфер, чтобы человеку было что чинить. Перехват console.log выглядит
|
|
103
|
+
// грубо, и это осознанный размен: альтернатива — протащить флаг через четыреста строк печати,
|
|
104
|
+
// где каждая строка стала бы условной. Перехват локален, снимается в том же вызове и объяснён
|
|
105
|
+
// здесь; условие в каждой строке объяснить было бы негде.
|
|
106
|
+
function beginBrief() {
|
|
107
|
+
const lines = [];
|
|
108
|
+
const real = console.log;
|
|
109
|
+
console.log = (...a) => lines.push(a.join(" "));
|
|
110
|
+
return { lines, restore: () => { console.log = real; } };
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Печать краткого итога. Совет — не чаще раза в сутки и с явным способом отказаться: то, что
|
|
114
|
+
// видишь тридцатый раз, перестаёт читаться и пролистывается вместе с настоящими находками рядом.
|
|
115
|
+
// Отметка времени лежит в .aqk/, который в .gitignore: это состояние машины, а не проекта.
|
|
116
|
+
async function finishBrief(buf, state, todoRecs, ok) {
|
|
117
|
+
if (!buf) return;
|
|
118
|
+
buf.restore();
|
|
119
|
+
console.log(briefLine(state, L));
|
|
120
|
+
|
|
121
|
+
// Уведомление об обновлении — ДО разбора вердикта: оно от него не зависит. Сначала было
|
|
122
|
+
// после, и у любого проекта, где чего-то не хватает, версия не спрашивалась никогда —
|
|
123
|
+
// то есть у всех, кому комплект и нужен. Поймано первым же живым запуском.
|
|
124
|
+
await maybeUpdateNotice();
|
|
125
|
+
|
|
126
|
+
// При провале печатаем ВЕСЬ буфер: человеку нужно чинить, а одной строкой не починишь.
|
|
127
|
+
if (!ok) { console.log(buf.lines.join("\n")); return; }
|
|
128
|
+
|
|
129
|
+
if (process.env.AQK_ADVICE === "0" || !state.todo) return;
|
|
130
|
+
const stampFile = join(CWD, TARGET_DIR, "advice-shown");
|
|
131
|
+
let last = null;
|
|
132
|
+
try { last = (await readFile(stampFile, "utf8")).trim(); } catch { /* не показывали ещё */ }
|
|
133
|
+
if (!adviceDue(last)) return;
|
|
134
|
+
const advice = pickAdvice(todoRecs);
|
|
135
|
+
if (!advice) return;
|
|
136
|
+
console.log(c.dim(L.brief.advise(advice.slug, advice.intent || "")));
|
|
137
|
+
console.log(c.dim(L.brief.adviseOff(`${SELF} why ${advice.slug}`, "AQK_ADVICE=0")));
|
|
138
|
+
try {
|
|
139
|
+
await mkdir(join(CWD, TARGET_DIR), { recursive: true });
|
|
140
|
+
await writeFile(stampFile, new Date().toISOString(), "utf8");
|
|
141
|
+
} catch { /* не смогли записать отметку — совет повторится, это не беда */ }
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Спрашивает реестр npm о своей версии. РАЗ В СУТКИ, НЕ В КОНВЕЙЕРЕ, С ТАЙМАУТОМ, И МОЛЧА
|
|
145
|
+
// ПРИ ЛЮБОЙ ОШИБКЕ. До этой строки комплект не делал ни одного исходящего запроса — так
|
|
146
|
+
// написано в README и SECURITY.md, и там же теперь написано про этот. Сделать тихо то, за что
|
|
147
|
+
// мы ругаем других, нельзя: весь смысл в том, что заявленное совпадает с происходящим.
|
|
148
|
+
//
|
|
149
|
+
// Код возврата не меняется никогда: уведомление, роняющее коммит, выключат в тот же день —
|
|
150
|
+
// и вместе с ним всё остальное, что печатает эта строка.
|
|
151
|
+
async function maybeUpdateNotice() {
|
|
152
|
+
if (!updateWanted()) return;
|
|
153
|
+
const stamp = join(CWD, TARGET_DIR, "update-checked");
|
|
154
|
+
let last = null;
|
|
155
|
+
try { last = (await readFile(stamp, "utf8")).trim(); } catch { /* ещё не спрашивали */ }
|
|
156
|
+
if (!adviceDue(last)) return;
|
|
157
|
+
|
|
158
|
+
let current = "";
|
|
159
|
+
try { current = JSON.parse(await readFile(join(PKG_ROOT, "package.json"), "utf8")).version || ""; } catch { return; }
|
|
160
|
+
|
|
161
|
+
// ОТМЕТКА СТАВИТСЯ ДО ЗАПРОСА, а не после удачного ответа. Сперва было наоборот, и замер
|
|
162
|
+
// показал цену: человек без сети платил бы ожиданием на КАЖДОМ коммите, а не раз в сутки.
|
|
163
|
+
// Из двух ошибок выбрана дешёвая: пропущенное за день уведомление против ежедневного стопора.
|
|
164
|
+
try {
|
|
165
|
+
await mkdir(join(CWD, TARGET_DIR), { recursive: true });
|
|
166
|
+
await writeFile(stamp, new Date().toISOString(), "utf8");
|
|
167
|
+
} catch { /* не смогли записать — спросим ещё раз, это не беда */ }
|
|
168
|
+
|
|
169
|
+
let latest = "";
|
|
170
|
+
try {
|
|
171
|
+
// Три секунды, а не полторы. Замерено 2026-09-08: тёплый запрос к реестру — 533 мс,
|
|
172
|
+
// а первый, с разрешением имени и рукопожатием, в полторы секунды не уложился. Слишком
|
|
173
|
+
// тугой срок означал бы, что уведомление не приходит никогда и никто не знает почему.
|
|
174
|
+
const r = await fetch("https://registry.npmjs.org/agent-quality-kit/latest", {
|
|
175
|
+
signal: AbortSignal.timeout(3000),
|
|
176
|
+
headers: { accept: "application/vnd.npm.install-v1+json" },
|
|
177
|
+
});
|
|
178
|
+
if (!r.ok) return;
|
|
179
|
+
latest = String((await r.json()).version || "");
|
|
180
|
+
} catch {
|
|
181
|
+
// Сети нет, реестр молчит, таймаут — всё это НЕ повод сказать хоть слово. Инструмент,
|
|
182
|
+
// который жалуется на отсутствие интернета посреди коммита, выключают.
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const notice = updateNotice(current, latest, process.env, L);
|
|
187
|
+
if (notice) console.log(c.dim(notice));
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Наружу — только то, что зовут снаружи. `cmpVer` и `ADVICE_EVERY_MS` внутренние: экспорт,
|
|
191
|
+
// который никто не импортирует, читается как часть договора и мешает менять внутренности.
|
|
192
|
+
export { briefLine, adviceDue, pickAdvice, updateNotice, updateWanted, beginBrief, finishBrief };
|
package/tool/lib/core.mjs
CHANGED
|
@@ -57,6 +57,37 @@ function selfCmd() {
|
|
|
57
57
|
|
|
58
58
|
const SELF = selfCmd();
|
|
59
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
|
+
{ name: "vitals", args: "", text: h.vitals },
|
|
87
|
+
{ name: "version", args: "", text: h.version },
|
|
88
|
+
];
|
|
89
|
+
}
|
|
90
|
+
|
|
60
91
|
function die(msg) {
|
|
61
92
|
console.error(c.red(msg));
|
|
62
93
|
process.exit(1);
|
|
@@ -114,5 +145,5 @@ export {
|
|
|
114
145
|
copyDir, writeIfAbsent,
|
|
115
146
|
PKG_ROOT, CWD, DOCS_SRC, RULES_SRC, TARGET_DIR, docPath,
|
|
116
147
|
MANIFEST, GATES_SRC, PROJECT_GATES, RATCHET_DIR, RATCHET_LIB,
|
|
117
|
-
SELF, REPO_URL, c, exists, die, FEEDBACK_MARK,
|
|
148
|
+
SELF, REPO_URL, c, exists, die, FEEDBACK_MARK, commandRows,
|
|
118
149
|
};
|