agent-quality-kit 0.12.0 → 0.14.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 +43 -7
- package/README.ru.md +44 -7
- package/kit/gates/api-contract-has-arbiter/README.md +16 -1
- package/kit/gates/api-contract-has-arbiter/check.sh +66 -23
- package/kit/gates/complexity-limit/gate.yml +4 -0
- package/kit/gates/dead-code/gate.yml +4 -0
- package/kit/gates/entry-commands-exist/README.md +64 -0
- package/kit/gates/entry-commands-exist/check.sh +110 -0
- package/kit/gates/entry-commands-exist/gate.yml +19 -0
- package/kit/gates/entry-commands-exist/green/AGENTS.md +13 -0
- package/kit/gates/entry-commands-exist/green/Makefile +6 -0
- package/kit/gates/entry-commands-exist/green/justfile +2 -0
- package/kit/gates/entry-commands-exist/green/package.json +10 -0
- package/kit/gates/entry-commands-exist/red/AGENTS.md +9 -0
- package/kit/gates/entry-commands-exist/red/Makefile +2 -0
- package/kit/gates/entry-commands-exist/red/package.json +9 -0
- package/kit/gates/env-secrets-not-committed/README.md +73 -0
- package/kit/gates/env-secrets-not-committed/check.sh +139 -0
- package/kit/gates/env-secrets-not-committed/gate.yml +21 -0
- package/kit/gates/env-secrets-not-committed/green/.aqk-tracked +10 -0
- package/kit/gates/env-secrets-not-committed/green/.env +10 -0
- package/kit/gates/env-secrets-not-committed/green/.env.production +5 -0
- package/kit/gates/env-secrets-not-committed/green/.env.test +2 -0
- package/kit/gates/env-secrets-not-committed/red/.aqk-tracked +5 -0
- package/kit/gates/env-secrets-not-committed/red/.env +7 -0
- package/kit/gates/no-print-in-prod/gate.yml +4 -0
- package/kit/gates/swallowed-error/gate.yml +4 -0
- package/kit/gates/todo-without-task/gate.yml +4 -0
- package/llms.txt +8 -2
- package/package.json +1 -1
- package/tool/commands/badge.mjs +1 -1
- package/tool/commands/context.mjs +81 -9
- package/tool/commands/doctor-catalog.mjs +222 -0
- package/tool/commands/doctor.mjs +81 -209
- package/tool/commands/learn.mjs +119 -19
- package/tool/commands/probe.mjs +50 -68
- package/tool/commands/project.mjs +6 -1
- package/tool/commands/prompt.mjs +69 -0
- package/tool/commands/report.mjs +1 -1
- package/tool/commands/vitals.mjs +15 -11
- package/tool/i18n/en-docs.mjs +22 -1
- package/tool/i18n/en-gates.mjs +6 -1
- package/tool/i18n/en.mjs +78 -4
- package/tool/i18n/index.mjs +42 -3
- package/tool/i18n/ru-docs.mjs +24 -1
- package/tool/i18n/ru-gates.mjs +6 -1
- package/tool/i18n/ru.mjs +87 -4
- package/tool/lib/adopt.mjs +15 -1
- package/tool/lib/advice.mjs +115 -0
- package/tool/lib/annotate.mjs +66 -0
- package/tool/lib/brief.mjs +3 -1
- package/tool/lib/cadence.mjs +40 -1
- package/tool/lib/core.mjs +40 -1
- package/tool/lib/gate-worker.mjs +18 -0
- package/tool/lib/history.mjs +34 -5
- package/tool/lib/manifest.mjs +65 -21
- package/tool/lib/repo.mjs +47 -35
- package/tool/lib/run.mjs +98 -9
- package/tool/program.mjs +4 -0
- package/tool/selfcheck/smoke/_fixture.mjs +8 -3
- package/tool/selfcheck/smoke/api-contract.test.mjs +37 -0
- package/tool/selfcheck/smoke/corpus.test.mjs +151 -0
- package/tool/selfcheck/smoke/first-run.test.mjs +144 -0
- package/tool/selfcheck/smoke/verdict.test.mjs +91 -3
- package/tool/selfcheck/smoke.sh +10 -2
- package/tool/selfcheck/units-annotate.mjs +67 -0
- package/tool/selfcheck/units-cadence.mjs +40 -1
- package/tool/selfcheck/units-context.mjs +64 -1
- package/tool/selfcheck/units-learn.mjs +32 -0
- package/tool/selfcheck/units-level.mjs +97 -3
- package/tool/selfcheck/units-probe.mjs +2 -1
- package/tool/selfcheck/units-prompt.mjs +106 -0
- package/tool/selfcheck/units-repo.mjs +44 -1
- package/tool/selfcheck/units-verdict.mjs +76 -0
package/tool/i18n/en.mjs
CHANGED
|
@@ -7,14 +7,20 @@ import { enDocs } from "./en-docs.mjs";
|
|
|
7
7
|
|
|
8
8
|
import { templates } from "./templates-en.mjs";
|
|
9
9
|
|
|
10
|
+
const ago = (n) => (n === null || n === undefined ? "" : ` (${n} commit${n === 1 ? "" : "s"} ago)`);
|
|
11
|
+
|
|
10
12
|
import { enGates } from "./en-gates.mjs";
|
|
11
13
|
|
|
12
14
|
export const en = {
|
|
13
15
|
learn: {
|
|
14
16
|
title: "Said out loud, never written down",
|
|
15
17
|
noLogs: (p) => `no logs for this project: ${p}\n The command reads Claude Code transcripts on this machine. Empty means nobody worked here.`,
|
|
16
|
-
counted: (s, typed, said, fresh) =>
|
|
17
|
-
`sessions: ${s} · typed by a human: ${typed} · looks like an instruction: ${said} · not in the entry point: ${fresh}`,
|
|
18
|
+
counted: (s, typed, said, fresh, again) =>
|
|
19
|
+
`sessions: ${s} · typed by a human: ${typed} · looks like an instruction: ${said} · not in the entry point: ${fresh} · repeated: ${again}`,
|
|
20
|
+
ruleTitle: "Written down, yet you still have to correct it:",
|
|
21
|
+
ruleHow: "The rule is in the entry point, and you are repeating it to the agent again — text alone does not hold it. It needs a machine guard: aqk find \"…\" or aqk new <name>.",
|
|
22
|
+
repeatTitle: "Repeated — you have said this before (\"I told you\", \"again\"):",
|
|
23
|
+
restTitle: "The rest that looks like a rule and is not written down:",
|
|
18
24
|
nothing: "everything that looks like a rule is already in the entry point",
|
|
19
25
|
andMore: (n) => `… and ${n} more`,
|
|
20
26
|
warn:
|
|
@@ -44,6 +50,8 @@ export const en = {
|
|
|
44
50
|
blob: "assemble the guides into a single GOD_AI.md",
|
|
45
51
|
learn: "rule candidates from local transcripts: said out loud, never written down",
|
|
46
52
|
context: "the project state in one block — for an agent's context, not for reading",
|
|
53
|
+
vitals: "is what the kit runs on wired up: gate tools, hooks, version freshness",
|
|
54
|
+
prompt: "one task for the agent: what to fix, in order, and how to prove it is done",
|
|
47
55
|
contextInstall: "the same in full — the map and the rulebook — installed as a hook",
|
|
48
56
|
report: "the mandatory report form: what is in place, what is not, what was not read; --since <ref> adds what proves the diff",
|
|
49
57
|
badge: "a level badge for your README — and a check that it does not lie",
|
|
@@ -65,6 +73,22 @@ export const en = {
|
|
|
65
73
|
gitignore: "repository hygiene",
|
|
66
74
|
git: "project under version control",
|
|
67
75
|
|
|
76
|
+
runtimeTracked: (f, cmd) => `${f} is tracked by git — every run rewrites it, and the tree always shows a modified file. Take it out: ${cmd}`,
|
|
77
|
+
runtimeNotIgnored: (f, cmd) => `${f} is this machine's state, yet git sees it: one \`git add .\` and it is in a commit. Hide it: ${cmd}`,
|
|
78
|
+
layoutAdvice: "missing — advice, it does not fail the run",
|
|
79
|
+
coversImpossible: (entry, gate, linter) => `the claim "${gate} holds ${entry}" is wrong: ${linter} has no rule for this class — there is nothing to hold it with`,
|
|
80
|
+
coversCantCheck: (entry, gate) => `cannot check the claim "${gate} holds ${entry}": the gate's linter is not recognised or the entry has no rules for it — taken on trust`,
|
|
81
|
+
selectUnknown: (names, groups) => `--only/--skip: "${names}" is neither a gate from gates: nor a group from groups:${groups ? ` (groups: ${groups})` : ""}. Running everything instead of skipping would be a lie, so stopping.`,
|
|
82
|
+
selectSkipped: (names) => `not run (by --only/--skip): ${names} — their state is unknown, they are not "green"`,
|
|
83
|
+
claudeShim: {
|
|
84
|
+
missing: "Claude Code is set up here (.claude/), but the rules live in AGENTS.md — it does not read that file. Fix: a CLAUDE.md with the single line \"@AGENTS.md\".",
|
|
85
|
+
noImport: "CLAUDE.md does not import AGENTS.md — Claude Code only sees CLAUDE.md. Fix: add the line \"@AGENTS.md\" to CLAUDE.md (mentioning the file in prose does not load it).",
|
|
86
|
+
},
|
|
87
|
+
annotDropped: (n, more) => `pull request annotations: ${n}, ${more} more not shown — GitHub takes about ten per step; every finding is in the log above`,
|
|
88
|
+
heldQuiet: (n, cmd) => `held by the machine: ${n} — by name: ${cmd}`,
|
|
89
|
+
skipQuiet: (n, cmd) => `not applicable to this repository: ${n} — by name and why: ${cmd}`,
|
|
90
|
+
passedQuiet: (n) => `${n} more passed — by name: --verbose`,
|
|
91
|
+
jobsBad: (v) => `--jobs expects a whole number from 1: "${v}" will not do. A one-by-one run passed off as parallel would be a lie, so stopping.`,
|
|
68
92
|
rulesByHuman: (total, machine, human) =>
|
|
69
93
|
`${human} of ${total} rules in the entry point are guarded by a HUMAN, ${machine} by a machine.`,
|
|
70
94
|
rulesByHumanWhy: "A rule guarded by a human is guarded by nobody the day the human is busy. That is the hole this kit exists to close — and the reminder belongs to the human, not only to the agent. Counted in the ENTRY POINT only: a promise kept in any other file is checked by nothing at all, and this kit will not tell you it exists.",
|
|
@@ -86,6 +110,17 @@ export const en = {
|
|
|
86
110
|
toReach: (n) => `To reach AQK-${n}:`,
|
|
87
111
|
gives: (what) => `What it buys you: ${what}`,
|
|
88
112
|
allDone: "All levels reached.",
|
|
113
|
+
limitsTitle: "A level measures tooling, not reliability. What it does not prove:",
|
|
114
|
+
limitsProbe: {
|
|
115
|
+
never: (_, cmd) => `defects in your files: the probe has never run — ${cmd}`,
|
|
116
|
+
off: () => "defects in your files: the probe is off (probe: 0) — whether your checks catch them is unknown",
|
|
117
|
+
blind: ({ names, behind }) => `defects in your files: the probe${ago(behind)} did NOT catch — ${names.join(", ")}`,
|
|
118
|
+
partial: ({ caught, unknown, behind }, cmd) => `defects in your files: the probe${ago(behind)} caught ${caught} classes, ${unknown} unproven (${cmd})`,
|
|
119
|
+
caught: ({ caught, behind }) => `defects in your files: the probe${ago(behind)} caught all ${caught} planted classes — only those the catalog has`,
|
|
120
|
+
nothing: ({ behind }, cmd) => `defects in your files: the probe${ago(behind)} planted nothing — ${cmd}`,
|
|
121
|
+
old: ({ behind }, cmd) => `defects in your files: the probe ran${ago(behind)}; ${cmd} shows the result`,
|
|
122
|
+
},
|
|
123
|
+
limitsCi: "pipeline: whether it passed is not visible from here — we only check that gates are declared in it",
|
|
89
124
|
|
|
90
125
|
gatesHeading: "Gates",
|
|
91
126
|
langs: "languages",
|
|
@@ -108,7 +143,9 @@ export const en = {
|
|
|
108
143
|
blindRan: (g) => `gate ${g} is declared and was run — and still missed the defect in this file`,
|
|
109
144
|
blindInstalled: "declared, but the probe did not run this gate (added later or too slow) — the next probe will show whether it catches",
|
|
110
145
|
blindMore: (cmd) => `what was planted and where — ${cmd}`,
|
|
111
|
-
probeNever: (cmd) => `Whether your checks catch a real defect has not been tested yet: ${cmd} plants one in a copy of the project and shows (a minute or two, your files are not
|
|
146
|
+
probeNever: (cmd) => `Whether your checks catch a real defect has not been tested yet: ${cmd} plants one in a copy of the project and shows (a minute or two, your project files are not changed).`,
|
|
147
|
+
todoRest: (n) => `The other entries that apply here (${n}):`,
|
|
148
|
+
todoRestHow: (self) => `install any: ${self} add <name> · what it catches and why: ${self} why <name>`,
|
|
112
149
|
startWith: "Start with these three — born from a real failure, and each closes with one ready command:",
|
|
113
150
|
startCmd: (cmd) => `one line, no kit needed: ${cmd}`,
|
|
114
151
|
startTool: (url) => `the tool: ${url}`,
|
|
@@ -199,12 +236,13 @@ export const en = {
|
|
|
199
236
|
has_docker: ["no Dockerfile or compose", "docker is already here"],
|
|
200
237
|
has_deps: ["no dependency file in sight", "dependencies are declared"],
|
|
201
238
|
has_tests: ["no tests in sight", "tests exist"],
|
|
239
|
+
has_biome: ["the linter is not Biome", "the project's linter is Biome"],
|
|
202
240
|
has_env: ["no environment file", "an environment file exists"],
|
|
203
241
|
has_agent_config: ["the agent was never configured here", "agent settings exist"],
|
|
204
242
|
has_agent_entry: ["no entry point for an agent here", "an entry point for an agent exists"],
|
|
205
243
|
has_ui: ["no stylesheets or UI components in sight", "a UI exists: stylesheets or components"],
|
|
206
244
|
has_mcp: ["no MCP tools are wired up for the agent here", "MCP servers are declared"],
|
|
207
|
-
has_api_spec: ["no API
|
|
245
|
+
has_api_spec: ["no API contract in sight: no OpenAPI file, no tRPC, ts-rest or Fastify type provider", "an API contract exists: a specification file or schemas in code"],
|
|
208
246
|
},
|
|
209
247
|
},
|
|
210
248
|
|
|
@@ -230,5 +268,41 @@ export const en = {
|
|
|
230
268
|
alreadyDeclared: "already declared",
|
|
231
269
|
},
|
|
232
270
|
|
|
271
|
+
prompt: {
|
|
272
|
+
title: "# Task: get this project's checks actually working",
|
|
273
|
+
intro: "Written by AQK from the current state of the repository. Do the items in order.",
|
|
274
|
+
rulesTitle: "## Ground rules",
|
|
275
|
+
rules: [
|
|
276
|
+
"Use only commands from this task and from the repository. Do not invent any.",
|
|
277
|
+
"Run an item's check first and see it fail. Then fix. Done means the same command passes.",
|
|
278
|
+
"Fix the code, not the check: do not loosen a threshold, add exclusions or switch a gate off. If you think a check is wrong, stop and ask the owner.",
|
|
279
|
+
"A decision only the owner can make (what to add to the project, which rules to adopt) — ask, do not guess.",
|
|
280
|
+
"Change only what these items need.",
|
|
281
|
+
],
|
|
282
|
+
tasksTitle: "## What to do",
|
|
283
|
+
empty: "Nothing to do: no gate is red, the probe found nothing, nothing to add. Still run the verification below.",
|
|
284
|
+
done: "Done —",
|
|
285
|
+
item: {
|
|
286
|
+
init: (s) => `Create the manifest: \`${s} init\` — without it the other commands refuse. Done — \`${s} doctor\` shows a level.`,
|
|
287
|
+
runNone: (s) => `There has been no run yet. Run \`${s} doctor --run\` and fix whatever turns red, one gate at a time: \`${s} doctor --run --only <name>\`. Done — the run passes.`,
|
|
288
|
+
runStale: (s, when) => `The run from ${when} is older than the last commit — the red list below may describe other code. Run \`${s} doctor --run\` again. Done — you have a fresh result and have checked the items below against it.`,
|
|
289
|
+
missed: ({ slug, file }, s) => `Gate \`${slug}\` is installed but missed the defect the probe planted into \`${file}\`. Find out why — a common cause is that the gate does not look at this file type or folder; \`${s} probe\` has the details. Done — \`${s} probe\` no longer names this class.`,
|
|
290
|
+
red: (name, s) => `Gate \`${name}\` is red. Run \`${s} doctor --run --only ${name}\`, read the findings and fix the code. Done — that command passes.`,
|
|
291
|
+
blind: ({ slug, file, command }, s) => `The probe planted a \`${slug}\` defect into \`${file}\` and the project's checks did not notice. Add a check: \`${s} add ${slug}\`${command ? ` (the same as one line, without the kit: \`${command}\`)` : ""}. Done — \`${s} doctor --run --only ${slug}\` passes and \`${s} probe\` no longer names this class.`,
|
|
292
|
+
adopt: (gates, s) => `The project already has its own checks: ${gates.map((g) => `\`${g.cmd}\` (${g.source})`).join(", ")}. Declare them under gates: in .aqk.yml — ${gates.map((g) => `\`${g.name}: "${g.cmd}"\``).join(", ")}. Done — \`${s} doctor --run\` runs them.`,
|
|
293
|
+
shim: {
|
|
294
|
+
missing: (s) => `Claude Code is set up here, but the rules live in AGENTS.md — it only reads CLAUDE.md. Create a CLAUDE.md with the single line \`@AGENTS.md\`. Done — \`${s} doctor\` no longer warns about it.`,
|
|
295
|
+
noImport: (s) => `CLAUDE.md does not import AGENTS.md — Claude Code only sees CLAUDE.md. Add the line \`@AGENTS.md\` to CLAUDE.md (mentioning the file in prose does not load it). Done — \`${s} doctor\` no longer warns about it.`,
|
|
296
|
+
},
|
|
297
|
+
start: ({ slug, intent, command }, s) => `Propose the \`${slug}\` check to the owner${intent ? ` — ${intent}` : ""}. If they agree — \`${s} add ${slug}\`${command ? ` (the same as one line, without the kit: \`${command}\`)` : ""}. If it fails on existing code, do not silence it — show the findings to the owner. Done — \`${s} doctor --run --only ${slug}\` passes and \`${s} prove\` shows it proven.`,
|
|
298
|
+
},
|
|
299
|
+
more: (n, s) => `And ${n} more — the full list: \`${s} doctor\`. Finish these first.`,
|
|
300
|
+
verifyTitle: "## How to verify it is done",
|
|
301
|
+
verify: (s) => [
|
|
302
|
+
`\`${s} doctor --run\` — the run passes.`,
|
|
303
|
+
`\`${s} prove\` — no gate is broken: each one fails on its own red sample.`,
|
|
304
|
+
"Name these commands and their results in your report. \"Looks like it works\" is not done.",
|
|
305
|
+
],
|
|
306
|
+
},
|
|
233
307
|
...enGates,
|
|
234
308
|
};
|
package/tool/i18n/index.mjs
CHANGED
|
@@ -29,13 +29,19 @@ import { en } from "./en.mjs";
|
|
|
29
29
|
// Переменная окружения оставлена ВЫШЕ манифеста намеренно: человек, набравший AQK_LANG=en
|
|
30
30
|
// руками, хочет английский именно сейчас, и спорить с ним манифестом значит отнять у него
|
|
31
31
|
// последнее средство. Манифест выше локали — он про проект, локаль про машину.
|
|
32
|
-
|
|
32
|
+
//
|
|
33
|
+
// Третьим — ЯЗЫК СВОДА ПРОЕКТА, между манифестом и локалью. Отзыв с живого проекта 2026-09-11:
|
|
34
|
+
// свод и методички на русском, Windows без LANG — весь вывод английский, пока руками не впишешь
|
|
35
|
+
// `lang:`. Текст, который проект сам о себе написал, — такое же свойство проекта, как поле
|
|
36
|
+
// манифеста, только необъявленное: поэтому ниже поля и выше машины.
|
|
37
|
+
function pickLang(env = process.env, man = null, docs = "") {
|
|
33
38
|
const forced = String(env.AQK_LANG || "").toLowerCase();
|
|
34
39
|
if (forced.startsWith("ru")) return "ru";
|
|
35
40
|
if (forced.startsWith("en")) return "en";
|
|
36
41
|
const declared = String(man?.lang || "").toLowerCase();
|
|
37
42
|
if (declared.startsWith("ru")) return "ru";
|
|
38
43
|
if (declared.startsWith("en")) return "en";
|
|
44
|
+
if (docs === "ru" || docs === "en") return docs;
|
|
39
45
|
const locale = String(env.LC_ALL || env.LC_MESSAGES || env.LANG || "").toLowerCase();
|
|
40
46
|
if (locale.startsWith("ru")) return "ru";
|
|
41
47
|
return "en";
|
|
@@ -59,8 +65,41 @@ function manifestLang(cwd = process.cwd()) {
|
|
|
59
65
|
}
|
|
60
66
|
}
|
|
61
67
|
|
|
68
|
+
// Язык прозы: код, пути и адреса вырезаются — в русском своде половина слов это `npm run check`
|
|
69
|
+
// и `packages/contract`, и по всем буквам подряд он вышел бы английским. Порог с запасом в обе
|
|
70
|
+
// стороны: кириллицы больше половины букв — русский, меньше десятой при хотя бы двухстах
|
|
71
|
+
// буквах — английский; между ними и на коротком тексте свод молчит, и решает локаль.
|
|
72
|
+
function langFromDocs(text) {
|
|
73
|
+
const prose = String(text)
|
|
74
|
+
.replace(/```[\s\S]*?```/g, " ")
|
|
75
|
+
.replace(/`[^`\n]*`/g, " ")
|
|
76
|
+
.replace(/<!--[\s\S]*?-->/g, " ")
|
|
77
|
+
.replace(/https?:\/\/\S+/g, " ");
|
|
78
|
+
const cyr = (prose.match(/[а-яё]/gi) || []).length;
|
|
79
|
+
const lat = (prose.match(/[a-z]/gi) || []).length;
|
|
80
|
+
const all = cyr + lat;
|
|
81
|
+
if (all < 200) return "";
|
|
82
|
+
if (cyr / all >= 0.5) return "ru";
|
|
83
|
+
if (cyr / all <= 0.1) return "en";
|
|
84
|
+
return "";
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Первые 8 КБ каждого из трёх файлов: язык виден по первому экрану, а читать мегабайтный README
|
|
88
|
+
// ради одной буквы в начале каждой команды — дорого.
|
|
89
|
+
function docsLang(cwd = process.cwd()) {
|
|
90
|
+
let text = "";
|
|
91
|
+
for (const name of ["AGENTS.md", "CLAUDE.md", "README.md"]) {
|
|
92
|
+
try {
|
|
93
|
+
text += readFileSync(join(cwd, name), "utf8").slice(0, 8192) + "\n";
|
|
94
|
+
} catch {
|
|
95
|
+
// Файла нет — язык решат остальные.
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return langFromDocs(text);
|
|
99
|
+
}
|
|
100
|
+
|
|
62
101
|
const CATALOGS = { ru, en };
|
|
63
|
-
const LANG = pickLang(process.env, manifestLang());
|
|
102
|
+
const LANG = pickLang(process.env, manifestLang(), docsLang());
|
|
64
103
|
const L = CATALOGS[LANG];
|
|
65
104
|
|
|
66
|
-
export { L, LANG, pickLang, langFromText, CATALOGS };
|
|
105
|
+
export { L, LANG, pickLang, langFromText, langFromDocs, CATALOGS };
|
package/tool/i18n/ru-docs.mjs
CHANGED
|
@@ -68,7 +68,7 @@ const ruDocs = {
|
|
|
68
68
|
runClean: (when) => `Последний прогон ${when} — красных нет.`,
|
|
69
69
|
runRed: (when, names) => `Последний прогон ${when} — КРАСНЫЕ: ${names}.`,
|
|
70
70
|
andMore: (n) => `и ещё ${n}`,
|
|
71
|
-
skipped: (n) => `Не запускались: ${n} — инструмента нет на этой
|
|
71
|
+
skipped: (n) => `Не запускались: ${n} — пропущены по --only/--skip или инструмента нет на этой машине; их состояние неизвестно.`,
|
|
72
72
|
probeNever: "Проба покрытия не делалась — что здесь не прикрыто ничем, НЕИЗВЕСТНО. Это не «прикрыто»: `aqk probe`.",
|
|
73
73
|
probeOff: "Проба покрытия выключена в манифесте (`probe: 0`) — что здесь не прикрыто ничем, никто не считает.",
|
|
74
74
|
probeUnknown: "Пробу покрытия провести не удалось — что здесь не прикрыто ничем, НЕИЗВЕСТНО. Это не «прикрыто».",
|
|
@@ -81,6 +81,28 @@ const ruDocs = {
|
|
|
81
81
|
(behind ? ` (отстала на ${behind} коммитов)` : "") + ".",
|
|
82
82
|
ratchets: (list) => `Храповики: ${list}. Список может только укорачиваться, увеличивать нельзя.`,
|
|
83
83
|
where: (entry) => `Свод правил: ${entry}. Чем доказан диф: \`aqk report --since main\`.`,
|
|
84
|
+
nextTitle: "Дальше, по порядку — вычислено из этого репозитория, а не общий совет:",
|
|
85
|
+
// По виду шага — таблица, а не лесенка тернарников: её не держат в голове, и наш же
|
|
86
|
+
// complexity-limit её поймал.
|
|
87
|
+
nextStep: {
|
|
88
|
+
init: () => "Заведи стандарт: `aqk init` — без .aqk.yml `aqk add` откажет.",
|
|
89
|
+
adopt: (s) => `Объяви проверки, которые у проекта уже есть: в .aqk.yml, в gates: ${s.gates.map((g) => `${g.name}: "${g.cmd}"`).join(", ")}. Затем \`aqk doctor --run\`.`,
|
|
90
|
+
blind: (s) => `Брак «${s.slug}», подсаженный пробой в ${s.file}, ваши проверки НЕ поймали.` +
|
|
91
|
+
(s.command ? ` Поймать сейчас: \`${s.command}\`.` : "") + ` Держать всегда: \`aqk add ${s.slug}\`.`,
|
|
92
|
+
start: (s) => `Поставь ${s.slug}: \`aqk add ${s.slug}\`` + (s.command ? ` (одной строкой, без комплекта: \`${s.command}\`)` : "") +
|
|
93
|
+
", затем `aqk prove` — гейт обязан покраснеть на своём красном образце.",
|
|
94
|
+
},
|
|
95
|
+
nextMore: (n) => `И ещё ${n} — весь список: \`aqk doctor\`.`,
|
|
96
|
+
whenTitle: "Когда что делать:",
|
|
97
|
+
whenCommit: (hook) => hook === true
|
|
98
|
+
? "Перед коммитом → хук `.git/hooks/pre-commit` прогонит проверки сам; не обходи его (`--no-verify`)."
|
|
99
|
+
: "Перед коммитом → `aqk doctor --run --since main`: хука перед коммитом нет, само это не случится.",
|
|
100
|
+
whenRules: [
|
|
101
|
+
"Добавил или поменял проверку → `aqk prove`: гейт обязан покраснеть на своём красном образце, иначе он не проверяет ничего.",
|
|
102
|
+
"Пишешь правило в свод → рядом метка арбитра `<!-- aqk: имя-гейта -->`; гейта нет — `<!-- aqk: человек -->`, и это признание, а не проверка.",
|
|
103
|
+
"Проверка мешает → не ослабляй её (`|| true`, `--exit-zero`, подавление без кода правила): остановись и спроси владельца.",
|
|
104
|
+
"Считаешь, что готово → `aqk report --since main`: каждый изменённый файл должен быть назван проверкой.",
|
|
105
|
+
],
|
|
84
106
|
mapTitle: "ЧТО УМЕЕТ ЭТОТ ИНСТРУМЕНТ. Полный список команд — не пересказ, а тот же список,\nиз которого собрана справка:",
|
|
85
107
|
rulesTitle: (e) => `СВОД ПРАВИЛ ЭТОГО ПРОЕКТА (${e}) — дословно, целиком. Это не приглашение\nпрочитать: он уже здесь.`,
|
|
86
108
|
hookAlready: (p) => `хук уже стоит в ${p} — ничего не меняю.`,
|
|
@@ -198,6 +220,7 @@ const ruDocs = {
|
|
|
198
220
|
},
|
|
199
221
|
],
|
|
200
222
|
report: {
|
|
223
|
+
skippedBySelect: "не запускался (по --only/--skip), состояние неизвестно",
|
|
201
224
|
title: "aqk doctor --run",
|
|
202
225
|
version: "версия",
|
|
203
226
|
level: "уровень",
|
package/tool/i18n/ru-gates.mjs
CHANGED
|
@@ -160,6 +160,7 @@ export const ruGates = {
|
|
|
160
160
|
"методички в .aqk/docs/ остаются на русском — решение, а не недоделка.",
|
|
161
161
|
created: (n) => `создано (${n}):`,
|
|
162
162
|
andMore: (n) => `… и ещё ${n}`,
|
|
163
|
+
ignored: (list) => `служебные файлы комплекта дописаны в .gitignore — это состояние машины, в git ему не место: ${list}`,
|
|
163
164
|
kept: (n) => `уже были на месте, не тронуты (${n}):`,
|
|
164
165
|
overwrite: (cmd) => `перезаписать: ${cmd}`,
|
|
165
166
|
nextTitle: "Что дальше — по порядку:",
|
|
@@ -257,16 +258,20 @@ export const ruGates = {
|
|
|
257
258
|
badEvery: (v) => `в манифесте probe: «${v}» — это не число коммитов. Проба НЕ делается: подставить умолчание значило бы делать не то, что написано.`,
|
|
258
259
|
autoFirst: "пробы покрытия здесь ещё не делали — делаю её сам. Выключить: AQK_PROBE=0",
|
|
259
260
|
auto: (n) => `прошло ${n} коммитов с прошлой пробы — делаю её сам. Выключить: AQK_PROBE=0`,
|
|
261
|
+
autoNotInCi: (cmd) => `в конвейере проба сама не запускается — это минуты сюрпризом в быстрой проверке; поставьте её отдельной задачей: ${cmd} (или AQK_PROBE=1)`,
|
|
260
262
|
title: "aqk probe — чего объявленные проверки не видят",
|
|
261
263
|
method: (files, entries, gates) =>
|
|
262
264
|
`способ: красный образец записи каталога подсаживается в КОПИЮ проекта, и там гоняются ` +
|
|
263
265
|
`ОБЪЯВЛЕННЫЕ гейты — команда берётся как написана, в неё ничего не подставляется. ` +
|
|
264
266
|
`Файлов: ${files}, применимых записей: ${entries}, гейтов зелёных на чистом дереве: ${gates}. ` +
|
|
265
|
-
|
|
267
|
+
`Файлы проекта не меняются; итог пробы пишется в .aqk/last-probe.md (он в .gitignore после init).`,
|
|
266
268
|
fixes: (n) => `починок в истории: ${n}`,
|
|
267
269
|
caught: (names) => `ловит: ${names}`,
|
|
268
270
|
blind: "НЕ ЛОВИТ НИКТО",
|
|
269
271
|
unknown: "проверить нечем — гейт не состоялся (нет делегированной программы)",
|
|
272
|
+
nameless: "не назвал подсаженный файл",
|
|
273
|
+
planting: "краснеет и на зелёном образце — падает от самой подсадки",
|
|
274
|
+
unattributed: (g) => `покраснело: ${g} — поимка не доказана`,
|
|
270
275
|
blindWhere: (file, fixes) => `где: ${file} — починок в истории: ${fixes}`,
|
|
271
276
|
blindWhat: "что подсадили в ваш файл:",
|
|
272
277
|
blindFix: (cmd) => `поймать прямо сейчас, без комплекта: ${cmd}`,
|
package/tool/i18n/ru.mjs
CHANGED
|
@@ -10,12 +10,20 @@ import { templates } from "./templates-ru.mjs";
|
|
|
10
10
|
|
|
11
11
|
import { ruGates } from "./ru-gates.mjs";
|
|
12
12
|
|
|
13
|
+
// «(коммитов с тех пор: 3)» — без склонений: число рядом со словом в родительном падеже
|
|
14
|
+
// читается одинаково при любом числе.
|
|
15
|
+
const ago = (n) => (n === null || n === undefined ? "" : ` (коммитов с тех пор: ${n})`);
|
|
16
|
+
|
|
13
17
|
export const ru = {
|
|
14
18
|
learn: {
|
|
15
19
|
title: "Сказано вслух и не записано",
|
|
16
20
|
noLogs: (p) => `логов этого проекта нет: ${p}\n Команда читает переписку Claude Code на этой машине. Пусто — значит здесь не работали.`,
|
|
17
|
-
counted: (s, typed, said, fresh) =>
|
|
18
|
-
`сессий: ${s} · напечатано человеком: ${typed} · похоже на наставление: ${said} · нет в точке входа: ${fresh}`,
|
|
21
|
+
counted: (s, typed, said, fresh, again) =>
|
|
22
|
+
`сессий: ${s} · напечатано человеком: ${typed} · похоже на наставление: ${said} · нет в точке входа: ${fresh} · повторено: ${again}`,
|
|
23
|
+
ruleTitle: "Записано, а поправлять всё равно приходится:",
|
|
24
|
+
ruleHow: "Правило стоит в своде, а вы повторяете его агенту снова — текстом оно не держится. Ему нужен сторож-машина: aqk find \"…\" или aqk new <имя>.",
|
|
25
|
+
repeatTitle: "Повторено — вы говорили это не в первый раз («я же говорил», «опять», «снова»):",
|
|
26
|
+
restTitle: "Остальное, похожее на правило и не записанное:",
|
|
19
27
|
nothing: "всё, что похоже на правило, уже стоит в точке входа",
|
|
20
28
|
andMore: (n) => `… и ещё ${n}`,
|
|
21
29
|
warn:
|
|
@@ -45,6 +53,8 @@ export const ru = {
|
|
|
45
53
|
blob: "собрать методички в один файл GOD_AI.md",
|
|
46
54
|
learn: "кандидаты в правила из локальной переписки: сказано вслух и не записано",
|
|
47
55
|
context: "состояние проекта одним блоком — для контекста агента, а не для чтения",
|
|
56
|
+
vitals: "подключено ли то, чем комплект работает: инструменты гейтов, хуки, свежесть версии",
|
|
57
|
+
prompt: "одно задание для агента: что починить, по порядку, и чем доказать, что готово",
|
|
48
58
|
contextInstall: "то же самое, но целиком — карта и свод правил — и хуком в контекст",
|
|
49
59
|
report: "обязательная форма отчёта: что стоит, что нет, что не прочитано; --since <ссылка> — ещё и чем доказан диф",
|
|
50
60
|
badge: "значок уровня для README — и проверка, что он не врёт",
|
|
@@ -66,6 +76,23 @@ export const ru = {
|
|
|
66
76
|
gitignore: "гигиена репозитория",
|
|
67
77
|
git: "проект под контролем версий",
|
|
68
78
|
|
|
79
|
+
runtimeTracked: (f, cmd) => `${f} отслеживается git — каждый прогон его переписывает, и в дереве вечно висит изменённый файл. Вынуть: ${cmd}`,
|
|
80
|
+
runtimeNotIgnored: (f, cmd) => `${f} — состояние этой машины, а git его видит: одно \`git add .\`, и он в коммите. Спрятать: ${cmd}`,
|
|
81
|
+
layoutAdvice: "нет — это совет, прогон не роняет",
|
|
82
|
+
coversImpossible: (entry, gate, linter) => `заявка «${gate} держит ${entry}» неверна: у ${linter} нет правила под этот класс — закрывать его нечем`,
|
|
83
|
+
coversCantCheck: (entry, gate) => `заявку «${gate} держит ${entry}» проверить не умею: линтер гейта не распознан или правил записи для него нет — принято на слово`,
|
|
84
|
+
selectUnknown: (names, groups) => `--only/--skip: не знаю «${names}» — это не гейт из gates: и не группа из groups:${groups ? ` (группы: ${groups})` : ""}. Прогон всего подряд вместо пропуска был бы неправдой, поэтому стоп.`,
|
|
85
|
+
selectSkipped: (names) => `не запускались (по --only/--skip): ${names} — их состояние неизвестно, это не «зелёные»`,
|
|
86
|
+
// Claude Code читает CLAUDE.md, а не AGENTS.md — документация Claude Code, раздел «AGENTS.md».
|
|
87
|
+
claudeShim: {
|
|
88
|
+
missing: "Claude Code здесь настроен (.claude/), а свод лежит в AGENTS.md — он его не читает. Почини: CLAUDE.md с одной строкой «@AGENTS.md».",
|
|
89
|
+
noImport: "CLAUDE.md не подключает AGENTS.md — Claude Code видит только CLAUDE.md. Почини: строка «@AGENTS.md» в CLAUDE.md (упоминание словами не загружает файл).",
|
|
90
|
+
},
|
|
91
|
+
annotDropped: (n, more) => `пометок в pull request: ${n}, ещё ${more} не показано — GitHub принимает около десяти за шаг; все находки — в логе выше`,
|
|
92
|
+
heldQuiet: (n, cmd) => `держит машина: ${n} — поимённо: ${cmd}`,
|
|
93
|
+
skipQuiet: (n, cmd) => `не применимо к этому репозиторию: ${n} — поимённо и почему: ${cmd}`,
|
|
94
|
+
passedQuiet: (n) => `прошли ещё ${n} — поимённо: --verbose`,
|
|
95
|
+
jobsBad: (v) => `--jobs ждёт целое число от 1: «${v}» не подходит. Прогон по одному под видом параллельного был бы неправдой, поэтому стоп.`,
|
|
69
96
|
rulesByHuman: (total, machine, human) =>
|
|
70
97
|
`правил в точке входа: ${total}. Сторож — ЧЕЛОВЕК у ${human}, машина у ${machine}.`,
|
|
71
98
|
rulesByHumanWhy: "Правило со сторожем-человеком не сторожит никто в тот день, когда человек занят. Это та самая дыра, ради которой комплект и написан, — и напоминание про неё нужно человеку, а не только агенту. Считаны правила ТОЧКИ ВХОДА: обещание, живущее в любом другом файле, не проверяет вообще ничто, и комплект даже не скажет вам, что оно есть.",
|
|
@@ -87,6 +114,19 @@ export const ru = {
|
|
|
87
114
|
toReach: (n) => `Чтобы достичь AQK-${n}:`,
|
|
88
115
|
gives: (what) => `Что это даст: ${what}`,
|
|
89
116
|
allDone: "Все ступени пройдены.",
|
|
117
|
+
// Чего уровень не доказывает. Ступени меряют оснащённость: гейты показаны на образцах
|
|
118
|
+
// каталога. Про файлы проекта знает только проба — строка говорит ровно то, что знает она.
|
|
119
|
+
limitsTitle: "Уровень — это оснащённость, а не надёжность. Чего он не доказывает:",
|
|
120
|
+
limitsProbe: {
|
|
121
|
+
never: (_, cmd) => `брак в ваших файлах: проба не запускалась — ${cmd}`,
|
|
122
|
+
off: () => "брак в ваших файлах: проба выключена (probe: 0) — ловят ли его проверки, неизвестно",
|
|
123
|
+
blind: ({ names, behind }) => `брак в ваших файлах: проба${ago(behind)} НЕ поймала — ${names.join(", ")}`,
|
|
124
|
+
partial: ({ caught, unknown, behind }, cmd) => `брак в ваших файлах: проба${ago(behind)} — поймано классов ${caught}, у ${unknown} поимка не доказана (${cmd})`,
|
|
125
|
+
caught: ({ caught, behind }) => `брак в ваших файлах: проба${ago(behind)} поймала все ${caught} подсаженных классов — только тех, что есть в каталоге`,
|
|
126
|
+
nothing: ({ behind }, cmd) => `брак в ваших файлах: проба${ago(behind)} ничего не подсадила — ${cmd}`,
|
|
127
|
+
old: ({ behind }, cmd) => `брак в ваших файлах: проба была${ago(behind)}, итог покажет ${cmd}`,
|
|
128
|
+
},
|
|
129
|
+
limitsCi: "конвейер: прошёл ли он, отсюда не видно — проверяется только, что гейты в нём объявлены",
|
|
90
130
|
|
|
91
131
|
gatesHeading: "Гейты",
|
|
92
132
|
langs: "языки",
|
|
@@ -109,7 +149,9 @@ export const ru = {
|
|
|
109
149
|
blindRan: (g) => `гейт ${g} стоит и прогонялся — а брак в этом файле пропустил`,
|
|
110
150
|
blindInstalled: "объявлено, но проба этот гейт не гоняла (поставлен позже или медленный) — поймает ли, покажет следующая",
|
|
111
151
|
blindMore: (cmd) => `что именно подсадили и куда — ${cmd}`,
|
|
112
|
-
probeNever: (cmd) => `Ловят ли ваши проверки настоящий брак, ещё не проверялось: ${cmd} подсадит его в копию проекта и покажет (минута-две,
|
|
152
|
+
probeNever: (cmd) => `Ловят ли ваши проверки настоящий брак, ещё не проверялось: ${cmd} подсадит его в копию проекта и покажет (минута-две, файлы проекта не меняет).`,
|
|
153
|
+
todoRest: (n) => `Остальные записи, применимые у вас (${n}):`,
|
|
154
|
+
todoRestHow: (self) => `поставить любую: ${self} add <имя> · что она ловит и зачем: ${self} why <имя>`,
|
|
113
155
|
startWith: "Начните с этих трёх — они родились из настоящего отказа и закрываются одной готовой командой:",
|
|
114
156
|
startCmd: (cmd) => `одной строкой, без комплекта: ${cmd}`,
|
|
115
157
|
startTool: (url) => `инструмент: ${url}`,
|
|
@@ -205,12 +247,13 @@ export const ru = {
|
|
|
205
247
|
has_docker: ["нет Dockerfile или compose", "docker уже есть"],
|
|
206
248
|
has_deps: ["не видно файла зависимостей", "зависимости объявлены"],
|
|
207
249
|
has_tests: ["не видно тестов", "тесты есть"],
|
|
250
|
+
has_biome: ["линтер не Biome", "линтер проекта — Biome"],
|
|
208
251
|
has_env: ["нет файла окружения", "файл окружения есть"],
|
|
209
252
|
has_agent_config: ["агента здесь не настраивали", "настройки агента есть"],
|
|
210
253
|
has_agent_entry: ["свода для агента здесь нет", "свод для агента есть"],
|
|
211
254
|
has_ui: ["не видно стилей и компонентов интерфейса", "интерфейс есть: стили или компоненты"],
|
|
212
255
|
has_mcp: ["агенту здесь не подключали внешних инструментов через MCP", "MCP-серверы объявлены"],
|
|
213
|
-
has_api_spec: ["не видно
|
|
256
|
+
has_api_spec: ["не видно договора API: ни файла OpenAPI, ни tRPC, ts-rest или провайдера типов Fastify", "договор API есть: файл спецификации или схемы в коде"],
|
|
214
257
|
},
|
|
215
258
|
},
|
|
216
259
|
|
|
@@ -237,5 +280,45 @@ export const ru = {
|
|
|
237
280
|
alreadyDeclared: "уже объявлен",
|
|
238
281
|
},
|
|
239
282
|
|
|
283
|
+
// Задание агенту одним текстом (`aqk prompt`). У КАЖДОГО пункта хвост «Готово — …» с
|
|
284
|
+
// командой-арбитром: пункт без неё агент закроет словами «сделал» — это сторожит модульная проверка.
|
|
285
|
+
prompt: {
|
|
286
|
+
title: "# Задание: довести проверки проекта до рабочих",
|
|
287
|
+
intro: "Составлено AQK по состоянию репозитория. Выполняй пункты по порядку.",
|
|
288
|
+
rulesTitle: "## Правила",
|
|
289
|
+
rules: [
|
|
290
|
+
"Команды бери только из этого задания и из репозитория. Не выдумывай.",
|
|
291
|
+
"Сначала запусти проверку пункта и убедись, что она красная. Потом чини. Готово — когда та же команда зелёная.",
|
|
292
|
+
"Чини код, а не проверку: не ослабляй порог, не добавляй исключений, не выключай гейт. Считаешь проверку неправой — остановись и спроси владельца.",
|
|
293
|
+
"Решение, которое может принять только владелец (что ставить в проект, какие правила вводить), — спроси, а не угадывай.",
|
|
294
|
+
"Меняй только то, что нужно для этих пунктов.",
|
|
295
|
+
],
|
|
296
|
+
tasksTitle: "## Что сделать",
|
|
297
|
+
empty: "Делать нечего: красных гейтов нет, проба ничего не нашла, ставить нечего. Всё равно выполни проверку ниже.",
|
|
298
|
+
done: "Готово —",
|
|
299
|
+
item: {
|
|
300
|
+
init: (s) => `Заведи манифест: \`${s} init\` — без него остальные команды отказывают. Готово — \`${s} doctor\` показывает уровень.`,
|
|
301
|
+
runNone: (s) => `Прогона ещё не было. Запусти \`${s} doctor --run\` и почини то, что покраснеет, по одному гейту: \`${s} doctor --run --only <имя>\`. Готово — прогон зелёный.`,
|
|
302
|
+
runStale: (s, when) => `Прогон ${when} старше последнего коммита — список красных ниже может быть про другой код. Запусти \`${s} doctor --run\` заново. Готово — у тебя свежий итог, и пункты ниже сверены с ним.`,
|
|
303
|
+
missed: ({ slug, file }, s) => `Гейт \`${slug}\` стоит, но пропустил брак, который проба подсадила в \`${file}\`. Разберись почему — частая причина: гейт не смотрит этот тип файлов или каталог; подробности даст \`${s} probe\`. Готово — \`${s} probe\` больше не называет этот класс.`,
|
|
304
|
+
red: (name, s) => `Гейт \`${name}\` красный. Запусти \`${s} doctor --run --only ${name}\`, прочитай находки и почини код. Готово — эта команда зелёная.`,
|
|
305
|
+
blind: ({ slug, file, command }, s) => `Проба подсадила брак класса \`${slug}\` в \`${file}\`, и проверки проекта его не заметили. Поставь проверку: \`${s} add ${slug}\`${command ? ` (то же одной строкой, без комплекта: \`${command}\`)` : ""}. Готово — \`${s} doctor --run --only ${slug}\` проходит, а \`${s} probe\` больше не называет этот класс.`,
|
|
306
|
+
adopt: (gates, s) => `У проекта уже есть свои проверки: ${gates.map((g) => `\`${g.cmd}\` (${g.source})`).join(", ")}. Впиши их в gates: манифеста .aqk.yml — ${gates.map((g) => `\`${g.name}: "${g.cmd}"\``).join(", ")}. Готово — \`${s} doctor --run\` их запускает.`,
|
|
307
|
+
shim: {
|
|
308
|
+
missing: (s) => `Claude Code здесь настроен, а правила лежат в AGENTS.md — он читает только CLAUDE.md. Создай CLAUDE.md с одной строкой \`@AGENTS.md\`. Готово — \`${s} doctor\` об этом больше не предупреждает.`,
|
|
309
|
+
noImport: (s) => `CLAUDE.md не подключает AGENTS.md — Claude Code видит только CLAUDE.md. Добавь в CLAUDE.md строку \`@AGENTS.md\` (упоминание словами файл не загружает). Готово — \`${s} doctor\` об этом больше не предупреждает.`,
|
|
310
|
+
},
|
|
311
|
+
// Запись каталога — предложение, а не факт о проекте: ставить или нет, решает владелец.
|
|
312
|
+
// Красный гейт и брак из пробы — факты, там «почини»; здесь — «предложи».
|
|
313
|
+
start: ({ slug, intent, command }, s) => `Предложи владельцу проверку \`${slug}\`${intent ? ` — ${intent}` : ""}. Согласен — \`${s} add ${slug}\`${command ? ` (то же одной строкой, без комплекта: \`${command}\`)` : ""}. Краснеет на существующем коде — не глуши её, покажи находки владельцу. Готово — \`${s} doctor --run --only ${slug}\` проходит, а \`${s} prove\` показывает её доказанной.`,
|
|
314
|
+
},
|
|
315
|
+
more: (n, s) => `И ещё ${n} — полный список: \`${s} doctor\`. Сначала закончи эти.`,
|
|
316
|
+
verifyTitle: "## Как проверить, что готово",
|
|
317
|
+
verify: (s) => [
|
|
318
|
+
`\`${s} doctor --run\` — прогон зелёный.`,
|
|
319
|
+
`\`${s} prove\` — ни один гейт не сломан: каждый краснеет на своём красном образце.`,
|
|
320
|
+
"В отчёте назови эти команды и их итог. «Выглядит рабочим» — не готово.",
|
|
321
|
+
],
|
|
322
|
+
},
|
|
240
323
|
...ruGates,
|
|
241
324
|
};
|
package/tool/lib/adopt.mjs
CHANGED
|
@@ -95,4 +95,18 @@ function proposeGates(files = {}) {
|
|
|
95
95
|
const ADOPT_FILES = ["package.json", "Makefile", ".pre-commit-config.yaml", "tox.ini", "pyproject.toml"];
|
|
96
96
|
const ADOPT_SCRIPTS = ["scripts/test", "scripts/check", "scripts/lint"];
|
|
97
97
|
|
|
98
|
-
|
|
98
|
+
// Чтение с диска — отдельно от разбора: разбор проверяется перебором случаев, чтение — прогоном.
|
|
99
|
+
async function readAdoptFiles(cwd) {
|
|
100
|
+
const { readFile, access } = await import("node:fs/promises");
|
|
101
|
+
const { join } = await import("node:path");
|
|
102
|
+
const files = {};
|
|
103
|
+
for (const n of ADOPT_FILES) {
|
|
104
|
+
try { files[n] = await readFile(join(cwd, n), "utf8"); } catch { /* нет — и ладно */ }
|
|
105
|
+
}
|
|
106
|
+
for (const n of ADOPT_SCRIPTS) {
|
|
107
|
+
try { await access(join(cwd, n)); files[n] = ""; } catch { /* нет — и ладно */ }
|
|
108
|
+
}
|
|
109
|
+
return files;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export { proposeGates, readAdoptFiles };
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
// tool/lib/advice.mjs — ЧТО СОВЕТОВАТЬ ПРОЕКТУ: какие записи каталога его касаются, с каких
|
|
2
|
+
// начать и какой командой закрыть класс брака прямо сейчас, без комплекта.
|
|
3
|
+
//
|
|
4
|
+
// ОТДЕЛЬНЫМ ФАЙЛОМ, а не в repo.mjs: там осмотр репозитория (факты, триггеры, рецепты), здесь —
|
|
5
|
+
// решение, что из этого сказать человеку и агенту. Одно знание на `doctor`, `probe` и блок для
|
|
6
|
+
// агента: жило в двух файлах и росло в обоих; шов вскрыл наш же file-size-limit — repo.mjs
|
|
7
|
+
// дорос до 494 строк при пределе 500.
|
|
8
|
+
import { triggerVerdict } from "./repo.mjs";
|
|
9
|
+
|
|
10
|
+
// Корзины каталога для ЭТОГО проекта: держит · к установке · закрыто другим арбитром ·
|
|
11
|
+
// неприменимо. Одна раскладка на `doctor` и на блок для агента: жила внутри `doctor`, и блоку
|
|
12
|
+
// пришлось бы завести вторую — а два счёта одного и того же расходятся первыми.
|
|
13
|
+
function catalogBuckets(catalog, facts, covered = new Map()) {
|
|
14
|
+
const held = [], todo = [], skip = [], byOther = [];
|
|
15
|
+
for (const rec of catalog) {
|
|
16
|
+
const v = triggerVerdict(rec, facts);
|
|
17
|
+
if (!v.applies) skip.push([rec, v.why]);
|
|
18
|
+
else if (facts.gateKeys.includes(rec.slug)) held.push(rec);
|
|
19
|
+
else if (covered.has(rec.slug)) byOther.push([rec, covered.get(rec.slug)]);
|
|
20
|
+
else todo.push(rec);
|
|
21
|
+
}
|
|
22
|
+
return { held, todo, skip, byOther };
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// С ЧЕГО НАЧАТЬ: три записи вместо двадцати равнозначных крестов.
|
|
26
|
+
//
|
|
27
|
+
// Двадцать одинаковых требований — это ноль требований: закрывают первое попавшееся или не
|
|
28
|
+
// закрывают ничего. Порядок НЕ по нашему вкусу; два признака, оба — факты, которые у нас уже
|
|
29
|
+
// есть:
|
|
30
|
+
// · запись родилась из настоящего отказа (`lifecycle: stable` — `proof` ссылается на журнал
|
|
31
|
+
// шишек), то есть она про боль, которая СЛУЧАЛАСЬ, а не про «хорошую практику»;
|
|
32
|
+
// · её можно закрыть одной готовой командой — цена входа минутная.
|
|
33
|
+
// Сначала то, что и больно, и дёшево.
|
|
34
|
+
//
|
|
35
|
+
// При равенстве признаков — по имени: одинаковый ввод обязан давать одинаковый ответ, иначе
|
|
36
|
+
// человек видит разный совет на двух прогонах подряд и перестаёт верить обоим.
|
|
37
|
+
function startWith(entries, facts, n = 3) {
|
|
38
|
+
const langs = facts?.langs ? [...facts.langs] : [];
|
|
39
|
+
const cheap = (e) => {
|
|
40
|
+
const r = e?.recipes && typeof e.recipes === "object" ? e.recipes : {};
|
|
41
|
+
return [...langs, "native"].some((k) => r[k] && !/\{gate\}/.test(r[k])) ? 1 : 0;
|
|
42
|
+
};
|
|
43
|
+
// Зрелость НЕ поле записи, а вычисляемый признак: `proof` ссылается на журнал шишек. Тот же
|
|
44
|
+
// признак, которым каталог отделяет условную запись с первого дня (`entryLifecycle`).
|
|
45
|
+
// Заводить второй счёт нельзя: разъехавшись, они дали бы разные ответы про одну запись.
|
|
46
|
+
const hurt = (e) => (/incidents\//.test(String(e?.proof || "")) ? 1 : 0);
|
|
47
|
+
return [...entries]
|
|
48
|
+
.sort((a, b) =>
|
|
49
|
+
(hurt(b) + cheap(b)) - (hurt(a) + cheap(a)) ||
|
|
50
|
+
cheap(b) - cheap(a) ||
|
|
51
|
+
String(a.slug).localeCompare(String(b.slug)))
|
|
52
|
+
.slice(0, n);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Адрес Biome сверен по реестру github 2026-09-11.
|
|
56
|
+
const BIOME_URL = "https://github.com/biomejs/biome";
|
|
57
|
+
|
|
58
|
+
// Совет по НЕПОКРЫТОМУ классу: команда, которую можно вставить прямо сейчас.
|
|
59
|
+
//
|
|
60
|
+
// Проба находит настоящие дыры и печатала про них «close it: aqk add <имя>» — то есть «поставь
|
|
61
|
+
// нашу штуку». Человек, впервые увидевший комплект, закрывает окно. А готовая однострочная
|
|
62
|
+
// команда под его стек У НАС УЖЕ ЛЕЖИТ в `recipes` записи каталога; мы её не показывали.
|
|
63
|
+
//
|
|
64
|
+
// Замер руками на `requests` (самый скачиваемый python-пакет) 2026-09-10: в
|
|
65
|
+
// `src/requests/utils.py` — 75 коммитов-починок; дописана функция с `except Exception: pass`;
|
|
66
|
+
// их собственные `ruff` и `pytest` дали 0 и на чистой копии, и на подсаженной. Строка, которая
|
|
67
|
+
// поймала бы это, лежала в нашем каталоге всё это время.
|
|
68
|
+
//
|
|
69
|
+
// Переносимый рецепт (`any`) в совет НЕ идёт: он зовёт файл из комплекта, и человеку без
|
|
70
|
+
// комплекта вставить его некуда. Нет родного рецепта под стек — команды нет, и это честнее
|
|
71
|
+
// выдуманной.
|
|
72
|
+
function blindAdvice(entry, facts, hot = {}) {
|
|
73
|
+
const recipes = entry?.recipes && typeof entry.recipes === "object" ? entry.recipes : {};
|
|
74
|
+
// `langs` приходит МНОЖЕСТВОМ, а не массивом — `Array.isArray` тихо давал пустой список, и
|
|
75
|
+
// совет не печатался вовсе. Поймано на живом `requests`: langs = Set(1) { python }.
|
|
76
|
+
const langs = facts?.langs ? [...facts.langs] : [];
|
|
77
|
+
// Тот же порядок, что у `pickRecipe`: свой язык → безъязыковой родной → ничего. Переносимый
|
|
78
|
+
// (`any`) сюда не идёт никогда: он зовёт файл из комплекта, и человеку без комплекта вставить
|
|
79
|
+
// его некуда.
|
|
80
|
+
// Проект на Biome — совет на языке Biome, а не eslint (отзыв с живого проекта 2026-09-11:
|
|
81
|
+
// «начни с трёх» советовал завести eslint проекту на Biome). Одно правило разово через `--only`;
|
|
82
|
+
// `--error-on-warnings` обязателен — правило вне рекомендованных Biome ставит на
|
|
83
|
+
// «предупреждение», и без флага команда выходила с нулём, напечатав находку (Biome 2.5.12).
|
|
84
|
+
const br = typeof entry?.biome_rules === "string" ? entry.biome_rules.trim() : "";
|
|
85
|
+
if (facts?.has_biome && br && br !== "none" && langs.some((l) => l === "javascript" || l === "typescript")) {
|
|
86
|
+
const only = br.split(",").map((x) => x.trim()).filter(Boolean).map((r) => `--only=${r}`).join(" ");
|
|
87
|
+
return { command: `npx @biomejs/biome lint --error-on-warnings ${only} .`, tool: BIOME_URL,
|
|
88
|
+
file: hot.file ?? null, fixes: hot.fixes ?? null, slug: entry?.slug ?? null };
|
|
89
|
+
}
|
|
90
|
+
let cmd = null;
|
|
91
|
+
for (const key of [...langs, "native"]) {
|
|
92
|
+
const r = recipes[key];
|
|
93
|
+
if (!r || /\{gate\}/.test(r)) continue;
|
|
94
|
+
cmd = String(r).replace(/\{dir\}/g, ".")
|
|
95
|
+
// Вычистить то, что относится к НАМ, а не к его проекту. Исключение наших красных
|
|
96
|
+
// образцов нужно УСТАНОВЛЕННОМУ гейту — рядом с ним лежат образцы. Человеку, который
|
|
97
|
+
// команду только копирует, этих каталогов не существует, и флаги про них подрывают
|
|
98
|
+
// доверие: инструмент говорит про чужое хозяйство вместо его кода.
|
|
99
|
+
.replace(/\s--ignore-pattern\s+'[^']*gates\/[^']*'/g, "")
|
|
100
|
+
.replace(/\s--ignore-paths=?\s*'[^']*gates\/[^']*'/g, "")
|
|
101
|
+
.replace(/\s+/g, " ")
|
|
102
|
+
.trim();
|
|
103
|
+
break;
|
|
104
|
+
}
|
|
105
|
+
// Адрес — того инструмента, которым команда начинается: поле `tool` общее на все языки, и
|
|
106
|
+
// python-проекту показывалась ссылка на eslint. Первые три слова, а не одно: `npx knip`,
|
|
107
|
+
// `python -m vulture`. Не совпало — весь список: лишняя ссылка лучше, чем ни одной.
|
|
108
|
+
const urls = entry?.tool ? String(entry.tool).split(/\s+·\s+/) : [];
|
|
109
|
+
const head = cmd ? cmd.split(" ").slice(0, 3) : [];
|
|
110
|
+
const own = urls.find((u) => head.includes(u.replace(/\/+$/, "").split("/").pop()));
|
|
111
|
+
const tool = own ?? (entry?.tool ? String(entry.tool) : null);
|
|
112
|
+
return { command: cmd, tool, file: hot.file ?? null, fixes: hot.fixes ?? null, slug: entry?.slug ?? null };
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export { catalogBuckets, startWith, blindAdvice };
|