abf2060328
Сессионный флаг standby-mode + управляющий UserPromptSubmit-хук рукопожатия + SessionStart-сброс. Страж if standbyActive в 12 блокирующих хуках; рельсы floor/snapshot/verify-gate не тронуты. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
56 lines
3.2 KiB
JavaScript
56 lines
3.2 KiB
JavaScript
#!/usr/bin/env node
|
|
/**
|
|
* standby-mode-control — UserPromptSubmit-хук штатного режима.
|
|
* Двухтактное рукопожатие включения, выключение одной фразой, баннер пока активно.
|
|
* Флаг пишется ТОЛЬКО здесь (реакция на промпт владельца). Fail-quiet: ошибка не ломает промпт.
|
|
*/
|
|
import { fileURLToPath } from 'url';
|
|
import { readStdin, parseEventJson, writeSentinel, readSentinel, removeSentinel, standbyActive } from './enforce-hook-helpers.mjs';
|
|
|
|
const ENABLE_RE = /штатн\p{L}*\s+режим/iu;
|
|
const CONFIRM_RE = /(?:^|\s)да,?\s*штатн/iu;
|
|
const DISABLE_RE = /выключ\p{L}*\s+штатн/iu;
|
|
|
|
const BANNER = '⚠️ ШТАТНЫЙ РЕЖИМ — стен нет (оставлены: пол, точки возврата, проверка-перед-пушем). Верни стену фразой «выключи штатный».';
|
|
const CONFIRM_ASK = '⚠️ Включить ШТАТНЫЙ — стен нет (останутся пол + проверка пуша). Точно? Подтверди фразой: «да, штатный».';
|
|
const ENABLED_MSG = '✅ ШТАТНЫЙ ВКЛЮЧЁН. ' + BANNER;
|
|
const DISABLED_MSG = '🔒 Штатный выключен — стена возвращена (предреализация).';
|
|
|
|
/** Чистое решение. Порядок проверок фиксирован: disable → confirm → enable-trigger → banner. */
|
|
export function decideControl({ text, active, pending }) {
|
|
if (DISABLE_RE.test(text)) return { action: 'disable', context: DISABLED_MSG };
|
|
if (CONFIRM_RE.test(text) && pending) return { action: 'enable', context: ENABLED_MSG };
|
|
if (ENABLE_RE.test(text) && !active && !pending) return { action: 'pending', context: CONFIRM_ASK };
|
|
if (active) return { action: 'banner', context: BANNER };
|
|
return { action: 'none', context: null };
|
|
}
|
|
|
|
function output(context) {
|
|
if (context) {
|
|
process.stdout.write(JSON.stringify({ hookSpecificOutput: { hookEventName: 'UserPromptSubmit', additionalContext: context } }));
|
|
} else {
|
|
process.stdout.write('{}');
|
|
}
|
|
process.exit(0);
|
|
}
|
|
|
|
async function main() {
|
|
try {
|
|
const event = parseEventJson(await readStdin());
|
|
const sess = event.session_id || 'unknown';
|
|
const text = String(event.prompt || event.user_prompt || '');
|
|
const active = standbyActive(sess);
|
|
const pending = !!readSentinel('standby-pending', sess);
|
|
const r = decideControl({ text, active, pending });
|
|
if (r.action === 'disable') { removeSentinel('standby-mode', sess); removeSentinel('standby-pending', sess); }
|
|
else if (r.action === 'enable') { writeSentinel('standby-mode', sess, { active: true }); removeSentinel('standby-pending', sess); }
|
|
else if (r.action === 'pending') { writeSentinel('standby-pending', sess, { pending: true }); }
|
|
output(r.context);
|
|
} catch {
|
|
process.stdout.write('{}'); process.exit(0); // fail-quiet: ошибка не ломает промпт, режим не меняется
|
|
}
|
|
}
|
|
|
|
const isCli = process.argv[1] && fileURLToPath(import.meta.url) === process.argv[1];
|
|
if (isCli) main();
|