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
This commit is contained in:
Claude
2026-09-29 00:30:04 +03:00
parent 224d0106fd
commit 8dcc1cad4e
21 changed files with 232 additions and 111 deletions
+16 -3
View File
@@ -4,6 +4,7 @@ import { basename } from 'node:path';
import { readFileSync } from 'node:fs';
import { fileURLToPath } from 'node:url';
import { bundleCommitErrors } from './bundle-policy.mjs';
import { classify } from './change-classes.mjs';
const TRAILER = /^([A-Za-z][A-Za-z0-9-]*):\s*(.*?)\s*$/;
export const ENFORCEMENT_BOUNDARY = '8e2973fa7a7cb1a80204ff95ecf3f2d7c36ed2ce';
@@ -52,15 +53,27 @@ export function terminalTrailers(message) {
return out;
}
/**
* #701 (PROCESS.md §3 п.10): документационный коммит — только файлы класса C —
* трейлеров не требует, как `skip issue` у CPython. Правило №1 охраняет
* продуктовый код, а не опечатку в гайде. Пустой список файлов (сообщение без
* `--staged`) — не документационный коммит: судить нечем, правило прежнее.
* Трейлеры, если они есть, судятся всегда.
*/
export function isDocsOnlyCommit(changedFiles = []) {
return changedFiles.length > 0 && changedFiles.every((file) => classify(file.replaceAll('\\', '/')) === 'C');
}
export function validateCommitMessage(message, changedFiles = [], { baselineIndex = undefined, authorDate = null } = {}) {
const trailers = terminalTrailers(message);
const errors = [];
const issues = trailers.get('Issue') || [];
if (!issues.length || issues.some((value) => !/^#[1-9][0-9]*$/.test(value))) {
const visible = trailers.get('User-Visible') || [];
const exempt = isDocsOnlyCommit(changedFiles) && !issues.length && !visible.length;
if (!exempt && (!issues.length || issues.some((value) => !/^#[1-9][0-9]*$/.test(value)))) {
errors.push("missing or invalid terminal 'Issue: #<positive number>' trailer");
}
const visible = trailers.get('User-Visible') || [];
if (visible.length !== 1 || !/^(yes|no)$/.test(visible[0])) {
if (!exempt && (visible.length !== 1 || !/^(yes|no)$/.test(visible[0]))) {
errors.push("expected exactly one terminal 'User-Visible: yes|no' trailer");
}
const normalizedFiles = changedFiles.map((file) => file.replaceAll('\\', '/'));