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
@@ -14,7 +14,9 @@ DIR="${1:-.}"
14
14
  #
15
15
  # На настоящем проекте без этого различия 285 находок из 330 пришли из scripts/ и оснастки.
16
16
  # Гейт, который на 86% состоит из ложных сработок, выключают целиком.
17
- TOOLING="--exclude-dir=scripts --exclude-dir=tools --exclude-dir=bin --exclude-dir=examples --exclude-dir=notebooks --exclude-dir=docs --exclude-dir=.claude --exclude-dir=gates --exclude-dir=gates-reference"
17
+ TOOLING="--exclude-dir=scripts --exclude-dir=tools --exclude-dir=bin --exclude-dir=examples --exclude-dir=notebooks --exclude-dir=docs --exclude-dir=.claude --exclude-dir=gates"
18
+ # `gates-reference` отсюда убран: это имя каталога ОДНОГО проекта, зашитое в общий
19
+ # инструмент. Такому место в .aqkignore самого проекта — он появился позже этой строки.
18
20
 
19
21
  # Проект называет СВОИ каталоги, где печать — интерфейс, а не отладка: у программы командной
20
22
  # строки это её исходники целиком. Объявляется в манифесте, рядом с командой, и потому видно
@@ -1,4 +1,5 @@
1
1
  intent: отладочная печать не доезжает до прод-кода
2
+ intent_en: debug printing does not reach production code
2
3
 
3
4
  # Запись касается только языков, где есть эта конструкция. В проекте на Go или
4
5
  # Rust она не показывается вовсе.
@@ -1,4 +1,5 @@
1
1
  intent: ключи, пароли и приватные ключи не попадают в код
2
+ intent_en: keys, passwords and private keys do not end up in the code
2
3
 
3
4
  trigger:
4
5
  always: true
@@ -1,4 +1,5 @@
1
1
  intent: ошибка не глушится молча — она обработана и записана либо проброшена
2
+ intent_en: an error is not silently swallowed — it is handled and logged, or re-raised
2
3
 
3
4
  trigger:
4
5
  always: true
@@ -1,4 +1,5 @@
1
1
  intent: маркеров «доделать потом» нет в готовом коде — вместо них заведённая задача
2
+ intent_en: "fix later" markers are absent from finished code — a filed task replaces them
2
3
 
3
4
  trigger:
4
5
  always: true
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-quality-kit",
3
- "version": "0.2.4",
3
+ "version": "0.3.0",
4
4
  "description": "AQK — Agent Quality Kit: переносимый комплект, приводящий проект в состояние, пригодное для работы агентов. Правила, механические упоры, накопленные уроки. Одна команда, любой инструмент.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -10,6 +10,7 @@
10
10
  "tool",
11
11
  "kit",
12
12
  "README.md",
13
+ "README.ru.md",
13
14
  "LICENSE"
14
15
  ],
