mirror of
https://github.com/Matysh/houseplan-card
synced 2026-09-29 03:09:36 +00:00
docs(hygiene): сократить вход агента, у правила — один дом (#680)
Волна 3 эпика #674. AGENTS.md 650 → 187 строк: карта пакета, маршрут чтения, правило №1, классы и треки одной строкой со ссылками, трейлеры, рабочие деревья, хендофф и ожидание вердикта; пересказы PROCESS.md — ссылками на разделы. Неверный список «Gate jobs» снят (списки jobs не копируются в прозу, шапка PROCESS.md). Правила, жившие только в AGENTS, получили дом: жёлтый вердикт при выполненных AC — PROCESS §2.7; свежесть бандла, съёмка только в Linux (#455, HP_ALLOW_FOREIGN_CAPTURE) и смоки из AC до S7 (#151) — TESTING.md; причуда демо-стенда и среда-зависимый smoke_opening_measure — DEVELOPMENT › Smoke tests; отказ публикации без `Release:` и при несвежем отпечатке бандла, отмена Validate новым пушем, кандидат беты не promotion-only, fail-closed реестра Labs — DEVELOPMENT; предупреждение и ошибка свежести скриншотов — CONTRIBUTING. PROCESS.md: §13 (внедрение с открытым ⏳), §14 (блок со ссылкой на несуществующий docs/PROCESS.md) и §7.3 (история) удалены. Ссылки «§7.2» на правило полного разбора после ребейза ведут в §2.10, на сверку SHA перед выводом — в §2.7; то же в сообщениях scripts/branch-state.mjs, merge-candidate.mjs, review-doc-guard.mjs, pre-push-gate.mjs, в промпте _process.yml и TESTING.md. Число `any` в прозе → `node scripts/no-new-any.mjs --total` (новый режим, юнит-тест; было «1034 в 49 файлах», сейчас 862 в 52), дата-число замороженного списка якорей монолита снято. Устаревшая команда пересъёмки скриншотов в §8 заменена ссылкой на действующий путь. STATUS.md 113 → 61 строка: сгенерированный снимок, текущий цикл и девять строк решений; Workflow, CI, Toolchain, Tests, Scope, open items и политика документации — ссылками (PROCESS §2.6, DEVELOPMENT › Release, TESTING); локали en/ru/de/fr; закрытые «coverage, mypy strict» сняты. DEVELOPMENT.md: file-sync и «Reproducible scripts» (прототип) удалены; раздел Release — единственный дом релизной механики: введение, правила тела стабильного релиза (#328, release:notes), шаг continuity:screencast, источники версии по release-contract. CONTRIBUTING: ссылка на Release вместо пересказа, замеры клона без чисел. TESTING: any-гейт — ссылкой на PROCESS §8. entry-cost: автор 11 125 → 5 407 слов, ревьюер 8 464 → 4 285. Issue: #680 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:
@@ -21,7 +21,7 @@ export function rebaseAdvice({ behind, base = 'origin/dev' }) {
|
||||
const commits = count === 1 ? 'коммит' : count < 5 ? 'коммита' : 'коммитов';
|
||||
return `ветка отстала от ${base} на ${count} ${commits}.`
|
||||
+ ` Конвейер приведёт её сам перед ревью, но после ребейза разбор станет`
|
||||
+ ` полным, а не по дельте (§7.2). Дешевле сделать это здесь:`
|
||||
+ ` полным, а не по дельте (§2.10). Дешевле сделать это здесь:`
|
||||
+ ` git fetch origin && git rebase ${base}`;
|
||||
}
|
||||
|
||||
@@ -46,5 +46,5 @@ export function devMovedNote({ moved, sha }) {
|
||||
const at = sha ? ` Материал ревью — \`${sha}\`.` : '';
|
||||
return `Пока шло ревью, \`dev\` продвинулся на ${count} ${commits}.${at}`
|
||||
+ ' Вердикт вынесен по дереву, которое уже не совпадает с вершиной линии:'
|
||||
+ ' слияние приведёт ветку к dev, и это другой код (§7.2).';
|
||||
+ ' слияние приведёт ветку к dev, и это другой код (§2.10).';
|
||||
}
|
||||
|
||||
@@ -82,7 +82,7 @@ export function commentFor(action, ctx) {
|
||||
+ `1. \`git fetch origin\`, затем \`git rebase origin/dev\` в ветке задачи, разрешить конфликт;\n2. запушить ветку;\n3. вернуть метку \`S7-code-review\`.\n\n`
|
||||
+ `Повторный прогон ревью — не формальность: после ребейза на новый \`dev\` это другой код, и принимать его без проверки нельзя. Цикл считается по этапу, лимит на код-ревью тратится отдельно от ревью ТЗ.`;
|
||||
case 'rereview':
|
||||
return `**Дифф изменился при ребейзе на \`dev@${short(ctx.devNow)}\` — вердикт к нему не применим (§7.2, #492).**\n\n`
|
||||
return `**Дифф изменился при ребейзе на \`dev@${short(ctx.devNow)}\` — вердикт к нему не применим (§2.10, #492).**\n\n`
|
||||
+ `Материал ревью \`${short(ctx.material)}\` и кандидат \`${short(ctx.candidate)}\` дают разные patch-id: соседние правки в \`dev\` изменили содержимое патча. Кандидат опубликован в ветку; задача возвращена в \`S7-code-review\` — новый заход ревью читает актуальный код.`;
|
||||
case 'validation-red':
|
||||
return `**Кандидат после ребейза на \`dev@${short(ctx.devNow)}\` красный (#492).**\n\n`
|
||||
|
||||
+42
-7
@@ -5,11 +5,13 @@
|
||||
* node scripts/no-new-any.mjs # origin/dev...HEAD
|
||||
* node scripts/no-new-any.mjs --base origin/dev --head HEAD
|
||||
* node scripts/no-new-any.mjs --diff patch.diff # или `-` для stdin
|
||||
* node scripts/no-new-any.mjs --total # весь долг src/** (#680)
|
||||
*
|
||||
* Зачем гейт, а не разовая типизация. В `src/**` сейчас 1034 вхождения явного
|
||||
* `any` в 49 файлах — перетипизировать это одним заходом значит месяц риска ради
|
||||
* нуля пользовательской ценности. Долг снимается при плановом извлечении
|
||||
* подсистем (#425, прежний #34). Задача гейта одна: не давать долгу расти.
|
||||
* Зачем гейт, а не разовая типизация. Явного `any` в `src/**` — сотни
|
||||
* вхождений (точное число печатает `--total`); перетипизировать это одним
|
||||
* заходом значит месяц риска ради нуля пользовательской ценности. Долг
|
||||
* снимается при плановом извлечении подсистем (#425, прежний #34). Задача
|
||||
* гейта одна: не давать долгу расти.
|
||||
*
|
||||
* Практический вред уже случался: несоответствие форм (`d.source.kind` против
|
||||
* строкового `source`) компилятор не поймал, потому что путь был через `any`, и
|
||||
@@ -51,8 +53,8 @@
|
||||
* добавление: повторная вставка того же блока остаётся новым кодом.
|
||||
*/
|
||||
import { spawnSync } from 'node:child_process';
|
||||
import { existsSync, readFileSync } from 'node:fs';
|
||||
import { dirname, resolve } from 'node:path';
|
||||
import { existsSync, readFileSync, readdirSync } from 'node:fs';
|
||||
import { dirname, join, relative, resolve } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import ts from 'typescript';
|
||||
|
||||
@@ -103,6 +105,34 @@ export function anyKeywordLines(path, text) {
|
||||
return lines;
|
||||
}
|
||||
|
||||
/**
|
||||
* Весь существующий долг (#680): число узлов `AnyKeyword` и файлов с ними по
|
||||
* продуктовому TypeScript. Документы называют эту команду вместо числа — число в
|
||||
* прозе отставало от дерева с первой недели.
|
||||
*/
|
||||
export function totalAny(files) {
|
||||
let occurrences = 0;
|
||||
let withAny = 0;
|
||||
for (const file of files) {
|
||||
let count = 0;
|
||||
for (const perLine of anyKeywordLines(file.path, file.text).values()) count += perLine;
|
||||
occurrences += count;
|
||||
if (count) withAny += 1;
|
||||
}
|
||||
return { occurrences, files: withAny };
|
||||
}
|
||||
|
||||
function productTypeScriptFiles(dir = join(ROOT, 'src')) {
|
||||
const out = [];
|
||||
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
||||
const full = join(dir, entry.name);
|
||||
if (entry.isDirectory()) { out.push(...productTypeScriptFiles(full)); continue; }
|
||||
const path = relative(ROOT, full).split('\\').join('/');
|
||||
if (isProductTypeScript(path)) out.push({ path, text: readFileSync(full, 'utf8') });
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Нарушения: `any` на строке, которую диапазон объявил добавленной.
|
||||
*
|
||||
@@ -303,6 +333,11 @@ function main(argv) {
|
||||
return index >= 0 && argv[index + 1] && !argv[index + 1].startsWith('--')
|
||||
? argv[index + 1] : fallback;
|
||||
};
|
||||
if (argv.includes('--total')) {
|
||||
const total = totalAny(productTypeScriptFiles());
|
||||
console.log(`Явный any в src/**/*.ts: ${total.occurrences} вхождений в ${total.files} файл(ах).`);
|
||||
return 0;
|
||||
}
|
||||
const diffArg = value('diff');
|
||||
let diff;
|
||||
if (diffArg) {
|
||||
@@ -352,7 +387,7 @@ function main(argv) {
|
||||
console.error('\nЛибо типизируйте, либо обоснуйте на той же строке:');
|
||||
console.error(' // any-ok: <конкретная причина, почему тип недоступен>');
|
||||
console.error('Существующий долг снимается при извлечении подсистем (#34, #342),');
|
||||
console.error('а не разовой заменой: в src/** его 1034 вхождения в 49 файлах.');
|
||||
console.error('а не разовой заменой; сколько его — node scripts/no-new-any.mjs --total.');
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
@@ -223,7 +223,7 @@ function manualGate(argv) {
|
||||
|
||||
// ---- приведена ли ветка к dev ---------------------------------------------
|
||||
// Конвейер ребейзит сам (#257), но после ребейза разбор становится полным, а не
|
||||
// по дельте (§7.2), и конфликт всплывает в комментарии через сорок минут вместо
|
||||
// по дельте (§2.10), и конфликт всплывает в комментарии через сорок минут вместо
|
||||
// машины автора. Поэтому предупреждение, а не гейт: гейтом остаётся конвейер.
|
||||
if (!flag('no-rebase-check')) {
|
||||
const fetched = capture('git', ['fetch', '-q', 'origin', base.replace(/^origin\//, '')]);
|
||||
|
||||
@@ -17,8 +17,8 @@
|
||||
// Решения, принятые при реализации. Продуктового поведения не касаются, поэтому
|
||||
// приняты здесь, а не у владельца; ревьюер вправе оспорить любое.
|
||||
//
|
||||
// 1. «Release vX.Y.Z-beta.N candidate» — НЕ релизный коммит. Promotion-only по
|
||||
// AGENTS.md — это стабильный релиз; кандидат беты несёт саму работу и живёт
|
||||
// 1. «Release vX.Y.Z-beta.N candidate» — НЕ релизный коммит. Promotion-only
|
||||
// (`PROCESS.md` §3 п.16, `docs/DEVELOPMENT.md` › Release) — это стабильный релиз; кандидат беты несёт саму работу и живёт
|
||||
// по общим правилам, включая трейлер Issue.
|
||||
// 2. Классы дополнены: package.json, package-lock.json, pytest.ini, .gitignore,
|
||||
// .gitattributes, .githooks/** — класс B (конфигурация сборки и гейтов).
|
||||
|
||||
@@ -129,7 +129,7 @@ export function materialAnchorsFrom(text) {
|
||||
* issue. Коммита с таким именем в репозитории нет и не было: клон не мелкий,
|
||||
* `git rev-list --all` его не знает. Скорее всего значение снято до `amend`
|
||||
* или `rebase` при публикации — то есть проверка `git rev-parse HEAD` перед
|
||||
* выводом отчёта, которую требует §7.2, не выполнялась ни у автора, ни у
|
||||
* выводом отчёта, которую требует §2.7, не выполнялась ни у автора, ни у
|
||||
* ревьюера.
|
||||
*
|
||||
* Цена уже заплачена на следующем раунде: пункт «найти SHA, на котором получен
|
||||
@@ -182,7 +182,7 @@ export function danglingMaterialRefusal(
|
||||
+ 'Команда `git diff <sha>..HEAD` из PROCESS.md §2.10 на таком отчёте не'
|
||||
+ ' работает, а следующий раунд восстанавливает коммит по содержимому'
|
||||
+ ' диффа руками (#413). Сверьте SHA командой `git rev-parse HEAD`'
|
||||
+ ' непосредственно перед выводом отчёта — §7.2 требует именно этого,'
|
||||
+ ' непосредственно перед выводом отчёта — §2.7 требует именно этого,'
|
||||
+ ' а не значения, записанного до amend или rebase.';
|
||||
}
|
||||
|
||||
@@ -531,7 +531,7 @@ export function anchorVerdictFrom(text) {
|
||||
* - текущее дерево отличается от якоря НИЧЕМ, кроме docs/reviews/** —
|
||||
* сравнение делает git по содержимому, так что ребейз на ушедший dev,
|
||||
* правка теста, фикстуры, скрипта или ТЗ в docs/specs дают отличие и
|
||||
* полный разбор (§7.2). Смена базы без изменения дерева невозможна:
|
||||
* полный разбор (§2.10). Смена базы без изменения дерева невозможна:
|
||||
* дерево ветки после ребейза включает содержимое dev.
|
||||
* Любое сомнение — `null`, и ревью идёт как обычно.
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user