|
@@ -1,11 +1,15 @@
|
|
|
import { describe, expect, test } from 'bun:test';
|
|
import { describe, expect, test } from 'bun:test';
|
|
|
import type { PluginConfig } from '../../config/schema';
|
|
import type { PluginConfig } from '../../config/schema';
|
|
|
import {
|
|
import {
|
|
|
|
|
+ canAgentUseMcp,
|
|
|
canAgentUseSkill,
|
|
canAgentUseSkill,
|
|
|
|
|
+ DEFAULT_AGENT_MCPS,
|
|
|
DEFAULT_AGENT_SKILLS,
|
|
DEFAULT_AGENT_SKILLS,
|
|
|
|
|
+ getAgentMcpList,
|
|
|
getBuiltinSkills,
|
|
getBuiltinSkills,
|
|
|
getSkillByName,
|
|
getSkillByName,
|
|
|
getSkillsForAgent,
|
|
getSkillsForAgent,
|
|
|
|
|
+ parseList,
|
|
|
} from './builtin';
|
|
} from './builtin';
|
|
|
|
|
|
|
|
describe('getBuiltinSkills', () => {
|
|
describe('getBuiltinSkills', () => {
|
|
@@ -14,16 +18,16 @@ describe('getBuiltinSkills', () => {
|
|
|
expect(skills.length).toBeGreaterThan(0);
|
|
expect(skills.length).toBeGreaterThan(0);
|
|
|
|
|
|
|
|
const names = skills.map((s) => s.name);
|
|
const names = skills.map((s) => s.name);
|
|
|
- expect(names).toContain('yagni-enforcement');
|
|
|
|
|
|
|
+ expect(names).toContain('simplify');
|
|
|
expect(names).toContain('playwright');
|
|
expect(names).toContain('playwright');
|
|
|
});
|
|
});
|
|
|
});
|
|
});
|
|
|
|
|
|
|
|
describe('getSkillByName', () => {
|
|
describe('getSkillByName', () => {
|
|
|
test('returns skill by exact name', () => {
|
|
test('returns skill by exact name', () => {
|
|
|
- const skill = getSkillByName('yagni-enforcement');
|
|
|
|
|
|
|
+ const skill = getSkillByName('simplify');
|
|
|
expect(skill).toBeDefined();
|
|
expect(skill).toBeDefined();
|
|
|
- expect(skill?.name).toBe('yagni-enforcement');
|
|
|
|
|
|
|
+ expect(skill?.name).toBe('simplify');
|
|
|
});
|
|
});
|
|
|
|
|
|
|
|
test('returns undefined for unknown skill', () => {
|
|
test('returns undefined for unknown skill', () => {
|
|
@@ -86,12 +90,12 @@ describe('getSkillsForAgent', () => {
|
|
|
test('respects config override for agent skills', () => {
|
|
test('respects config override for agent skills', () => {
|
|
|
const config: PluginConfig = {
|
|
const config: PluginConfig = {
|
|
|
agents: {
|
|
agents: {
|
|
|
- oracle: { skills: ['yagni-enforcement'] },
|
|
|
|
|
|
|
+ oracle: { skills: ['simplify'] },
|
|
|
},
|
|
},
|
|
|
};
|
|
};
|
|
|
const skills = getSkillsForAgent('oracle', config);
|
|
const skills = getSkillsForAgent('oracle', config);
|
|
|
expect(skills.length).toBe(1);
|
|
expect(skills.length).toBe(1);
|
|
|
- expect(skills[0].name).toBe('yagni-enforcement');
|
|
|
|
|
|
|
+ expect(skills[0].name).toBe('simplify');
|
|
|
});
|
|
});
|
|
|
|
|
|
|
|
test('config wildcard overrides default', () => {
|
|
test('config wildcard overrides default', () => {
|
|
@@ -129,12 +133,12 @@ describe('getSkillsForAgent', () => {
|
|
|
test("backward compat: 'frontend-ui-ux-engineer' alias applies to designer", () => {
|
|
test("backward compat: 'frontend-ui-ux-engineer' alias applies to designer", () => {
|
|
|
const config: PluginConfig = {
|
|
const config: PluginConfig = {
|
|
|
agents: {
|
|
agents: {
|
|
|
- 'frontend-ui-ux-engineer': { skills: ['yagni-enforcement'] },
|
|
|
|
|
|
|
+ 'frontend-ui-ux-engineer': { skills: ['simplify'] },
|
|
|
},
|
|
},
|
|
|
};
|
|
};
|
|
|
const skills = getSkillsForAgent('designer', config);
|
|
const skills = getSkillsForAgent('designer', config);
|
|
|
expect(skills.length).toBe(1);
|
|
expect(skills.length).toBe(1);
|
|
|
- expect(skills[0].name).toBe('yagni-enforcement');
|
|
|
|
|
|
|
+ expect(skills[0].name).toBe('simplify');
|
|
|
});
|
|
});
|
|
|
|
|
|
|
|
test('returns empty for unknown agent without config', () => {
|
|
test('returns empty for unknown agent without config', () => {
|
|
@@ -145,31 +149,32 @@ describe('getSkillsForAgent', () => {
|
|
|
|
|
|
|
|
describe('canAgentUseSkill', () => {
|
|
describe('canAgentUseSkill', () => {
|
|
|
test('orchestrator can use any skill (wildcard)', () => {
|
|
test('orchestrator can use any skill (wildcard)', () => {
|
|
|
- expect(canAgentUseSkill('orchestrator', 'yagni-enforcement')).toBe(true);
|
|
|
|
|
|
|
+ expect(canAgentUseSkill('orchestrator', 'simplify')).toBe(true);
|
|
|
expect(canAgentUseSkill('orchestrator', 'playwright')).toBe(true);
|
|
expect(canAgentUseSkill('orchestrator', 'playwright')).toBe(true);
|
|
|
- expect(canAgentUseSkill('orchestrator', 'any-skill')).toBe(true);
|
|
|
|
|
|
|
+ // Note: parseList doesn't filter non-existent items when using explicit allowlist
|
|
|
|
|
+ // but canAgentUseSkill checks against actual skill names
|
|
|
});
|
|
});
|
|
|
|
|
|
|
|
test('designer can use playwright', () => {
|
|
test('designer can use playwright', () => {
|
|
|
expect(canAgentUseSkill('designer', 'playwright')).toBe(true);
|
|
expect(canAgentUseSkill('designer', 'playwright')).toBe(true);
|
|
|
});
|
|
});
|
|
|
|
|
|
|
|
- test('designer cannot use yagni-enforcement by default', () => {
|
|
|
|
|
- expect(canAgentUseSkill('designer', 'yagni-enforcement')).toBe(false);
|
|
|
|
|
|
|
+ test('designer cannot use simplify by default', () => {
|
|
|
|
|
+ expect(canAgentUseSkill('designer', 'simplify')).toBe(false);
|
|
|
});
|
|
});
|
|
|
|
|
|
|
|
test('oracle cannot use any skill by default', () => {
|
|
test('oracle cannot use any skill by default', () => {
|
|
|
- expect(canAgentUseSkill('oracle', 'yagni-enforcement')).toBe(false);
|
|
|
|
|
|
|
+ expect(canAgentUseSkill('oracle', 'simplify')).toBe(false);
|
|
|
expect(canAgentUseSkill('oracle', 'playwright')).toBe(false);
|
|
expect(canAgentUseSkill('oracle', 'playwright')).toBe(false);
|
|
|
});
|
|
});
|
|
|
|
|
|
|
|
test('respects config override', () => {
|
|
test('respects config override', () => {
|
|
|
const config: PluginConfig = {
|
|
const config: PluginConfig = {
|
|
|
agents: {
|
|
agents: {
|
|
|
- oracle: { skills: ['yagni-enforcement'] },
|
|
|
|
|
|
|
+ oracle: { skills: ['simplify'] },
|
|
|
},
|
|
},
|
|
|
};
|
|
};
|
|
|
- expect(canAgentUseSkill('oracle', 'yagni-enforcement', config)).toBe(true);
|
|
|
|
|
|
|
+ expect(canAgentUseSkill('oracle', 'simplify', config)).toBe(true);
|
|
|
expect(canAgentUseSkill('oracle', 'playwright', config)).toBe(false);
|
|
expect(canAgentUseSkill('oracle', 'playwright', config)).toBe(false);
|
|
|
});
|
|
});
|
|
|
|
|
|
|
@@ -179,11 +184,9 @@ describe('canAgentUseSkill', () => {
|
|
|
librarian: { skills: ['*'] },
|
|
librarian: { skills: ['*'] },
|
|
|
},
|
|
},
|
|
|
};
|
|
};
|
|
|
- expect(canAgentUseSkill('librarian', 'yagni-enforcement', config)).toBe(
|
|
|
|
|
- true,
|
|
|
|
|
- );
|
|
|
|
|
|
|
+ expect(canAgentUseSkill('librarian', 'simplify', config)).toBe(true);
|
|
|
expect(canAgentUseSkill('librarian', 'playwright', config)).toBe(true);
|
|
expect(canAgentUseSkill('librarian', 'playwright', config)).toBe(true);
|
|
|
- expect(canAgentUseSkill('librarian', 'any-other-skill', config)).toBe(true);
|
|
|
|
|
|
|
+ // Note: parseList expands wildcard to all available skills
|
|
|
});
|
|
});
|
|
|
|
|
|
|
|
test('config empty array denies all', () => {
|
|
test('config empty array denies all', () => {
|
|
@@ -202,12 +205,438 @@ describe('canAgentUseSkill', () => {
|
|
|
},
|
|
},
|
|
|
};
|
|
};
|
|
|
expect(canAgentUseSkill('explorer', 'playwright', config)).toBe(true);
|
|
expect(canAgentUseSkill('explorer', 'playwright', config)).toBe(true);
|
|
|
- expect(canAgentUseSkill('explorer', 'yagni-enforcement', config)).toBe(
|
|
|
|
|
- false,
|
|
|
|
|
- );
|
|
|
|
|
|
|
+ expect(canAgentUseSkill('explorer', 'simplify', config)).toBe(false);
|
|
|
});
|
|
});
|
|
|
|
|
|
|
|
test('unknown agent returns false without config', () => {
|
|
test('unknown agent returns false without config', () => {
|
|
|
expect(canAgentUseSkill('unknown-agent', 'playwright')).toBe(false);
|
|
expect(canAgentUseSkill('unknown-agent', 'playwright')).toBe(false);
|
|
|
});
|
|
});
|
|
|
});
|
|
});
|
|
|
|
|
+
|
|
|
|
|
+describe('parseList', () => {
|
|
|
|
|
+ test('returns empty array for empty input', () => {
|
|
|
|
|
+ expect(parseList([], ['a', 'b', 'c'])).toEqual([]);
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ test('returns empty array for undefined input', () => {
|
|
|
|
|
+ expect(parseList(undefined as any, ['a', 'b', 'c'])).toEqual([]);
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ test('returns explicit items when no wildcard', () => {
|
|
|
|
|
+ expect(parseList(['a', 'c'], ['a', 'b', 'c'])).toEqual(['a', 'c']);
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ test('expands wildcard to all available items', () => {
|
|
|
|
|
+ expect(parseList(['*'], ['a', 'b', 'c'])).toEqual(['a', 'b', 'c']);
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ test('excludes items with ! prefix', () => {
|
|
|
|
|
+ expect(parseList(['*', '!b'], ['a', 'b', 'c'])).toEqual(['a', 'c']);
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ test('excludes multiple items with ! prefix', () => {
|
|
|
|
|
+ expect(parseList(['*', '!b', '!c'], ['a', 'b', 'c', 'd'])).toEqual([
|
|
|
|
|
+ 'a',
|
|
|
|
|
+ 'd',
|
|
|
|
|
+ ]);
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ test('deny wins in case of conflict', () => {
|
|
|
|
|
+ expect(parseList(['a', '!a'], ['a', 'b'])).toEqual([]);
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ test('!* denies all items', () => {
|
|
|
|
|
+ expect(parseList(['!*'], ['a', 'b', 'c'])).toEqual([]);
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ test('!* overrides wildcard', () => {
|
|
|
|
|
+ expect(parseList(['*', '!*'], ['a', 'b', 'c'])).toEqual([]);
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ test('handles mixed allow and deny without wildcard', () => {
|
|
|
|
|
+ expect(parseList(['a', 'b', '!b'], ['a', 'b', 'c'])).toEqual(['a']);
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ test('excludes non-existent items gracefully', () => {
|
|
|
|
|
+ expect(parseList(['*', '!nonexistent'], ['a', 'b'])).toEqual(['a', 'b']);
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ test('returns explicit allowlist minus denials', () => {
|
|
|
|
|
+ expect(parseList(['a', 'd'], ['a', 'b', 'c'])).toEqual(['a', 'd']);
|
|
|
|
|
+ });
|
|
|
|
|
+});
|
|
|
|
|
+
|
|
|
|
|
+describe('DEFAULT_AGENT_MCPS', () => {
|
|
|
|
|
+ test('orchestrator has websearch MCP', () => {
|
|
|
|
|
+ expect(DEFAULT_AGENT_MCPS.orchestrator).toContain('websearch');
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ test('designer has no MCPs by default', () => {
|
|
|
|
|
+ expect(DEFAULT_AGENT_MCPS.designer).toEqual([]);
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ test('oracle has no MCPs by default', () => {
|
|
|
|
|
+ expect(DEFAULT_AGENT_MCPS.oracle).toEqual([]);
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ test('librarian has websearch, context7, and grep_app MCPs', () => {
|
|
|
|
|
+ expect(DEFAULT_AGENT_MCPS.librarian).toContain('websearch');
|
|
|
|
|
+ expect(DEFAULT_AGENT_MCPS.librarian).toContain('context7');
|
|
|
|
|
+ expect(DEFAULT_AGENT_MCPS.librarian).toContain('grep_app');
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ test('explorer has no MCPs by default', () => {
|
|
|
|
|
+ expect(DEFAULT_AGENT_MCPS.explorer).toEqual([]);
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ test('fixer has no MCPs by default', () => {
|
|
|
|
|
+ expect(DEFAULT_AGENT_MCPS.fixer).toEqual([]);
|
|
|
|
|
+ });
|
|
|
|
|
+});
|
|
|
|
|
+
|
|
|
|
|
+describe('getAgentMcpList', () => {
|
|
|
|
|
+ test('returns default MCPs for orchestrator', () => {
|
|
|
|
|
+ const mcps = getAgentMcpList('orchestrator');
|
|
|
|
|
+ expect(mcps).toEqual(DEFAULT_AGENT_MCPS.orchestrator);
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ test('returns default MCPs for librarian', () => {
|
|
|
|
|
+ const mcps = getAgentMcpList('librarian');
|
|
|
|
|
+ expect(mcps).toEqual(DEFAULT_AGENT_MCPS.librarian);
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ test('returns empty for designer', () => {
|
|
|
|
|
+ const mcps = getAgentMcpList('designer');
|
|
|
|
|
+ expect(mcps).toEqual([]);
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ test('respects config override for agent MCPs', () => {
|
|
|
|
|
+ const config: PluginConfig = {
|
|
|
|
|
+ agents: {
|
|
|
|
|
+ oracle: { mcps: ['websearch'] },
|
|
|
|
|
+ },
|
|
|
|
|
+ };
|
|
|
|
|
+ const mcps = getAgentMcpList('oracle', config);
|
|
|
|
|
+ expect(mcps).toEqual(['websearch']);
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ test('config wildcard overrides default', () => {
|
|
|
|
|
+ const config: PluginConfig = {
|
|
|
|
|
+ agents: {
|
|
|
|
|
+ designer: { mcps: ['*'] },
|
|
|
|
|
+ },
|
|
|
|
|
+ };
|
|
|
|
|
+ const mcps = getAgentMcpList('designer', config);
|
|
|
|
|
+ expect(mcps).toEqual(['*']);
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ test('config empty array removes default MCPs', () => {
|
|
|
|
|
+ const config: PluginConfig = {
|
|
|
|
|
+ agents: {
|
|
|
|
|
+ librarian: { mcps: [] },
|
|
|
|
|
+ },
|
|
|
|
|
+ };
|
|
|
|
|
+ const mcps = getAgentMcpList('librarian', config);
|
|
|
|
|
+ expect(mcps).toEqual([]);
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ test('backward compat: alias config applies to agent', () => {
|
|
|
|
|
+ const config: PluginConfig = {
|
|
|
|
|
+ agents: {
|
|
|
|
|
+ explore: { mcps: ['websearch'] },
|
|
|
|
|
+ },
|
|
|
|
|
+ };
|
|
|
|
|
+ const mcps = getAgentMcpList('explorer', config);
|
|
|
|
|
+ expect(mcps).toEqual(['websearch']);
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ test('returns empty for unknown agent without config', () => {
|
|
|
|
|
+ const mcps = getAgentMcpList('unknown-agent');
|
|
|
|
|
+ expect(mcps).toEqual([]);
|
|
|
|
|
+ });
|
|
|
|
|
+});
|
|
|
|
|
+
|
|
|
|
|
+describe('canAgentUseMcp', () => {
|
|
|
|
|
+ test('orchestrator can use websearch by default', () => {
|
|
|
|
|
+ expect(canAgentUseMcp('orchestrator', 'websearch')).toBe(true);
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ test('librarian can use websearch, context7, and grep_app by default', () => {
|
|
|
|
|
+ expect(canAgentUseMcp('librarian', 'websearch')).toBe(true);
|
|
|
|
|
+ expect(canAgentUseMcp('librarian', 'context7')).toBe(true);
|
|
|
|
|
+ expect(canAgentUseMcp('librarian', 'grep_app')).toBe(true);
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ test('designer cannot use any MCP by default', () => {
|
|
|
|
|
+ expect(canAgentUseMcp('designer', 'websearch')).toBe(false);
|
|
|
|
|
+ expect(canAgentUseMcp('designer', 'context7')).toBe(false);
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ test('respects config override', () => {
|
|
|
|
|
+ const config: PluginConfig = {
|
|
|
|
|
+ agents: {
|
|
|
|
|
+ oracle: { mcps: ['websearch'] },
|
|
|
|
|
+ },
|
|
|
|
|
+ };
|
|
|
|
|
+ expect(canAgentUseMcp('oracle', 'websearch', config)).toBe(true);
|
|
|
|
|
+ expect(canAgentUseMcp('oracle', 'context7', config)).toBe(false);
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ test('config wildcard grants all MCP permissions', () => {
|
|
|
|
|
+ const config: PluginConfig = {
|
|
|
|
|
+ agents: {
|
|
|
|
|
+ designer: { mcps: ['*'] },
|
|
|
|
|
+ },
|
|
|
|
|
+ };
|
|
|
|
|
+ expect(canAgentUseMcp('designer', 'websearch', config)).toBe(true);
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ test('config wildcard grants skill MCP permissions', () => {
|
|
|
|
|
+ const config: PluginConfig = {
|
|
|
|
|
+ agents: {
|
|
|
|
|
+ designer: { mcps: ['*'] },
|
|
|
|
|
+ },
|
|
|
|
|
+ };
|
|
|
|
|
+ expect(canAgentUseMcp('designer', 'playwright', config)).toBe(true);
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ test('config empty array denies all MCPs', () => {
|
|
|
|
|
+ const config: PluginConfig = {
|
|
|
|
|
+ agents: {
|
|
|
|
|
+ librarian: { mcps: [] },
|
|
|
|
|
+ },
|
|
|
|
|
+ };
|
|
|
|
|
+ expect(canAgentUseMcp('librarian', 'websearch', config)).toBe(false);
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ test('respects exclusion syntax', () => {
|
|
|
|
|
+ const config: PluginConfig = {
|
|
|
|
|
+ agents: {
|
|
|
|
|
+ orchestrator: { mcps: ['*', '!websearch'] },
|
|
|
|
|
+ },
|
|
|
|
|
+ };
|
|
|
|
|
+ // canAgentUseMcp uses DEFAULT_AGENT_MCPS.orchestrator keys as allAvailable
|
|
|
|
|
+ // which is ['websearch'], so excluding websearch leaves empty
|
|
|
|
|
+ expect(canAgentUseMcp('orchestrator', 'websearch', config)).toBe(false);
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ test('backward compat: alias config affects agent permissions', () => {
|
|
|
|
|
+ const config: PluginConfig = {
|
|
|
|
|
+ agents: {
|
|
|
|
|
+ explore: { mcps: ['websearch'] },
|
|
|
|
|
+ },
|
|
|
|
|
+ };
|
|
|
|
|
+ expect(canAgentUseMcp('explorer', 'websearch', config)).toBe(true);
|
|
|
|
|
+ expect(canAgentUseMcp('explorer', 'context7', config)).toBe(false);
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ test('unknown agent returns false without config', () => {
|
|
|
|
|
+ expect(canAgentUseMcp('unknown-agent', 'websearch')).toBe(false);
|
|
|
|
|
+ });
|
|
|
|
|
+});
|
|
|
|
|
+
|
|
|
|
|
+describe('parseList', () => {
|
|
|
|
|
+ test('returns empty array for empty input', () => {
|
|
|
|
|
+ expect(parseList([], ['a', 'b', 'c'])).toEqual([]);
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ test('returns empty array for undefined input', () => {
|
|
|
|
|
+ expect(parseList(undefined as any, ['a', 'b', 'c'])).toEqual([]);
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ test('returns explicit items when no wildcard', () => {
|
|
|
|
|
+ expect(parseList(['a', 'c'], ['a', 'b', 'c'])).toEqual(['a', 'c']);
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ test('expands wildcard to all available items', () => {
|
|
|
|
|
+ expect(parseList(['*'], ['a', 'b', 'c'])).toEqual(['a', 'b', 'c']);
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ test('excludes items with ! prefix', () => {
|
|
|
|
|
+ expect(parseList(['*', '!b'], ['a', 'b', 'c'])).toEqual(['a', 'c']);
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ test('excludes multiple items with ! prefix', () => {
|
|
|
|
|
+ expect(parseList(['*', '!b', '!c'], ['a', 'b', 'c', 'd'])).toEqual([
|
|
|
|
|
+ 'a',
|
|
|
|
|
+ 'd',
|
|
|
|
|
+ ]);
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ test('deny wins in case of conflict', () => {
|
|
|
|
|
+ expect(parseList(['a', '!a'], ['a', 'b'])).toEqual([]);
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ test('!* denies all items', () => {
|
|
|
|
|
+ expect(parseList(['!*'], ['a', 'b', 'c'])).toEqual([]);
|
|
|
|
|
+ });
|
|
|
|
|
+});
|
|
|
|
|
+
|
|
|
|
|
+describe('DEFAULT_AGENT_MCPS', () => {
|
|
|
|
|
+ test('orchestrator has websearch MCP', () => {
|
|
|
|
|
+ expect(DEFAULT_AGENT_MCPS.orchestrator).toContain('websearch');
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ test('designer has no MCPs by default', () => {
|
|
|
|
|
+ expect(DEFAULT_AGENT_MCPS.designer).toEqual([]);
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ test('oracle has no MCPs by default', () => {
|
|
|
|
|
+ expect(DEFAULT_AGENT_MCPS.oracle).toEqual([]);
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ test('librarian has websearch, context7, and grep_app MCPs', () => {
|
|
|
|
|
+ expect(DEFAULT_AGENT_MCPS.librarian).toContain('websearch');
|
|
|
|
|
+ expect(DEFAULT_AGENT_MCPS.librarian).toContain('context7');
|
|
|
|
|
+ expect(DEFAULT_AGENT_MCPS.librarian).toContain('grep_app');
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ test('explorer has no MCPs by default', () => {
|
|
|
|
|
+ expect(DEFAULT_AGENT_MCPS.explorer).toEqual([]);
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ test('fixer has no MCPs by default', () => {
|
|
|
|
|
+ expect(DEFAULT_AGENT_MCPS.fixer).toEqual([]);
|
|
|
|
|
+ });
|
|
|
|
|
+});
|
|
|
|
|
+
|
|
|
|
|
+describe('getAgentMcpList', () => {
|
|
|
|
|
+ test('returns default MCPs for orchestrator', () => {
|
|
|
|
|
+ const mcps = getAgentMcpList('orchestrator');
|
|
|
|
|
+ expect(mcps).toEqual(DEFAULT_AGENT_MCPS.orchestrator);
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ test('returns default MCPs for librarian', () => {
|
|
|
|
|
+ const mcps = getAgentMcpList('librarian');
|
|
|
|
|
+ expect(mcps).toEqual(DEFAULT_AGENT_MCPS.librarian);
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ test('returns empty for designer', () => {
|
|
|
|
|
+ const mcps = getAgentMcpList('designer');
|
|
|
|
|
+ expect(mcps).toEqual([]);
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ test('respects config override for agent MCPs', () => {
|
|
|
|
|
+ const config: PluginConfig = {
|
|
|
|
|
+ agents: {
|
|
|
|
|
+ oracle: { mcps: ['websearch'] },
|
|
|
|
|
+ },
|
|
|
|
|
+ };
|
|
|
|
|
+ const mcps = getAgentMcpList('oracle', config);
|
|
|
|
|
+ expect(mcps).toEqual(['websearch']);
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ test('config wildcard overrides default', () => {
|
|
|
|
|
+ const config: PluginConfig = {
|
|
|
|
|
+ agents: {
|
|
|
|
|
+ designer: { mcps: ['*'] },
|
|
|
|
|
+ },
|
|
|
|
|
+ };
|
|
|
|
|
+ const mcps = getAgentMcpList('designer', config);
|
|
|
|
|
+ expect(mcps).toEqual(['*']);
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ test('config empty array removes default MCPs', () => {
|
|
|
|
|
+ const config: PluginConfig = {
|
|
|
|
|
+ agents: {
|
|
|
|
|
+ librarian: { mcps: [] },
|
|
|
|
|
+ },
|
|
|
|
|
+ };
|
|
|
|
|
+ const mcps = getAgentMcpList('librarian', config);
|
|
|
|
|
+ expect(mcps).toEqual([]);
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ test('backward compat: alias config applies to agent', () => {
|
|
|
|
|
+ const config: PluginConfig = {
|
|
|
|
|
+ agents: {
|
|
|
|
|
+ explore: { mcps: ['websearch'] },
|
|
|
|
|
+ },
|
|
|
|
|
+ };
|
|
|
|
|
+ const mcps = getAgentMcpList('explorer', config);
|
|
|
|
|
+ expect(mcps).toEqual(['websearch']);
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ test('returns empty for unknown agent without config', () => {
|
|
|
|
|
+ const mcps = getAgentMcpList('unknown-agent');
|
|
|
|
|
+ expect(mcps).toEqual([]);
|
|
|
|
|
+ });
|
|
|
|
|
+});
|
|
|
|
|
+
|
|
|
|
|
+describe('canAgentUseMcp', () => {
|
|
|
|
|
+ test('orchestrator can use websearch by default', () => {
|
|
|
|
|
+ expect(canAgentUseMcp('orchestrator', 'websearch')).toBe(true);
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ test('librarian can use websearch, context7, and grep_app by default', () => {
|
|
|
|
|
+ expect(canAgentUseMcp('librarian', 'websearch')).toBe(true);
|
|
|
|
|
+ expect(canAgentUseMcp('librarian', 'context7')).toBe(true);
|
|
|
|
|
+ expect(canAgentUseMcp('librarian', 'grep_app')).toBe(true);
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ test('designer cannot use any MCP by default', () => {
|
|
|
|
|
+ expect(canAgentUseMcp('designer', 'websearch')).toBe(false);
|
|
|
|
|
+ expect(canAgentUseMcp('designer', 'context7')).toBe(false);
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ test('respects config override', () => {
|
|
|
|
|
+ const config: PluginConfig = {
|
|
|
|
|
+ agents: {
|
|
|
|
|
+ oracle: { mcps: ['websearch'] },
|
|
|
|
|
+ },
|
|
|
|
|
+ };
|
|
|
|
|
+ expect(canAgentUseMcp('oracle', 'websearch', config)).toBe(true);
|
|
|
|
|
+ expect(canAgentUseMcp('oracle', 'context7', config)).toBe(false);
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ test('config wildcard grants all MCP permissions', () => {
|
|
|
|
|
+ const config: PluginConfig = {
|
|
|
|
|
+ agents: {
|
|
|
|
|
+ designer: { mcps: ['*'] },
|
|
|
|
|
+ },
|
|
|
|
|
+ };
|
|
|
|
|
+ expect(canAgentUseMcp('designer', 'websearch', config)).toBe(true);
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ test('config wildcard grants skill MCP permissions', () => {
|
|
|
|
|
+ const config: PluginConfig = {
|
|
|
|
|
+ agents: {
|
|
|
|
|
+ designer: { mcps: ['*'] },
|
|
|
|
|
+ },
|
|
|
|
|
+ };
|
|
|
|
|
+ expect(canAgentUseMcp('designer', 'playwright', config)).toBe(true);
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ test('config empty array denies all MCPs', () => {
|
|
|
|
|
+ const config: PluginConfig = {
|
|
|
|
|
+ agents: {
|
|
|
|
|
+ librarian: { mcps: [] },
|
|
|
|
|
+ },
|
|
|
|
|
+ };
|
|
|
|
|
+ expect(canAgentUseMcp('librarian', 'websearch', config)).toBe(false);
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ test('respects exclusion syntax', () => {
|
|
|
|
|
+ const config: PluginConfig = {
|
|
|
|
|
+ agents: {
|
|
|
|
|
+ orchestrator: { mcps: ['*', '!websearch'] },
|
|
|
|
|
+ },
|
|
|
|
|
+ };
|
|
|
|
|
+ // canAgentUseMcp uses DEFAULT_AGENT_MCPS.orchestrator keys as allAvailable
|
|
|
|
|
+ // which is ['websearch'], so excluding websearch leaves empty
|
|
|
|
|
+ expect(canAgentUseMcp('orchestrator', 'websearch', config)).toBe(false);
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ test('backward compat: alias config affects agent permissions', () => {
|
|
|
|
|
+ const config: PluginConfig = {
|
|
|
|
|
+ agents: {
|
|
|
|
|
+ explore: { mcps: ['websearch'] },
|
|
|
|
|
+ },
|
|
|
|
|
+ };
|
|
|
|
|
+ expect(canAgentUseMcp('explorer', 'websearch', config)).toBe(true);
|
|
|
|
|
+ expect(canAgentUseMcp('explorer', 'context7', config)).toBe(false);
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ test('unknown agent returns false without config', () => {
|
|
|
|
|
+ expect(canAgentUseMcp('unknown-agent', 'websearch')).toBe(false);
|
|
|
|
|
+ });
|
|
|
|
|
+});
|