Files
houseplan-card/test/testing-notes-index.test.mjs
T
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

44 lines
2.9 KiB
JavaScript

// #634: docs/TESTING.md — действующая инструкция (AC3: ≤ 800 строк), приложения
// по issue и ручные чек-листы — в docs/testing-notes/, доступны по индексу.
import assert from 'node:assert/strict';
import { existsSync, readFileSync, readdirSync } from 'node:fs';
import { join } from 'node:path';
import test from 'node:test';
import { fileURLToPath } from 'node:url';
import { headings, markdownLinks } from '../scripts/md-anchors.mjs';
const ROOT = fileURLToPath(new URL('..', import.meta.url));
const NOTES = 'docs/testing-notes';
const read = (rel) => readFileSync(join(ROOT, rel), 'utf8').replace(/\r\n?/g, '\n');
const noteFiles = () => readdirSync(join(ROOT, NOTES)).filter((name) => name.endsWith('.md') && name !== 'README.md').sort();
test('#634 TESTING.md: инструкция не длиннее 800 строк и ведёт к индексу приложений (AC3)', () => {
const testing = read('docs/TESTING.md');
const lines = testing.split('\n').length - (testing.endsWith('\n') ? 1 : 0);
assert.ok(lines <= 800, `docs/TESTING.md: ${lines} строк > 800 — приложение по issue кладётся в ${NOTES}/`);
assert.ok(markdownLinks(testing).some((link) => link.file === 'testing-notes/README.md'), 'ссылка на индекс');
// Правила для новых тестов остаются в инструкции — их читают все.
assert.match(testing, /^## Правила для новых тестов \(issue #85\) — обязательны$/m);
});
test('#634 индекс приложений: каждый раздел каждого приложения — строкой, каждая ссылка жива', () => {
const index = read(`${NOTES}/README.md`);
const links = markdownLinks(index);
const listed = new Set(links.map((link) => `${link.file}#${link.anchor}`));
const files = noteFiles();
assert.ok(files.length >= 5, 'приложения на месте');
for (const file of files) {
assert.ok(links.some((link) => link.file === file && !link.anchor), `${file}: нет заголовка группы в индексе`);
for (const heading of headings(read(`${NOTES}/${file}`)).filter((h) => h.level >= 2)) {
assert.ok(listed.has(`${file}#${heading.anchor}`), `${file}: раздел «${heading.text}» не в индексе`);
}
}
for (const link of links) {
if (link.file.startsWith('../')) { assert.ok(existsSync(join(ROOT, 'docs', link.file.slice(3)))); continue; }
assert.ok(files.includes(link.file), `индекс ссылается на отсутствующий файл ${link.file}`);
if (!link.anchor) continue;
const anchors = new Set(headings(read(`${NOTES}/${link.file}`)).map((heading) => heading.anchor));
assert.ok(anchors.has(link.anchor), `индекс: нет заголовка ${link.file}#${link.anchor}`);
}
});