agent-quality-kit 0.2.4 → 0.3.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.
Files changed (37) hide show
  1. package/README.md +118 -94
  2. package/README.ru.md +178 -0
  3. package/kit/gates/_skip.sh +31 -3
  4. package/kit/gates/commit-explains-itself/gate.yml +1 -0
  5. package/kit/gates/complexity-limit/gate.yml +1 -0
  6. package/kit/gates/dead-code/gate.yml +1 -0
  7. package/kit/gates/deps-are-pinned/gate.yml +1 -0
  8. package/kit/gates/duplicate-code/gate.yml +1 -0
  9. package/kit/gates/entry-links-exist/gate.yml +1 -0
  10. package/kit/gates/file-size-limit/gate.yml +1 -0
  11. package/kit/gates/gate-has-samples/gate.yml +1 -0
  12. package/kit/gates/gates-are-runnable/gate.yml +1 -0
  13. package/kit/gates/gates-run-in-ci/gate.yml +1 -0
  14. package/kit/gates/lesson-has-outcome/gate.yml +1 -0
  15. package/kit/gates/no-print-in-prod/check.sh +3 -1
  16. package/kit/gates/no-print-in-prod/gate.yml +1 -0
  17. package/kit/gates/secrets-not-in-code/gate.yml +1 -0
  18. package/kit/gates/swallowed-error/gate.yml +1 -0
  19. package/kit/gates/todo-without-task/gate.yml +1 -0
  20. package/package.json +2 -1
  21. package/tool/commands/doctor.mjs +38 -41
  22. package/tool/commands/gates.mjs +103 -109
  23. package/tool/commands/project.mjs +84 -85
  24. package/tool/commands/report.mjs +194 -0
  25. package/tool/i18n/en.mjs +423 -0
  26. package/tool/i18n/index.mjs +33 -0
  27. package/tool/i18n/ru.mjs +424 -0
  28. package/tool/i18n/templates-en.mjs +164 -0
  29. package/tool/i18n/templates-ru.mjs +170 -0
  30. package/tool/lib/core.mjs +5 -1
  31. package/tool/lib/manifest.mjs +12 -31
  32. package/tool/lib/repo.mjs +45 -21
  33. package/tool/lib/templates.mjs +38 -182
  34. package/tool/program.mjs +31 -9
  35. package/tool/selfcheck/gates.sh +7 -1
  36. package/tool/selfcheck/smoke.sh +97 -0
  37. package/tool/selfcheck/units.mjs +80 -5
