import { describe, it, expect } from 'vitest'; import { SkillRegistry } from './registry.js'; import type { Skill } from './types.js'; /** * Helper to create a Skill with sensible defaults. The `name` convenience * field sets `manifest.name` so callers don't have to nest it every time. */ function makeSkill(overrides: Partial & { name?: string } = {}): Skill { const name = overrides.name ?? overrides.manifest?.name ?? 'test-skill'; return { manifest: { name, description: 'Test skill', version: '1.0.0', tier: 'bundled' as const, ...overrides.manifest, }, instructions: overrides.instructions ?? '# Test\nDo things.', directory: overrides.directory ?? `/fake/path/${name}`, available: overrides.available ?? true, ...overrides, }; } describe('SkillRegistry', () => { // Objective: verify the in-memory skill registry for registration, lookup, // listing, filtering, unregistration, and system prompt generation. it('registers and retrieves a skill by name', () => { // Positive: a registered skill should be returned by get(). const registry = new SkillRegistry(); const skill = makeSkill({ name: 'git' }); registry.register(skill); expect(registry.get('git')).toBe(skill); }); it('returns undefined for an unknown skill', () => { // Negative: looking up a name that was never registered should yield undefined. const registry = new SkillRegistry(); expect(registry.get('nonexistent')).toBeUndefined(); }); it('lists all registered skills', () => { // Positive: list() should return every registered skill regardless of availability. const registry = new SkillRegistry(); const skillA = makeSkill({ name: 'skill-a' }); const skillB = makeSkill({ name: 'skill-b', available: false }); registry.register(skillA); registry.register(skillB); const all = registry.list(); expect(all).toHaveLength(2); expect(all.map((s) => s.manifest.name)).toContain('skill-a'); expect(all.map((s) => s.manifest.name)).toContain('skill-b'); }); it('listAvailable returns only available skills', () => { // Positive: unavailable skills should be filtered out. const registry = new SkillRegistry(); const available = makeSkill({ name: 'available', available: true }); const unavailable = makeSkill({ name: 'unavailable', available: false }); registry.register(available); registry.register(unavailable); const result = registry.listAvailable(); expect(result).toHaveLength(1); expect(result[0].manifest.name).toBe('available'); }); it('replaces an existing skill with the same name', () => { // Positive: re-registering a name should overwrite (managed overrides bundled). const registry = new SkillRegistry(); const original = makeSkill({ name: 'web', instructions: '# Original' }); const replacement = makeSkill({ name: 'web', instructions: '# Replacement' }); registry.register(original); registry.register(replacement); expect(registry.list()).toHaveLength(1); expect(registry.get('web')!.instructions).toBe('# Replacement'); }); it('unregisters a skill by name', () => { // Positive: unregister should remove the skill and return true. const registry = new SkillRegistry(); const skill = makeSkill({ name: 'removable' }); registry.register(skill); const result = registry.unregister('removable'); expect(result).toBe(true); expect(registry.get('removable')).toBeUndefined(); expect(registry.list()).toHaveLength(0); }); it('returns false when unregistering a nonexistent skill', () => { // Negative: unregistering a name that doesn't exist should return false. const registry = new SkillRegistry(); expect(registry.unregister('ghost')).toBe(false); }); it('getSystemPromptAdditions returns empty string with no skills', () => { // Negative: with nothing registered the prompt additions should be empty. const registry = new SkillRegistry(); expect(registry.getSystemPromptAdditions()).toBe(''); }); it('getSystemPromptAdditions formats available skills correctly', () => { // Positive: each available skill should appear as a "## Skill: " section. const registry = new SkillRegistry(); registry.register(makeSkill({ name: 'alpha', instructions: 'Alpha instructions.' })); registry.register(makeSkill({ name: 'beta', instructions: 'Beta instructions.' })); const prompt = registry.getSystemPromptAdditions(); expect(prompt).toContain('## Skill: alpha'); expect(prompt).toContain('Alpha instructions.'); expect(prompt).toContain('## Skill: beta'); expect(prompt).toContain('Beta instructions.'); }); it('getSystemPromptAdditions excludes unavailable skills', () => { // Positive: only available skills contribute to the prompt. const registry = new SkillRegistry(); registry.register(makeSkill({ name: 'enabled', instructions: 'Enabled content.' })); registry.register( makeSkill({ name: 'disabled', instructions: 'Disabled content.', available: false }), ); const prompt = registry.getSystemPromptAdditions(); expect(prompt).toContain('## Skill: enabled'); expect(prompt).not.toContain('## Skill: disabled'); expect(prompt).not.toContain('Disabled content.'); }); it('getSkillNames returns names of available skills only', () => { // Positive: unavailable skills should not appear in the name list. const registry = new SkillRegistry(); registry.register(makeSkill({ name: 'active', available: true })); registry.register(makeSkill({ name: 'dormant', available: false })); const names = registry.getSkillNames(); expect(names).toContain('active'); expect(names).not.toContain('dormant'); }); });