Skip to main content
Glama
packSkill.test.ts15.5 kB
import { describe, expect, test, vi } from 'vitest'; import type { ProcessedFile } from '../../../src/core/file/fileTypes.js'; import { generateSkillMdFromReferences, generateSkillReferences, type PackSkillParams, packSkill, type SkillReferencesResult, } from '../../../src/core/skill/packSkill.js'; import { createMockConfig } from '../../testing/testUtils.js'; // Mock processed files const createMockProcessedFiles = (): ProcessedFile[] => [ { path: 'src/index.ts', content: 'console.log("hello");' }, { path: 'src/utils.ts', content: 'export const add = (a, b) => a + b;' }, { path: 'package.json', content: '{"name": "test", "dependencies": {"react": "^18.0.0"}}' }, ]; describe('packSkill', () => { describe('generateSkillReferences', () => { test('should generate all reference sections with valid data', async () => { const mockConfig = createMockConfig(); const mockFiles = createMockProcessedFiles(); const mockDeps = { buildOutputGeneratorContext: vi.fn().mockResolvedValue({ config: mockConfig, generationDate: new Date().toISOString(), treeString: 'src/\n index.ts\n utils.ts', processedFiles: mockFiles, instruction: '', }), sortOutputFiles: vi.fn().mockResolvedValue(mockFiles), }; const result = await generateSkillReferences( 'test-skill', ['/test/project'], mockConfig, mockFiles, ['src/index.ts', 'src/utils.ts', 'package.json'], undefined, undefined, undefined, undefined, mockDeps, ); expect(result.skillName).toBe('test-skill'); expect(result.projectName).toBeTruthy(); expect(result.skillDescription).toContain('Reference codebase'); expect(result.totalFiles).toBe(3); expect(result.references.summary).toBeTruthy(); expect(result.references.structure).toBeTruthy(); expect(result.references.files).toBeTruthy(); }); test('should normalize skill name', async () => { const mockConfig = createMockConfig(); const mockFiles = createMockProcessedFiles(); const mockDeps = { buildOutputGeneratorContext: vi.fn().mockResolvedValue({ config: mockConfig, generationDate: new Date().toISOString(), treeString: '', processedFiles: mockFiles, instruction: '', }), sortOutputFiles: vi.fn().mockResolvedValue(mockFiles), }; const result = await generateSkillReferences( 'MyTestSkill', ['/test/project'], mockConfig, mockFiles, [], undefined, undefined, undefined, undefined, mockDeps, ); expect(result.skillName).toBe('my-test-skill'); }); test('should use provided skillProjectName when available', async () => { const mockConfig = createMockConfig(); const mockFiles = createMockProcessedFiles(); const mockDeps = { buildOutputGeneratorContext: vi.fn().mockResolvedValue({ config: mockConfig, generationDate: new Date().toISOString(), treeString: '', processedFiles: mockFiles, instruction: '', }), sortOutputFiles: vi.fn().mockResolvedValue(mockFiles), }; const result = await generateSkillReferences( 'test-skill', ['/tmp/repomix-abc123'], // Temp directory that would generate bad name mockConfig, mockFiles, [], undefined, undefined, 'Vite', // Provided skillProjectName undefined, mockDeps, ); expect(result.projectName).toBe('Vite'); expect(result.skillDescription).toContain('Vite'); }); test('should detect tech stack when available', async () => { const mockConfig = createMockConfig(); const mockFiles: ProcessedFile[] = [{ path: 'package.json', content: '{"dependencies": {"react": "^18.0.0"}}' }]; const mockDeps = { buildOutputGeneratorContext: vi.fn().mockResolvedValue({ config: mockConfig, generationDate: new Date().toISOString(), treeString: '', processedFiles: mockFiles, instruction: '', }), sortOutputFiles: vi.fn().mockResolvedValue(mockFiles), }; const result = await generateSkillReferences( 'test-skill', ['/test/project'], mockConfig, mockFiles, ['package.json'], undefined, undefined, undefined, undefined, mockDeps, ); expect(result.hasTechStack).toBe(true); expect(result.references.techStack).toBeTruthy(); }); test('should handle empty processed files', async () => { const mockConfig = createMockConfig(); const mockFiles: ProcessedFile[] = []; const mockDeps = { buildOutputGeneratorContext: vi.fn().mockResolvedValue({ config: mockConfig, generationDate: new Date().toISOString(), treeString: '', processedFiles: mockFiles, instruction: '', }), sortOutputFiles: vi.fn().mockResolvedValue(mockFiles), }; const result = await generateSkillReferences( 'test-skill', ['/test/project'], mockConfig, mockFiles, [], undefined, undefined, undefined, undefined, mockDeps, ); expect(result.totalFiles).toBe(0); expect(result.totalLines).toBe(0); }); }); describe('generateSkillMdFromReferences', () => { test('should generate SKILL.md with all metadata', () => { const referencesResult: SkillReferencesResult = { references: { summary: 'Summary content', structure: 'Structure content', files: 'Files content', techStack: 'Tech stack content', }, skillName: 'test-skill', projectName: 'Test Project', skillDescription: 'Test description', totalFiles: 10, totalLines: 500, statisticsSection: 'Statistics', hasTechStack: true, }; const result = generateSkillMdFromReferences(referencesResult, 1000); expect(result.skillMd).toContain('test-skill'); expect(result.skillMd).toContain('Test Project'); expect(result.skillMd).toContain('10 files'); expect(result.skillMd).toContain('500 lines'); expect(result.skillMd).toContain('1000 tokens'); expect(result.references).toBe(referencesResult.references); }); test('should handle hasTechStack false', () => { const referencesResult: SkillReferencesResult = { references: { summary: 'Summary content', structure: 'Structure content', files: 'Files content', }, skillName: 'test-skill', projectName: 'Test Project', skillDescription: 'Test description', totalFiles: 5, totalLines: 100, statisticsSection: 'Statistics', hasTechStack: false, }; const result = generateSkillMdFromReferences(referencesResult, 500); expect(result.skillMd).toContain('test-skill'); expect(result.references.techStack).toBeUndefined(); }); test('should include source URL when provided', () => { const referencesResult: SkillReferencesResult = { references: { summary: 'Summary content', structure: 'Structure content', files: 'Files content', }, skillName: 'test-skill', projectName: 'Test Project', skillDescription: 'Test description', totalFiles: 5, totalLines: 100, statisticsSection: 'Statistics', hasTechStack: false, sourceUrl: 'https://github.com/vitejs/vite', }; const result = generateSkillMdFromReferences(referencesResult, 500); expect(result.skillMd).toContain('https://github.com/vitejs/vite'); expect(result.skillMd).toContain('from [Test Project](https://github.com/vitejs/vite)'); }); test('should not include source URL when not provided', () => { const referencesResult: SkillReferencesResult = { references: { summary: 'Summary content', structure: 'Structure content', files: 'Files content', }, skillName: 'test-skill', projectName: 'Test Project', skillDescription: 'Test description', totalFiles: 5, totalLines: 100, statisticsSection: 'Statistics', hasTechStack: false, }; const result = generateSkillMdFromReferences(referencesResult, 500); expect(result.skillMd).toContain('This skill was generated by [Repomix]'); expect(result.skillMd).not.toContain('from ['); }); }); describe('packSkill', () => { test('should throw error when skillDir is missing', async () => { const mockConfig = createMockConfig({ skillGenerate: 'test-skill' }); const mockFiles = createMockProcessedFiles(); const params: PackSkillParams = { rootDirs: ['/test/project'], config: mockConfig, options: { skillName: 'test-skill' }, // skillDir is missing processedFiles: mockFiles, allFilePaths: ['src/index.ts'], gitDiffResult: undefined, gitLogResult: undefined, suspiciousFilesResults: [], suspiciousGitDiffResults: [], suspiciousGitLogResults: [], safeFilePaths: ['src/index.ts'], skippedFiles: [], progressCallback: vi.fn(), }; const mockDeps = { buildOutputGeneratorContext: vi.fn().mockResolvedValue({ config: mockConfig, generationDate: new Date().toISOString(), treeString: '', processedFiles: mockFiles, instruction: '', }), sortOutputFiles: vi.fn().mockResolvedValue(mockFiles), calculateMetrics: vi.fn().mockResolvedValue({ totalFiles: 1, totalCharacters: 100, totalTokens: 50, }), writeSkillOutput: vi.fn().mockResolvedValue('/test/skill'), generateDefaultSkillName: vi.fn().mockReturnValue('test-skill'), }; await expect(packSkill(params, mockDeps)).rejects.toThrow('skillDir is required for skill generation'); }); test('should generate complete skill package', async () => { const mockConfig = createMockConfig({ skillGenerate: 'test-skill' }); const mockFiles = createMockProcessedFiles(); const params: PackSkillParams = { rootDirs: ['/test/project'], config: mockConfig, options: { skillName: 'test-skill', skillDir: '/test/.claude/skills/test-skill' }, processedFiles: mockFiles, allFilePaths: ['src/index.ts', 'src/utils.ts', 'package.json'], gitDiffResult: undefined, gitLogResult: undefined, suspiciousFilesResults: [], suspiciousGitDiffResults: [], suspiciousGitLogResults: [], safeFilePaths: ['src/index.ts', 'src/utils.ts', 'package.json'], skippedFiles: [], progressCallback: vi.fn(), }; const mockDeps = { buildOutputGeneratorContext: vi.fn().mockResolvedValue({ config: mockConfig, generationDate: new Date().toISOString(), treeString: 'src/\n index.ts\n utils.ts', processedFiles: mockFiles, instruction: '', }), sortOutputFiles: vi.fn().mockResolvedValue(mockFiles), calculateMetrics: vi.fn().mockResolvedValue({ totalFiles: 3, totalCharacters: 500, totalTokens: 100, }), writeSkillOutput: vi.fn().mockResolvedValue('/test/.claude/skills/test-skill'), generateDefaultSkillName: vi.fn().mockReturnValue('test-skill'), }; const result = await packSkill(params, mockDeps); expect(result.totalFiles).toBe(3); expect(result.totalTokens).toBe(100); expect(mockDeps.writeSkillOutput).toHaveBeenCalled(); expect(params.progressCallback).toHaveBeenCalledWith('Writing skill output...'); }); test('should use config.skillGenerate as skill name when string', async () => { const mockConfig = createMockConfig({ skillGenerate: 'custom-skill-name' }); const mockFiles = createMockProcessedFiles(); const params: PackSkillParams = { rootDirs: ['/test/project'], config: mockConfig, options: { skillDir: '/test/.claude/skills/custom-skill-name' }, // No skillName processedFiles: mockFiles, allFilePaths: [], gitDiffResult: undefined, gitLogResult: undefined, suspiciousFilesResults: [], suspiciousGitDiffResults: [], suspiciousGitLogResults: [], safeFilePaths: [], skippedFiles: [], progressCallback: vi.fn(), }; const mockDeps = { buildOutputGeneratorContext: vi.fn().mockResolvedValue({ config: mockConfig, generationDate: new Date().toISOString(), treeString: '', processedFiles: mockFiles, instruction: '', }), sortOutputFiles: vi.fn().mockResolvedValue(mockFiles), calculateMetrics: vi.fn().mockResolvedValue({ totalFiles: 0, totalCharacters: 0, totalTokens: 0, }), writeSkillOutput: vi.fn().mockResolvedValue('/test/.claude/skills/custom-skill-name'), generateDefaultSkillName: vi.fn().mockReturnValue('default-name'), }; await packSkill(params, mockDeps); // generateDefaultSkillName should NOT be called since config.skillGenerate is a string expect(mockDeps.generateDefaultSkillName).not.toHaveBeenCalled(); }); test('should generate default skill name when skillGenerate is boolean', async () => { const mockConfig = createMockConfig({ skillGenerate: true }); const mockFiles = createMockProcessedFiles(); const params: PackSkillParams = { rootDirs: ['/test/project'], config: mockConfig, options: { skillDir: '/test/.claude/skills/generated-name' }, // No skillName processedFiles: mockFiles, allFilePaths: [], gitDiffResult: undefined, gitLogResult: undefined, suspiciousFilesResults: [], suspiciousGitDiffResults: [], suspiciousGitLogResults: [], safeFilePaths: [], skippedFiles: [], progressCallback: vi.fn(), }; const mockDeps = { buildOutputGeneratorContext: vi.fn().mockResolvedValue({ config: mockConfig, generationDate: new Date().toISOString(), treeString: '', processedFiles: mockFiles, instruction: '', }), sortOutputFiles: vi.fn().mockResolvedValue(mockFiles), calculateMetrics: vi.fn().mockResolvedValue({ totalFiles: 0, totalCharacters: 0, totalTokens: 0, }), writeSkillOutput: vi.fn().mockResolvedValue('/test/.claude/skills/generated-name'), generateDefaultSkillName: vi.fn().mockReturnValue('repomix-reference-project'), }; await packSkill(params, mockDeps); // generateDefaultSkillName SHOULD be called since config.skillGenerate is boolean expect(mockDeps.generateDefaultSkillName).toHaveBeenCalledWith(['/test/project']); }); }); });

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/yamadashy/repomix'

If you have feedback or need assistance with the MCP directory API, please join our Discord server