@@ -0,0 +1,424 @@
1
+ // tool/i18n/ru.mjs — русский каталог строк вывода.
2
+ //
3
+ // Правка здесь обязана иметь пару в en.mjs с тем же ключом: расхождение ловит модульная
4
+ // проверка «оба каталога несут одни и те же ключи». Иначе один язык молча отстаёт, а
5
+ // обещание «два языка» превращается в слова.
6
+
7
+ import { templates } from "./templates-ru.mjs";
8
+
9
+ export const ru = {
10
+ templates,
11
+ help: {
12
+ tagline: "оснастка для разработки с агентами",
13
+ name: "<имя>",
14
+ init: "разложить правила и методички в текущий проект",
15
+ initForce: "перезаписать уже существующие файлы",
16
+ start: "кода ещё нет: сторожа дня 0 и порядок работы",
17
+ doctor: "проверить, что разложено и чего не хватает",
18
+ doctorRun: "ещё и запустить объявленные гейты",
19
+ add: "поставить гейт из каталога в проект",
20
+ find: "есть ли уже такой гейт — сверка по намерению",
21
+ why: "поймал ошибку — почему её не поймал сторож",
22
+ ratchet: "храповик: старые нарушения — долг, новые не пускать",
23
+ new: "заготовка своего гейта для каталога",
24
+ note: "записать урок в общий журнал шишек",
25
+ blob: "собрать методички в один файл GOD_AI.md",
26
+ report: "обязательная форма отчёта: что стоит, что нет, что не прочитано",
27
+ noInstall: "Без установки: npx agent-quality-kit init",
28
+ language: "Язык вывода: AQK_LANG=en (или ru), иначе по системной локали",
29
+ },
30
+
31
+ doctor: {
32
+ docsKit: "методички — здесь оригиналы, а не копия",
33
+ docs: "методички",
34
+ rulesKit: "стандарты — здесь оригиналы, а не копия",
35
+ rules: "стандарты",
36
+ agents: "точка входа для агентов",
37
+ gitignore: "гигиена репозитория",
38
+ git: "проект под контролем версий",
39
+
40
+ emptyCommands: (n) => `В AGENTS.md ${n} незаполненных команд.`,
41
+ emptyCommandsWhy: "Агент не может выполнить пустую строку.",
42
+
43
+ levelHeading: "Уровень соответствия AQK",
44
+ levelNone: "нет",
45
+ levelNotSet: "Уровень: стандарт в этом репозитории не заведён.",
46
+ levelNotSetWhy: [
47
+ "Это не оценка проекта. Уровень мерит не зрелость практики, а то, можно ли",
48
+ "прочитать её машиной. Проверки могут стоять и работать — но пока они не",
49
+ "объявлены в .aqk.yml, ни агент, ни конвейер, ни новый человек о них не знают.",
50
+ ],
51
+ levelManifestNoZero: "Уровень: манифест есть, но AQK-0 не пройден.",
52
+ level: (n) => `Уровень: AQK-${n}.`,
53
+ toReach: (n) => `Чтобы достичь AQK-${n}:`,
54
+ gives: (what) => `Что это даст: ${what}`,
55
+ allDone: "Все ступени пройдены.",
56
+
57
+ gatesHeading: "Гейты",
58
+ langs: "языки",
59
+ langsUnknown: "не определены",
60
+ files: "файлов",
61
+ hasThings: "есть",
62
+ install: (cmd) => `поставить: ${cmd}`,
63
+ notApplicable: (n) => `Не применимо к этому репозиторию (${n}):`,
64
+ total: "Итого:",
65
+ totalHeld: (n) => `держит машина ${n}`,
66
+ totalTodo: (n) => `применимо но не поставлено ${n}`,
67
+ totalSkip: (n) => `скрыто ${n}`,
68
+
69
+ runHeading: "Прогон объявленных гейтов",
70
+ timeout: "не уложился в 5 минут",
71
+ exitCode: (code) => `код ${code}`,
72
+ moreLines: (n) => `… и ещё ${n} строк`,
73
+ declaredNotRun: (n) => `${n} гейтов объявлено, но не запускалось.`,
74
+ declaredNotRunWhy: (cmd) => ` «Объявлен» и «работает» — разные утверждения: ${cmd}`,
75
+
76
+ thresholdPass: (min) => `Порог AQK-${min} пройден.`,
77
+ thresholdFail: (min, now) => `Порог AQK-${min} НЕ пройден: сейчас AQK-${now}.`,
78
+ },
79
+
80
+ trigger: {
81
+ noLangs: (langs) => `нет языков: ${langs}`,
82
+ tooFewFiles: (n) => `меньше ${n} файлов — рано`,
83
+ tooManyFiles: (n) => `больше ${n} файлов`,
84
+ notSet: "триггер не задан",
85
+ unknown: (key) => `условие «${key}» программа не умеет считать`,
86
+ flags: {
87
+ has_gates: ["в манифесте не объявлено ни одного гейта", "гейты уже объявлены"],
88
+ has_ci: ["в репозитории нет конвейера", "конвейер уже есть"],
89
+ has_db: ["не видно базы данных: ни миграций, ни sql", "база данных есть"],
90
+ has_docker: ["нет Dockerfile или compose", "docker уже есть"],
91
+ has_deps: ["не видно файла зависимостей", "зависимости объявлены"],
92
+ has_tests: ["не видно тестов", "тесты есть"],
93
+ has_env: ["нет файла окружения", "файл окружения есть"],
94
+ },
95
+ },
96
+
97
+ recipe: {
98
+ skipped: (lang, prog) => `рецепт под ${lang} пропущен: «${prog}» не установлен`,
99
+ none: "рецепт не описан",
100
+ },
101
+
102
+ manifest: {
103
+ noGatesBlock: "в .aqk.yml нет блока gates:",
104
+ alreadyDeclared: "уже объявлен",
105
+ },
106
+
107
+ add: {
108
+ noSuchGate: (slug, cmd) => `Нет такого гейта: ${slug}\nСписок применимых — ${cmd}`,
109
+ noRecipe: (slug, stack) => `У записи ${slug} нет команды ни под ${stack}, ни общей.`,
110
+ thisStack: "этот стек",
111
+ needName: (cmd, doctor) => `Укажи имя гейта: ${cmd}. Список — ${doctor}`,
112
+ noManifest: (cmd) => `Нет .aqk.yml — сначала ${cmd}`,
113
+ notApplicable: (why) => `Этот гейт к репозиторию не применим: ${why}`,
114
+ installAnyway: "Ставлю всё равно — решение твоё, но сторожить ему нечего.",
115
+ copied: (n) => `${n} файлов: проверка и образцы`,
116
+ declared: (cmd) => `гейт объявлен: ${cmd}`,
117
+ notDeclared: (why, slug, cmd) => `не тронут (${why}). Впиши сам: ${slug}: "${cmd}"`,
118
+ nextTitle: "Дальше:",
119
+ next1: "Проверь, что он краснеет и молчит там, где должен:",
120
+ expectFail: "→ ожидается отказ",
121
+ expectSilence: "→ ожидается тишина",
122
+ next2: "Впиши команду в хук коммита и в конвейер.",
123
+ next2Why: "Гейт, который никто не запускает, — не гейт.",
124
+ next3: (cmd) => `Прогон всех объявленных: ${cmd}`,
125
+ },
126
+
127
+ gnew: {
128
+ needName: (cmd) => `Укажи имя: ${cmd}`,
129
+ badName: (slug) => `Имя «${slug}» не годится: латиница через дефис, например secrets-not-in-code.\nИмя читают в чужих проектах — оно часть словаря.`,
130
+ looksExisting: (slug) => `Похоже, такое уже есть: ${slug}`,
131
+ recipeNotGate: "Рецепт под другой стек — это строка в recipes существующей записи.",
132
+ forceHint: (cmd) => `Всё равно завести новую: ${cmd}`,
133
+ exists: (path) => `${path} уже существует.`,
134
+ nextTitle: "Дальше — по порядку:",
135
+ n1: "Проверь, нет ли готового правила",
136
+ n1Where: "в ruff, eslint, semgrep.",
137
+ n1Why: "Готовое точнее, подробнее и его поддерживают без тебя. Своя проверка — запасная.",
138
+ n2: "Положи образцы.",
139
+ n2Red: "— код, на котором проверка обязана сработать.",
140
+ n2Green: "— тот же код, но правильный.",
141
+ n2Why: "Зелёный важнее: он ловит проверку, которая краснеет на исправном коде.",
142
+ n3: "Напиши проверку",
143
+ n3Where: "в check.sh. В тексте отказа — что именно сделать.",
144
+ n4: "Заполни gate.yml:",
145
+ n4What: "намерение, триггер, доказательство отказом.",
146
+ n5: "Прогони:",
147
+ n5Why: "Арбитр обязан покраснеть на red/ и промолчать на green/. Не прошло — не запись.",
148
+ },
149
+
150
+ ratchet: {
151
+ needName: (cmd) => `Укажи гейт: ${cmd}. Он должен быть уже объявлен в .aqk.yml`,
152
+ noManifest: (cmd) => `Нет .aqk.yml — сначала ${cmd}`,
153
+ notDeclared: (slug, cmd) => `Гейт «${slug}» не объявлен в .aqk.yml. Сначала: ${cmd}`,
154
+ already: (slug) => `На гейте «${slug}» храповик уже стоит.`,
155
+ notRunnable: (slug, cmd) =>
156
+ `Гейт «${slug}» не запускается: ${cmd}\n` +
157
+ `Снимать долг с несуществующего сторожа нельзя — в реестр попадут его же сообщения\n` +
158
+ `об ошибке, и он станет разрешением. Сначала почини команду.`,
159
+ registryHead: (slug, stamp) =>
160
+ `# Реестр долга: ${slug}\n` +
161
+ `# Снят ${stamp}. Список разрешается ТОЛЬКО укорачивать.\n` +
162
+ `# Новое нарушение красит гейт; исправленное вычёркивается автоматически.\n`,
163
+ recorded: (n) => `${n} нарушений записано долгом`,
164
+ libCopied: "обёртка скопирована в проект",
165
+ wrapped: "команда завёрнута в храповик",
166
+ changesTitle: "Что это меняет:",
167
+ fromToday: "со дня установки",
168
+ changes1: (fromToday) => `Правило действует ${fromToday}. Старый код трогать не надо, но новое`,
169
+ changes2: "нарушение того же класса гейт не пропустит.",
170
+ test1: "Проверка, что это храповик, а не советчик: «может ли новый код добавить нарушение",
171
+ test2: "и пройти?» Может — значит гейта нет.",
172
+ run: (cmd) => `Прогнать: ${cmd}`,
173
+ },
174
+
175
+ find: {
176
+ needQuery: (cmd) => `Опиши намерение словами: ${cmd}`,
177
+ example: 'find "отладочная печать не доезжает до прода"',
178
+ exists: "Такое уже есть — новую запись заводить не надо:",
179
+ match: (pct) => `совпадение ${pct}%`,
180
+ recipes: (langs) => `рецепты: ${langs}общий`,
181
+ existsWhy1: "Если у тебя рецепт под другой стек — это строка в recipes существующей",
182
+ existsWhy2: "записи, а не новый гейт. Намерение одно, исполнителей может быть много.",
183
+ near: "Точного совпадения нет, но рядом лежит:",
184
+ nearWhy: "Прочитай их README. Если намерение то же — дополняй, а не заводи новое.",
185
+ none: "Такого намерения в каталоге нет.",
186
+ journal: "В журнале есть шишка на эту тему:",
187
+ journalWhy: "Шишка записана — значит доказательство для новой записи уже есть.",
188
+ howTitle: "Как добавить свой гейт:",
189
+ how1: "Назови отказ.",
190
+ how1What: "Какой конкретный брак он поймал в живом проекте, чего это стоило.",
191
+ how1Why: "«Это хорошая практика» не принимается: так каталог набирает сотни пунктов и умирает.",
192
+ how2: "Заведи папку",
193
+ how2What: "— gate.yml, red/, green/, README.md.",
194
+ how2Why: "Норма записи со всеми полями — kit/gates/README.md",
195
+ how3: "Проверь машиной:",
196
+ how3Why: "Арбитр обязан покраснеть на red/ и промолчать на green/. Не прошло — не запись.",
197
+ how4: "Пришли изменением",
198
+ how4What: "в репозиторий комплекта.",
199
+ },
200
+
201
+ why: {
202
+ needQuery: (cmd) => `Опиши, что пропустили: ${cmd}`,
203
+ example: 'why "файл вырос до девяти тысяч строк"',
204
+ ciAtOnce: "разом: doctor --run",
205
+ ciOwnStep: "отдельным шагом",
206
+ unsure: "Уверенного совпадения нет. Похоже на эти записи:",
207
+ unsureByName: (cmd) => `Назови запись именем: ${cmd}`,
208
+ unsureNone: (cmd) => `Ни одна не подходит — значит сторожа не было: ${cmd}`,
209
+ decideTitle: "Дальше решаешь ты:",
210
+ decideQ: "это твоя частность или общий случай?",
211
+ decideWhy: "Общий — идёт в каталог и достаётся всем. Частный — остаётся у тебя.",
212
+ decideNote: (cmd) => `Урок в общий журнал в любом случае: ${cmd}`,
213
+ fix: "Почини так:",
214
+ noGuard: "Сторожа не было.",
215
+ noGuardWhy: "В каталоге нет записи с таким намерением.",
216
+ noGuardFix: (cmd) => `заведи запись — ${cmd}`,
217
+ noGuardHint: "Красный образец бери прямо из этой поломки: она уже случилась, выдумывать нечего.",
218
+ closest: (slug, pct) => `Ближайшая запись каталога: ${slug} совпадение ${pct}%`,
219
+ notInstalled: "Сторож есть в каталоге, но в этом проекте не поставлен.",
220
+ notInstalledHint1: "Он покраснеет на старом коде — это нормально: старое закрывается храповиком,",
221
+ notInstalledHint2: (cmd) => `новое ловится со дня установки. ${cmd}`,
222
+ declaredAs: (cmd) => `Объявлен: ${cmd}`,
223
+ notRunning: "Сторож объявлен, но не запускается.",
224
+ notRunningWhy: "Худший случай: тишина читается как успех.",
225
+ notRunningFix: "путь или программа из команды не существуют — проверь их.",
226
+ notRunningHint: "Отсутствие сигнала неотличимо от успеха, поэтому это не «мелочь в конфиге».",
227
+ bypassed: "Сторож есть и эту поломку ловит — значит его обошли.",
228
+ noCiFix: "конвейера нет. Проверка, которую гоняет только человек,",
229
+ noCiHint: "работает ровно до первого «забыл».",
230
+ notInCiFix: "конвейер есть, но этот гейт в нём не запускается.",
231
+ notInCiHint: (cmd) => `Дешевле всего одним шагом: ${cmd} — он гоняет всё объявленное.`,
232
+ inCiFix: (how) => `конвейер его гоняет (${how}) — значит красный прогон`,
233
+ inCiHint1: "кто-то пропустил или обошёл. Перенеси правило из текста в механику:",
234
+ inCiHint2: "блокирующий шаг, а не необязательный; запрет слияния при красном.",
235
+ blind: "Сторож есть, стоит и запускается — но этой поломки не видит.",
236
+ blindFix: (dir) => `положи в ${dir} кусок кода из этой поломки`,
237
+ blindHint1: "и доведи проверку до красного на нём. Порядок обратный привычному: сначала",
238
+ blindHint2: "образец, потом правка — иначе непонятно, что именно починено.",
239
+ blindCheck: "Проверить после правки: bash tool/selfcheck/gates.sh",
240
+ },
241
+
242
+ init: {
243
+ noDocs: (dir) => `Не найден корпус методичек: ${dir}\nПохоже, пакет установлен не полностью.`,
244
+ created: (n) => `создано (${n}):`,
245
+ andMore: (n) => `… и ещё ${n}`,
246
+ kept: (n) => `уже были на месте, не тронуты (${n}):`,
247
+ overwrite: (cmd) => `перезаписать: ${cmd}`,
248
+ nextTitle: "Что дальше — по порядку:",
249
+ n1a: "Открой",
250
+ n1b: "и заполни раздел «Команды». Команда, которую нельзя",
251
+ n1c: "скопировать и выполнить, — не команда, а пожелание.",
252
+ n2a: "Прочитай",
253
+ n2b: "— это обязательный минимум",
254
+ n2c: "проекта без привязки к языку. Пройди сверху вниз и отметь, чего нет.",
255
+ n3a: "Заполни",
256
+ n3b: "— гейты, образцы, журнал. Уровень соответствия AQK",
257
+ n3c: (cmd) => `считается по нему: ${cmd}.`,
258
+ n4a: "Поднимайся по ступеням",
259
+ n4b: "по одной",
260
+ n4c: ". Гейт стережёт существующий артефакт:",
261
+ n4d: "проверка на код, которого ещё нет, — мёртвое правило.",
262
+ burned: (cmd) => `Обжёгся на чём-то — запиши: ${cmd}`,
263
+ },
264
+
265
+ feedback: {
266
+ title: "Если пригодилось:",
267
+ star: (url) => `Поставь звезду — ${url}`,
268
+ issue: "Нашёл баг или не подошло — заведи Issue, самая полезная обратная связь: и то и другое.",
269
+ once: "Это разовое сообщение: больше не покажется на этой машине.",
270
+ },
271
+
272
+ note: {
273
+ journalTitle: "Журнал шишек",
274
+ needTitle: (cmd) => `Нужен заголовок: ${cmd}`,
275
+ noJournal: (url) => `Клон журнала не найден.\nСделай один раз:\n git clone ${url}.git ~/projects/aqk\nили укажи путь: export AQK_HOME=/путь/к/aqk`,
276
+ journalMissing: (path) => `Журнал не найден: ${path}`,
277
+ emptyBody: `Тело записи пустое. Передай его на стандартный ввод, например:\n\n aqk note "заголовок" <<'EOF'\n **Что случилось.** ...\n **Чем это стоило.** ...\n **Вывод.** 🔧 ...\n EOF`,
278
+ noOutcome:
279
+ "В записи нет отметки решения. Урок без вывода — это история, а не урок.\n" +
280
+ "Припиши одну из трёх:\n" +
281
+ " ✅ **Стало гейтом:** <имя записи каталога>\n" +
282
+ " 🔧 **Стало правкой оснастки:** <что именно изменено>\n" +
283
+ " 👤 **Гейтом не станет:** <почему>",
284
+ unknownProject: "неизвестно",
285
+ projectField: "Проект",
286
+ pushed: (title) => `Записано и отправлено: ${title}`,
287
+ localOnly: (cmd) => `Записано локально, push не прошёл. Отправить: ${cmd}`,
288
+ },
289
+
290
+ blob: {
291
+ noDocs: (dir) => `Не найдены методички: ${dir}`,
292
+ header: (stamp) =>
293
+ `<!-- СОБРАНО КОМАНДОЙ aqk blob ${stamp} из kit/docs. Не править руками:\n` +
294
+ ` правки затрёт следующая сборка. Источник — отдельные файлы. -->\n\n` +
295
+ `# AQK — методички одним файлом\n`,
296
+ source: (path) => `источник: ${path}`,
297
+ done: (n, kb) => `GOD_AI.md — ${n} файлов, ${kb} КБ`,
298
+ rebuilt: "Собирается заново каждой командой. Править надо оригиналы в kit/docs.",
299
+ },
300
+
301
+ start: {
302
+ initFailed: (cmd) => `Не получилось разложить комплект. Начни с ${cmd}`,
303
+ tooManyFiles: (n) => `В репозитории уже ${n} файлов кода — это другой сценарий.`,
304
+ useDoctor: (cmd) => `${cmd} осмотрит, что есть, и разделит записи на три списка:`,
305
+ threeLists: "держит машина · применимо и не поставлено · не применимо и почему.",
306
+ anyway: (cmd) => `Всё равно поставить сторожей дня 0: ${cmd}`,
307
+ expectRed: "Готовься к красному: сторож, поставленный на живой код, краснеет на нём весь.",
308
+ expectRedFix: (cmd) => `Это лечится храповиком — ${cmd}, — а не отключением.`,
309
+ installed: (n) => `Поставлено сторожей дня 0: ${n}`,
310
+ noDebt1: "Долга нет: на пустом проекте им нечего пропускать. Тот же сторож, поставленный",
311
+ noDebt2: "через полгода, покраснел бы на всём старом коде — и его бы выключили.",
312
+ allDeclared: "Все применимые записи уже объявлены.",
313
+ notYet: "Не применимо пока:",
314
+ notYetWhy: "Появится признак — запись покажется сама.",
315
+ orderTitle: "Порядок, в котором это делают:",
316
+ o1: "Задача словами.",
317
+ o1What: "Что и кому, без единого технического слова.",
318
+ o1Why: "Пока задача не описана словами, любая архитектура защищает неизвестно что.",
319
+ o2: "Ограничения.",
320
+ o2What: "Сроки, деньги, нагрузка, чем нельзя пользоваться.",
321
+ o2Why: "Ограничения выбирают решение куда чаще, чем вкус: без них выбирают вкусом.",
322
+ o3: "Сайзинг.",
323
+ o3What: "Сколько данных, запросов, людей — числами, хотя бы порядком.",
324
+ o3Why: "Число отделяет «нужна очередь» от «хватит таблицы». Без него спорят словами.",
325
+ o4: "Архитектура.",
326
+ o4What: "И только теперь — из первых трёх, а не до них.",
327
+ softNote1: "Этот порядок программа не проверяет: он в .aqk/docs/, и агент может его",
328
+ softNote2: "проигнорировать. Машина держит другое — сторожей выше. Разница между",
329
+ softNote3: "мягким и жёстким тут ровно такая: текст просят, команду выполняют.",
330
+ next: "Дальше:",
331
+ nextWhy: "— прогнать всё, что объявлено",
332
+ },
333
+
334
+ manifestDoc: {
335
+ head: [
336
+ "# .aqk.yml — манифест Agent Quality Kit",
337
+ "# Что это: машиночитаемое описание того, как в этом репозитории живут агенты.",
338
+ "# Уровень соответствия считает `aqk doctor`. Пустое поле = ступень не пройдена,",
339
+ "# и это честно: заполнять заглушками бессмысленно, проверяются файлы, а не слова.",
340
+ ],
341
+ entry: "# AQK-0 — что агент читает первым.",
342
+ rules: "# AQK-1 — где стандарты и какие проверки обязательны.",
343
+ gates: [
344
+ " # Имя: команда, возвращающая 0 или не 0. Пустое объявление защиты не даёт и бракуется",
345
+ " # проверкой «объявленный гейт запускается» — поэтому здесь примеры, а не заготовки.",
346
+ ' # lint: "ruff check ."',
347
+ ' # test: "pytest -q"',
348
+ " # Поставить готовую запись из каталога вместе с образцами: aqk add <имя>",
349
+ ],
350
+ samples: [
351
+ "# AQK-2 — чем доказано, что гейты работают, и где реестры долга.",
352
+ "# samples: каталог с красными и зелёными образцами (гейт обязан краснеть на первом",
353
+ "# и молчать на втором). ratchets: списки известных нарушений, которые могут только",
354
+ "# укорачиваться.",
355
+ ],
356
+ lessons: "# AQK-3 — где копятся уроки. Путь или адрес.",
357
+ },
358
+
359
+ report2: {
360
+ title: "Отчёт AQK",
361
+ noManifest: (cmd) => `Нет .aqk.yml — отчитываться не о чем. Начни с ${cmd}`,
362
+ level: "Уровень",
363
+ holdsTitle: "Что держит машина (прогон, а не манифест)",
364
+ nothingRuns: "⬜ ни один гейт не объявлен",
365
+ native: (prog) => `родной рецепт: ${prog}`,
366
+ portable: "переносимая проверка",
367
+ weakerTitle: "Стоит слабее возможного",
368
+ weaker: (progs) => `в системе есть ${progs}, но гейт стоит на переносимой проверке — она ловит меньше`,
369
+ missingTitle: "Чего нет",
370
+ nothingMissing: "✅ все применимые записи поставлены",
371
+ needsTool: (prog) => `нужен ${prog} — его нет в системе`,
372
+ notInstalled: "применимо, но не поставлено",
373
+ hiddenTitle: "Не применимо к этому репозиторию",
374
+ readTitle: "Что комплект велел прочитать",
375
+ readWarn:
376
+ "Значок означает только наличие файла на диске. Прочитан он или нет, машина не знает и " +
377
+ "не притворяется, что знает: это отвечает тот, кто отчитывается.",
378
+ ignoreTitle: "Что спрятано .aqkignore",
379
+ ignoreNone: "файла .aqkignore нет — ничего не спрятано",
380
+ ignoreWarn:
381
+ "Скрытое молча — тот же класс, что молчащий гейт: гейты по этим путям не смотрят вовсе. " +
382
+ "Строка здесь означает, что защиты там нет и не будет.",
383
+ whyTitle: "Зачем это нужно — коротко",
384
+ whyNothing: "нечего добавлять: применимое уже стоит",
385
+ saved: (path) => `Сохранено: ${path}`,
386
+ docs: {
387
+ baseline: "обязательный минимум проекта, без привязки к языку",
388
+ readyMade: "карта готовых правил: сперва ищи готовое, потом пиши своё",
389
+ rulesGeneral: "общие правила работы",
390
+ rulesTesting: "правила про тесты",
391
+ rulesSecurity: "правила про безопасность",
392
+ },
393
+ },
394
+
395
+ levels: [
396
+ {
397
+ title: "манифест и точка входа",
398
+ need: "создай .aqk.yml и укажи в entry файл, который агент читает первым (AGENTS.md)",
399
+ gives: "любой инструмент понимает, что читать в этом репозитории",
400
+ },
401
+ {
402
+ title: "правила и работающие гейты",
403
+ need: "укажи rules (каталог стандартов) и заполни хотя бы один гейт в gates реальной командой",
404
+ gives: "проверки объявлены командами, а не описаны словами",
405
+ },
406
+ {
407
+ title: "гейты доказаны, долг под храповиком",
408
+ need: "заведи samples (красные и зелёные образцы гейтов) и ratchets (реестры долга)",
409
+ gives: "гейт доказал, что ловит брак и молчит на исправном коде",
410
+ },
411
+ {
412
+ title: "уроки возвращаются в работу",
413
+ need: "укажи lessons — путь или адрес журнала, где каждый инцидент даёт вывод",
414
+ gives: "проект учится: одна и та же шишка не набивается дважды",
415
+ },
416
+ ],
417
+
418
+ report: {
419
+ title: "aqk doctor --run",
420
+ version: "версия",
421
+ level: "уровень",
422
+ summary: (ok, all) => `итого: ${ok} из ${all} зелёных`,
423
+ },
424
+ };
@@ -0,0 +1,164 @@
1
+ // tool/i18n/templates-en.mjs — английские тексты, которые программа кладёт в чужой проект.
2
+ //
3
+ // Ключи обязаны совпадать с templates-ru.mjs: каталоги подмешивают их полем templates, и
4
+ // модульная проверка «одни и те же ключи» заходит внутрь.
5
+
6
+ const AGENTS_MD = `# AGENTS.md
7
+
8
+ > The entry point for an agent. Keep this file short: a bloated rulebook pushes the actual task
9
+ > out of the context window, and then every rule gets ignored at once. Anything long goes behind
10
+ > the links below.
11
+
12
+ ## Hard rules
13
+
14
+ - **A plan before code.** A non-trivial task starts with a plan a human approved in words.
15
+ - **A red test before code.** First a check that fails, then the implementation.
16
+ - **Three attempts maximum.** Not solved in three — stop and ask a human, not a fourth try.
17
+ - **Secrets only in the environment.** Never in code, logs or commits.
18
+ - **Only the files the task is about.** No fixing things "while we are here".
19
+ - **Done = proven.** Name the arbiter: a test, a live run, a check against the source.
20
+ "Looks like it works" is not done.
21
+ - **Never swallow an error.** Either handled and logged, or re-raised.
22
+ - **A fork in the road is a question for a human.** Departing from an agreed decision is not
23
+ documented with a code comment.
24
+ - **Report on your work with the kit with a command, not with words.** When you are done, run
25
+ \`aqk report\`. It is assembled from an actual run: a summary from memory always picks the
26
+ convenient parts and stays quiet about a gate standing on the weakest recipe.
27
+
28
+ ## The AQK tooling is your commands, not the human's
29
+
30
+ This repository has the AQK kit installed. Its point: **a promise the project makes turns into a
31
+ command with an exit code**, and from then on a machine holds it, not somebody's attention.
32
+ "We do not leave debug printing" is text you can ignore; a command returning non-zero is not.
33
+
34
+ Running them is your job. The human looks at the list of holes and decides which to close.
35
+
36
+ | Command | What it does | When to reach for it |
37
+ |---|---|---|
38
+ | \`aqk doctor\` | inspects the repository and prints three lists: what a machine already holds, what is missing, and what this project does not need and why | starting work on quality; "what is even here" |
39
+ | \`aqk doctor --run\` | **runs** the declared checks and shows which one found a defect | before handing work over; after changes; whenever you need a fact rather than a promise |
40
+ | \`aqk add <name>\` | installs a check from the catalogue: copies it and its samples into the project, declares it in the manifest | the human agreed to close a hole from the \`doctor\` list |
41
+ | \`aqk ratchet <name>\` | records existing violations as debt and stops letting new ones through | the check goes red on old code nobody is going to fix right now |
42
+ | \`aqk find "…"\` | searches by meaning for an existing check | before inventing your own |
43
+ | \`aqk note "…"\` | writes a lesson into the shared journal | the process or an instrument let you down: a check lied, a rule was bypassed |
44
+ | \`aqk report\` | assembles a report from an actual run: what is in place and with which recipe, what is missing, what the kit told you to read | **mandatory** at the end of working with the kit — instead of a summary from memory |
45
+
46
+ If there is no \`aqk\` command on the system, the kit was used without installing. Then write
47
+ \`npx agent-quality-kit\` instead of \`aqk\`. Every command prints the invocation that will
48
+ actually work for you.
49
+
50
+ **Three things to understand rather than memorise:**
51
+
52
+ 1. **"Declared" and "works" are different claims.** \`doctor\` without \`--run\` honestly says it
53
+ ran nothing. Do not present the declared as the working.
54
+ 2. **A check without two samples proves nothing.** The red one is code it must fire on; the green
55
+ one is correct code it must stay quiet on. The green one matters more: without it, one day the
56
+ check goes red on correct code and gets switched off along with all the others.
57
+ 3. **A rule is introduced with a ratchet, not with a big cleanup.** The cleanup is postponed
58
+ forever, because it is big. The ratchet gives you a rule in force from the day it lands.
59
+
60
+ **A defect of the same class repeating is not a reason to be more careful — it is a reason to add
61
+ a check.** Discipline does not scale; mechanics do.
62
+
63
+ ## Third-party code inside the repository
64
+
65
+ Code that lives here but was not written here (a reference copy, vendored code, generated
66
+ clients) is excluded with \`.aqkignore\` in the root — one pattern per line. Editing the copy of
67
+ \`_skip.sh\` is not configuration: the next \`aqk add\` overwrites it.
68
+
69
+ ## Where things live
70
+
71
+ - \`.aqk/rules/\` — standards: general, tests, security
72
+ - \`.aqk/docs/\` — guides: the project minimum, the harness, process, research
73
+ - \`.aqk/docs/project-baseline.md\` — **start here** if the project is new
74
+
75
+ ## Commands
76
+
77
+ <!-- Fill this in for your project. A command you cannot copy and run is not a command. -->
78
+
79
+ - build: \`\`
80
+ - tests: \`\`
81
+ - linter: \`\`
82
+ - everything at once before pushing: \`\`
83
+
84
+ ## What this project does not have
85
+
86
+ <!-- Be honest here. An unwritten "no" is something the agent will assume is a "yes". -->
87
+ `;
88
+
89
+ const CLAUDE_MD = `# CLAUDE.md
90
+
91
+ The rules for this project live in \`AGENTS.md\` — read that.
92
+
93
+ One rulebook, several entry points: \`AGENTS.md\` for agents that understand it, \`CLAUDE.md\`
94
+ for Claude Code. Keeping two diverging rulebooks is not an option: within a month they lie in
95
+ different ways, and nobody knows which one is real.
96
+
97
+ @AGENTS.md
98
+ `;
99
+
100
+
101
+ const GATE_YML_TEMPLATE = (slug) => `# An AQK catalogue entry. The format and every field — kit/gates/README.md
102
+ # Until the lines below are filled in, the check will reject this entry — by design.
103
+
104
+ # In one phrase: which class of defect it catches. Deduplication runs on this field.
105
+ intent: FILL IN — which class of defect it catches
106
+ intent_en: FILL IN — which class of defect it catches
107
+
108
+ # When the entry is shown to a human. The condition must be a query about the repository
109
+ # that the program can evaluate: always | langs: python, go | has_gates: true
110
+ trigger:
111
+ always: true
112
+
113
+ # The arbiter command per stack. {gate} is the entry folder, {dir} is what we check.
114
+ # any — a portable command that needs no third-party programs.
115
+ recipes:
116
+ any: bash {gate}/check.sh {dir}
117
+
118
+ # A real failure this check caught. "A good practice" is not accepted:
119
+ # point at a journal entry — incidents/README.md
120
+ proof: FILL IN — which failure it caught and what that cost
121
+ `;
122
+
123
+ const CHECK_SH_TEMPLATE = `#!/usr/bin/env sh
124
+ # A check. Returns 0 — clean, non-zero — defect. The failure text must SAY what to do:
125
+ # it lands straight in the agent's context, and with an instruction the agent fixes it itself.
126
+ DIR="\${1:-.}"
127
+ . "$(dirname "$0")/../_skip.sh" 2>/dev/null || SKIP_NAMES=".git .aqk node_modules .venv"
128
+
129
+ # own_samples_filter hides ONLY gates/<name>/red|green/ — not every folder with that name in
130
+ # the project. --exclude-dir=red by bare name would one day hide a real user folder called
131
+ # red/ (found on secrets-not-in-code — see the journal, 2026-09-04).
132
+ HITS=$(grep -rnE $(skip_grep "$DIR") 'FILL_IN_SEARCH_PATTERN' "$DIR" 2>/dev/null | own_samples_filter "$DIR")
133
+ if [ -n "$HITS" ]; then
134
+ echo "$HITS"
135
+ echo " fix: FILL IN — what exactly to do"
136
+ exit 1
137
+ fi
138
+ exit 0
139
+ `;
140
+
141
+ const README_TEMPLATE = (slug) => `# FILL IN — a one-line title
142
+
143
+ **Intent.** What must not reach the code, and why.
144
+
145
+ **Which failure this caught.** What broke, in which project, and what it cost. Without this the
146
+ entry is not accepted: "it is a good practice" collects hundreds of entries you cannot choose
147
+ between.
148
+
149
+ **Why a machine and not attention.** The moment at which a human misses this.
150
+
151
+ **An off-the-shelf equivalent.** Does ruff, eslint or semgrep already have this check? If it
152
+ does, the recipe for that language must use it, and your own check stays the fallback. If it does
153
+ not, write down what exactly you checked: "did not look" and "there is none" are different claims.
154
+
155
+ **What it does NOT catch.** Name the boundary honestly. A gate whose limits go unmentioned is
156
+ more dangerous than a missing one: people will rely on it.
157
+
158
+ **Samples.** \`red/\` — what is violated here. \`green/\` — the same thing, done right.
159
+ `;
160
+
161
+ export const templates = {
162
+ AGENTS_MD, CLAUDE_MD,
163
+ GATE_YML_TEMPLATE, CHECK_SH_TEMPLATE, README_TEMPLATE,
164
+ };