b739d5adad
Болезни B (роутер в пустоту) + A (наставник не заворачивал) — лечение Р7/Р8 (Подход 1): наставник — единый мозг-рецензент, зовёт classify() как функцию (3 слоя + граф nodes.yaml + карточки — код не тронут, новый вызыватель), судит спеку+план+выбор скилов, заворачивает NO-GO. - validateMentorVerdict + промпты (план/спека): явное decision GO|NO-GO (поглощённый Р7) - plan-skills.mjs: parsePlanSkills (skills-json) + extractPlanGoal (зеркало extractGoal судьи) - mentor-seam: renderSkillContext; onPlanWrite зовёт classifyImpl (fail-safe: сбой → без скил-сверки) - decideMentorObjection: заворот на decision=NO-GO ИЛИ сломанный вердикт; mentor-GO только на чистом GO - formatMentorObjection доносит суть (recommendation + reasoning + plan_points), GO -> пусто - enforce-mentor main: loadRegistry + classify; счётчик L1 decision-aware (Р7/§3.4) - скил-сверка — только план (gate2); спека (gate1) — по сути + decision - включает redesign согласования L1->L2 (Фазы 0-6, способ B: наставник->судья->печать) - регрессия tools-only 3901 passed + 2 skip (база 3877, +24 теста, 0 регрессий) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
51 lines
2.5 KiB
JavaScript
51 lines
2.5 KiB
JavaScript
#!/usr/bin/env node
|
|
/**
|
|
* mentor-go-store (способ B, Фаза 2) — наставник в Post записывает подписанное «я одобрил
|
|
* ЭТОТ план» (привязка к plan_hash, нах.F4). Судья (хук ПОСЛЕ наставника) читает запись и
|
|
* судит/печатает ТОЛЬКО при валидном mentor-GO; нет одобрения наставника → судья молчит
|
|
* (fail-safe). Зеркало judge-go-store, домен подписи MENTOR_GO.
|
|
*/
|
|
import fsDefault from 'node:fs';
|
|
import { assertSafeSessionId } from './action-journal.mjs';
|
|
import { signPayload, verifyReceipt, RECEIPT_DOMAINS } from './receipt-sign.mjs';
|
|
|
|
const DOMAIN = RECEIPT_DOMAINS.MENTOR_GO;
|
|
|
|
function mentorGoPath(runtimeDir, sessionId) {
|
|
assertSafeSessionId(sessionId);
|
|
const sep = runtimeDir.endsWith('/') ? '' : '/';
|
|
return `${runtimeDir}${sep}mentor-go-${sessionId}.json`;
|
|
}
|
|
|
|
/** Чистая сборка подписанной записи «наставник одобрил» для плана (plan_hash — binding нах.F4). */
|
|
export function buildMentorGo({ planHash, judgeMode = null, key, nowMs = null }) {
|
|
const base = {
|
|
plan_hash: planHash ?? null,
|
|
approved: true,
|
|
at: typeof nowMs === 'number' ? nowMs : null,
|
|
};
|
|
return { ...base, sig: signPayload(base, key, DOMAIN) };
|
|
}
|
|
|
|
/** Запись валидна И принадлежит ЭТОМУ плану И подпись цела? Иначе false (fail-closed). */
|
|
export function mentorGoValidFor(record, { planHash, key } = {}) {
|
|
if (!record || typeof record !== 'object') return false;
|
|
if (record.plan_hash !== planHash) return false;
|
|
if (record.approved !== true) return false;
|
|
return verifyReceipt(record, key, DOMAIN);
|
|
}
|
|
|
|
/** Атомарная запись одобрения наставника в ~/.claude/runtime/mentor-go-<sess>.json. */
|
|
export function persistMentorGo({ record, sessionId, runtimeDir, fsImpl = fsDefault }) {
|
|
const p = mentorGoPath(runtimeDir, sessionId);
|
|
const tmp = `${p}.tmp`;
|
|
fsImpl.writeFileSync(tmp, JSON.stringify(record));
|
|
fsImpl.renameSync(tmp, p);
|
|
}
|
|
|
|
/** Загрузка одобрения наставника (нет файла → null). */
|
|
export function loadMentorGo({ sessionId, runtimeDir, fsImpl = fsDefault }) {
|
|
try { return JSON.parse(fsImpl.readFileSync(mentorGoPath(runtimeDir, sessionId), 'utf8')); }
|
|
catch (e) { if (e && e.code === 'ENOENT') return null; throw e; }
|
|
}
|