agent-quality-kit 0.13.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 +19 -5
- package/README.ru.md +20 -5
- 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 +34 -15
- package/tool/commands/doctor-catalog.mjs +222 -0
- package/tool/commands/doctor.mjs +28 -211
- package/tool/commands/learn.mjs +119 -19
- package/tool/commands/probe.mjs +4 -2
- package/tool/commands/prompt.mjs +69 -0
- package/tool/i18n/en.mjs +65 -3
- package/tool/i18n/index.mjs +42 -3
- package/tool/i18n/ru.mjs +74 -3
- package/tool/lib/annotate.mjs +66 -0
- package/tool/lib/cadence.mjs +30 -1
- package/tool/lib/core.mjs +1 -0
- package/tool/lib/repo.mjs +45 -4
- package/tool/lib/run.mjs +20 -2
- 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 +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-cadence.mjs +26 -1
- 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 };
|
|
@@ -0,0 +1,69 @@
|
|
|
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 { readRun, readAdvice, portableSelf } from "./context.mjs";
|
|
19
|
+
import { probeStatus } from "./probe.mjs";
|
|
20
|
+
import { L } from "../i18n/index.mjs";
|
|
21
|
+
|
|
22
|
+
// Больше пяти пунктов за раз агент не удержит — как и человек; остальное называется числом.
|
|
23
|
+
const MAX_ITEMS = 5;
|
|
24
|
+
|
|
25
|
+
// Чистая функция: состояние → строки задания. Порядок — по тому, насколько пункт ФАКТ о проекте
|
|
26
|
+
// и насколько без него невыполнимы остальные: манифест → прогон → красное → гейт стоит, но
|
|
27
|
+
// пропустил брак из пробы → брак, для которого гейта нет → проверки, которые у проекта уже есть
|
|
28
|
+
// → свод, невидимый Claude Code → «начните с этих трёх» из каталога. Класс из пробы в последнем
|
|
29
|
+
// списке не повторяется.
|
|
30
|
+
function taskText(st, T = L.prompt) {
|
|
31
|
+
const self = st.self || "aqk";
|
|
32
|
+
const it = [];
|
|
33
|
+
if (!st.manifest) it.push(T.item.init(self));
|
|
34
|
+
if (!st.run) it.push(T.item.runNone(self));
|
|
35
|
+
else if (st.run.stale) it.push(T.item.runStale(self, st.run.when));
|
|
36
|
+
for (const name of st.run?.red || []) it.push(T.item.red(name, self));
|
|
37
|
+
for (const m of st.missed || []) it.push(T.item.missed(m, self));
|
|
38
|
+
for (const b of st.blind || []) it.push(T.item.blind(b, self));
|
|
39
|
+
if ((st.adopt || []).length) it.push(T.item.adopt(st.adopt, self));
|
|
40
|
+
if (st.shim) it.push(T.item.shim[st.shim](self));
|
|
41
|
+
const seen = new Set((st.blind || []).map((b) => b.slug));
|
|
42
|
+
for (const s of st.start || []) if (!seen.has(s.slug)) it.push(T.item.start(s, self));
|
|
43
|
+
|
|
44
|
+
const out = [T.title, "", T.intro, "", T.rulesTitle, ...T.rules.map((r) => `- ${r}`), "", T.tasksTitle];
|
|
45
|
+
if (!it.length) out.push(T.empty);
|
|
46
|
+
it.slice(0, MAX_ITEMS).forEach((line, i) => out.push(`${i + 1}. ${line}`));
|
|
47
|
+
if (it.length > MAX_ITEMS) out.push(T.more(it.length - MAX_ITEMS, self));
|
|
48
|
+
out.push("", T.verifyTitle, ...T.verify(self).map((r) => `- ${r}`));
|
|
49
|
+
return out;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async function cmdPrompt() {
|
|
53
|
+
const man = await readManifest();
|
|
54
|
+
let probe = null;
|
|
55
|
+
try { probe = await probeStatus(); } catch { /* пробы нет — пунктов из неё не будет */ }
|
|
56
|
+
let advice = { adopt: [], blind: [], start: [], missed: [] };
|
|
57
|
+
try { advice = await readAdvice(man, probe); } catch { /* не посчитали — выдумывать пункты нельзя */ }
|
|
58
|
+
// Команда уходит в чужой контекст и, возможно, в чужие руки: абсолютный путь к нашей
|
|
59
|
+
// программе там не сработает — тот же довод, что у хука `context --install`.
|
|
60
|
+
console.log(taskText({
|
|
61
|
+
self: portableSelf(SELF),
|
|
62
|
+
manifest: Boolean(man),
|
|
63
|
+
run: await readRun(),
|
|
64
|
+
shim: await claudeShimFor(CWD),
|
|
65
|
+
...advice,
|
|
66
|
+
}).join("\n"));
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export { cmdPrompt, taskText };
|
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:
|
|
@@ -45,6 +51,7 @@ export const en = {
|
|
|
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",
|
|
47
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",
|
|
48
55
|
contextInstall: "the same in full — the map and the rulebook — installed as a hook",
|
|
49
56
|
report: "the mandatory report form: what is in place, what is not, what was not read; --since <ref> adds what proves the diff",
|
|
50
57
|
badge: "a level badge for your README — and a check that it does not lie",
|
|
@@ -73,6 +80,14 @@ export const en = {
|
|
|
73
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`,
|
|
74
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.`,
|
|
75
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`,
|
|
76
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.`,
|
|
77
92
|
rulesByHuman: (total, machine, human) =>
|
|
78
93
|
`${human} of ${total} rules in the entry point are guarded by a HUMAN, ${machine} by a machine.`,
|
|
@@ -95,6 +110,17 @@ export const en = {
|
|
|
95
110
|
toReach: (n) => `To reach AQK-${n}:`,
|
|
96
111
|
gives: (what) => `What it buys you: ${what}`,
|
|
97
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",
|
|
98
124
|
|
|
99
125
|
gatesHeading: "Gates",
|
|
100
126
|
langs: "languages",
|
|
@@ -216,7 +242,7 @@ export const en = {
|
|
|
216
242
|
has_agent_entry: ["no entry point for an agent here", "an entry point for an agent exists"],
|
|
217
243
|
has_ui: ["no stylesheets or UI components in sight", "a UI exists: stylesheets or components"],
|
|
218
244
|
has_mcp: ["no MCP tools are wired up for the agent here", "MCP servers are declared"],
|
|
219
|
-
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"],
|
|
220
246
|
},
|
|
221
247
|
},
|
|
222
248
|
|
|
@@ -242,5 +268,41 @@ export const en = {
|
|
|
242
268
|
alreadyDeclared: "already declared",
|
|
243
269
|
},
|
|
244
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
|
+
},
|
|
245
307
|
...enGates,
|
|
246
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.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:
|
|
@@ -46,6 +54,7 @@ export const ru = {
|
|
|
46
54
|
learn: "кандидаты в правила из локальной переписки: сказано вслух и не записано",
|
|
47
55
|
context: "состояние проекта одним блоком — для контекста агента, а не для чтения",
|
|
48
56
|
vitals: "подключено ли то, чем комплект работает: инструменты гейтов, хуки, свежесть версии",
|
|
57
|
+
prompt: "одно задание для агента: что починить, по порядку, и чем доказать, что готово",
|
|
49
58
|
contextInstall: "то же самое, но целиком — карта и свод правил — и хуком в контекст",
|
|
50
59
|
report: "обязательная форма отчёта: что стоит, что нет, что не прочитано; --since <ссылка> — ещё и чем доказан диф",
|
|
51
60
|
badge: "значок уровня для README — и проверка, что он не врёт",
|
|
@@ -74,6 +83,15 @@ export const ru = {
|
|
|
74
83
|
coversCantCheck: (entry, gate) => `заявку «${gate} держит ${entry}» проверить не умею: линтер гейта не распознан или правил записи для него нет — принято на слово`,
|
|
75
84
|
selectUnknown: (names, groups) => `--only/--skip: не знаю «${names}» — это не гейт из gates: и не группа из groups:${groups ? ` (группы: ${groups})` : ""}. Прогон всего подряд вместо пропуска был бы неправдой, поэтому стоп.`,
|
|
76
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`,
|
|
77
95
|
jobsBad: (v) => `--jobs ждёт целое число от 1: «${v}» не подходит. Прогон по одному под видом параллельного был бы неправдой, поэтому стоп.`,
|
|
78
96
|
rulesByHuman: (total, machine, human) =>
|
|
79
97
|
`правил в точке входа: ${total}. Сторож — ЧЕЛОВЕК у ${human}, машина у ${machine}.`,
|
|
@@ -96,6 +114,19 @@ export const ru = {
|
|
|
96
114
|
toReach: (n) => `Чтобы достичь AQK-${n}:`,
|
|
97
115
|
gives: (what) => `Что это даст: ${what}`,
|
|
98
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: "конвейер: прошёл ли он, отсюда не видно — проверяется только, что гейты в нём объявлены",
|
|
99
130
|
|
|
100
131
|
gatesHeading: "Гейты",
|
|
101
132
|
langs: "языки",
|
|
@@ -222,7 +253,7 @@ export const ru = {
|
|
|
222
253
|
has_agent_entry: ["свода для агента здесь нет", "свод для агента есть"],
|
|
223
254
|
has_ui: ["не видно стилей и компонентов интерфейса", "интерфейс есть: стили или компоненты"],
|
|
224
255
|
has_mcp: ["агенту здесь не подключали внешних инструментов через MCP", "MCP-серверы объявлены"],
|
|
225
|
-
has_api_spec: ["не видно
|
|
256
|
+
has_api_spec: ["не видно договора API: ни файла OpenAPI, ни tRPC, ts-rest или провайдера типов Fastify", "договор API есть: файл спецификации или схемы в коде"],
|
|
226
257
|
},
|
|
227
258
|
},
|
|
228
259
|
|
|
@@ -249,5 +280,45 @@ export const ru = {
|
|
|
249
280
|
alreadyDeclared: "уже объявлен",
|
|
250
281
|
},
|
|
251
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
|
+
},
|
|
252
323
|
...ruGates,
|
|
253
324
|
};
|