mirror of
https://github.com/Nikolay-Shirokov/cc-1c-skills.git
synced 2026-09-21 09:45:53 +03:00
test: гард check-typeset-coverage — вывод meta-info замыкается на вход meta-compile
Связь «прочитал вывод → подал на вход» держалась на честном слове и рвалась молча в обе стороны: meta-info печатал метки, которых нет в словаре meta-compile (33 из 41), а в карте meta-info не было видов, которые meta-compile эмитит, — и они печатались по-английски посреди русского вывода. Снэпшоты этого не видят: кейсы читают и компилируют по отдельности, круг не замыкая. Инвариант строгий в одну сторону: каждая метка читалки обязана быть ключом в typeSynonyms у meta-compile и вести ровно в тот же канон. Обратное неверно намеренно — компилятор знает формы, которых читалка не печатает. Заодно гард сверяет objectTypeMap и refTypeMap между портами навыка: переводы — часть вывода, и расхождение портов дало бы разный текст на один и тот же файл. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
74e88e75c2
commit
1d8a6f2f58
@@ -108,6 +108,7 @@ node tests/skills/check-inline-drift.mjs --list # реестр: семья →
|
|||||||
| `check-type-synonyms.mjs` | словари типов: по общему ключу meta-edit понимает то же, что meta-compile (авторитет) |
|
| `check-type-synonyms.mjs` | словари типов: по общему ключу meta-edit понимает то же, что meta-compile (авторитет) |
|
||||||
| `check-inline-drift.mjs` | копии общих inline-утилит совпадают с эталонами своих вариантов |
|
| `check-inline-drift.mjs` | копии общих inline-утилит совпадают с эталонами своих вариантов |
|
||||||
| `check-type-maps.mjs` | карты типов метаданных согласованы с таблицей `ChildObjects` из `docs/1c-configuration-spec.md` |
|
| `check-type-maps.mjs` | карты типов метаданных согласованы с таблицей `ChildObjects` из `docs/1c-configuration-spec.md` |
|
||||||
|
| `check-typeset-coverage.mjs` | имена типов: всё, что печатает `meta-info`, принимает `meta-compile` (авторитет); карты не разошлись между портами |
|
||||||
| `check-format-versions.mjs` | проверенный диапазон версий формата: границы одинаковы во всех навыках и сходятся с лестницей из `docs/1c-configuration-spec.md` |
|
| `check-format-versions.mjs` | проверенный диапазон версий формата: границы одинаковы во всех навыках и сходятся с лестницей из `docs/1c-configuration-spec.md` |
|
||||||
| `check-form-purposes.mjs` | таблица назначений форм в `form-add`: состав видов, свойство «основная форма» и оба порта сходятся с `docs/1c-form-spec.md` |
|
| `check-form-purposes.mjs` | таблица назначений форм в `form-add`: состав видов, свойство «основная форма» и оба порта сходятся с `docs/1c-form-spec.md` |
|
||||||
| `check-positional-binding.mjs` | read-only навыки (`*-info` / `*-validate` / `cfe-diff`): позиционным остаётся только путь ко входу, лишний позиционный аргумент не перезаписывает указанный файл |
|
| `check-positional-binding.mjs` | read-only навыки (`*-info` / `*-validate` / `cfe-diff`): позиционным остаётся только путь ко входу, лишний позиционный аргумент не перезаписывает указанный файл |
|
||||||
@@ -122,6 +123,24 @@ node tests/skills/check-inline-drift.mjs --list # реестр: семья →
|
|||||||
|
|
||||||
Правишь общую утилиту — правишь эталон, копируешь во всех потребителей варианта, прогоняешь гард.
|
Правишь общую утилиту — правишь эталон, копируешь во всех потребителей варианта, прогоняешь гард.
|
||||||
|
|
||||||
|
`check-typeset-coverage.mjs` замыкает круг «прочитал вывод → подал на вход». `meta-info` —
|
||||||
|
читающий навык, и модель несёт его строки обратно в `meta-compile`; до гарда эта связь держалась на
|
||||||
|
честном слове и рвалась молча в обе стороны. `meta-info` печатал `ДокументОбъект.Заказ` в источниках
|
||||||
|
подписки, а компилятор отвечал «Неизвестный тип»: русских объектных имён в словаре не было — 33
|
||||||
|
метки из 41. И наоборот, в карте читалки не было `ConstantValueManager` и
|
||||||
|
`ChartOfCalculationTypesObject`, хотя компилятор их эмитит, — типы печатались по-английски посреди
|
||||||
|
русского вывода. Снэпшоты этого не видят: кейсы читают и компилируют по отдельности, круг не
|
||||||
|
замыкая.
|
||||||
|
|
||||||
|
Инвариант строгий в одну сторону: каждая метка, которую умеет печатать `meta-info`, обязана быть
|
||||||
|
ключом в `typeSynonyms` у `meta-compile` и вести ровно в тот же канон. Обратное неверно намеренно —
|
||||||
|
компилятор знает формы, которых читалка не печатает (английские, полные варианты рядом с
|
||||||
|
аббревиатурами). Заодно гард сверяет `objectTypeMap`/`refTypeMap` между портами навыка: значения
|
||||||
|
переводов — часть вывода, и расхождение портов дало бы разный текст на один и тот же файл.
|
||||||
|
|
||||||
|
Добавляешь вид в карту `meta-info` — заводи русскую метку в словарь `meta-compile` (и в копию
|
||||||
|
`meta-edit`, её держит `check-type-synonyms.mjs`).
|
||||||
|
|
||||||
`check-nonascii-fs.mjs` держит `tests/common/fsutil.mjs` — единственное место, где тесты, хуки и
|
`check-nonascii-fs.mjs` держит `tests/common/fsutil.mjs` — единственное место, где тесты, хуки и
|
||||||
движок `web-test` удаляют и копируют файлы. На Windows `fs.rmSync` и `fs.cpSync` **молча** ничего
|
движок `web-test` удаляют и копируют файлы. На Windows `fs.rmSync` и `fs.cpSync` **молча** ничего
|
||||||
не делают, если не-ASCII символы есть в самом аргументе пути ([nodejs/node#61067][nafs]): фикстура
|
не делают, если не-ASCII символы есть в самом аргументе пути ([nodejs/node#61067][nafs]): фикстура
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ const GUARDS = [
|
|||||||
['check-inline-drift.mjs', 'общие inline-реализации: копии совпадают с эталонами'],
|
['check-inline-drift.mjs', 'общие inline-реализации: копии совпадают с эталонами'],
|
||||||
['check-ps-define-before-call.mjs', 'порядок в .ps1: функция объявлена выше точки вызова'],
|
['check-ps-define-before-call.mjs', 'порядок в .ps1: функция объявлена выше точки вызова'],
|
||||||
['check-type-maps.mjs', 'карты типов метаданных: согласованы со спецификацией'],
|
['check-type-maps.mjs', 'карты типов метаданных: согласованы со спецификацией'],
|
||||||
|
['check-typeset-coverage.mjs', 'имена типов: вывод meta-info замыкается на вход meta-compile'],
|
||||||
['check-format-versions.mjs', 'проверенный диапазон версий формата: согласован со спецификацией'],
|
['check-format-versions.mjs', 'проверенный диапазон версий формата: согласован со спецификацией'],
|
||||||
['check-form-purposes.mjs', 'назначения форм в form-add: согласованы со спецификацией и между портами'],
|
['check-form-purposes.mjs', 'назначения форм в form-add: согласованы со спецификацией и между портами'],
|
||||||
['check-positional-binding.mjs', 'read-only навыки: позиционным остаётся только путь ко входу'],
|
['check-positional-binding.mjs', 'read-only навыки: позиционным остаётся только путь ко входу'],
|
||||||
|
|||||||
@@ -0,0 +1,112 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
// Раундтрип имён типов: всё, что ПЕЧАТАЕТ meta-info, обязан ПРИНИМАТЬ meta-compile (АВТОРИТЕТ).
|
||||||
|
//
|
||||||
|
// Зачем гард. meta-info — читающий навык, и его вывод модель подаёт обратно на вход компилятора.
|
||||||
|
// До этой проверки связь держалась на честном слове и рвалась молча в обе стороны:
|
||||||
|
// * meta-info печатал «ДокументОбъект.Заказ» в источниках подписки, а meta-compile отвечал
|
||||||
|
// «Неизвестный тип» — не были заведены синонимы русских объектных имён (33 метки из 41);
|
||||||
|
// * в карте meta-info не было ConstantValueManager и ChartOfCalculationTypesObject, хотя
|
||||||
|
// meta-compile их эмитит, — и типы печатались по-английски посреди русского вывода.
|
||||||
|
// Ни один снэпшот этого не видел: кейсы читают и компилируют по отдельности, круг не замыкая.
|
||||||
|
//
|
||||||
|
// Инвариант СТРОГИЙ в одну сторону: для каждой метки, которую умеет печатать meta-info, в словаре
|
||||||
|
// meta-compile должен быть ключ (метка в нижнем регистре), ведущий РОВНО в тот же канон. Обратное
|
||||||
|
// неверно намеренно: компилятор знает формы, которых читалка не печатает (английские, полные
|
||||||
|
// варианты рядом с аббревиатурами).
|
||||||
|
//
|
||||||
|
// Плюс проверяем, что карты не разошлись между портами навыка: значения переводов — часть вывода,
|
||||||
|
// а расхождение портов дало бы разный текст на один и тот же файл.
|
||||||
|
//
|
||||||
|
// Парсит .ps1 как канон, .py как копию. Выход 1 при расхождении.
|
||||||
|
// Запуск: node tests/skills/check-typeset-coverage.mjs
|
||||||
|
import { readFileSync } from 'node:fs';
|
||||||
|
import { fileURLToPath } from 'node:url';
|
||||||
|
import { dirname, join } from 'node:path';
|
||||||
|
|
||||||
|
const ROOT = join(dirname(fileURLToPath(import.meta.url)), '..', '..');
|
||||||
|
const read = (f) => readFileSync(join(ROOT, f), 'utf8');
|
||||||
|
|
||||||
|
// Метки, зашитые прямо в Format-SingleTypeSet / Format-SingleType, а не в карту.
|
||||||
|
const INLINE_LABELS = {
|
||||||
|
Characteristic: 'Характеристика',
|
||||||
|
DefinedType: 'ОпределяемыйТип',
|
||||||
|
AnyRef: 'ЛюбаяСсылка',
|
||||||
|
AnyIBRef: 'ЛюбаяСсылкаИБ',
|
||||||
|
};
|
||||||
|
|
||||||
|
// $objectTypeMap = @{ "CatalogObject"="СправочникОбъект"; … } — записи в одну или несколько строк.
|
||||||
|
function parsePs1Map(text, name) {
|
||||||
|
const m = new RegExp(`\\$${name} = @\\{([\\s\\S]*?)\\n\\}`).exec(text);
|
||||||
|
if (!m) throw new Error(`карта $${name} не найдена в .ps1 — реестр протух`);
|
||||||
|
const map = new Map();
|
||||||
|
const re = /"([A-Za-z]+)"\s*=\s*"([^"]+)"/g;
|
||||||
|
let e;
|
||||||
|
while ((e = re.exec(m[1])) !== null) map.set(e[1], e[2]);
|
||||||
|
if (map.size === 0) throw new Error(`карта $${name} пуста — реестр протух`);
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
// object_type_map = { "CatalogObject": "СправочникОбъект", … }
|
||||||
|
function parsePyMap(text, name) {
|
||||||
|
const m = new RegExp(`^${name} = \\{([\\s\\S]*?)^\\}`, 'm').exec(text);
|
||||||
|
if (!m) throw new Error(`карта ${name} не найдена в .py — реестр протух`);
|
||||||
|
const map = new Map();
|
||||||
|
const re = /"([A-Za-z]+)"\s*:\s*"([^"]+)"/g;
|
||||||
|
let e;
|
||||||
|
while ((e = re.exec(m[1])) !== null) map.set(e[1], e[2]);
|
||||||
|
if (map.size === 0) throw new Error(`карта ${name} пуста — реестр протух`);
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseSynonyms(text) {
|
||||||
|
const map = new Map();
|
||||||
|
const re = /\$script:typeSynonyms\["([^"]+)"\]\s*=\s*"([^"]*)"/g;
|
||||||
|
let m;
|
||||||
|
while ((m = re.exec(text)) !== null) map.set(m[1], m[2]);
|
||||||
|
if (map.size === 0) throw new Error('словарь typeSynonyms не найден в meta-compile — реестр протух');
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
const infoPs1 = read('.claude/skills/meta-info/scripts/meta-info.ps1');
|
||||||
|
const infoPy = read('.claude/skills/meta-info/scripts/meta-info.py');
|
||||||
|
const synonyms = parseSynonyms(read('.claude/skills/meta-compile/scripts/meta-compile.ps1'));
|
||||||
|
|
||||||
|
let drift = 0;
|
||||||
|
|
||||||
|
// ── 1. Порты навыка-читалки не разошлись ────────────────────────────────────
|
||||||
|
for (const [ps1Name, pyName] of [['objectTypeMap', 'object_type_map'], ['refTypeMap', 'ref_type_map']]) {
|
||||||
|
const a = parsePs1Map(infoPs1, ps1Name);
|
||||||
|
const b = parsePyMap(infoPy, pyName);
|
||||||
|
for (const [k, v] of a) {
|
||||||
|
if (!b.has(k)) { console.log(`DRIFT ${pyName}: нет ключа "${k}" (в .ps1 = "${v}")`); drift++; }
|
||||||
|
else if (b.get(k) !== v) { console.log(`DRIFT ${pyName}."${k}" = "${b.get(k)}" != .ps1 "${v}"`); drift++; }
|
||||||
|
}
|
||||||
|
for (const k of b.keys()) {
|
||||||
|
if (!a.has(k)) { console.log(`DRIFT ${ps1Name}: нет ключа "${k}", который есть в .py`); drift++; }
|
||||||
|
}
|
||||||
|
console.log(` ${ps1Name}: ${a.size} меток, порты совпадают`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── 2. Каждая метка читалки принимается компилятором ────────────────────────
|
||||||
|
const labels = new Map([
|
||||||
|
...parsePs1Map(infoPs1, 'objectTypeMap'),
|
||||||
|
...parsePs1Map(infoPs1, 'refTypeMap'),
|
||||||
|
...Object.entries(INLINE_LABELS),
|
||||||
|
]);
|
||||||
|
|
||||||
|
for (const [canon, label] of [...labels].sort((x, y) => x[1].localeCompare(y[1]))) {
|
||||||
|
const key = label.toLowerCase();
|
||||||
|
if (!synonyms.has(key)) {
|
||||||
|
console.log(`DRIFT meta-info печатает "${label}", а meta-compile его не знает (ждётся synonyms["${key}"] = "${canon}")`);
|
||||||
|
drift++;
|
||||||
|
} else if (synonyms.get(key) !== canon) {
|
||||||
|
console.log(`DRIFT synonyms["${key}"] = "${synonyms.get(key)}", а meta-info печатает это как "${canon}"`);
|
||||||
|
drift++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (drift === 0) console.log(` меток читалки: ${labels.size}, все приняты компилятором`);
|
||||||
|
|
||||||
|
console.log(drift === 0
|
||||||
|
? `OK — вывод meta-info замыкается на вход meta-compile (${labels.size} меток)`
|
||||||
|
: `\n${drift} DRIFT(s) — завести метку в словарь meta-compile или убрать из карты meta-info.`);
|
||||||
|
process.exit(drift ? 1 : 0);
|
||||||
Reference in New Issue
Block a user