Files
brain/tools/judge-anchor.test.mjs
T

46 lines
2.0 KiB
JavaScript

// tools/judge-anchor.test.mjs
import { describe, it, expect } from 'vitest';
import { classifyObjection, partitionObjections, ANCHOR_KINDS } from './judge-anchor.mjs';
describe('classifyObjection (§9.3 anchor gate)', () => {
it('anchored objection (valid kind + ref) → can block', () => {
const r = classifyObjection({ text: 'X не закрыто', anchor: { kind: 'spec_section', ref: '§3' } });
expect(r.anchored).toBe(true);
expect(r.disposition).toBe('block');
});
it('every valid anchor kind is accepted', () => {
for (const kind of ANCHOR_KINDS) {
expect(classifyObjection({ text: 't', anchor: { kind, ref: 'r' } }).anchored).toBe(true);
}
});
it('anchorless objection → demoted to advice (not silenced)', () => {
const r = classifyObjection({ text: 'вообще тревожно' });
expect(r.anchored).toBe(false);
expect(r.disposition).toBe('advice');
});
it('invalid anchor kind → not anchored → advice', () => {
expect(classifyObjection({ text: 't', anchor: { kind: 'vibes', ref: 'x' } }).disposition).toBe('advice');
});
it('anchor present but empty ref → not anchored → advice', () => {
expect(classifyObjection({ text: 't', anchor: { kind: 'test_name', ref: ' ' } }).disposition).toBe('advice');
});
});
describe('partitionObjections (both-ways gate)', () => {
it('splits into blocking (anchored) and advisory (anchorless)', () => {
const objs = [
{ text: 'a', anchor: { kind: 'failed_criterion', ref: 'tY' } },
{ text: 'b' },
{ text: 'c', anchor: { kind: 'card_need', ref: 'frontend.mobile' } },
];
const r = partitionObjections(objs);
expect(r.blocking).toHaveLength(2);
expect(r.advisory).toHaveLength(1);
});
it('a ghost NO with no anchor never reaches the blocking set', () => {
const r = partitionObjections([{ text: 'НЕТ, не нравится', verdict: 'NO' }]);
expect(r.blocking).toEqual([]);
expect(r.advisory).toHaveLength(1);
});
});