Files
houseplan-card/test/entry-cost.test.mjs
Claudeandclaude[bot] 7dc7597260 docs(process): ролевые конспекты, замер входа, Snapshot генерируется, TESTING.md разделён
Вход агента до первого файла кода стоил ≈ 26 700 слов (аудит 22.09).

- docs/process/AUTHOR.md и REVIEWER.md — выжимки PROCESS.md: каждый пункт
  ссылается на раздел канона, ключевые формулировки дословные;
  test/process-digests.test.mjs сверяет якоря, ссылки и правила.
- scripts/entry-cost.mjs — маршрут чтения по роли и бюджет (автор ≤ 12 000
  слов, AC1); AGENTS.md «Read this first» называет те же маршруты.
- docs/STATUS.md: блок Snapshot генерирует scripts/status-snapshot.mjs
  (версии — release-contract, счётчики — inventory, теги — git); feature
  surface и ранние milestones перенесены дословно в docs/STATUS-FEATURES.md.
- docs/TESTING.md — действующая инструкция (684 строки, AC3); ручные
  чек-листы и приложения по issue перенесены дословно в docs/testing-notes/
  с индексом и тестом на полноту.
- Промпт ревьюера в process.yml читает конспект вместо пересказа правил;
  машинные требования (строка вердикта, REVIEW_DOC, запрет fetch, таблица
  «чем краснеет», разделы повторного раунда) сохранены и закреплены тестом.
- PROCESS.md: правила не менялись; добавлены ссылка на конспекты в шапке и
  уточнение в §10.4, что ревьюер конвейера читает конспект.
- 7 мутантов в реестре.

Issue: #634
User-Visible: no
2026-09-24 02:33:08 +00:00

47 lines
2.6 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);
});