Files
brain/tools/enforce-verdict-ack.mjs
T

40 lines
2.0 KiB
JavaScript

#!/usr/bin/env node
/** enforce-verdict-ack — Stop-хук. Если на ходе был surfaced-вердикт (pending-ack), а в ответе нет
* строки `вердикт:` — громкая претензия (маркер остаётся → нагнетает до подтверждения). При ack —
* чистит маркер. Fail-quiet через exitDisciplineDecision. (SP1) */
import { readStdin, parseEventJson, readTranscript, lastAssistantText, exitDisciplineDecision } from './enforce-hook-helpers.mjs';
import { parseVerdictAck } from './verdict-outcome-line.mjs';
import { readPendingAck, clearPendingAck } from './verdict-surface-store.mjs';
/** Чистая: решение по наличию pending-ack и факту подтверждения в тексте. */
export function decide({ pendingAck, assistantText }) {
if (!pendingAck || pendingAck.length === 0) return { block: false };
if (parseVerdictAck(assistantText)) return { block: false, acked: true };
return {
block: true,
message: [
`[enforce-verdict-ack] на ходе был показан вердикт (${pendingAck.join(', ')}), но ответ его не подтвердил.`,
'ПЕРВОЙ строкой следующего ответа: `вердикт: <outcome>` (повтори исход из баннера).',
].join('\n'),
};
}
async function main() {
const ev = parseEventJson(await readStdin());
const sid = ev.session_id || ev.sessionId || 'unknown';
await exitDisciplineDecision(
() => {
const transcript = readTranscript(ev.transcript_path);
const assistantText = lastAssistantText(transcript);
const pendingAck = readPendingAck(sid);
const r = decide({ pendingAck, assistantText });
if (r.acked) clearPendingAck(sid);
return r;
},
{ label: 'enforce-verdict-ack' },
);
}
const isCli = process.argv[1] && process.argv[1].replace(/\\/g, '/').endsWith('/enforce-verdict-ack.mjs');
if (isCli) main();