Files
brain/tools/capability-vocabulary.mjs
T

48 lines
2.6 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env node
/**
* capability-vocabulary — контролируемый словарь capability-токенов (спека v2 §3,
* OPEN-1). Единственный источник допустимых токенов для needs/produces контрактов.
* Чистые функции (без LLM): валидация формы словаря + сверка токенов контракта.
*/
import fsDefault from 'node:fs';
const KEBAB = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
/** Валидация формы словаря → {ok, tokens:Set, errors[]}. */
export function validateVocabulary(raw) {
if (!raw || typeof raw !== 'object' || !Array.isArray(raw.tokens))
return { ok: false, tokens: new Set(), errors: ['vocabulary: объект с массивом tokens обязателен'] };
const errors = [];
const tokens = new Set();
raw.tokens.forEach((t, i) => {
if (!t || typeof t !== 'object' || typeof t.token !== 'string' || !t.token.trim()) {
errors.push(`tokens[${i}].token: непустая строка обязательна`);
return;
}
const tok = t.token.trim();
if (!KEBAB.test(tok)) errors.push(`tokens[${i}].token "${tok}": требуется kebab-case`);
if (typeof t.label !== 'string' || !t.label.trim()) errors.push(`tokens[${i}] (${tok}).label: непустая строка обязательна`);
if (typeof t.description !== 'string' || !t.description.trim()) errors.push(`tokens[${i}] (${tok}).description: непустая строка обязательна`);
if (tokens.has(tok)) { errors.push(`tokens[${i}].token "${tok}": duplicate`); return; }
tokens.add(tok);
});
return { ok: errors.length === 0, tokens, errors };
}
/** Загрузка словаря с диска (fs инъектируется). Бросает на битом JSON. */
export function loadVocabulary({ path, fsImpl = fsDefault }) {
const raw = JSON.parse(fsImpl.readFileSync(path, 'utf8'));
return validateVocabulary(raw);
}
/** Неизвестные токены контракта в needs/produces (отсутствуют в словаре). [{field, token}].
* Сверка по String(tok).trim(); в выдаче — исходное значение token (не нормализованное). */
export function unknownTokens(contract, tokenSet) {
const set = tokenSet instanceof Set ? tokenSet : new Set(tokenSet || []);
const out = [];
for (const field of ['needs', 'produces'])
for (const tok of contract?.[field] || [])
if (!set.has(String(tok).trim())) out.push({ field, token: tok });
return out;
}