a8489a22c7
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
85 lines
3.6 KiB
JavaScript
85 lines
3.6 KiB
JavaScript
#!/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 {}; }
|
|
}
|