/** * PreToolUse(Edit|Write|MultiEdit|Bash) wrapper for tools/self-debrief-detector.mjs. * Router-gate v4.1 spec §3.12 (NEW). * * Reads last controller text from transcript; if it matches self-debrief patterns * (я заметил паттерн / generalisable lesson / etc.) AND no self-retrospect or * brain-retro Skill in recent turns — block. * * Fail-CLOSE on internal error. */ import { fileURLToPath } from 'url'; import { readStdin, parseEventJson, readTranscript, exitDecision, } from './enforce-hook-helpers.mjs'; import { detectSelfDebrief } from './self-debrief-detector.mjs'; /** Extract last assistant (controller) text from transcript. */ export function lastControllerText(transcript) { const recs = transcript || []; for (let i = recs.length - 1; i >= 0; i--) { const r = recs[i]; if (r && r.type === 'text' && r.role === 'assistant') return String(r.text || ''); if (r && r.role === 'assistant' && typeof r.content === 'string') return r.content; } return ''; } export function decide({ controllerText, transcript }) { const r = detectSelfDebrief(controllerText, transcript || []); if (r.action === 'hard_block_next_mutating') { return { block: true, reason: r.reason }; } return { block: false, reason: null }; } async function main() { try { const raw = await readStdin(); const event = parseEventJson(raw); const mutating = ['Edit', 'Write', 'MultiEdit', 'Bash']; if (!mutating.includes(event.tool_name)) return exitDecision({ block: false }); const transcript = readTranscript(event.transcript_path); const controllerText = lastControllerText(transcript); const r = decide({ controllerText, transcript }); if (r.block) { return exitDecision({ block: true, message: `[self-debrief-detector] ${r.reason}` }); } return exitDecision({ block: false }); } catch { return exitDecision({ block: true, message: '[self-debrief-detector] внутренняя ошибка — fail-CLOSE' }); } } const isCli = process.argv[1] && fileURLToPath(import.meta.url) === process.argv[1]; if (isCli) main();