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

83 lines
4.5 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env node
// #634: стоимость входа агента — сколько слов он читает до первого файла кода.
//
// Аудит 22.09 намерил ≈ 26 700 слов обязательного входа (SCOPE, AGENTS, полный
// PROCESS, STATUS) — 43–77 k токенов до первой строки кода. Ролевые конспекты
// `docs/process/AUTHOR.md` и `REVIEWER.md` заменяют на входе полный канон, а
// этот скрипт держит цену входа измеримой: маршрут по роли — один список здесь,
// `AGENTS.md` («Read this first») называет те же файлы в том же порядке, и тест
// `test/entry-cost.test.mjs` сверяет оба и краснеет на превышении бюджета.
//
// node scripts/entry-cost.mjs таблица по всем маршрутам
// node scripts/entry-cost.mjs --check exit 1, если маршрут с бюджетом его превысил
// node scripts/entry-cost.mjs --json
//
// Слово — как у `wc -w`: непустой отрезок между пробельными символами. Токены
// не считаются: их число зависит от токенизатора, а слова — нет.
// Вне замера сознательно: `CODEX-RUNBOOK.md` и `CLAUDE.md` папки владельца —
// они не в репозитории, и CI их не видит; тело issue — своё у каждой задачи.
import { readFileSync } from 'node:fs';
import { join } from 'node:path';
import { fileURLToPath } from 'node:url';
import { isMainModule } from './spawn-portable.mjs';
const ROOT = fileURLToPath(new URL('..', import.meta.url));
/** Маршруты входа по роли. `budget: null` — только замер, без порога. */
export const ROUTES = Object.freeze({
// #701: `docs/STATUS.md` ушёл из входа автора — снимок версий и цикла нужен,
// когда сессия возобновляет работу или готовит релиз, а задачу ведёт её пакет
// (`task-packet.mjs`). Минус 691 слово на каждом входе.
author: {
budget: 12000, // AC1 #634
files: ['docs/SCOPE.md', 'AGENTS.md', 'docs/process/AUTHOR.md'],
},
reviewer: {
budget: 9000,
files: ['docs/SCOPE.md', 'AGENTS.md', 'docs/process/REVIEWER.md'],
},
// Тот, кто правит конвейер, гейты или сам процесс, читает канон целиком.
canon: {
budget: null,
files: ['docs/SCOPE.md', 'AGENTS.md', 'PROCESS.md', 'docs/STATUS.md'],
},
});
export const countWords = (text) => text.split(/\s+/).filter(Boolean).length;
/** Замер одного маршрута: слова по файлам, сумма и вердикт бюджета. */
export function measureRoute(route, read = (rel) => readFileSync(join(ROOT, rel), 'utf8')) {
const files = route.files.map((file) => ({ file, words: countWords(read(file)) }));
const total = files.reduce((sum, row) => sum + row.words, 0);
const over = route.budget != null && total > route.budget;
return { files, total, budget: route.budget, over };
}
export function measureAll(routes = ROUTES, read) {
return Object.fromEntries(Object.entries(routes).map(([name, route]) => [name, measureRoute(route, read)]));
}
export function renderTable(results) {
const lines = [];
for (const [name, result] of Object.entries(results)) {
const limit = result.budget == null ? 'без бюджета' : `бюджет ${result.budget}`;
const mark = result.over ? ' ПРЕВЫШЕН' : '';
lines.push(`${name}: ${result.total} слов (${limit})${mark}`);
for (const row of result.files) lines.push(` ${String(row.words).padStart(6)} ${row.file}`);
}
return lines.join('\n');
}
if (isMainModule(import.meta.url)) {
const results = measureAll();
if (process.argv.includes('--json')) console.log(JSON.stringify(results, null, 2));
else console.log(renderTable(results));
if (process.argv.includes('--check')) {
const failed = Object.entries(results).filter(([, result]) => result.over).map(([name]) => name);
if (failed.length) {
console.error(`entry-cost: превышен бюджет входа: ${failed.join(', ')}`);
process.exit(1);
}
}
}