a5d30f38a3
Этап 2c эпика роутер-реестр: оживление машины охвата как замена цепочкам L.
- registry-initial-inputs.mjs: токены-данности (category:given) для initialInputs.
- registry-graph-health.test.mjs: граф ацикличен, рёбра producer-consumer.
- coverage-wiring.mjs: мост recommended skills -> readinessChecklist -> {cards, ready, holes}; ready=нет-дыр.
- enforce-judge-gate.mjs: coverageCardsFor/coverageGate — карточки + стоп при дыре (инъекция-выкл).
- замок словаря (vocabTokens) на живом пути; гайд по стене: автономность + уроки сессии.
Регрессия: 4375 passed (канонический свод владельца).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
62 lines
3.3 KiB
JavaScript
62 lines
3.3 KiB
JavaScript
#!/usr/bin/env node
|
||
/**
|
||
* coverage-wiring — тонкий мост между списком рекомендованных навыков и машиной охвата.
|
||
* Заменяет реестр цепочек L механической проверкой полноты: грузит контракты с замком
|
||
* словаря (D5), выбирает рекомендованные, считает readinessChecklist и отдаёт карточки +
|
||
* вердикт готовности для живого гейта судьи. Без LLM (set/graph-операции).
|
||
*
|
||
* `ready` (спека D3/D4) = НЕТ ДЫР покрытия И НЕТ ЦИКЛА: твёрдый стоп печати — на дыре
|
||
* (нужда, которую никто в наборе не производит и которой нет в данностях). Сироты
|
||
* (scope-creep) и непокрытые просьбы — мягкие сигналы в `items`, печать не стопорят.
|
||
*/
|
||
import fsDefault from 'node:fs';
|
||
import { loadRegistry, dispatchContract } from './skill-contract-registry.mjs';
|
||
import { loadVocabulary } from './capability-vocabulary.mjs';
|
||
import { readinessChecklist } from './coverage-machine.mjs';
|
||
import { loadInitialInputs } from './registry-initial-inputs.mjs';
|
||
|
||
const HOLES_POINTER = '§A findHoles';
|
||
const CYCLE_POINTER = '§A topoOrder';
|
||
|
||
/**
|
||
* Построить вход охвата для гейта по рекомендованным навыкам.
|
||
* 1) loadRegistry с vocabTokens (D5 — замок словаря на живом пути);
|
||
* 2) выбрать контракты рекомендованных навыков (dispatchContract, mode 'exact');
|
||
* 3) readinessChecklist({ выбранные, requests, initialInputs:данности });
|
||
* 4) → { cards, ready (нет дыр && нет цикла), holes, cycle, items, errors }.
|
||
* fsImpl/dir/vocabPath инъектируются (тесты — синтетические контракты).
|
||
*/
|
||
export function buildCoverageInput({
|
||
recommendedSkills = [],
|
||
dir = 'docs/registry/contracts',
|
||
vocabPath = 'docs/registry/capability-vocabulary.json',
|
||
requests = [],
|
||
fsImpl = fsDefault,
|
||
} = {}) {
|
||
const vocab = loadVocabulary({ path: vocabPath, fsImpl });
|
||
const registry = loadRegistry({ dir, fsImpl, vocabTokens: vocab.tokens }); // D5: замок на живом пути
|
||
const initialInputs = loadInitialInputs({ path: vocabPath, fsImpl });
|
||
|
||
const selected = [];
|
||
for (const skill of recommendedSkills || []) {
|
||
const d = dispatchContract(registry, skill);
|
||
if (d.mode === 'exact' && d.contract) selected.push(d.contract);
|
||
}
|
||
|
||
const cards = selected.map((c) => ({
|
||
skill: c.skill,
|
||
needs: c.needs || [],
|
||
produces: c.produces || [],
|
||
}));
|
||
|
||
const checklist = readinessChecklist({ contracts: selected, requests, initialInputs });
|
||
const items = checklist.items || [];
|
||
const holes = (items.find((i) => i && i.pointer === HOLES_POINTER) || {}).detail || [];
|
||
const cycle = (items.find((i) => i && i.pointer === CYCLE_POINTER) || {}).detail || null;
|
||
|
||
// Спека D3/D4: стоп только на дыре/цикле (полнота+корректность), не на сироте/scope-creep.
|
||
const ready = holes.length === 0 && cycle === null;
|
||
|
||
return { cards, ready, holes, cycle, items, errors: registry.errors };
|
||
}
|