62 lines
3.1 KiB
JavaScript
62 lines
3.1 KiB
JavaScript
#!/usr/bin/env node
|
|
/**
|
|
* on-plan-write (W3, A0 — нах.F5/C-1) — оркестратор записи плана: при (пере)записи
|
|
* плана присваивает persisted first-plan-anchor task-id (✅O17), производит mentor-вердикт
|
|
* (runMentorVerdict, binding plan_hash нах.F4) и best-effort пишет журнал переговоров
|
|
* (SE10: сбой журнала НЕ крашит круг). Чистое ядро — I/O/llmCall/имплы инъектируются;
|
|
* РЕГИСТРАЦИЯ как PostToolUse-хука на запись плана — шаг владельца (вне scope).
|
|
* Контракты инъекций — план C2 W7 (planHash ОБЯЗАТЕЛЕН для freeze-gate F-C4;
|
|
* journalKey — ключ подписи головы цепи).
|
|
*/
|
|
import { planId } from './plan-lock.mjs';
|
|
import { deriveTaskId } from './router-task-id.mjs';
|
|
import { runMentorVerdict } from './mentor-verdict.mjs';
|
|
import { appendNegotiation, roundCount } from './mentor-journal.mjs';
|
|
|
|
/**
|
|
* @returns {{taskId, taskIdPersisted, ok, wired, verdict, reason?, journal, journalOk}}
|
|
* verdict.plan_hash === planId(planSteps) (нах.F4) — caller отдаёт его freeze-gate.
|
|
*/
|
|
export async function onPlanWrite({
|
|
planSteps = [],
|
|
existingTaskId = null,
|
|
persistTaskIdImpl = null,
|
|
llmCall,
|
|
runMentorVerdictImpl = runMentorVerdict,
|
|
appendNegotiationImpl = appendNegotiation,
|
|
journalEntries = [],
|
|
journalKey = null,
|
|
nowMs = null,
|
|
verifiedContext = [],
|
|
negotiationLog = [],
|
|
graphSection = null,
|
|
} = {}) {
|
|
const planHash = planId(planSteps);
|
|
// ✅O17: существующий task-id побеждает (re-issue не сбрасывает); первый план — якорь.
|
|
const taskId = deriveTaskId({ existingTaskId, firstPlanHash: planHash });
|
|
let taskIdPersisted = false;
|
|
if (taskId && typeof persistTaskIdImpl === 'function') {
|
|
try { persistTaskIdImpl(taskId); taskIdPersisted = true; } catch { taskIdPersisted = false; }
|
|
}
|
|
// Производитель вердикта (C T5b): сбой → ok:false/wired:false (SE-R6-6, не суд).
|
|
const r = await runMentorVerdictImpl({
|
|
plan: { steps: planSteps }, planHash, verifiedContext, negotiationLog, graphSection, llmCall,
|
|
});
|
|
// SE10 (A4): журнал — best-effort; throw ловится, круг не падает. Обоснование непустое
|
|
// всегда (F-C2/ДР-6): из вердикта либо из reason сбоя.
|
|
let journal = null;
|
|
let journalOk = false;
|
|
try {
|
|
const round = roundCount(journalEntries, taskId) + 1;
|
|
journal = appendNegotiationImpl(journalEntries, {
|
|
taskId,
|
|
round,
|
|
side: 'mentor',
|
|
utterance: (r.verdict && r.verdict.recommendation) || 'вердикт не произведён',
|
|
justification: (r.verdict && r.verdict.reasoning) || r.reason || 'сбой производителя вердикта',
|
|
}, { key: journalKey, nowMs });
|
|
journalOk = true;
|
|
} catch { journal = null; journalOk = false; }
|
|
return { taskId, taskIdPersisted, ok: r.ok, wired: r.wired, verdict: r.verdict ?? null, reason: r.reason, journal, journalOk };
|
|
}
|