Files
brain/tools/enforce-reconcile.test.mjs
T

74 lines
4.2 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// tools/enforce-reconcile.test.mjs
// 8.2 (Δ3, Машина 5 Пакет 8) — PostToolUse-реконсилер: двунаправленная сверка журнала
// НАМЕРЕНИЙ (пред-запись supreme-gate, 8.1) с фактом исполнения.
// reconcileAction — исполненное действие имеет совпадающую пред-запись? нет → action-without-record.
// findOrphanIntents — пред-записи без соответствующего исполнения → record-without-action.
// WARN-уровень (не блок): расхождение — сигнал владельцу, не остановка (PreToolUse-пол уже отработал).
import { describe, it, expect } from 'vitest';
import { reconcileAction, findOrphanIntents } from './enforce-reconcile.mjs';
describe('reconcileAction (8.2): действие без журнальной пред-записи', () => {
const journal = [
{ op: 'Write', object: 'tools/foo.mjs', step: 1 },
{ op: 'Bash', object: 'npx vitest run foo', step: 2 },
];
it('действие с совпадающей пред-записью → matched', () => {
expect(reconcileAction({ action: { op: 'Write', object: 'tools/foo.mjs' }, journalEntries: journal }).matched).toBe(true);
});
it('действие БЕЗ пред-записи → action-without-record (возможен обход стены)', () => {
const r = reconcileAction({ action: { op: 'Write', object: 'tools/evil.mjs' }, journalEntries: journal });
expect(r.matched).toBe(false);
expect(r.flag).toBe('action-without-record');
});
it('пустой журнал → action-without-record', () => {
expect(reconcileAction({ action: { op: 'Bash', object: 'x' }, journalEntries: [] }).matched).toBe(false);
});
});
describe('findOrphanIntents (8.2): запись без действия', () => {
const journal = [
{ op: 'Write', object: 'tools/foo.mjs', step: 1 },
{ op: 'Bash', object: 'npx vitest run foo', step: 2 },
];
it('все намерения исполнены → ok, без сирот', () => {
const executed = [{ op: 'Write', object: 'tools/foo.mjs' }, { op: 'Bash', object: 'npx vitest run foo' }];
const r = findOrphanIntents({ journalEntries: journal, executedActions: executed });
expect(r.ok).toBe(true);
expect(r.orphans).toEqual([]);
});
it('намерение без исполнения → record-without-action (сирота)', () => {
const executed = [{ op: 'Write', object: 'tools/foo.mjs' }];
const r = findOrphanIntents({ journalEntries: journal, executedActions: executed });
expect(r.ok).toBe(false);
expect(r.flag).toBe('record-without-action');
expect(r.orphans).toHaveLength(1);
expect(r.orphans[0].object).toBe('npx vitest run foo');
});
});
// ── R-30/reconcile reader: pure reconcileEvent (harness-событие → WARN или null) ──
import { reconcileEvent } from './enforce-reconcile.mjs';
describe('reconcileEvent (reconcile reader, pure)', () => {
it('действие совпало с пред-записью → null (нет WARN)', () => {
const event = { tool_name: 'Write', tool_input: { file_path: '/x/y.txt' } };
const journalEntries = [{ op: 'Write', object: '/x/y.txt' }];
expect(reconcileEvent({ event, journalEntries })).toBe(null);
});
it('действие без пред-записи → WARN-строка', () => {
const event = { tool_name: 'Write', tool_input: { file_path: '/x/y.txt' } };
const w = reconcileEvent({ event, journalEntries: [] });
expect(typeof w).toBe('string');
expect(w).toContain('без журнальной пред-записи');
});
it('нет имени инструмента → null (не кричим)', () => {
expect(reconcileEvent({ event: {}, journalEntries: [] })).toBe(null);
expect(reconcileEvent({ event: null, journalEntries: [] })).toBe(null);
});
it('Bash action матчится по команде', () => {
const event = { tool_name: 'Bash', tool_input: { command: 'git status' } };
const journalEntries = [{ op: 'Bash', object: 'git status' }];
expect(reconcileEvent({ event, journalEntries })).toBe(null);
});
});