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.
- package/README.md +118 -94
- package/README.ru.md +178 -0
- package/kit/gates/_skip.sh +31 -3
- package/kit/gates/commit-explains-itself/gate.yml +1 -0
- package/kit/gates/complexity-limit/gate.yml +1 -0
- package/kit/gates/dead-code/gate.yml +1 -0
- package/kit/gates/deps-are-pinned/gate.yml +1 -0
- package/kit/gates/duplicate-code/gate.yml +1 -0
- package/kit/gates/entry-links-exist/gate.yml +1 -0
- package/kit/gates/file-size-limit/gate.yml +1 -0
- package/kit/gates/gate-has-samples/gate.yml +1 -0
- package/kit/gates/gates-are-runnable/gate.yml +1 -0
- package/kit/gates/gates-run-in-ci/gate.yml +1 -0
- package/kit/gates/lesson-has-outcome/gate.yml +1 -0
- package/kit/gates/no-print-in-prod/check.sh +3 -1
- package/kit/gates/no-print-in-prod/gate.yml +1 -0
- package/kit/gates/secrets-not-in-code/gate.yml +1 -0
- package/kit/gates/swallowed-error/gate.yml +1 -0
- package/kit/gates/todo-without-task/gate.yml +1 -0
- package/package.json +2 -1
- package/tool/commands/doctor.mjs +38 -41
- package/tool/commands/gates.mjs +103 -109
- package/tool/commands/project.mjs +84 -85
- package/tool/commands/report.mjs +194 -0
- package/tool/i18n/en.mjs +423 -0
- package/tool/i18n/index.mjs +33 -0
- package/tool/i18n/ru.mjs +424 -0
- package/tool/i18n/templates-en.mjs +164 -0
- package/tool/i18n/templates-ru.mjs +170 -0
- package/tool/lib/core.mjs +5 -1
- package/tool/lib/manifest.mjs +12 -31
- package/tool/lib/repo.mjs +45 -21
- package/tool/lib/templates.mjs +38 -182
- package/tool/program.mjs +31 -9
- package/tool/selfcheck/gates.sh +7 -1
- package/tool/selfcheck/smoke.sh +97 -0
- 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
|
|
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
|
# строки это её исходники целиком. Объявляется в манифесте, рядом с командой, и потому видно
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agent-quality-kit",
|
|
3
|
-
"version": "0.
|
|
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": {
|
package/tool/commands/doctor.mjs
CHANGED
|
@@ -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
|
|
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(`
|
|
28
|
-
(marks.length ? ` ·
|
|
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(`
|
|
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
|
|
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(
|
|
42
|
-
c.dim(
|
|
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(
|
|
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(
|
|
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:
|
|
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(
|
|
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(`
|
|
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
|
-
`#
|
|
104
|
-
version ?
|
|
105
|
-
|
|
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 ||
|
|
108
|
+
...results.map((r) => `${r.ok ? "✔" : "✘"} ${r.name} — ${r.secs}s${r.ok ? "" : ` (${r.note || L.doctor.exitCode(r.code)})`}`),
|
|
108
109
|
"",
|
|
109
|
-
|
|
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",
|
|
133
|
-
inKit ? ["kit/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("!")}
|
|
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(
|
|
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(
|
|
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
|
|
184
|
-
: c.green(`\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(
|
|
190
|
-
console.log(c.dim(`
|
|
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(
|
|
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(
|
|
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(`
|
|
221
|
-
: c.red(`
|
|
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 };
|