15
16
  "engines": {
@@ -6,6 +6,7 @@ import { spawnSync } from "node:child_process";
6
6
  import { CWD, PKG_ROOT, TARGET_DIR, SELF, c, exists } from "../lib/core.mjs";
7
7
  import { readManifest, assessLevel } from "../lib/manifest.mjs";
8
8
  import { detectFacts, readCatalog, triggerVerdict, recipeFor } from "../lib/repo.mjs";
9
+ import { L } from "../i18n/index.mjs";
9
10
 
10
11
  async function reportCatalog(man, facts) {
11
12
  const catalog = await readCatalog();
@@ -19,27 +20,27 @@ async function reportCatalog(man, facts) {
19
20
  else todo.push(rec);
20
21
  }
21
22
 
22
- console.log(c.bold(`\n Гейты\n`));
23
+ console.log(c.bold(`\n ${L.doctor.gatesHeading}\n`));
23
24
  const marks = ["has_ci", "has_db", "has_docker", "has_tests", "has_deps"]
24
25
  .filter((k) => facts[k])
25
26
  .map((k) => k.replace("has_", ""));
26
27
  console.log(
27
- c.dim(` языки: ${[...facts.langs].join(", ") || "не определены"} · файлов: ${facts.files}` +
28
- (marks.length ? ` · есть: ${marks.join(", ")}` : "") + "\n")
28
+ c.dim(` ${L.doctor.langs}: ${[...facts.langs].join(", ") || L.doctor.langsUnknown} · ${L.doctor.files}: ${facts.files}` +
29
+ (marks.length ? ` · ${L.doctor.hasThings}: ${marks.join(", ")}` : "") + "\n")
29
30
  );
30
31
 
31
32
  for (const rec of held) console.log(` ${c.green("✔")} ${rec.slug.padEnd(22)} ${c.dim(rec.intent || "")}`);
32
33
  for (const rec of todo) {
33
34
  console.log(` ${c.yellow("✘")} ${rec.slug.padEnd(22)} ${rec.intent || ""}`);
34
- console.log(c.dim(` поставить: ${SELF} add ${rec.slug}`));
35
+ console.log(c.dim(` ${L.doctor.install(`${SELF} add ${rec.slug}`)}`));
35
36
  }
36
37
  if (skip.length) {
37
- console.log(c.dim(`\n Не применимо к этому репозиторию (${skip.length}):`));
38
+ console.log(c.dim(`\n ${L.doctor.notApplicable(skip.length)}`));
38
39
  for (const [rec, why] of skip) console.log(c.dim(` · ${rec.slug.padEnd(22)} ${why}`));
39
40
  }
40
41
  console.log(
41
- `\n ${c.bold("Итого:")} держит машина ${held.length}, применимо но не поставлено ${c.yellow(todo.length)}, ` +
42
- c.dim(`скрыто ${skip.length}`) + "\n"
42
+ `\n ${c.bold(L.doctor.total)} ${L.doctor.totalHeld(held.length)}, ${L.doctor.totalTodo(c.yellow(todo.length))}, ` +
43
+ c.dim(L.doctor.totalSkip(skip.length)) + "\n"
43
44
  );
44
45
  }
45
46
 
@@ -61,7 +62,7 @@ function runGates(man) {
61
62
  const gates = declaredGates(man);
62
63
  if (!gates.length) return { failed: 0, ran: 0, results: [] };
63
64
 
64
- console.log(c.bold("\n Прогон объявленных гейтов\n"));
65
+ console.log(c.bold(`\n ${L.doctor.runHeading}\n`));
65
66
  let failed = 0;
66
67
  const results = [];
67
68
 
@@ -71,9 +72,9 @@ function runGates(man) {
71
72
  const secs = (Math.max(0, Date.now() - t0) / 1000).toFixed(1);
72
73
 
73
74
  if (r.error && r.error.code === "ETIMEDOUT") {
74
- console.log(` ${c.red("✘")} ${name.padEnd(14)} ${c.red("не уложился в 5 минут")}`);
75
+ console.log(` ${c.red("✘")} ${name.padEnd(14)} ${c.red(L.doctor.timeout)}`);
75
76
  failed++;
76
- results.push({ name, cmd, ok: false, secs, note: "не уложился в 5 минут" });
77
+ results.push({ name, cmd, ok: false, secs, note: L.doctor.timeout });
77
78
  continue;
78
79
  }
79
80
  const code = r.status;
@@ -83,9 +84,9 @@ function runGates(man) {
83
84
  } else {
84
85
  failed++;
85
86
  const out = `${r.stdout || ""}${r.stderr || ""}`.trim().split("\n").filter(Boolean);
86
- console.log(` ${c.red("✘")} ${name.padEnd(14)} ${c.red(`код ${code}`)} ${c.dim(`· ${secs}s · ${cmd}`)}`);
87
+ console.log(` ${c.red("✘")} ${name.padEnd(14)} ${c.red(L.doctor.exitCode(code))} ${c.dim(`· ${secs}s · ${cmd}`)}`);
87
88
  for (const line of out.slice(0, 3)) console.log(c.dim(` ${line.slice(0, 100)}`));
88
- if (out.length > 3) console.log(c.dim(` … и ещё ${out.length - 3} строк`));
89
+ if (out.length > 3) console.log(c.dim(` ${L.doctor.moreLines(out.length - 3)}`));
89
90
  results.push({ name, cmd, ok: false, secs, code });
90
91
  }
91
92
  }
@@ -100,13 +101,13 @@ async function writeRunReport({ version, reached, results }) {
100
101
  const stamp = new Date().toISOString().replace("T", " ").slice(0, 16);
101
102
  const ok = results.filter((r) => r.ok).length;
102
103
  const lines = [
103
- `# aqk doctor --run — ${stamp}`,
104
- version ? `версия: ${version}` : null,
105
- `уровень: AQK-${reached < 0 ? "нет" : reached}`,
104
+ `# ${L.report.title} — ${stamp}`,
105
+ version ? `${L.report.version}: ${version}` : null,
106
+ `${L.report.level}: AQK-${reached < 0 ? L.doctor.levelNone : reached}`,
106
107
  "",
107
- ...results.map((r) => `${r.ok ? "✔" : "✘"} ${r.name} — ${r.secs}s${r.ok ? "" : ` (${r.note || `код ${r.code}`})`}`),
108
+ ...results.map((r) => `${r.ok ? "✔" : "✘"} ${r.name} — ${r.secs}s${r.ok ? "" : ` (${r.note || L.doctor.exitCode(r.code)})`}`),
108
109
  "",
109
- `итого: ${ok} из ${results.length} зелёных`,
110
+ L.report.summary(ok, results.length),
110
111
  ].filter((l) => l !== null);
111
112
 
112
113
  const dst = join(CWD, TARGET_DIR, "last-run.md");
@@ -129,11 +130,11 @@ async function cmdDoctor() {
129
130
  // репозитории и требовал разложить комплект в комплект.
130
131
  const inKit = resolve(CWD) === resolve(PKG_ROOT);
131
132
  const checks = [
132
- inKit ? ["kit/docs", "методички — здесь оригиналы, а не копия"] : [".aqk/docs", "методички"],
133
- inKit ? ["kit/rules", "стандарты — здесь оригиналы, а не копия"] : [".aqk/rules", "стандарты"],
134
- ["AGENTS.md", "точка входа для агентов"],
135
- [".gitignore", "гигиена репозитория"],
136
- [".git", "проект под контролем версий"],
133
+ inKit ? ["kit/docs", L.doctor.docsKit] : [".aqk/docs", L.doctor.docs],
134
+ inKit ? ["kit/rules", L.doctor.rulesKit] : [".aqk/rules", L.doctor.rules],
135
+ ["AGENTS.md", L.doctor.agents],
136
+ [".gitignore", L.doctor.gitignore],
137
+ [".git", L.doctor.git],
137
138
  ];
138
139
 
139
140
  let missing = 0;
@@ -150,8 +151,8 @@ async function cmdDoctor() {
150
151
  const emptyCommands = (text.match(/^- [^:]+: ``$/gm) || []).length;
151
152
  if (emptyCommands) {
152
153
  console.log(
153
- `\n ${c.yellow("!")} В AGENTS.md ${emptyCommands} незаполненных команд. ` +
154
- c.dim("Агент не может выполнить пустую строку.")
154
+ `\n ${c.yellow("!")} ${L.doctor.emptyCommands(emptyCommands)} ` +
155
+ c.dim(L.doctor.emptyCommandsWhy)
155
156
  );
156
157
  }
157
158
  }
@@ -159,7 +160,7 @@ async function cmdDoctor() {
159
160
  const man = await readManifest();
160
161
  const { reached, steps } = await assessLevel(man);
161
162
 
162
- console.log(c.bold("\n Уровень соответствия AQK\n"));
163
+ console.log(c.bold(`\n ${L.doctor.levelHeading}\n`));
163
164
  for (const s of steps) {
164
165
  const mark = s.ok ? c.green("✔") : reached + 1 === s.level ? c.yellow("→") : c.dim("·");
165
166
  console.log(` ${mark} AQK-${s.level} ${s.title}`);
@@ -171,25 +172,21 @@ async function cmdDoctor() {
171
172
  // работающих проверок и без манифеста стоит на нуле — и это сообщение обязано это объяснить,
172
173
  // иначе человек услышит «у тебя плохо» и закроет.
173
174
  if (reached < 0 && !man) {
174
- console.log(c.yellow("\n Уровень: стандарт в этом репозитории не заведён.\n"));
175
- console.log(
176
- c.dim(" Это не оценка проекта. Уровень мерит не зрелость практики, а то, можно ли\n") +
177
- c.dim(" прочитать её машиной. Проверки могут стоять и работать — но пока они не\n") +
178
- c.dim(" объявлены в .aqk.yml, ни агент, ни конвейер, ни новый человек о них не знают.\n")
179
- );
175
+ console.log(c.yellow(`\n ${L.doctor.levelNotSet}\n`));
176
+ console.log(L.doctor.levelNotSetWhy.map((line) => c.dim(` ${line}`)).join("\n") + "\n");
180
177
  } else {
181
178
  console.log(
182
179
  reached < 0
183
- ? c.yellow(`\n Уровень: манифест есть, но AQK-0 не пройден.\n`)
184
- : c.green(`\n Уровень: AQK-${reached}.\n`)
180
+ ? c.yellow(`\n ${L.doctor.levelManifestNoZero}\n`)
181
+ : c.green(`\n ${L.doctor.level(reached)}\n`)
185
182
  );
186
183
  }
187
184
 
188
185
  if (next) {
189
- console.log(` ${c.bold(`Чтобы достичь AQK-${next.level}:`)} ${next.need}`);
190
- console.log(c.dim(` Что это даст: ${next.gives}\n`));
186
+ console.log(` ${c.bold(L.doctor.toReach(next.level))} ${next.need}`);
187
+ console.log(c.dim(` ${L.doctor.gives(next.gives)}\n`));
191
188
  } else {
192
- console.log(c.green(" Все ступени пройдены.\n"));
189
+ console.log(c.green(` ${L.doctor.allDone}\n`));
193
190
  }
194
191
 
195
192
  const facts = await detectFacts(man);
@@ -205,8 +202,8 @@ async function cmdDoctor() {
205
202
  await writeRunReport({ version, reached, results: run.results });
206
203
  } else if (gates.length) {
207
204
  console.log(
208
- c.yellow(` ${gates.length} гейтов объявлено, но не запускалось.`) +
209
- c.dim(` «Объявлен» и «работает» — разные утверждения: ${SELF} doctor --run\n`)
205
+ c.yellow(` ${L.doctor.declaredNotRun(gates.length)}`) +
206
+ c.dim(L.doctor.declaredNotRunWhy(`${SELF} doctor --run`) + "\n")
210
207
  );
211
208
  }
212
209
 
@@ -217,8 +214,8 @@ async function cmdDoctor() {
217
214
  const pass = reached >= min && gateFailed === 0;
218
215
  console.log(
219
216
  pass
220
- ? c.green(` Порог AQK-${min} пройден.\n`)
221
- : c.red(` Порог AQK-${min} НЕ пройден: сейчас AQK-${reached < 0 ? "нет" : reached}.\n`)
217
+ ? c.green(` ${L.doctor.thresholdPass(min)}\n`)
218
+ : c.red(` ${L.doctor.thresholdFail(min, reached < 0 ? L.doctor.levelNone : reached)}\n`)
222
219
  );
223
220
  process.exit(pass ? 0 : 1);
224
221
  }
@@ -227,4 +224,4 @@ async function cmdDoctor() {
227
224
 
228
225
  // Наружу — только команда. Остальное здесь же и используется: экспорт, который никто не
229
226
  // импортирует, читается как «это часть договора» и мешает менять внутренности.
230
- export { cmdDoctor };
227
+ export { cmdDoctor, runGates, declaredGates };