From 368d6bb2786ca2fefaf9addfdfa1a6fa62341a76 Mon Sep 17 00:00:00 2001 From: Nick Shirokov Date: Sat, 5 Sep 2026 17:46:30 +0300 Subject: [PATCH] =?UTF-8?q?test(check-inline-drift):=20=D0=B3=D0=B0=D1=80?= =?UTF-8?q?=D0=B4=20=D0=B2=D0=B8=D0=B4=D0=B8=D1=82=20=D0=BA=D0=BE=D0=BF?= =?UTF-8?q?=D0=B8=D0=B8=20=D0=B2=D0=BE=20=D0=B2=D1=81=D0=B5=D1=85=20=D1=81?= =?UTF-8?q?=D0=BA=D1=80=D0=B8=D0=BF=D1=82=D0=B0=D1=85=20=D0=BD=D0=B0=D0=B2?= =?UTF-8?q?=D1=8B=D0=BA=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Индекс брал первый файл навыка по алфавиту, поэтому второй скрипт был для гарда невидим. Единственный такой навык — epf-build (epf-build + stub-db-create), и в невидимой копии прожили три расхождения с эталонами, включая потерянную POSIX-ветку run_v8. Теперь сверяются все копии, в сообщении указывается файл. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_013MQkqXxErBepYmoUycfkbn --- tests/skills/check-inline-drift.mjs | 47 +++++++++++++++++++---------- 1 file changed, 31 insertions(+), 16 deletions(-) diff --git a/tests/skills/check-inline-drift.mjs b/tests/skills/check-inline-drift.mjs index cc4d5f97a..9716295b5 100644 --- a/tests/skills/check-inline-drift.mjs +++ b/tests/skills/check-inline-drift.mjs @@ -281,9 +281,7 @@ const FAMILIES = [ { id: 'base', authority: 'db-create', consumers: ['db-cfe-admin', 'db-dump-cf', 'db-dump-dt', 'db-dump-xml', 'db-load-cf', 'db-load-dt', 'db-load-git', 'db-load-xml', 'db-repo', 'db-run', 'db-update', 'epf-build', 'epf-dump'] }, - // NB: stub-db-create.py внутри epf-build держит свою копию с выводом в stderr, но гарду она - // не видна: индекс берёт первый файл навыка по алфавиту (epf-build.py). Правя эту семью, - // не забывать про стаб — автоматически он не проверяется. + // В epf-build два скрипта, и копия семьи есть в каждом — гард сверяет обе. ], }, { @@ -636,8 +634,12 @@ function hashBody(body, lang) { // ─── Индекс: навык+порт → функции ─────────────────────────────────────────── +// Навык может держать несколько скриптов (epf-build: epf-build + stub-db-create), и копия +// семьи живёт в каждом из них. Раньше индекс брал ПЕРВУЮ копию по алфавиту, и вторая +// была гарду невидима: в stub-db-create.py так прожили три расхождения, одно из них — +// потерянная POSIX-ветка run_v8. Теперь сверяются ВСЕ копии. function buildIndex() { - const index = new Map(); // `${skill}|${lang}` -> Map(name -> body) + const index = new Map(); // `${skill}|${lang}` -> Map(name -> [{ file, body }]) for (const skill of readdirSync(SKILLS)) { const dir = join(SKILLS, skill, 'scripts'); if (!existsSync(dir)) continue; @@ -648,13 +650,18 @@ function buildIndex() { const fns = lang === 'py' ? extractPy(text) : extractPs1(text); const key = `${skill}|${lang}`; const acc = index.get(key) || new Map(); - for (const [n, b] of fns) if (!acc.has(n)) acc.set(n, b); + for (const [n, b] of fns) acc.set(n, [...(acc.get(n) || []), { file, body: b }]); index.set(key, acc); } } return index; } +// Все копии функции в навыке: [{ file, body }] +function copiesOf(index, skill, lang, name) { + return index.get(`${skill}|${lang}`)?.get(name) || []; +} + // ─── Проверка ─────────────────────────────────────────────────────────────── const index = buildIndex(); @@ -687,21 +694,27 @@ for (const family of FAMILIES) { // 1. Каждый объявленный потребитель содержит функцию, и внутри варианта тела совпадают. const hashByVariant = new Map(); for (const { v, members } of effective) { - const hashes = new Map(); + const hashes = new Map(); // `${skill}` или `${skill} (${file})` -> хеш for (const skill of members) { - const body = index.get(`${skill}|${lang}`)?.get(fnName); - if (!body) { + const copies = copiesOf(index, skill, lang, fnName); + if (!copies.length) { errors.push(`${family.name} [${lang}]: ${skill} объявлен в варианте '${v.id}', но функции ${fnName} в нём нет`); continue; } - hashes.set(skill, hashBody(body, lang)); + for (const { file, body } of copies) { + // Эталон берётся из главного скрипта навыка (`<навык>.ps1`/`.py`), остальные + // его скрипты — такие же копии и сверяются наравне. + const main = file.startsWith(`${skill}.`); + const label = main ? skill : `${skill} (${file})`; + hashes.set(label, { hash: hashBody(body, lang), main, skill }); + } } - const authorityHash = hashes.get(v.authority); + const authorityHash = hashes.get(v.authority)?.hash; if (authorityHash === undefined) continue; hashByVariant.set(v.id, authorityHash); - for (const [skill, h] of hashes) { - if (h !== authorityHash) { - errors.push(`${family.name} [${lang}]: ${skill} разошёлся с эталоном ${v.authority} (вариант '${v.id}')`); + for (const [label, { hash }] of hashes) { + if (hash !== authorityHash) { + errors.push(`${family.name} [${lang}]: ${label} разошёлся с эталоном ${v.authority} (вариант '${v.id}')`); } } } @@ -746,9 +759,11 @@ for (const family of DRIFTED) { for (const [key, fns] of index) { const [skill, l] = key.split('|'); if (l !== lang || !fns.has(fnName)) continue; - const h = hashBody(fns.get(fnName), lang); - if (!groups.has(h)) groups.set(h, []); - groups.get(h).push(skill); + for (const { file, body } of fns.get(fnName)) { + const h = hashBody(body, lang); + if (!groups.has(h)) groups.set(h, []); + groups.get(h).push(file.startsWith(`${skill}.`) ? skill : `${skill} (${file})`); + } } if (!groups.size) continue; const limit = family.maxVariants[lang];