6bad00973a
classifyByRegex(prompt, registry) → {taskType, micro, recommendedNode, confidence, source}.
Read-only, без fs/exec/net. RU+EN keyword'ы для типа задачи + детект micro
+ матч по keyword/classification триггерам активных узлов реестра.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
106 lines
3.9 KiB
JavaScript
106 lines
3.9 KiB
JavaScript
import { describe, it, expect, beforeEach } from 'vitest';
|
|
import { classifyByRegex } from './router-classifier.mjs';
|
|
|
|
const fakeRegistry = {
|
|
nodes: [
|
|
{ id: '#19', name: 'Superpowers', status: 'active', triggers: [
|
|
{ classification: 'feature', weight: 1.0 },
|
|
{ classification: 'planning', weight: 1.0 },
|
|
] },
|
|
{ id: '#62', name: 'billing-audit', status: 'active', triggers: [
|
|
{ keyword: 'списание', weight: 1.0 },
|
|
{ keyword: 'биллинг', weight: 1.0 },
|
|
{ classification: 'bugfix', weight: 0.5 },
|
|
] },
|
|
{ id: '#74', name: 'marketing', status: 'active', triggers: [
|
|
{ keyword: 'email-рассылка', weight: 1.0 },
|
|
{ keyword: 'кампания', weight: 1.0 },
|
|
{ classification: 'marketing', weight: 1.0 },
|
|
] },
|
|
{ id: '#11', name: 'pint', status: 'active', triggers: [
|
|
{ classification: 'refactor', weight: 1.0 },
|
|
{ classification: 'cleanup', weight: 1.0 },
|
|
] },
|
|
],
|
|
};
|
|
|
|
describe('classifyByRegex — task type', () => {
|
|
it('detects feature from RU keyword «фича»', () => {
|
|
const r = classifyByRegex('давай сделаем новую фичу для биллинга', fakeRegistry);
|
|
expect(r.taskType).toBe('feature');
|
|
});
|
|
|
|
it('detects planning from RU «план»', () => {
|
|
const r = classifyByRegex('напиши план рефакторинга модуля X', fakeRegistry);
|
|
expect(r.taskType).toBe('planning');
|
|
});
|
|
|
|
it('detects bugfix from EN «bug»', () => {
|
|
const r = classifyByRegex('there is a bug in the auth flow', fakeRegistry);
|
|
expect(r.taskType).toBe('bugfix');
|
|
});
|
|
|
|
it('detects micro for typo', () => {
|
|
const r = classifyByRegex('опечатка в файле X', fakeRegistry);
|
|
expect(r.micro).toBe(true);
|
|
});
|
|
|
|
it('detects micro for rename', () => {
|
|
const r = classifyByRegex('переименуй функцию foo в bar', fakeRegistry);
|
|
expect(r.micro).toBe(true);
|
|
});
|
|
|
|
it('returns taskType=unknown when no signal', () => {
|
|
const r = classifyByRegex('просто привет', fakeRegistry);
|
|
expect(r.taskType).toBe('unknown');
|
|
expect(r.micro).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe('classifyByRegex — domain node match', () => {
|
|
it('picks #62 billing-audit on «списание»', () => {
|
|
const r = classifyByRegex('почини двойное списание лида', fakeRegistry);
|
|
expect(r.recommendedNode).toBe('#62');
|
|
});
|
|
|
|
it('picks #74 marketing on «email-рассылка»', () => {
|
|
const r = classifyByRegex('составь email-рассылку для тарифа Бизнес', fakeRegistry);
|
|
expect(r.recommendedNode).toBe('#74');
|
|
});
|
|
|
|
it('falls back to classification trigger when no keyword match', () => {
|
|
const r = classifyByRegex('рефакторинг кода', fakeRegistry);
|
|
// 'рефакторинг' → classification: refactor → #11 pint
|
|
expect(r.recommendedNode).toBe('#11');
|
|
});
|
|
|
|
it('returns null when no node matched', () => {
|
|
const r = classifyByRegex('просто вопрос', fakeRegistry);
|
|
expect(r.recommendedNode).toBeNull();
|
|
});
|
|
|
|
it('case-insensitive keyword match', () => {
|
|
const r = classifyByRegex('СПИСАНИЕ дублируется', fakeRegistry);
|
|
expect(r.recommendedNode).toBe('#62');
|
|
});
|
|
});
|
|
|
|
describe('classifyByRegex — source tag', () => {
|
|
it('always marks source: regex', () => {
|
|
const r = classifyByRegex('test', fakeRegistry);
|
|
expect(r.source).toBe('regex');
|
|
});
|
|
});
|
|
|
|
describe('classifyByRegex — confidence', () => {
|
|
it('returns confidence>=0.8 for clean keyword match', () => {
|
|
const r = classifyByRegex('списание дублируется', fakeRegistry);
|
|
expect(r.confidence).toBeGreaterThanOrEqual(0.8);
|
|
});
|
|
|
|
it('returns confidence<0.5 when ambiguous (no clean match)', () => {
|
|
const r = classifyByRegex('что-то непонятное', fakeRegistry);
|
|
expect(r.confidence).toBeLessThan(0.5);
|
|
});
|
|
});
|