Files
portal/tools/router-classifier-regex-fallback.test.mjs
T
Дмитрий 81cbd8c1c2 feat(brain-retro #7): C1+C2+C3+C4 router-discipline fixes
retro #7 (docs/observer/notes/2026-05-27-brain-retro-7.md) surfaced 4
candidates against 23 turns since retro #6. All four implemented TDD.

C1 — translit slang vocabulary in router-classifier-regex-fallback.mjs.
TASK_TYPE_KEYWORDS += deploy bucket (push / запушь / выкат);
memory-sync += обнови мозг / эталон / пилот / memory dump.

C2 — short_ambiguous_block in router-tool-gate.mjs + router-prehook.mjs.
prehook persists prompt_length; gate blocks Edit/Write/MultiEdit/Bash
when task_type in {ambiguous, unknown} AND prompt_length <= 30 AND
skill not invoked AND no direct_justified tag.

C3 — self-assessment timeout 30s to 50s in observer-self-assessment-api.mjs.
Windows TLS handshake + Sonnet latency exceeded 30s. Stop-hook has 60s
budget; 50s leaves headroom. DEFAULT_TIMEOUT_MS exported for tests.

C4 — Reviewer findings block in status-md-generator.mjs. New helper
computeReviewerFindingsBlock surfaces 51 actionable findings without
running /brain-retro. Detects batch-reviewed via
outcome_reviewed_source=direct_api_batch. MD012 guard test added.

C5 (gitleaks-before-push) intentionally skipped — pre-push hook already
blocks at server side.

Tests: 956/956 root tools, 0 regressions. LEFTHOOK=0 used per quirk #111.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 06:46:55 +03:00

68 lines
2.8 KiB
JavaScript

import { describe, it, expect } from 'vitest';
import { classifyByRegex, TASK_TYPE_KEYWORDS } from './router-classifier-regex-fallback.mjs';
const fakeRegistry = {
nodes: [
{ id: '#19', name: 'Superpowers', status: 'active', triggers: [{ classification: 'feature', weight: 1.0 }] },
{ id: '#33', name: 'claude-md-management', status: 'active', triggers: [
{ classification: 'memory-sync', weight: 1.0 },
] },
{ id: '#3', name: 'GitHub MCP', status: 'active', triggers: [
{ classification: 'deploy', weight: 1.0 },
] },
],
};
// brain-retro #7 C1 (2026-05-27): translit slang vocabulary direct tests.
describe('classifyByRegex — C1 translit slang dictionary', () => {
it('«обнови мозг» → memory-sync', () => {
expect(classifyByRegex('обнови мозг', fakeRegistry).taskType).toBe('memory-sync');
});
it('«обнови эталон» → memory-sync', () => {
expect(classifyByRegex('обнови эталон после деплоя', fakeRegistry).taskType).toBe('memory-sync');
});
it('«обнови пилот» → memory-sync', () => {
expect(classifyByRegex('обнови пилот', fakeRegistry).taskType).toBe('memory-sync');
});
it('«пуш» → deploy', () => {
expect(classifyByRegex('пуш на main', fakeRegistry).taskType).toBe('deploy');
});
it('«push» (EN) → deploy', () => {
expect(classifyByRegex('push origin main', fakeRegistry).taskType).toBe('deploy');
});
it('«запушь» → deploy', () => {
expect(classifyByRegex('запушь ветку', fakeRegistry).taskType).toBe('deploy');
});
it('compound «пуш и обнови пилот» — NOT unknown (some meaningful classification)', () => {
expect(classifyByRegex('пуш и обнови пилот', fakeRegistry).taskType).not.toBe('unknown');
});
it('TASK_TYPE_KEYWORDS includes a deploy bucket', () => {
expect(TASK_TYPE_KEYWORDS.deploy).toBeDefined();
expect(TASK_TYPE_KEYWORDS.deploy.length).toBeGreaterThan(0);
});
it('memory-sync bucket carries the translit nouns', () => {
const memSync = TASK_TYPE_KEYWORDS['memory-sync'] || [];
expect(memSync).toEqual(expect.arrayContaining(['обнови мозг', 'обнови эталон', 'обнови пилот']));
});
// Guard against regressions on pre-existing entries.
it('keeps original memory-sync entries (запомни / CLAUDE.md / MEMORY.md)', () => {
const memSync = TASK_TYPE_KEYWORDS['memory-sync'] || [];
expect(memSync).toEqual(expect.arrayContaining(['запомни', 'CLAUDE.md', 'MEMORY.md']));
});
});
describe('classifyByRegex — C1 source tag', () => {
it('source is regex (degraded path) for translit hits too', () => {
expect(classifyByRegex('обнови мозг', fakeRegistry).source).toBe('regex');
});
});