mirror of
https://github.com/Matysh/houseplan-card
synced 2026-10-06 14:39:22 +00:00
fix(process): free the reviewer prompt budget, rules stay in REVIEWER.md (#750)
The code-review prompt sat at exactly 1 400 of its 1 400 words (#634), so any new line turned the budget test red, and #707/#726 already had to route their notes through job outputs. Part of the text was dead or a retelling of the reviewer digest, which #634 says the prompt must not repeat: - the mutants fragment of the track line: since #709 `mutants` is always false, so «прогнаны Validate» was unreachable; - «Отсутствие мутантов по диффу — не находка» in the show line: a rule of every track, already in REVIEWER.md «Трек show» and §10.4; - three retellings — the repeated round, the gate scope and the severity paragraph — now one-line references to the REVIEWER.md sections. The ban on a separate issue for an in-scope Medium stays in the prompt: the model files issues itself, and that mistake is expensive. What only the prompt said moves into REVIEWER.md with links to the canon: the spec delta is the diff of the issue body, a doubt about locality means a full review with a stated reason, the three smoke-select answers (docs/TESTING.md), and geometry without invariants in the report is an unrun gate. The prompt is now 1 130 words; the 1 400 threshold stays, the difference is headroom. A new test ties every «docs/process/REVIEWER.md, «X»» reference in the prompt to an existing `## X` section. Issue: #750 User-Visible: no Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018qZfe7YS4rqEMKoVeS3GKd
This commit is contained in:
@@ -194,7 +194,42 @@ test('#634 промпт ревьюера: конспект вместо пере
|
||||
'Затем верни JSON по схеме',
|
||||
]) assert.ok(flat.includes(required), `промпт потерял: ${required}`);
|
||||
// Правила живут в каноне; промпт, снова набравший пересказ, — возврат к 60–90 k
|
||||
// контекста до первого git diff (аудит 22.09). До #634 — 1 642 слова.
|
||||
// контекста до первого git diff (аудит 22.09). До #634 — 1 642 слова, к #750 —
|
||||
// ровно 1 400, после #750 — 1 130: порог прежний, разница — запас.
|
||||
const words = prompt.split(/\s+/).filter(Boolean).length;
|
||||
assert.ok(words <= 1400, `промпт ревьюера ${words} слов > 1400`);
|
||||
// #750 AC1: мёртвая строка мутантов (с #709 `mutants` всегда false) и правило
|
||||
// всех треков, которое REVIEWER.md уже несёт, в промпт не возвращаются.
|
||||
assert.doesNotMatch(prompt, /needs\.prepare\.outputs\.mutants/, 'промпт читает мёртвый выход mutants');
|
||||
assert.doesNotMatch(flat, /Отсутствие мутантов/, 'правило «отсутствие мутантов — не находка» — в REVIEWER.md, не в промпте');
|
||||
});
|
||||
|
||||
// #750 AC2: промпт вместо пересказа ссылается на разделы конспекта — ссылка
|
||||
// без раздела оставила бы ревьюера без правила, которое промпт больше не несёт.
|
||||
test('#750 AC2: ссылки промпта на REVIEWER.md ведут в существующие разделы; перенесённое на месте', () => {
|
||||
const workflow = read('.github/workflows/_process.yml');
|
||||
const prompt = workflow.slice(workflow.indexOf(' prompt: |\n'), workflow.indexOf(' claude_args: |'));
|
||||
const flat = prompt.replace(/\s+/g, ' ');
|
||||
const reviewer = read('docs/process/REVIEWER.md');
|
||||
const sections = new Set(headings(reviewer).filter((h) => h.level === 2).map((h) => h.text));
|
||||
const cited = [...flat.matchAll(/docs\/process\/REVIEWER\.md, «([^»]+)»/g)].map((m) => m[1]);
|
||||
for (const name of ['Трек show', 'Повторный раунд', 'Объём гейтов', 'Находки и вердикт']) {
|
||||
assert.ok(cited.includes(name), `промпт не ссылается на «${name}»`);
|
||||
}
|
||||
for (const name of cited) assert.ok(sections.has(name), `REVIEWER.md: нет раздела «## ${name}», на который ссылается промпт`);
|
||||
// Запрет на отдельный issue для Medium в скоупе остаётся в промпте: модель
|
||||
// заводит issue сама, и ошибка дорогая.
|
||||
assert.ok(flat.includes('отдельный issue НЕ заводится (#202)'), 'промпт потерял запрет на issue для Medium в скоупе');
|
||||
|
||||
const bullets = (anchor) => topLevelBullets(sectionText(reviewer, anchor) ?? '');
|
||||
const gates = bullets('объём-гейтов');
|
||||
const uncertain = gates.find((bullet) => bullet.includes('НЕОПРЕДЕЛЁННОСТЬ'));
|
||||
assert.ok(uncertain, '«Объём гейтов»: нет трёх ответов smoke-select с «НЕОПРЕДЕЛЁННОСТЬ»');
|
||||
assert.match(uncertain, /связь не доказана/);
|
||||
assert.ok(markdownLinks(uncertain).some((link) => link.file.endsWith('TESTING.md')), 'ответы smoke-select ссылаются на docs/TESTING.md');
|
||||
assert.ok(gates.some((bullet) => /инвариант/i.test(bullet) && bullet.includes('непрогнанный гейт')),
|
||||
'«Объём гейтов»: нет строки «геометрия без инвариантов в отчёте — непрогнанный гейт»');
|
||||
const delta = norm(bullets('повторный-раунд').join('\n'));
|
||||
assert.ok(delta.includes(norm('дифф тела issue')), '«Повторный раунд»: дельта ТЗ — дифф тела issue');
|
||||
assert.ok(delta.includes(norm('Сомнение в локальности — полный разбор')), '«Повторный раунд»: сомнение — полный разбор');
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user