Skip to content

Commit 6bbd487

Browse files
committed
test(mcp): add tests for intent-pattern-checks and taskmaestro-detector (#1181)
- Add 76 tests for INTENT_PATTERN_CHECKS covering structure, priority ordering, false positive prevention, and all 13 domain patterns - Add 7 tests for isTaskmaestroAvailable covering present/absent/error cases - Verify first-match-wins priority prevents greedy pattern false positives
1 parent 3309a0a commit 6bbd487

2 files changed

Lines changed: 478 additions & 0 deletions

File tree

Lines changed: 395 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,395 @@
1+
/**
2+
* Intent Pattern Checks Aggregation Tests
3+
*
4+
* Tests for INTENT_PATTERN_CHECKS including:
5+
* - All 13 domains are represented
6+
* - Priority ordering (first match wins)
7+
* - False positive prevention (agent name mentions vs domain)
8+
* - Each domain returns correct agent identifier
9+
*/
10+
11+
import { describe, it, expect } from 'vitest';
12+
import { INTENT_PATTERN_CHECKS } from './intent-pattern-checks';
13+
14+
describe('INTENT_PATTERN_CHECKS', () => {
15+
describe('structure and completeness', () => {
16+
it('should contain exactly 13 domain checks', () => {
17+
expect(INTENT_PATTERN_CHECKS).toHaveLength(13);
18+
});
19+
20+
it('should be a readonly array', () => {
21+
// ReadonlyArray prevents push/pop at type level;
22+
// at runtime we verify it is a plain array
23+
expect(Array.isArray(INTENT_PATTERN_CHECKS)).toBe(true);
24+
});
25+
26+
it('should have unique agent names', () => {
27+
const agents = INTENT_PATTERN_CHECKS.map(c => c.agent);
28+
expect(new Set(agents).size).toBe(agents.length);
29+
});
30+
31+
it('should have unique categories', () => {
32+
const categories = INTENT_PATTERN_CHECKS.map(c => c.category);
33+
expect(new Set(categories).size).toBe(categories.length);
34+
});
35+
36+
it.each([
37+
{ agent: 'agent-architect', category: 'Agent' },
38+
{ agent: 'test-engineer', category: 'Test' },
39+
{ agent: 'tooling-engineer', category: 'Tooling' },
40+
{ agent: 'platform-engineer', category: 'Platform' },
41+
{ agent: 'security-engineer', category: 'Security' },
42+
{ agent: 'systems-developer', category: 'Systems' },
43+
{ agent: 'data-engineer', category: 'Data' },
44+
{ agent: 'data-scientist', category: 'DataScience' },
45+
{ agent: 'ai-ml-engineer', category: 'AI/ML' },
46+
{ agent: 'backend-developer', category: 'Backend' },
47+
{ agent: 'frontend-developer', category: 'Frontend' },
48+
{ agent: 'devops-engineer', category: 'DevOps' },
49+
{ agent: 'mobile-developer', category: 'Mobile' },
50+
])('should include $agent with category $category', ({ agent, category }) => {
51+
const check = INTENT_PATTERN_CHECKS.find(c => c.agent === agent);
52+
expect(check).toBeDefined();
53+
expect(check!.category).toBe(category);
54+
});
55+
56+
it('should have non-empty patterns array for every check', () => {
57+
for (const check of INTENT_PATTERN_CHECKS) {
58+
expect(check.patterns.length).toBeGreaterThan(0);
59+
}
60+
});
61+
62+
it('should have valid IntentPattern items (pattern, confidence, description)', () => {
63+
for (const check of INTENT_PATTERN_CHECKS) {
64+
for (const p of check.patterns) {
65+
expect(p.pattern).toBeInstanceOf(RegExp);
66+
expect(typeof p.confidence).toBe('number');
67+
expect(p.confidence).toBeGreaterThan(0);
68+
expect(p.confidence).toBeLessThanOrEqual(1);
69+
expect(typeof p.description).toBe('string');
70+
expect(p.description.length).toBeGreaterThan(0);
71+
}
72+
}
73+
});
74+
});
75+
76+
describe('priority ordering', () => {
77+
const agentIndex = (agent: string): number =>
78+
INTENT_PATTERN_CHECKS.findIndex(c => c.agent === agent);
79+
80+
it('should place agent-architect first (index 0)', () => {
81+
expect(agentIndex('agent-architect')).toBe(0);
82+
});
83+
84+
it('should place test-engineer second (index 1)', () => {
85+
expect(agentIndex('test-engineer')).toBe(1);
86+
});
87+
88+
it('should place mobile-developer last (index 12)', () => {
89+
expect(agentIndex('mobile-developer')).toBe(12);
90+
});
91+
92+
it('should place agent-architect before backend-developer', () => {
93+
expect(agentIndex('agent-architect')).toBeLessThan(agentIndex('backend-developer'));
94+
});
95+
96+
it('should place test-engineer before backend-developer', () => {
97+
expect(agentIndex('test-engineer')).toBeLessThan(agentIndex('backend-developer'));
98+
});
99+
100+
it('should place test-engineer before frontend-developer', () => {
101+
expect(agentIndex('test-engineer')).toBeLessThan(agentIndex('frontend-developer'));
102+
});
103+
104+
it('should place security-engineer before backend-developer', () => {
105+
expect(agentIndex('security-engineer')).toBeLessThan(agentIndex('backend-developer'));
106+
});
107+
108+
it('should place data-engineer before backend-developer', () => {
109+
expect(agentIndex('data-engineer')).toBeLessThan(agentIndex('backend-developer'));
110+
});
111+
112+
it('should place backend-developer before mobile-developer', () => {
113+
expect(agentIndex('backend-developer')).toBeLessThan(agentIndex('mobile-developer'));
114+
});
115+
116+
it('should place frontend-developer before mobile-developer', () => {
117+
expect(agentIndex('frontend-developer')).toBeLessThan(agentIndex('mobile-developer'));
118+
});
119+
120+
const EXPECTED_ORDER = [
121+
'agent-architect',
122+
'test-engineer',
123+
'tooling-engineer',
124+
'platform-engineer',
125+
'security-engineer',
126+
'systems-developer',
127+
'data-engineer',
128+
'data-scientist',
129+
'ai-ml-engineer',
130+
'backend-developer',
131+
'frontend-developer',
132+
'devops-engineer',
133+
'mobile-developer',
134+
];
135+
136+
it('should follow the exact documented priority order', () => {
137+
const actual = INTENT_PATTERN_CHECKS.map(c => c.agent);
138+
expect(actual).toEqual(EXPECTED_ORDER);
139+
});
140+
});
141+
142+
describe('false positive prevention (first-match-wins simulation)', () => {
143+
/**
144+
* Simulates the for-of loop in act-agent.strategy.ts:
145+
* Iterates INTENT_PATTERN_CHECKS in order and returns the first match.
146+
*/
147+
const firstMatchAgent = (prompt: string): string | null => {
148+
for (const { agent, patterns } of INTENT_PATTERN_CHECKS) {
149+
if (patterns.some(({ pattern }) => pattern.test(prompt))) {
150+
return agent;
151+
}
152+
}
153+
return null;
154+
};
155+
156+
it('should match "에이전트 설계 for Mobile Developer" to agent-architect (agent patterns checked first)', () => {
157+
const result = firstMatchAgent('에이전트 설계 for Mobile Developer');
158+
expect(result).toBe('agent-architect');
159+
});
160+
161+
it('should match "agent design for mobile" to agent-architect', () => {
162+
const result = firstMatchAgent('agent design for mobile platform');
163+
expect(result).toBe('agent-architect');
164+
});
165+
166+
it('should match "implement Mobile Developer agent" to mobile-developer (greedy mobile pattern)', () => {
167+
// "Mobile Developer" matches /mobile\s*(app|develop|screen)/i before agent patterns
168+
const result = firstMatchAgent('implement Mobile Developer agent');
169+
expect(result).toBe('mobile-developer');
170+
});
171+
172+
it('should match "write unit tests for API" to test-engineer, not backend-developer', () => {
173+
const result = firstMatchAgent('write unit tests for API endpoints');
174+
expect(result).toBe('test-engineer');
175+
});
176+
177+
it('should match "TDD로 NestJS 서버 개발" to test-engineer (TDD keyword)', () => {
178+
const result = firstMatchAgent('TDD로 NestJS 서버 개발');
179+
expect(result).toBe('test-engineer');
180+
});
181+
182+
it('should match "React Native 앱 만들어줘" to mobile-developer (no ambiguity)', () => {
183+
const result = firstMatchAgent('React Native 앱 만들어줘');
184+
expect(result).toBe('mobile-developer');
185+
});
186+
187+
it('should match "MCP 서버 만들어줘" to agent-architect', () => {
188+
const result = firstMatchAgent('MCP 서버 만들어줘');
189+
expect(result).toBe('agent-architect');
190+
});
191+
192+
it('should match "Docker compose 설정" to devops-engineer', () => {
193+
const result = firstMatchAgent('Docker compose 설정');
194+
expect(result).toBe('devops-engineer');
195+
});
196+
197+
it('should match "Kubernetes 클러스터 설정" to platform-engineer', () => {
198+
const result = firstMatchAgent('Kubernetes 클러스터 설정');
199+
expect(result).toBe('platform-engineer');
200+
});
201+
202+
it('should match "데이터베이스 마이그레이션" to data-engineer', () => {
203+
const result = firstMatchAgent('데이터베이스 마이그레이션');
204+
expect(result).toBe('data-engineer');
205+
});
206+
207+
it('should match "React 컴포넌트 만들어줘" to frontend-developer', () => {
208+
const result = firstMatchAgent('React 컴포넌트 만들어줘');
209+
expect(result).toBe('frontend-developer');
210+
});
211+
212+
it('should match "NestJS API 만들어줘" to backend-developer', () => {
213+
const result = firstMatchAgent('NestJS API 만들어줘');
214+
expect(result).toBe('backend-developer');
215+
});
216+
217+
it('should match "webpack 설정 변경" to tooling-engineer', () => {
218+
const result = firstMatchAgent('webpack 설정 변경');
219+
expect(result).toBe('tooling-engineer');
220+
});
221+
222+
it('should match "XSS 취약점 수정" to security-engineer', () => {
223+
const result = firstMatchAgent('XSS 취약점 수정');
224+
expect(result).toBe('security-engineer');
225+
});
226+
227+
it('should match "Rust FFI 바인딩 구현" to systems-developer', () => {
228+
const result = firstMatchAgent('Rust FFI 바인딩 구현');
229+
expect(result).toBe('systems-developer');
230+
});
231+
232+
it('should match "pandas 데이터 분석" to data-scientist', () => {
233+
const result = firstMatchAgent('pandas 데이터 분석');
234+
expect(result).toBe('data-scientist');
235+
});
236+
237+
it('should match "LLM 파인튜닝 구현" to ai-ml-engineer', () => {
238+
const result = firstMatchAgent('LLM 파인튜닝 구현');
239+
expect(result).toBe('ai-ml-engineer');
240+
});
241+
242+
it('should match "Flutter 위젯 구현" to mobile-developer', () => {
243+
const result = firstMatchAgent('Flutter 위젯 구현');
244+
expect(result).toBe('mobile-developer');
245+
});
246+
247+
it('should return null for unrecognized prompt', () => {
248+
const result = firstMatchAgent('hello world');
249+
expect(result).toBeNull();
250+
});
251+
252+
it('should return null for empty string', () => {
253+
const result = firstMatchAgent('');
254+
expect(result).toBeNull();
255+
});
256+
});
257+
258+
describe('individual domain pattern matching', () => {
259+
const matchesDomain = (agent: string, prompt: string): boolean => {
260+
const check = INTENT_PATTERN_CHECKS.find(c => c.agent === agent);
261+
if (!check) return false;
262+
return check.patterns.some(({ pattern }) => pattern.test(prompt));
263+
};
264+
265+
describe('agent-architect patterns', () => {
266+
it('should match MCP server', () => {
267+
expect(matchesDomain('agent-architect', 'MCP 서버 만들어줘')).toBe(true);
268+
});
269+
270+
it('should match agent development', () => {
271+
expect(matchesDomain('agent-architect', 'agent design for mobile')).toBe(true);
272+
});
273+
274+
it('should match workflow automation', () => {
275+
expect(matchesDomain('agent-architect', 'workflow automation 구현')).toBe(true);
276+
});
277+
278+
it('should not match plain backend prompt', () => {
279+
expect(matchesDomain('agent-architect', 'NestJS API 만들어줘')).toBe(false);
280+
});
281+
});
282+
283+
describe('test-engineer patterns', () => {
284+
it('should match TDD keyword', () => {
285+
expect(matchesDomain('test-engineer', 'TDD로 개발해줘')).toBe(true);
286+
});
287+
288+
it('should match unit test', () => {
289+
expect(matchesDomain('test-engineer', 'unit test 작성')).toBe(true);
290+
});
291+
292+
it('should not match plain React prompt', () => {
293+
expect(matchesDomain('test-engineer', 'React 컴포넌트 개발')).toBe(false);
294+
});
295+
});
296+
297+
describe('mobile-developer patterns', () => {
298+
it('should match React Native', () => {
299+
expect(matchesDomain('mobile-developer', 'React Native 앱')).toBe(true);
300+
});
301+
302+
it('should match Flutter', () => {
303+
expect(matchesDomain('mobile-developer', 'Flutter 위젯')).toBe(true);
304+
});
305+
306+
it('should match SwiftUI', () => {
307+
expect(matchesDomain('mobile-developer', 'SwiftUI 뷰 만들어줘')).toBe(true);
308+
});
309+
310+
it('should match Jetpack Compose', () => {
311+
expect(matchesDomain('mobile-developer', 'Jetpack Compose UI')).toBe(true);
312+
});
313+
314+
it('should match Korean mobile app pattern', () => {
315+
expect(matchesDomain('mobile-developer', '모바일 앱 개발')).toBe(true);
316+
});
317+
318+
it('should not match plain agent prompt', () => {
319+
expect(matchesDomain('mobile-developer', 'agent 설계해줘')).toBe(false);
320+
});
321+
});
322+
323+
describe('backend-developer patterns', () => {
324+
it('should match NestJS', () => {
325+
expect(matchesDomain('backend-developer', 'NestJS 서비스 개발')).toBe(true);
326+
});
327+
328+
it('should match REST API', () => {
329+
expect(matchesDomain('backend-developer', 'REST API 설계')).toBe(true);
330+
});
331+
});
332+
333+
describe('frontend-developer patterns', () => {
334+
it('should match React component', () => {
335+
expect(matchesDomain('frontend-developer', 'React 컴포넌트 만들어줘')).toBe(true);
336+
});
337+
});
338+
339+
describe('devops-engineer patterns', () => {
340+
it('should match Docker compose', () => {
341+
expect(matchesDomain('devops-engineer', 'Docker compose 설정')).toBe(true);
342+
});
343+
344+
it('should match CI/CD', () => {
345+
expect(matchesDomain('devops-engineer', 'CI/CD 파이프라인 구축')).toBe(true);
346+
});
347+
348+
it('should match GitHub Actions', () => {
349+
expect(matchesDomain('devops-engineer', 'GitHub Actions 워크플로우')).toBe(true);
350+
});
351+
});
352+
353+
describe('security-engineer patterns', () => {
354+
it('should match XSS', () => {
355+
expect(matchesDomain('security-engineer', 'XSS 취약점 방지')).toBe(true);
356+
});
357+
});
358+
359+
describe('systems-developer patterns', () => {
360+
it('should match Rust', () => {
361+
expect(matchesDomain('systems-developer', 'Rust 코드 작성')).toBe(true);
362+
});
363+
});
364+
365+
describe('data-engineer patterns', () => {
366+
it('should match database migration', () => {
367+
expect(matchesDomain('data-engineer', '데이터베이스 마이그레이션')).toBe(true);
368+
});
369+
});
370+
371+
describe('data-scientist patterns', () => {
372+
it('should match pandas', () => {
373+
expect(matchesDomain('data-scientist', 'pandas 데이터 분석')).toBe(true);
374+
});
375+
});
376+
377+
describe('ai-ml-engineer patterns', () => {
378+
it('should match LLM', () => {
379+
expect(matchesDomain('ai-ml-engineer', 'LLM 파인튜닝')).toBe(true);
380+
});
381+
});
382+
383+
describe('tooling-engineer patterns', () => {
384+
it('should match webpack', () => {
385+
expect(matchesDomain('tooling-engineer', 'webpack 설정 변경')).toBe(true);
386+
});
387+
});
388+
389+
describe('platform-engineer patterns', () => {
390+
it('should match Kubernetes', () => {
391+
expect(matchesDomain('platform-engineer', 'Kubernetes 클러스터 설정')).toBe(true);
392+
});
393+
});
394+
});
395+
});

0 commit comments

Comments
 (0)