#!/usr/bin/env node /** verdict-surface-store — пер-сессионный транзиентный стор вердиктов + pending-ack маркер (SP1). * Fail-quiet: любая ошибка → безопасный no-op/пусто. Дефолт baseDir = ~/.claude/runtime. */ import { homedir } from 'node:os'; import { join } from 'node:path'; import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs'; function baseOf(baseDir) { return baseDir || join(homedir(), '.claude', 'runtime'); } function queuePath(sid, baseDir) { return join(baseOf(baseDir), `verdict-surface-${sid || 'unknown'}.json`); } function ackPath(sid, baseDir) { return join(baseOf(baseDir), `verdict-pending-ack-${sid || 'unknown'}.json`); } function readJson(p) { try { return JSON.parse(readFileSync(p, 'utf8')); } catch { return null; } } function ensureDir(p) { try { mkdirSync(baseOf(p), { recursive: true }); } catch {} } export function pushVerdict(sid, obj, baseDir) { try { ensureDir(baseDir); const p = queuePath(sid, baseDir); let arr = existsSync(p) ? readJson(p) : []; if (!Array.isArray(arr)) arr = []; arr.push(obj); writeFileSync(p, JSON.stringify(arr)); return true; } catch { return false; } } export function drainVerdicts(sid, baseDir) { try { const p = queuePath(sid, baseDir); if (!existsSync(p)) return []; const arr = readJson(p); try { writeFileSync(p, '[]'); } catch {} return Array.isArray(arr) ? arr : []; } catch { return []; } } export function markSurfaced(sid, outcomes, baseDir) { try { ensureDir(baseDir); writeFileSync(ackPath(sid, baseDir), JSON.stringify(outcomes || [])); return true; } catch { return false; } } export function readPendingAck(sid, baseDir) { try { const p = ackPath(sid, baseDir); if (!existsSync(p)) return null; const v = readJson(p); return Array.isArray(v) ? v : null; } catch { return null; } } export function clearPendingAck(sid, baseDir) { try { const p = ackPath(sid, baseDir); if (existsSync(p)) writeFileSync(p, 'null'); return true; } catch { return false; } } // Снимок стадий (видимость «всё в лоб»): персистентная карта решений по артефакту, читаемая в любой // момент (в отличие от очереди, дренящейся один раз). Файл = { [hash]: { [stage]: {status,reason,ts} } }. function snapshotPath(sid, baseDir) { return join(baseOf(baseDir), `verdict-snapshot-${sid || 'unknown'}.json`); } /** Upsert стадии решения по hash артефакта. status: pending → GO|NO-GO|degraded|skip|recommend|sealed. */ export function writeStage(sid, { stage, hash, status, reason = '', ts = 0 } = {}, baseDir) { try { if (!stage || !hash) return false; ensureDir(baseDir); const p = snapshotPath(sid, baseDir); let obj = existsSync(p) ? readJson(p) : {}; if (!obj || typeof obj !== 'object') obj = {}; if (!obj[hash] || typeof obj[hash] !== 'object') obj[hash] = {}; obj[hash][stage] = { status, reason, ts }; writeFileSync(p, JSON.stringify(obj)); return true; } catch { return false; } } /** Карта стадий по hash: { [stage]: {status,reason,ts} } или {} (неизвестный hash / сбой). */ export function readSnapshot(sid, hash, baseDir) { try { const p = snapshotPath(sid, baseDir); if (!existsSync(p)) return {}; const obj = readJson(p); if (!obj || typeof obj !== 'object') return {}; const v = obj[hash]; return v && typeof v === 'object' ? v : {}; } catch { return {}; } }