agent-quality-kit 0.13.0 → 0.15.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 +46 -8
- package/README.ru.md +49 -9
- 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/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/llms.txt +5 -1
- package/package.json +1 -1
- package/tool/commands/context.mjs +59 -44
- package/tool/commands/doctor-catalog.mjs +222 -0
- package/tool/commands/doctor.mjs +46 -237
- package/tool/commands/feedback.mjs +157 -0
- package/tool/commands/learn.mjs +119 -19
- package/tool/commands/probe.mjs +4 -2
- package/tool/commands/project.mjs +11 -13
- package/tool/commands/prompt.mjs +70 -0
- package/tool/i18n/en-docs.mjs +1 -0
- package/tool/i18n/en-gates.mjs +27 -0
- package/tool/i18n/en.mjs +70 -3
- package/tool/i18n/index.mjs +42 -3
- package/tool/i18n/ru-docs.mjs +1 -0
- package/tool/i18n/ru-gates.mjs +28 -0
- package/tool/i18n/ru.mjs +81 -3
- package/tool/lib/annotate.mjs +66 -0
- package/tool/lib/ask.mjs +118 -0
- package/tool/lib/brief.mjs +17 -38
- package/tool/lib/cadence.mjs +30 -1
- package/tool/lib/core.mjs +8 -6
- package/tool/lib/gate-worker.mjs +4 -1
- package/tool/lib/repo.mjs +45 -4
- package/tool/lib/run.mjs +140 -9
- package/tool/program.mjs +10 -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/fail-closed.test.mjs +96 -1
- package/tool/selfcheck/smoke/first-run.test.mjs +39 -0
- package/tool/selfcheck/smoke/verdict.test.mjs +41 -2
- package/tool/selfcheck/smoke.sh +4 -0
- package/tool/selfcheck/units-annotate.mjs +67 -0
- package/tool/selfcheck/units-ask.mjs +85 -0
- package/tool/selfcheck/units-brief.mjs +3 -13
- package/tool/selfcheck/units-cadence.mjs +26 -1
- package/tool/selfcheck/units-context.mjs +2 -1
- package/tool/selfcheck/units-feedback.mjs +137 -0
- package/tool/selfcheck/units-learn.mjs +32 -0
- package/tool/selfcheck/units-level.mjs +21 -1
- package/tool/selfcheck/units-prompt.mjs +106 -0
- package/tool/selfcheck/units-repo.mjs +20 -1
package/tool/commands/learn.mjs
CHANGED
|
@@ -13,10 +13,16 @@
|
|
|
13
13
|
// настоящие правила («файл не трогай», «делай прогон с базой обязательно», «никаких
|
|
14
14
|
// обходных временных путей») и разговорная шелуха примерно поровну.
|
|
15
15
|
//
|
|
16
|
-
// ЧЕГО ЗДЕСЬ НАМЕРЕННО НЕТ. Поиска
|
|
17
|
-
// agent-lint. Замер его не подтвердил: на 67 сессиях владелец не повторяет
|
|
18
|
-
// говорит его один раз и каждый раз иначе. Те «повторы», что нашлись,
|
|
19
|
-
// одной реплики в самом логе.
|
|
16
|
+
// ЧЕГО ЗДЕСЬ НАМЕРЕННО НЕТ. Поиска повторов ПО СХОДСТВУ ТЕКСТА — приёма, на котором построен
|
|
17
|
+
// session-analyzer у agent-lint. Замер его не подтвердил: на 67 сессиях владелец не повторяет
|
|
18
|
+
// правило дословно, он говорит его один раз и каждый раз иначе. Те «повторы», что нашлись,
|
|
19
|
+
// оказались задвоением одной реплики в самом логе.
|
|
20
|
+
//
|
|
21
|
+
// ЧТО ЕСТЬ ВМЕСТО НЕГО (2026-09-11). Повтор, который помечает САМ человек: «я же говорил»,
|
|
22
|
+
// «опять», «снова». Не угадывание, что две реплики об одном, а слова «это уже было». На логах
|
|
23
|
+
// двух проектов — 7 и 1 такая реплика, настоящих норм среди них 6 и 1; отбор по маркерам
|
|
24
|
+
// наставления не ловил ни одной. Такой повтор, совпавший с правилом свода, — «записано, а
|
|
25
|
+
// поправлять всё равно приходится»: правилу нужен сторож, текстом оно не держится.
|
|
20
26
|
//
|
|
21
27
|
// ПРИВАТНОСТЬ. Команда читает переписку. Поэтому: только логи ТЕКУЩЕГО проекта (или явно
|
|
22
28
|
// названного), только в терминал, ни строки на диск, код возврата всегда 0. Отчёт, который
|
|
@@ -47,14 +53,86 @@ const MARKERS = new RegExp(
|
|
|
47
53
|
|
|
48
54
|
// Признаки вставки, а не реплики: длина, код в тройных кавычках, много переносов, пути, ссылки.
|
|
49
55
|
// Каждый добавлен по итогу прогона, а не на всякий случай.
|
|
56
|
+
function isPaste(text) {
|
|
57
|
+
const t = String(text || "").trim();
|
|
58
|
+
if (!t || t.length > 400) return true;
|
|
59
|
+
if (t.includes("```")) return true;
|
|
60
|
+
if ((t.match(/\n/g) || []).length > 6) return true;
|
|
61
|
+
if (/https?:\/\//.test(t)) return true;
|
|
62
|
+
return (t.match(/\S+\/\S+/g) || []).length >= 3;
|
|
63
|
+
}
|
|
64
|
+
|
|
50
65
|
function looksLikeRule(text) {
|
|
66
|
+
return !isPaste(text) && MARKERS.test(String(text));
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// ПОВТОР — сигнал, который даёт сам человек: «я же говорил», «опять», «снова». Разбор AgentLint
|
|
70
|
+
// 2026-09-11 (research/competitors/agentlint-0xmariowu.md): их SS2 сопоставляет поправку с
|
|
71
|
+
// правилом свода по словам. Замер на логах владельца: из 45 поправок к записанным правилам
|
|
72
|
+
// относятся от силы две — такой приём дал бы шум. А реплик с пометкой повтора в том же проекте
|
|
73
|
+
// 14, настоящих норм среди них 5–6 («опять не хочу плодить файлы», «я же не просил, ты опять не
|
|
74
|
+
// так понял») — и отбор по маркерам наставления выше не ловил НИ ОДНОЙ: слов «всегда»/«никогда»
|
|
75
|
+
// в них нет. `\b` здесь не годится — в JavaScript он не видит границ кириллических слов.
|
|
76
|
+
// Две силы пометки. Сильная — «я же говорил», «сколько раз» — повтор при любой форме реплики.
|
|
77
|
+
// Слабая — «опять», «снова» — только в утверждении: второй прогон на тех же логах показал, что
|
|
78
|
+
// вопрос с ней — недоумение («че опять rust?», «опять в env добавить?»), а не норма. Реплика со
|
|
79
|
+
// значка статуса (⬜ ✅ ❌) — вставленная цитата ответа агента, а не слова человека.
|
|
80
|
+
const STRONG = new RegExp(
|
|
81
|
+
"(^|[^а-яёa-z])(я же (говорил|говорю|просил|сказал|писал)|говорил же|сколько (раз|можно)|" +
|
|
82
|
+
"в который раз|(ещё|еще) раз говорю|i (already )?told you)([^а-яёa-z]|$)",
|
|
83
|
+
"i",
|
|
84
|
+
);
|
|
85
|
+
const WEAK = /(^|[^а-яёa-z])(опять(?! же)|снова|again)([^а-яёa-z]|$)/i;
|
|
86
|
+
|
|
87
|
+
function isRepeat(text) {
|
|
51
88
|
const t = String(text || "").trim();
|
|
52
|
-
if (
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
89
|
+
if (isPaste(t) || /^[⬜✅❌☐☑]/u.test(t)) return false;
|
|
90
|
+
return STRONG.test(t) || (WEAK.test(t) && !t.includes("?"));
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Правила свода: пункты списка с меткой сторожа `<!-- aqk: … -->` — так их размечает комплект.
|
|
94
|
+
// Свод без меток — пункты под заголовком про правила. Заголовок правила — жирное начало или
|
|
95
|
+
// часть до двоеточия: по нему и сверяем, хвост пояснения совпал бы с чем угодно.
|
|
96
|
+
function entryRules(entryText) {
|
|
97
|
+
const items = [];
|
|
98
|
+
let cur = null;
|
|
99
|
+
let section = "";
|
|
100
|
+
for (const line of String(entryText).split(/\r?\n/)) {
|
|
101
|
+
const h = /^#{1,4}\s+(.+)$/.exec(line);
|
|
102
|
+
if (h) { section = h[1]; cur = null; continue; }
|
|
103
|
+
const b = /^[-*]\s+(.+)$/.exec(line);
|
|
104
|
+
if (b) { cur = { text: b[1], section }; items.push(cur); continue; }
|
|
105
|
+
if (cur && /^\s{2,}\S/.test(line)) cur.text += ` ${line.trim()}`;
|
|
106
|
+
else cur = null;
|
|
107
|
+
}
|
|
108
|
+
const marked = items.filter((r) => /<!--\s*aqk:/.test(r.text));
|
|
109
|
+
const pool = marked.length ? marked : items.filter((r) => /правил|rules|constraints|запрет/i.test(r.section));
|
|
110
|
+
return pool.map((r) => {
|
|
111
|
+
const body = r.text.replace(/<!--[\s\S]*?-->/g, "").trim();
|
|
112
|
+
const title = (/^\*\*([^*]+)\*\*/.exec(body) || /^([^:]{3,60}):/.exec(body) || [, body.slice(0, 100)])[1].trim();
|
|
113
|
+
return { title, arbiter: (/<!--\s*aqk:\s*(\S+?)\s*-->/.exec(r.text) || [])[1] || null };
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Слова-пометки повтора и служебные слова по правилу не сверяются: иначе «опять» совпало бы с
|
|
118
|
+
// любым правилом, где оно встретилось.
|
|
119
|
+
const NOT_TOPIC = new Set(["опять", "снова", "говор", "проси", "сказа", "писал", "тольк", "всегд",
|
|
120
|
+
"никог", "нужно", "можно", "котор", "когда", "чтобы", "этого", "again", "told", "always", "never"]);
|
|
121
|
+
const topic = (t) => new Set(keyWords(t).map(stem).filter((s) => !NOT_TOPIC.has(s)));
|
|
122
|
+
|
|
123
|
+
// Записано, а поправлять всё равно приходится: повтор, у которого с заголовком правила совпали
|
|
124
|
+
// две основы — или все, если заголовок короче трёх слов.
|
|
125
|
+
function repeatedRules(repeats, entryText) {
|
|
126
|
+
const out = [];
|
|
127
|
+
for (const r of entryRules(entryText)) {
|
|
128
|
+
const t = topic(r.title);
|
|
129
|
+
if (!t.size) continue;
|
|
130
|
+
for (const m of repeats) {
|
|
131
|
+
const common = [...topic(m.text)].filter((s) => t.has(s)).length;
|
|
132
|
+
if (common >= 2 || (t.size <= 2 && common === t.size)) out.push({ rule: r.title, arbiter: r.arbiter, ...m });
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return out;
|
|
58
136
|
}
|
|
59
137
|
|
|
60
138
|
// Слова, по которым сверяем сказанное с записанным. Короткие отброшены: на них совпадёт что
|
|
@@ -127,6 +205,7 @@ async function cmdLearn(argv = process.argv) {
|
|
|
127
205
|
try { files = (await readdir(root)).filter((f) => f.endsWith(".jsonl")); } catch { files = []; }
|
|
128
206
|
const seen = new Set();
|
|
129
207
|
const said = [];
|
|
208
|
+
const repeats = [];
|
|
130
209
|
let typedTotal = 0;
|
|
131
210
|
for (const f of files) {
|
|
132
211
|
let raw = "";
|
|
@@ -136,24 +215,45 @@ async function cmdLearn(argv = process.argv) {
|
|
|
136
215
|
const key = m.text.toLowerCase().slice(0, 200);
|
|
137
216
|
if (seen.has(key)) continue;
|
|
138
217
|
seen.add(key);
|
|
139
|
-
if (
|
|
218
|
+
if (isRepeat(m.text)) repeats.push(m);
|
|
219
|
+
else if (looksLikeRule(m.text)) said.push(m);
|
|
140
220
|
}
|
|
141
221
|
}
|
|
142
222
|
|
|
143
223
|
const entry = await readEntry(await readManifest());
|
|
144
|
-
const
|
|
145
|
-
fresh.
|
|
224
|
+
const byDate = (a, b) => String(b.when).localeCompare(String(a.when));
|
|
225
|
+
const fresh = said.filter((m) => saidNotWritten(m.text, entry)).sort(byDate);
|
|
226
|
+
const ruleHits = repeatedRules(repeats, entry).sort(byDate);
|
|
227
|
+
const onRule = new Set(ruleHits.map((h) => h.text));
|
|
228
|
+
const again = repeats.filter((m) => !onRule.has(m.text)).sort(byDate);
|
|
146
229
|
|
|
147
|
-
console.log(` ${c.dim(L.learn.counted(files.length, typedTotal, said.length, fresh.length))}\n`);
|
|
148
|
-
if (!fresh.length) {
|
|
230
|
+
console.log(` ${c.dim(L.learn.counted(files.length, typedTotal, said.length, fresh.length, repeats.length))}\n`);
|
|
231
|
+
if (!fresh.length && !repeats.length) {
|
|
149
232
|
console.log(` ${L.learn.nothing}\n`);
|
|
150
233
|
return;
|
|
151
234
|
}
|
|
152
|
-
|
|
153
|
-
|
|
235
|
+
const show = (m) => console.log(` ${c.dim(m.when)} ${m.text.slice(0, 150)}`);
|
|
236
|
+
// Первым — правило, которое ЗАПИСАНО, а человек всё равно поправляет: текстом оно не держится.
|
|
237
|
+
if (ruleHits.length) {
|
|
238
|
+
console.log(` ${c.bold(L.learn.ruleTitle)}`);
|
|
239
|
+
for (const h of ruleHits.slice(0, limit)) {
|
|
240
|
+
console.log(` ${c.yellow("!")} ${h.rule}${h.arbiter ? c.dim(` · aqk: ${h.arbiter}`) : ""}`);
|
|
241
|
+
console.log(` ${c.dim(h.when)} ${h.text.slice(0, 140)}`);
|
|
242
|
+
}
|
|
243
|
+
console.log(c.dim(` ${L.learn.ruleHow}\n`));
|
|
244
|
+
}
|
|
245
|
+
if (again.length) {
|
|
246
|
+
console.log(` ${c.bold(L.learn.repeatTitle)}`);
|
|
247
|
+
again.slice(0, limit).forEach(show);
|
|
248
|
+
if (again.length > limit) console.log(c.dim(` ${L.learn.andMore(again.length - limit)}`));
|
|
249
|
+
console.log("");
|
|
250
|
+
}
|
|
251
|
+
if (fresh.length) {
|
|
252
|
+
if (repeats.length) console.log(` ${c.bold(L.learn.restTitle)}`);
|
|
253
|
+
fresh.slice(0, limit).forEach(show);
|
|
254
|
+
if (fresh.length > limit) console.log(c.dim(`\n ${L.learn.andMore(fresh.length - limit)}`));
|
|
154
255
|
}
|
|
155
|
-
if (fresh.length > limit) console.log(c.dim(`\n ${L.learn.andMore(fresh.length - limit)}`));
|
|
156
256
|
console.log(`\n ${c.yellow(L.learn.warn)}\n`);
|
|
157
257
|
}
|
|
158
258
|
|
|
159
|
-
export { cmdLearn, logSlug, looksLikeRule, saidNotWritten, typedFrom };
|
|
259
|
+
export { cmdLearn, logSlug, looksLikeRule, saidNotWritten, typedFrom, isRepeat, repeatedRules };
|
package/tool/commands/probe.mjs
CHANGED
|
@@ -30,7 +30,7 @@ import { fixHotspots, probeSummary, probeVerdictPaired, countProbe, namesPlant,
|
|
|
30
30
|
import { detectFacts, readCatalog, triggerVerdict } from "../lib/repo.mjs";
|
|
31
31
|
import { blindAdvice } from "../lib/advice.mjs";
|
|
32
32
|
import { CWD, GATES_SRC, TARGET_DIR, c, SELF, exists } from "../lib/core.mjs";
|
|
33
|
-
import { probeState, probeEvery, PROBE_EVERY, blindLines, parseBlind, parseRan } from "../lib/cadence.mjs";
|
|
33
|
+
import { probeState, probeEvery, PROBE_EVERY, blindLines, parseBlind, parseRan, parseCounts } from "../lib/cadence.mjs";
|
|
34
34
|
import { L } from "../i18n/index.mjs";
|
|
35
35
|
import { gateCommand } from "../lib/execution.mjs";
|
|
36
36
|
|
|
@@ -422,6 +422,7 @@ async function cmdProbe(args, { auto = false } = {}) {
|
|
|
422
422
|
// Отметка нужна не для отчёта, а для КАДЕНЦИИ: по ней следующий прогон поймёт, что пора.
|
|
423
423
|
// Без неё команда снова становится тем, о чём надо вспомнить.
|
|
424
424
|
await writeMark(commitCount(), blind, [
|
|
425
|
+
`caught: ${n.caughtClasses}`, `unknown: ${n.unknownClasses}`,
|
|
425
426
|
`ran: ${probeGates.map(([name]) => name).join(" ")}`, "",
|
|
426
427
|
...blindLines(records), "",
|
|
427
428
|
...hot.map(({ path: p2, fixes }) => `- ${p2} (${P.fixes(fixes)})`),
|
|
@@ -442,7 +443,8 @@ async function probeStatus() {
|
|
|
442
443
|
if (every === null) return { state: "unknown", behind: null, badEvery: String(man?.probe) };
|
|
443
444
|
if (every === 0) return { state: "off", behind: null };
|
|
444
445
|
const mark = await readMark();
|
|
445
|
-
|
|
446
|
+
const text = mark?.text;
|
|
447
|
+
return { ...probeState(mark, commitCount(), every), classes: parseBlind(text), ran: parseRan(text), counts: parseCounts(text) };
|
|
446
448
|
}
|
|
447
449
|
|
|
448
450
|
export { cmdProbe, probeStatus, probeableGates, gatesState, extAlternatives, planProbeGates, isCode };
|
|
@@ -6,7 +6,8 @@ import { spawnSync } from "node:child_process";
|
|
|
6
6
|
import { join, dirname, relative } from "node:path";
|
|
7
7
|
import {
|
|
8
8
|
CWD, PKG_ROOT, DOCS_SRC, RULES_SRC, TARGET_DIR, MANIFEST, SELF, REPO_URL, c, exists, die,
|
|
9
|
-
copyDir, writeIfAbsent,
|
|
9
|
+
copyDir, writeIfAbsent, stateDirs, docPath, ensureIgnored } from "../lib/core.mjs";
|
|
10
|
+
import { askAllowed, markAsked } from "../lib/ask.mjs";
|
|
10
11
|
import { AGENTS_MD, CLAUDE_MD, MANIFEST_YML } from "../lib/templates.mjs";
|
|
11
12
|
import { banner } from "../lib/banner.mjs";
|
|
12
13
|
import { readManifest } from "../lib/manifest.mjs";
|
|
@@ -95,7 +96,10 @@ ${c.dim(L.init.burned(`${SELF} note "…"`))}
|
|
|
95
96
|
// Ничего не постится само: ссылки печатаются, дальше решает человек. Обратная связь важнее
|
|
96
97
|
// звезды, но без звезды меньше шансов, что кто-то вообще дойдёт до фидбека.
|
|
97
98
|
async function maybeAskFeedback() {
|
|
98
|
-
|
|
99
|
+
// Ограничитель — общий на все обращения комплекта (ask.mjs). Вид `install` разовый и живёт
|
|
100
|
+
// в доме пользователя: второй init в другом репозитории на том же компьютере молчит.
|
|
101
|
+
const dirs = stateDirs();
|
|
102
|
+
if (!(await askAllowed("install", dirs))) return;
|
|
99
103
|
const url = REPO_URL;
|
|
100
104
|
console.log(`
|
|
101
105
|
${c.bold(L.feedback.title)}
|
|
@@ -104,20 +108,14 @@ ${c.bold(L.feedback.title)}
|
|
|
104
108
|
${url}/issues/new
|
|
105
109
|
${c.dim(` ${L.feedback.once}`)}
|
|
106
110
|
`);
|
|
107
|
-
// Пометка «уже показывали» — удобство, а не работа
|
|
108
|
-
//
|
|
109
|
-
// /etc/passwd,
|
|
110
|
-
//
|
|
111
|
-
// docker. Локально не воспроизводилось случайно: uid разработчика 1000 совпадает с
|
|
112
|
-
// пользователем `node` в образе, у которого дом есть. Нашёл конвейер, где uid 1001.
|
|
111
|
+
// Пометка «уже показывали» — удобство, а не работа команды: `markAsked` не бросает, а
|
|
112
|
+
// возвращает, записалось ли. Дом бывает недоступен для записи (в контейнере с `--user
|
|
113
|
+
// 1001:127` у этого uid нет записи в /etc/passwd, homedir() даёт «/»), и до 2026-09-09 это
|
|
114
|
+
// роняло ВЕСЬ `init` — то есть любого, кто набрал команду из нашей же документации по docker.
|
|
113
115
|
//
|
|
114
116
|
// Молча глотать нельзя — это то, что красит наш же swallowed-error. Поэтому вслух: не
|
|
115
117
|
// запомнили, покажем снова. Установка при этом доходит до конца.
|
|
116
|
-
|
|
117
|
-
await writeIfAbsent(FEEDBACK_MARK, "shown\n", { force: false });
|
|
118
|
-
} catch {
|
|
119
|
-
console.log(c.dim(` ${L.feedback.notRemembered}`));
|
|
120
|
-
}
|
|
118
|
+
if (!(await markAsked("install", dirs))) console.log(c.dim(` ${L.feedback.notRemembered}`));
|
|
121
119
|
}
|
|
122
120
|
|
|
123
121
|
function findJournal() {
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
// tool/commands/prompt.mjs — `aqk prompt`: одно задание для агента — «почини вот это, это и это».
|
|
2
|
+
//
|
|
3
|
+
// ЗАЧЕМ. Между диагнозом и действием не было моста. `doctor` пишет человеку, `context` говорит
|
|
4
|
+
// агенту «как дела», а задание «сделай вот это» человек пересказывал сам — и пересказ терял
|
|
5
|
+
// команды. Идея — из разбора agentlint (research/competitors/agentlint.md, «Задание для агента»):
|
|
6
|
+
// правила поведения сверху, исправления по весу, в конце — как проверить.
|
|
7
|
+
//
|
|
8
|
+
// ЧЕГО У НИХ НЕ БЕРЁМ. У них «проверь» — это «балл вырос». У нас у КАЖДОГО пункта свой арбитр:
|
|
9
|
+
// команда, которая сейчас красная и должна стать зелёной. Иначе агент доложит «сделал» про
|
|
10
|
+
// пункт, который ничем не доказан, — тот самый отказ, против которого весь комплект.
|
|
11
|
+
//
|
|
12
|
+
// НИЧЕГО НЕ ЗАПУСКАЕТ. Читает то же, что `context`: манифест, `.aqk/last-run.md`, отметку пробы,
|
|
13
|
+
// каталог. Задание пишется за секунду, а прогон — работа агента, и она в задании первой строкой,
|
|
14
|
+
// если прогона нет или он устарел: иначе пустое задание прочиталось бы как «всё чисто».
|
|
15
|
+
import { readManifest } from "../lib/manifest.mjs";
|
|
16
|
+
import { claudeShimFor } from "../lib/repo.mjs";
|
|
17
|
+
import { CWD, SELF } from "../lib/core.mjs";
|
|
18
|
+
import { readAdvice, portableSelf } from "./context.mjs";
|
|
19
|
+
import { readRun } from "../lib/run.mjs";
|
|
20
|
+
import { probeStatus } from "./probe.mjs";
|
|
21
|
+
import { L } from "../i18n/index.mjs";
|
|
22
|
+
|
|
23
|
+
// Больше пяти пунктов за раз агент не удержит — как и человек; остальное называется числом.
|
|
24
|
+
const MAX_ITEMS = 5;
|
|
25
|
+
|
|
26
|
+
// Чистая функция: состояние → строки задания. Порядок — по тому, насколько пункт ФАКТ о проекте
|
|
27
|
+
// и насколько без него невыполнимы остальные: манифест → прогон → красное → гейт стоит, но
|
|
28
|
+
// пропустил брак из пробы → брак, для которого гейта нет → проверки, которые у проекта уже есть
|
|
29
|
+
// → свод, невидимый Claude Code → «начните с этих трёх» из каталога. Класс из пробы в последнем
|
|
30
|
+
// списке не повторяется.
|
|
31
|
+
function taskText(st, T = L.prompt) {
|
|
32
|
+
const self = st.self || "aqk";
|
|
33
|
+
const it = [];
|
|
34
|
+
if (!st.manifest) it.push(T.item.init(self));
|
|
35
|
+
if (!st.run) it.push(T.item.runNone(self));
|
|
36
|
+
else if (st.run.stale) it.push(T.item.runStale(self, st.run.when));
|
|
37
|
+
for (const name of st.run?.red || []) it.push(T.item.red(name, self));
|
|
38
|
+
for (const m of st.missed || []) it.push(T.item.missed(m, self));
|
|
39
|
+
for (const b of st.blind || []) it.push(T.item.blind(b, self));
|
|
40
|
+
if ((st.adopt || []).length) it.push(T.item.adopt(st.adopt, self));
|
|
41
|
+
if (st.shim) it.push(T.item.shim[st.shim](self));
|
|
42
|
+
const seen = new Set((st.blind || []).map((b) => b.slug));
|
|
43
|
+
for (const s of st.start || []) if (!seen.has(s.slug)) it.push(T.item.start(s, self));
|
|
44
|
+
|
|
45
|
+
const out = [T.title, "", T.intro, "", T.rulesTitle, ...T.rules.map((r) => `- ${r}`), "", T.tasksTitle];
|
|
46
|
+
if (!it.length) out.push(T.empty);
|
|
47
|
+
it.slice(0, MAX_ITEMS).forEach((line, i) => out.push(`${i + 1}. ${line}`));
|
|
48
|
+
if (it.length > MAX_ITEMS) out.push(T.more(it.length - MAX_ITEMS, self));
|
|
49
|
+
out.push("", T.verifyTitle, ...T.verify(self).map((r) => `- ${r}`));
|
|
50
|
+
return out;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async function cmdPrompt() {
|
|
54
|
+
const man = await readManifest();
|
|
55
|
+
let probe = null;
|
|
56
|
+
try { probe = await probeStatus(); } catch { /* пробы нет — пунктов из неё не будет */ }
|
|
57
|
+
let advice = { adopt: [], blind: [], start: [], missed: [] };
|
|
58
|
+
try { advice = await readAdvice(man, probe); } catch { /* не посчитали — выдумывать пункты нельзя */ }
|
|
59
|
+
// Команда уходит в чужой контекст и, возможно, в чужие руки: абсолютный путь к нашей
|
|
60
|
+
// программе там не сработает — тот же довод, что у хука `context --install`.
|
|
61
|
+
console.log(taskText({
|
|
62
|
+
self: portableSelf(SELF),
|
|
63
|
+
manifest: Boolean(man),
|
|
64
|
+
run: await readRun(),
|
|
65
|
+
shim: await claudeShimFor(CWD),
|
|
66
|
+
...advice,
|
|
67
|
+
}).join("\n"));
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export { cmdPrompt, taskText };
|
package/tool/i18n/en-docs.mjs
CHANGED
|
@@ -63,6 +63,7 @@ const enDocs = {
|
|
|
63
63
|
"No run has been made — which checks are red is UNKNOWN. This is not \"clean\": `aqk doctor --run`.",
|
|
64
64
|
runStale: (when) =>
|
|
65
65
|
`The last run ${when} is OLDER than the last commit — it describes different code.`,
|
|
66
|
+
runCannot: (names) => `COULD NOT CHECK (the checks themselves failed — not findings about the code): ${names}`,
|
|
66
67
|
runClean: (when) => `Last run ${when} — nothing red.`,
|
|
67
68
|
runRed: (when, names) => `Last run ${when} — RED: ${names}.`,
|
|
68
69
|
andMore: (n) => `and ${n} more`,
|
package/tool/i18n/en-gates.mjs
CHANGED
|
@@ -185,6 +185,33 @@ export const enGates = {
|
|
|
185
185
|
issue: "Found a bug or it did not fit — open an issue; both are the most useful feedback there is.",
|
|
186
186
|
notRemembered: "could not remember this (home is not writable) — the message will appear again",
|
|
187
187
|
once: "This message is shown once: it will not appear again on this machine.",
|
|
188
|
+
|
|
189
|
+
ask: {
|
|
190
|
+
cannot: (names, cmd) =>
|
|
191
|
+
`AQK could not check: ${names}. That is the most valuable thing to tell the author — ${cmd}`,
|
|
192
|
+
blind: (names, cmd) =>
|
|
193
|
+
`The probe found defect classes nobody catches here: ${names}. Does that match your experience? — ${cmd}`,
|
|
194
|
+
red: (names, cmd) =>
|
|
195
|
+
`The kit caught: ${names}. One line to the author — did it help: ${cmd}`,
|
|
196
|
+
once: "Asked once per project; switch it off with AQK_FEEDBACK=0",
|
|
197
|
+
agent: "Say this to the human in one sentence, and do not insist if they do not answer.",
|
|
198
|
+
},
|
|
199
|
+
how: "Sending is one click (a prefilled issue opens; the text stays editable):",
|
|
200
|
+
issueTitle: "Feedback on the kit",
|
|
201
|
+
nothingSent: "Nothing was sent: the kit makes no outgoing requests other than the version check.",
|
|
202
|
+
orPaste: (cmd) => `No GitHub? Forward the text above as is — all of it comes from ${cmd}`,
|
|
203
|
+
report: {
|
|
204
|
+
title: "### Feedback on the kit",
|
|
205
|
+
unknown: "unknown",
|
|
206
|
+
none: "none",
|
|
207
|
+
env: (v, node, os) => `version: ${v} · node: ${node} · system: ${os}`,
|
|
208
|
+
level: (x) => `level: ${x}`,
|
|
209
|
+
stack: (x) => `stack: ${x}`,
|
|
210
|
+
gates: (n, red, cannot) => `gates declared: ${n} · red: ${red} · could not check: ${cannot}`,
|
|
211
|
+
blind: (x) => `classes nobody catches here: ${x}`,
|
|
212
|
+
say: "What you would say in your own words (one line — the most useful part of the whole message):",
|
|
213
|
+
mark: (v) => `<!-- collected by "aqk feedback" ${v}: no paths, no code, no repository name -->`,
|
|
214
|
+
},
|
|
188
215
|
},
|
|
189
216
|
|
|
190
217
|
note: {
|
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,7 +50,9 @@ 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
|
+
feedback: "a report on how the kit worked plus a prefilled link — the only payment it asks",
|
|
47
54
|
vitals: "is what the kit runs on wired up: gate tools, hooks, version freshness",
|
|
55
|
+
prompt: "one task for the agent: what to fix, in order, and how to prove it is done",
|
|
48
56
|
contextInstall: "the same in full — the map and the rulebook — installed as a hook",
|
|
49
57
|
report: "the mandatory report form: what is in place, what is not, what was not read; --since <ref> adds what proves the diff",
|
|
50
58
|
badge: "a level badge for your README — and a check that it does not lie",
|
|
@@ -73,6 +81,14 @@ export const en = {
|
|
|
73
81
|
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`,
|
|
74
82
|
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.`,
|
|
75
83
|
selectSkipped: (names) => `not run (by --only/--skip): ${names} — their state is unknown, they are not "green"`,
|
|
84
|
+
claudeShim: {
|
|
85
|
+
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\".",
|
|
86
|
+
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).",
|
|
87
|
+
},
|
|
88
|
+
annotDropped: (n, more) => `pull request annotations: ${n}, ${more} more not shown — GitHub takes about ten per step; every finding is in the log above`,
|
|
89
|
+
heldQuiet: (n, cmd) => `held by the machine: ${n} — by name: ${cmd}`,
|
|
90
|
+
skipQuiet: (n, cmd) => `not applicable to this repository: ${n} — by name and why: ${cmd}`,
|
|
91
|
+
passedQuiet: (n) => `${n} more passed — by name: --verbose`,
|
|
76
92
|
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.`,
|
|
77
93
|
rulesByHuman: (total, machine, human) =>
|
|
78
94
|
`${human} of ${total} rules in the entry point are guarded by a HUMAN, ${machine} by a machine.`,
|
|
@@ -95,6 +111,17 @@ export const en = {
|
|
|
95
111
|
toReach: (n) => `To reach AQK-${n}:`,
|
|
96
112
|
gives: (what) => `What it buys you: ${what}`,
|
|
97
113
|
allDone: "All levels reached.",
|
|
114
|
+
limitsTitle: "A level measures tooling, not reliability. What it does not prove:",
|
|
115
|
+
limitsProbe: {
|
|
116
|
+
never: (_, cmd) => `defects in your files: the probe has never run — ${cmd}`,
|
|
117
|
+
off: () => "defects in your files: the probe is off (probe: 0) — whether your checks catch them is unknown",
|
|
118
|
+
blind: ({ names, behind }) => `defects in your files: the probe${ago(behind)} did NOT catch — ${names.join(", ")}`,
|
|
119
|
+
partial: ({ caught, unknown, behind }, cmd) => `defects in your files: the probe${ago(behind)} caught ${caught} classes, ${unknown} unproven (${cmd})`,
|
|
120
|
+
caught: ({ caught, behind }) => `defects in your files: the probe${ago(behind)} caught all ${caught} planted classes — only those the catalog has`,
|
|
121
|
+
nothing: ({ behind }, cmd) => `defects in your files: the probe${ago(behind)} planted nothing — ${cmd}`,
|
|
122
|
+
old: ({ behind }, cmd) => `defects in your files: the probe ran${ago(behind)}; ${cmd} shows the result`,
|
|
123
|
+
},
|
|
124
|
+
limitsCi: "pipeline: whether it passed is not visible from here — we only check that gates are declared in it",
|
|
98
125
|
|
|
99
126
|
gatesHeading: "Gates",
|
|
100
127
|
langs: "languages",
|
|
@@ -142,6 +169,10 @@ export const en = {
|
|
|
142
169
|
`either fix them and drop them from advisory, or admit the rule does not exist.`,
|
|
143
170
|
runHeading: "Running the declared gates",
|
|
144
171
|
timeout: "did not finish within 5 minutes",
|
|
172
|
+
cannotCheck: (why) => `could not check: ${why}`,
|
|
173
|
+
whySpawn: (code) => `failed to start${code ? ` (${code})` : ""}`,
|
|
174
|
+
whySignal: (sig) => `killed by signal ${sig || "?"}`,
|
|
175
|
+
whyExit: (code) => `exit ${code} — for this command that is a failure, not a finding`,
|
|
145
176
|
running: (i, n) => `[${i}/${n}] running…`,
|
|
146
177
|
proving: "checking that the gates catch defects on their own samples…",
|
|
147
178
|
exitCode: (code) => `exit ${code}`,
|
|
@@ -216,7 +247,7 @@ export const en = {
|
|
|
216
247
|
has_agent_entry: ["no entry point for an agent here", "an entry point for an agent exists"],
|
|
217
248
|
has_ui: ["no stylesheets or UI components in sight", "a UI exists: stylesheets or components"],
|
|
218
249
|
has_mcp: ["no MCP tools are wired up for the agent here", "MCP servers are declared"],
|
|
219
|
-
has_api_spec: ["no API
|
|
250
|
+
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"],
|
|
220
251
|
},
|
|
221
252
|
},
|
|
222
253
|
|
|
@@ -242,5 +273,41 @@ export const en = {
|
|
|
242
273
|
alreadyDeclared: "already declared",
|
|
243
274
|
},
|
|
244
275
|
|
|
276
|
+
prompt: {
|
|
277
|
+
title: "# Task: get this project's checks actually working",
|
|
278
|
+
intro: "Written by AQK from the current state of the repository. Do the items in order.",
|
|
279
|
+
rulesTitle: "## Ground rules",
|
|
280
|
+
rules: [
|
|
281
|
+
"Use only commands from this task and from the repository. Do not invent any.",
|
|
282
|
+
"Run an item's check first and see it fail. Then fix. Done means the same command passes.",
|
|
283
|
+
"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.",
|
|
284
|
+
"A decision only the owner can make (what to add to the project, which rules to adopt) — ask, do not guess.",
|
|
285
|
+
"Change only what these items need.",
|
|
286
|
+
],
|
|
287
|
+
tasksTitle: "## What to do",
|
|
288
|
+
empty: "Nothing to do: no gate is red, the probe found nothing, nothing to add. Still run the verification below.",
|
|
289
|
+
done: "Done —",
|
|
290
|
+
item: {
|
|
291
|
+
init: (s) => `Create the manifest: \`${s} init\` — without it the other commands refuse. Done — \`${s} doctor\` shows a level.`,
|
|
292
|
+
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.`,
|
|
293
|
+
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.`,
|
|
294
|
+
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.`,
|
|
295
|
+
red: (name, s) => `Gate \`${name}\` is red. Run \`${s} doctor --run --only ${name}\`, read the findings and fix the code. Done — that command passes.`,
|
|
296
|
+
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.`,
|
|
297
|
+
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.`,
|
|
298
|
+
shim: {
|
|
299
|
+
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.`,
|
|
300
|
+
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.`,
|
|
301
|
+
},
|
|
302
|
+
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.`,
|
|
303
|
+
},
|
|
304
|
+
more: (n, s) => `And ${n} more — the full list: \`${s} doctor\`. Finish these first.`,
|
|
305
|
+
verifyTitle: "## How to verify it is done",
|
|
306
|
+
verify: (s) => [
|
|
307
|
+
`\`${s} doctor --run\` — the run passes.`,
|
|
308
|
+
`\`${s} prove\` — no gate is broken: each one fails on its own red sample.`,
|
|
309
|
+
"Name these commands and their results in your report. \"Looks like it works\" is not done.",
|
|
310
|
+
],
|
|
311
|
+
},
|
|
245
312
|
...enGates,
|
|
246
313
|
};
|
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
|
@@ -65,6 +65,7 @@ const ruDocs = {
|
|
|
65
65
|
"Прогон не делался — какие проверки красные, НЕИЗВЕСТНО. Это не «чисто»: `aqk doctor --run`.",
|
|
66
66
|
runStale: (when) =>
|
|
67
67
|
`Последний прогон ${when} СТАРЕЕ последнего коммита — он описывает не тот код, что здесь.`,
|
|
68
|
+
runCannot: (names) => `НЕ СМОГЛИ ПРОВЕРИТЬ (сбой самих проверок, а не находки о коде): ${names}`,
|
|
68
69
|
runClean: (when) => `Последний прогон ${when} — красных нет.`,
|
|
69
70
|
runRed: (when, names) => `Последний прогон ${when} — КРАСНЫЕ: ${names}.`,
|
|
70
71
|
andMore: (n) => `и ещё ${n}`,
|
package/tool/i18n/ru-gates.mjs
CHANGED
|
@@ -187,6 +187,34 @@ export const ruGates = {
|
|
|
187
187
|
issue: "Нашёл баг или не подошло — заведи Issue, самая полезная обратная связь: и то и другое.",
|
|
188
188
|
notRemembered: "запомнить не удалось (дом недоступен для записи) — сообщение появится снова",
|
|
189
189
|
once: "Это разовое сообщение: больше не покажется на этой машине.",
|
|
190
|
+
|
|
191
|
+
// Просьба ПО ДЕЛУ: у неё всегда есть содержание, иначе её нет вовсе.
|
|
192
|
+
ask: {
|
|
193
|
+
cannot: (names, cmd) =>
|
|
194
|
+
`AQK не смог проверить: ${names}. Это самое ценное, что можно рассказать автору — ${cmd}`,
|
|
195
|
+
blind: (names, cmd) =>
|
|
196
|
+
`Проба нашла классы брака, которые здесь не ловит никто: ${names}. Совпало с вашим опытом? — ${cmd}`,
|
|
197
|
+
red: (names, cmd) =>
|
|
198
|
+
`Комплект поймал: ${names}. Одна строка автору — помогло или нет: ${cmd}`,
|
|
199
|
+
once: "Спрашиваем один раз на проект; выключить: AQK_FEEDBACK=0",
|
|
200
|
+
agent: "Скажи это человеку одной фразой и не настаивай, если он не ответил.",
|
|
201
|
+
},
|
|
202
|
+
how: "Отправить — одно нажатие (откроется готовая задача, текст можно править):",
|
|
203
|
+
issueTitle: "Отзыв о работе комплекта",
|
|
204
|
+
nothingSent: "Ничего не отправлено: комплект не делает исходящих запросов, кроме проверки версии.",
|
|
205
|
+
orPaste: (cmd) => `Не хочется GitHub — перешлите текст выше как есть: он весь собран ${cmd}`,
|
|
206
|
+
report: {
|
|
207
|
+
title: "### Отзыв о комплекте",
|
|
208
|
+
unknown: "неизвестно",
|
|
209
|
+
none: "нет",
|
|
210
|
+
env: (v, node, os) => `версия: ${v} · node: ${node} · система: ${os}`,
|
|
211
|
+
level: (x) => `уровень: ${x}`,
|
|
212
|
+
stack: (x) => `стек: ${x}`,
|
|
213
|
+
gates: (n, red, cannot) => `гейтов объявлено: ${n} · красных: ${red} · не смогли проверить: ${cannot}`,
|
|
214
|
+
blind: (x) => `классы, которые здесь не ловит никто: ${x}`,
|
|
215
|
+
say: "Что сказать своими словами (одна строка — самое полезное во всём письме):",
|
|
216
|
+
mark: (v) => `<!-- собрано «aqk feedback» ${v}: без путей, без кода, без имени репозитория -->`,
|
|
217
|
+
},
|
|
190
218
|
},
|
|
191
219
|
|
|
192
220
|
note: {
|