Files
Claude 8dcc1cad4e docs(process): канон без противоречий, вход автора короче (#701)
Сверка PROCESS.md, ролевых выжимок, AGENTS.md, TESTING.md, CONTRIBUTING.md
и скриптов по 26 найденным расхождениям (D1–D26): трейлеры по классам
изменений, gate:small как единственный источник состава, пороги ревью,
путь реестра мутантов, golden по ci:golden, порядок чтения промпта ревью.

- scripts/change-classes.mjs: классы A/B/C/D — один модуль для
  process-gate и проверки трейлеров.
- commit-msg: коммит только с файлами класса C (документация) трейлеров
  не требует; указанные трейлеры по-прежнему проверяются.
- Маршрут автора без docs/STATUS.md: 5345 → 4703 слова.
- Промпт ревью читает SCOPE → AGENTS → REVIEWER, как ROUTES.reviewer.

Issue: #701
User-Visible: no
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018qZfe7YS4rqEMKoVeS3GKd
2026-09-29 00:30:04 +03:00

57 lines
3.4 KiB
JavaScript

// #634: цена входа агента измерима и ограничена (AC1: автор ≤ 12 000 слов).
import assert from 'node:assert/strict';
import { readFileSync } from 'node:fs';
import test from 'node:test';
import { ROUTES, countWords, measureAll, measureRoute } from '../scripts/entry-cost.mjs';
const read = (rel) => readFileSync(new URL(`../${rel}`, import.meta.url), 'utf8');
test('#634 entry-cost: вход автора и ревьюера в бюджете (AC1)', () => {
const results = measureAll();
assert.equal(ROUTES.author.budget, 12000, 'бюджет автора — AC1 #634');
for (const [name, result] of Object.entries(results)) {
if (result.budget == null) continue;
assert.equal(result.over, false, `${name}: ${result.total} слов > бюджета ${result.budget}`);
assert.ok(result.total <= result.budget);
}
// Маршрут автора не содержит полного канона — иначе бюджет держится случайно.
assert.ok(!ROUTES.author.files.includes('PROCESS.md'));
assert.ok(ROUTES.author.files.includes('docs/process/AUTHOR.md'));
assert.ok(ROUTES.reviewer.files.includes('docs/process/REVIEWER.md'));
});
test('#634 entry-cost: превышение бюджета краснит замер', () => {
const files = { 'a.md': 'one two three', 'b.md': 'четыре пять\nшесть\t семь' };
const reader = (rel) => files[rel];
assert.equal(countWords(files['b.md']), 4);
const within = measureRoute({ budget: 7, files: ['a.md', 'b.md'] }, reader);
assert.deepEqual([within.total, within.over], [7, false]);
const over = measureRoute({ budget: 6, files: ['a.md', 'b.md'] }, reader);
assert.deepEqual([over.total, over.over], [7, true]);
assert.equal(measureRoute({ budget: null, files: ['a.md'] }, reader).over, false);
});
test('#634 entry-cost: AGENTS.md называет те же маршруты в том же порядке', () => {
const agents = read('AGENTS.md');
const section = agents.slice(agents.indexOf('**Reading order by role**'), agents.indexOf('The two digests'));
assert.ok(section.length > 0, 'раздел порядка чтения в AGENTS.md');
const route = (prefix) => {
const item = section.split('\n- ').find((chunk) => chunk.startsWith(prefix));
assert.ok(item, `AGENTS.md: нет пункта «${prefix}»`);
return [...item.matchAll(/`([^`]+\.md)`/g)].map((match) => match[1]);
};
assert.deepEqual(route('author'), ROUTES.author.files);
assert.deepEqual(route('reviewer'), ROUTES.reviewer.files);
assert.deepEqual(route('changing the pipeline'), ROUTES.canon.files);
});
test('#701 D14: промпт ревьюера читает маршрут reviewer в том же порядке, что AGENTS.md', async () => {
const { readFileSync } = await import('node:fs');
const { ROUTES } = await import('../scripts/entry-cost.mjs');
const workflow = readFileSync(new URL('../.github/workflows/_process.yml', import.meta.url), 'utf8');
const start = workflow.indexOf('Прочитай в этом порядке, прежде чем судить:');
assert.ok(start > 0, 'нумерованный порядок чтения в промпте найден');
const items = [...workflow.slice(start, start + 3000).matchAll(/^\s+(\d)\. (\S+)/gm)].slice(0, 3).map((m) => m[2]);
assert.deepEqual(items, ROUTES.reviewer.files);
});