import { describe, it, expect } from 'vitest'; import { tmpdir } from 'node:os'; import { mkdtempSync, writeFileSync, rmSync } from 'node:fs'; import { join } from 'node:path'; import { extractV4Signals } from './observer-v4-signals.mjs'; function write(dir, name, lines) { writeFileSync(join(dir, name), lines.map((l) => JSON.stringify(l)).join('\n') + '\n'); } describe('extractV4Signals', () => { it('counts rationalization flags inside the turn window only', () => { const dir = mkdtempSync(join(tmpdir(), 'v4-')); write(dir, 'rationalization-flags-s.jsonl', [ { ts: '2026-05-31T10:00:00.000Z', kind: 'x' }, // before window { ts: '2026-05-31T10:05:00.000Z', kind: 'y' }, // in { ts: '2026-05-31T10:06:00.000Z', kind: 'z' }, // in ]); write(dir, 'llm-judge-verdicts-s.jsonl', [ { ts: '2026-05-31T10:05:30.000Z', tool: 'Edit', verdict: 'YES' }, { ts: '2026-05-31T10:05:40.000Z', tool: 'Bash', verdict: 'block' }, ]); write(dir, 'safe-baseline-actions-s.jsonl', [ { ts: '2026-05-31T10:05:10.000Z', tool: 'Edit', action: 'soft_flag' }, { ts: '2026-05-31T10:05:50.000Z', tool: 'Write', action: 'hard_block' }, ]); writeFileSync(join(dir, 'llm-judge-budget-s.json'), JSON.stringify({ calls: 7 })); const sig = extractV4Signals('s', { startMs: Date.parse('2026-05-31T10:04:00.000Z'), endMs: Date.parse('2026-05-31T10:07:00.000Z'), baseDir: dir, }); expect(sig.rationalization_flag_count).toBe(2); expect(sig.judge_verdict).toBe('block'); // last in-window verdict expect(sig.safe_baseline_action).toBe('hard_block'); // worst in-window action expect(sig.judge_calls).toBe(7); rmSync(dir, { recursive: true, force: true }); }); it('returns zero/null defaults when files absent', () => { const sig = extractV4Signals('nope', { startMs: 0, endMs: 1, baseDir: tmpdir() }); expect(sig).toEqual({ rationalization_flag_count: 0, judge_verdict: null, safe_baseline_action: null, judge_calls: 0, }); }); });