mirror of
https://github.com/Matysh/houseplan-card
synced 2026-09-28 19:01:34 +00:00
Ревью #682 r1, Medium: перенос добавляет документу уровень вложенности (`docs/reviews/X.md` → `legacy/reviews/<тег>/X.md`, `docs/specs/` → `legacy/specs/`), а относительные ссылки внутри перенесённых документов и в соседях, ссылавшихся на них, никто не пересчитывал — на `97d19268` 53 битые ссылки в 46 файлах (заявление «все 26 резолвятся» в `7feb6177` было верно только до переноса документов ревью). Гейты архив не смотрят. `reviews-archive.mjs`: `repairLinks` пересчитывает ссылку, если она не резолвится от нового места, а цель находится от нового или старого места через карту переносов; битая и до переноса ссылка не трогается. `--apply` делает это само, `--repair-links=<rev>` — для всех переименований `<rev>..HEAD`, `--check-links` печатает битые. Этим коммитом `--repair-links=origin/dev` переписал ровно 53 ссылки в 46 файлах; остались две прежние «...»-заглушки в CODE-REVIEW-448-r2 (битые и на dev). Тесты: перенесённый документ, сосед со ссылкой в архив, ТЗ со ссылкой на позже перенесённое ревью, битая-до-переноса не трогается, в `legacy/` битых нет; мутант `reviews-archive-links-from-new-place-only`. PROCESS §2.10 и DEVELOPMENT › Release называют переписывание и `--check-links`. Issue: #682 User-Visible: no Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018qZfe7YS4rqEMKoVeS3GKd
121 lines
7.9 KiB
JavaScript
121 lines
7.9 KiB
JavaScript
// #682: архив документов ревью выпущенных линий — кому куда, решают трейлеры.
|
|
import assert from 'node:assert/strict';
|
|
import test from 'node:test';
|
|
import { ARCHIVE_DIR, LIVE_DIR, archivePlan, brokenLinks, renderPlan, repairLinks, stableTagsThrough } from '../scripts/reviews-archive.mjs';
|
|
|
|
const lines = [
|
|
{ tag: 'v1.76.0', issues: [500, 510, 520] },
|
|
{ tag: 'v1.77.0', issues: [520, 530, 540] },
|
|
];
|
|
const names = [
|
|
'INDEX.md',
|
|
'SPEC-REVIEW-500-r1.md', 'CODE-REVIEW-500-r1.md', 'CODE-REVIEW-500-r2.md',
|
|
'SPEC-REVIEW-520-r1.md', 'CODE-REVIEW-520-r1.md',
|
|
'CODE-REVIEW-540-r1.md', 'CODE-REVIEW-540-r2.md',
|
|
'CODE-REVIEW-600-r1.md',
|
|
'CODE-REVIEW-issue-068-2026-08-12.md',
|
|
'RELEASE-REVIEW-v1.77.0.md', 'RELEASE-REVIEW-v1.78.0.md',
|
|
'notes.md',
|
|
];
|
|
const where = (plan, name) => plan.moves.find((m) => m.name === name)?.to
|
|
?? plan.kept.find((k) => k.name === name)?.reason;
|
|
|
|
test('#682 архив: задача уходит в каталог последней своей линии, документы не разъезжаются', () => {
|
|
const plan = archivePlan({ names, lines, open: [600], through: 'v1.77.0' });
|
|
assert.equal(where(plan, 'CODE-REVIEW-500-r2.md'), `${ARCHIVE_DIR}/v1.76.0/CODE-REVIEW-500-r2.md`);
|
|
// #520 — трейлеры в обеих линиях: все раунды в последней.
|
|
assert.equal(where(plan, 'SPEC-REVIEW-520-r1.md'), `${ARCHIVE_DIR}/v1.77.0/SPEC-REVIEW-520-r1.md`);
|
|
assert.equal(where(plan, 'CODE-REVIEW-520-r1.md'), `${ARCHIVE_DIR}/v1.77.0/CODE-REVIEW-520-r1.md`);
|
|
assert.equal(where(plan, 'RELEASE-REVIEW-v1.77.0.md'), `${ARCHIVE_DIR}/v1.77.0/RELEASE-REVIEW-v1.77.0.md`);
|
|
assert.ok(plan.moves.every((m) => m.from === `${LIVE_DIR}/${m.name}`));
|
|
assert.ok(!plan.moves.some((m) => m.name === 'INDEX.md') && !plan.kept.some((k) => k.name === 'INDEX.md'));
|
|
});
|
|
|
|
test('#682 архив: задача из открытой линии остаётся целиком — её раунды ссылаются на прошлые', () => {
|
|
// #540 выпущена в v1.77.0, но у неё есть трейлер и после тега.
|
|
const plan = archivePlan({ names, lines, open: [540, 600], through: 'v1.77.0' });
|
|
assert.equal(where(plan, 'CODE-REVIEW-540-r1.md'), 'задача есть в открытой линии');
|
|
assert.equal(where(plan, 'CODE-REVIEW-540-r2.md'), 'задача есть в открытой линии');
|
|
assert.equal(where(plan, 'CODE-REVIEW-600-r1.md'), 'задача есть в открытой линии');
|
|
assert.equal(where(plan, 'RELEASE-REVIEW-v1.78.0.md'), 'ревью линии v1.78.0 не входит в архивируемые линии');
|
|
});
|
|
|
|
test('#682 архив: без трейлера — по линии добавления документа, иначе остаётся; чужое имя не трогается', () => {
|
|
const bare = archivePlan({ names, lines, open: [600], through: 'v1.77.0' });
|
|
assert.equal(where(bare, 'CODE-REVIEW-issue-068-2026-08-12.md'), 'нет трейлера ни в одной линии');
|
|
assert.equal(where(bare, 'notes.md'), 'вне схемы имён');
|
|
const resolved = archivePlan({
|
|
names, lines, open: [600], through: 'v1.77.0',
|
|
addedIn: new Map([['CODE-REVIEW-issue-068-2026-08-12.md', 'v1.76.0'], ['notes.md', 'v1.76.0']]),
|
|
});
|
|
assert.equal(where(resolved, 'CODE-REVIEW-issue-068-2026-08-12.md'), `${ARCHIVE_DIR}/v1.76.0/CODE-REVIEW-issue-068-2026-08-12.md`);
|
|
assert.equal(where(resolved, 'notes.md'), 'вне схемы имён');
|
|
// Тег добавления вне архивируемых линий (бета, будущая линия) — не повод переносить.
|
|
const future = archivePlan({ names, lines, open: [600], through: 'v1.77.0', addedIn: new Map([['CODE-REVIEW-issue-068-2026-08-12.md', 'v1.78.0']]) });
|
|
assert.equal(where(future, 'CODE-REVIEW-issue-068-2026-08-12.md'), 'нет трейлера ни в одной линии');
|
|
const report = renderPlan({ ...bare, through: 'v1.77.0' });
|
|
assert.match(report, /переносится 8 \(задач 3\), остаётся 4/);
|
|
assert.match(report, /CODE-REVIEW-issue-068-2026-08-12\.md — нет трейлера/);
|
|
});
|
|
|
|
test('#682 архив: линии — только стабильные теги не новее границы', () => {
|
|
assert.deepEqual(
|
|
stableTagsThrough(['v1.78.0-beta.1', 'v1.77.0', 'v1.76.0', 'v1.10.0', 'v1.9.0', 'v1.78.0', 'x'], 'v1.77.0'),
|
|
['v1.9.0', 'v1.10.0', 'v1.76.0', 'v1.77.0'],
|
|
);
|
|
assert.throws(() => stableTagsThrough([], 'v1.78.0-beta.1'), /not a stable release tag/);
|
|
assert.throws(() => archivePlan({ names: [], lines: [{ tag: 'v1.78.0', issues: [] }], open: [], through: 'v1.77.0' }), /newer than/);
|
|
});
|
|
|
|
// Ревью #682 r1 (Medium): перенос добавляет уровень вложенности, и относительные
|
|
// ссылки внутри перенесённых документов и в соседях, которые на них ссылаются,
|
|
// ломались молча — ни один гейт не смотрит в архив.
|
|
const tree = new Set([
|
|
'docs/specs/089-stage1.md',
|
|
'docs/reviews/CODE-REVIEW-635-r1.md',
|
|
'legacy/reviews/v1.77.0/CODE-REVIEW-594-r1.md',
|
|
'legacy/reviews/v1.77.0/CODE-REVIEW-594-r2.md',
|
|
'legacy/reviews/v1.68.0/SPEC-REVIEW-262-r1.md',
|
|
'legacy/specs/262-readd.md',
|
|
]);
|
|
const exists = (path) => tree.has(path);
|
|
const moved = new Map([
|
|
['docs/reviews/CODE-REVIEW-594-r1.md', 'legacy/reviews/v1.77.0/CODE-REVIEW-594-r1.md'],
|
|
['docs/reviews/CODE-REVIEW-594-r2.md', 'legacy/reviews/v1.77.0/CODE-REVIEW-594-r2.md'],
|
|
['docs/reviews/SPEC-REVIEW-262-r1.md', 'legacy/reviews/v1.68.0/SPEC-REVIEW-262-r1.md'],
|
|
['docs/specs/262-readd.md', 'legacy/specs/262-readd.md'],
|
|
]);
|
|
|
|
test('#682 r1 ссылки: перенесённый документ пересчитывает свои ссылки от нового места', () => {
|
|
const result = repairLinks({
|
|
text: 'ТЗ: [s](../specs/089-stage1.md#ac2); прошлый раунд: [r1](CODE-REVIEW-594-r1.md); сайт: [x](https://example.org/a.md)',
|
|
path: 'legacy/reviews/v1.77.0/CODE-REVIEW-594-r2.md',
|
|
oldPath: 'docs/reviews/CODE-REVIEW-594-r2.md',
|
|
moved, exists,
|
|
});
|
|
assert.equal(result.text, 'ТЗ: [s](../../../docs/specs/089-stage1.md#ac2); прошлый раунд: [r1](CODE-REVIEW-594-r1.md); сайт: [x](https://example.org/a.md)');
|
|
assert.equal(result.fixed, 1);
|
|
});
|
|
|
|
test('#682 r1 ссылки: сосед, ссылавшийся на перенесённый документ, ведёт в архив', () => {
|
|
const live = repairLinks({ text: '[r1](CODE-REVIEW-594-r1.md)', path: 'docs/reviews/CODE-REVIEW-635-r1.md', moved, exists });
|
|
assert.equal(live.text, '[r1](../../legacy/reviews/v1.77.0/CODE-REVIEW-594-r1.md)');
|
|
// Ссылка, уже переписанная прошлым переносом (ТЗ ушло в legacy/specs раньше документа ревью).
|
|
const spec = repairLinks({
|
|
text: '[r](../../docs/reviews/SPEC-REVIEW-262-r1.md)', path: 'legacy/specs/262-readd.md', oldPath: 'docs/specs/262-readd.md', moved, exists,
|
|
});
|
|
assert.equal(spec.text, '[r](../reviews/v1.68.0/SPEC-REVIEW-262-r1.md)');
|
|
});
|
|
|
|
test('#682 r1 ссылки: битая и до переноса ссылка не «чинится» наугад', () => {
|
|
const result = repairLinks({ text: '[x](...), [y](nowhere.md)', path: 'legacy/reviews/v1.72.0/CODE-REVIEW-448-r2.md', oldPath: 'docs/reviews/CODE-REVIEW-448-r2.md', moved, exists });
|
|
assert.equal(result.fixed, 0);
|
|
assert.equal(result.text, '[x](...), [y](nowhere.md)');
|
|
});
|
|
|
|
test('#682 r1 архив legacy/: относительные ссылки резолвятся (кроме известных «...»-заглушек)', () => {
|
|
const broken = brokenLinks({ roots: ['legacy/reviews', 'legacy/specs'] }).filter((item) => item.target !== '...');
|
|
assert.deepEqual(broken, []);
|
|
});
|
|
|