397777089e
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
60 lines
2.1 KiB
JavaScript
60 lines
2.1 KiB
JavaScript
/**
|
|
* 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();
|