import { describe, test, expect, beforeEach, afterEach } from 'bun:test'; import fs from 'fs'; import path from 'path'; import { parseFrontmatter, readFilesRecursive, readSourceFiles, ensureDir, cleanDir, writeFile, generateYamlFrontmatter, readPatterns, replacePlaceholders, replaceScriptProviderMarker, } from '../../scripts/lib/utils.js'; // Temporary test directory const TEST_DIR = path.join(process.cwd(), 'test-tmp'); describe('parseFrontmatter', () => { test('should parse basic frontmatter with simple key-value pairs', () => { const content = `--- name: test-skill description: A test skill --- This is the body content.`; const result = parseFrontmatter(content); expect(result.frontmatter.name).toBe('test-skill'); expect(result.frontmatter.description).toBe('A test skill'); expect(result.body).toBe('This is the body content.'); }); test('should parse frontmatter with argument-hint', () => { const content = `--- name: test-skill description: A test skill argument-hint: [TARGET=] --- Body here.`; const result = parseFrontmatter(content); expect(result.frontmatter.name).toBe('test-skill'); expect(result.frontmatter['argument-hint']).toBe(' [TARGET=]'); }); test('should return empty frontmatter when no frontmatter present', () => { const content = 'Just some content without frontmatter.'; const result = parseFrontmatter(content); expect(result.frontmatter).toEqual({}); expect(result.body).toBe(content); }); test('should handle empty body', () => { const content = `--- name: test --- `; const result = parseFrontmatter(content); expect(result.frontmatter.name).toBe('test'); expect(result.body).toBe(''); }); test('should handle frontmatter with license field', () => { const content = `--- name: skill-name description: A skill license: MIT --- Skill body.`; const result = parseFrontmatter(content); expect(result.frontmatter.license).toBe('MIT'); }); test('should parse user-invocable boolean', () => { const content = `--- name: test-skill user-invocable: true --- Body.`; const result = parseFrontmatter(content); expect(result.frontmatter['user-invocable']).toBe(true); }); test('should parse quoted user-invocable boolean as true', () => { const content = `--- name: test-skill user-invocable: 'true' --- Body.`; const result = parseFrontmatter(content); expect(result.frontmatter['user-invocable']).toBe(true); }); test('should keep quoted non-user-invocable booleans as plain strings', () => { const content = `--- name: test-skill description: 'true' --- Body.`; const result = parseFrontmatter(content); expect(result.frontmatter.description).toBe('true'); }); test('should parse allowed-tools field', () => { const content = `--- name: test-skill allowed-tools: Bash --- Body.`; const result = parseFrontmatter(content); expect(result.frontmatter['allowed-tools']).toBe('Bash'); }); }); describe('generateYamlFrontmatter', () => { test('should generate basic frontmatter', () => { const data = { name: 'test-skill', description: 'A test' }; const result = generateYamlFrontmatter(data); expect(result).toContain('---'); expect(result).toContain('name: test-skill'); expect(result).toContain('description: A test'); }); test('should generate frontmatter with argument-hint', () => { const data = { name: 'test', description: 'Test skill', 'argument-hint': ' [TARGET=]' }; const result = generateYamlFrontmatter(data); expect(result).toContain('argument-hint: [TARGET=]'); }); test('should generate frontmatter with boolean', () => { const data = { name: 'test', description: 'Test', 'user-invocable': true }; const result = generateYamlFrontmatter(data); expect(result).toContain('user-invocable: true'); }); test('should roundtrip: generate and parse back', () => { const original = { name: 'roundtrip-test', description: 'Testing roundtrip', 'argument-hint': '' }; const yaml = generateYamlFrontmatter(original); const content = `${yaml}\n\nBody content`; const parsed = parseFrontmatter(content); expect(parsed.frontmatter.name).toBe(original.name); expect(parsed.frontmatter.description).toBe(original.description); expect(parsed.frontmatter['argument-hint']).toBe(''); }); test('should quote strings containing colon-space (breaks plain scalars)', () => { const data = { name: 'impeccable', description: 'Design fluency. Also handles: critique, audit. Commands: craft, polish.' }; const result = generateYamlFrontmatter(data); // Must be wrapped in quotes so YAML parsers don't mis-read the inner `: ` as a mapping expect(result).toContain('description: "Design fluency. Also handles: critique, audit. Commands: craft, polish."'); // Roundtrip through our parser should recover the original string intact const parsed = parseFrontmatter(`${result}\n\nbody`); expect(parsed.frontmatter.description).toBe(data.description); }); test('should quote strings starting with YAML flow indicators', () => { const data = { name: 'test', 'argument-hint': '[command] [target]' }; const result = generateYamlFrontmatter(data); expect(result).toContain('argument-hint: "[command] [target]"'); }); test('should not quote plain strings without special chars', () => { const data = { name: 'simple', description: 'A plain description with no colons or hashes' }; const result = generateYamlFrontmatter(data); expect(result).toContain('description: A plain description with no colons or hashes'); expect(result).not.toContain('"A plain'); }); }); describe('ensureDir', () => { afterEach(() => { if (fs.existsSync(TEST_DIR)) { fs.rmSync(TEST_DIR, { recursive: true, force: true }); } }); test('should create directory if it does not exist', () => { const testPath = path.join(TEST_DIR, 'new-dir'); ensureDir(testPath); expect(fs.existsSync(testPath)).toBe(true); expect(fs.statSync(testPath).isDirectory()).toBe(true); }); test('should create nested directories', () => { const testPath = path.join(TEST_DIR, 'level1', 'level2', 'level3'); ensureDir(testPath); expect(fs.existsSync(testPath)).toBe(true); }); test('should not throw if directory already exists', () => { const testPath = path.join(TEST_DIR, 'existing'); fs.mkdirSync(testPath, { recursive: true }); expect(() => ensureDir(testPath)).not.toThrow(); }); }); describe('cleanDir', () => { beforeEach(() => { ensureDir(TEST_DIR); }); afterEach(() => { if (fs.existsSync(TEST_DIR)) { fs.rmSync(TEST_DIR, { recursive: true, force: true }); } }); test('should remove directory and all contents', () => { const filePath = path.join(TEST_DIR, 'test.txt'); fs.writeFileSync(filePath, 'content'); expect(fs.existsSync(filePath)).toBe(true); cleanDir(TEST_DIR); expect(fs.existsSync(TEST_DIR)).toBe(false); }); test('should not throw if directory does not exist', () => { const nonExistent = path.join(TEST_DIR, 'does-not-exist'); expect(() => cleanDir(nonExistent)).not.toThrow(); }); test('should remove nested directories', () => { const nestedPath = path.join(TEST_DIR, 'level1', 'level2'); ensureDir(nestedPath); fs.writeFileSync(path.join(nestedPath, 'file.txt'), 'content'); cleanDir(TEST_DIR); expect(fs.existsSync(TEST_DIR)).toBe(false); }); }); describe('writeFile', () => { afterEach(() => { if (fs.existsSync(TEST_DIR)) { fs.rmSync(TEST_DIR, { recursive: true, force: true }); } }); test('should write file with content', () => { const filePath = path.join(TEST_DIR, 'test.txt'); const content = 'Hello, world!'; writeFile(filePath, content); expect(fs.existsSync(filePath)).toBe(true); expect(fs.readFileSync(filePath, 'utf-8')).toBe(content); }); test('should create parent directories automatically', () => { const filePath = path.join(TEST_DIR, 'nested', 'deep', 'file.txt'); writeFile(filePath, 'content'); expect(fs.existsSync(filePath)).toBe(true); expect(fs.readFileSync(filePath, 'utf-8')).toBe('content'); }); test('should overwrite existing file', () => { const filePath = path.join(TEST_DIR, 'file.txt'); writeFile(filePath, 'first'); writeFile(filePath, 'second'); expect(fs.readFileSync(filePath, 'utf-8')).toBe('second'); }); }); describe('readFilesRecursive', () => { beforeEach(() => { ensureDir(TEST_DIR); }); afterEach(() => { if (fs.existsSync(TEST_DIR)) { fs.rmSync(TEST_DIR, { recursive: true, force: true }); } }); test('should find all markdown files in directory', () => { writeFile(path.join(TEST_DIR, 'file1.md'), 'content1'); writeFile(path.join(TEST_DIR, 'file2.md'), 'content2'); writeFile(path.join(TEST_DIR, 'file3.txt'), 'not markdown'); const files = readFilesRecursive(TEST_DIR); expect(files).toHaveLength(2); expect(files.some(f => f.endsWith('file1.md'))).toBe(true); expect(files.some(f => f.endsWith('file2.md'))).toBe(true); }); test('should find markdown files in nested directories', () => { writeFile(path.join(TEST_DIR, 'root.md'), 'root'); writeFile(path.join(TEST_DIR, 'sub', 'nested.md'), 'nested'); writeFile(path.join(TEST_DIR, 'sub', 'deep', 'deeper.md'), 'deeper'); const files = readFilesRecursive(TEST_DIR); expect(files).toHaveLength(3); expect(files.some(f => f.endsWith('root.md'))).toBe(true); expect(files.some(f => f.endsWith('nested.md'))).toBe(true); expect(files.some(f => f.endsWith('deeper.md'))).toBe(true); }); test('should return empty array for non-existent directory', () => { const files = readFilesRecursive(path.join(TEST_DIR, 'does-not-exist')); expect(files).toEqual([]); }); test('should return empty array for directory with no markdown files', () => { writeFile(path.join(TEST_DIR, 'file.txt'), 'text'); writeFile(path.join(TEST_DIR, 'file.js'), 'code'); const files = readFilesRecursive(TEST_DIR); expect(files).toEqual([]); }); }); describe('readSourceFiles', () => { const testRootDir = TEST_DIR; beforeEach(() => { ensureDir(testRootDir); }); afterEach(() => { if (fs.existsSync(testRootDir)) { fs.rmSync(testRootDir, { recursive: true, force: true }); } }); test('should read and parse SKILL.md from skill/', () => { const skillContent = `--- name: test-skill description: A test skill license: MIT --- Skill instructions here.`; const skillDir = path.join(testRootDir, 'skill'); ensureDir(skillDir); fs.writeFileSync(path.join(skillDir, 'SKILL.src.md'), skillContent); const { skills } = readSourceFiles(testRootDir); expect(skills).toHaveLength(1); expect(skills[0].name).toBe('test-skill'); expect(skills[0].description).toBe('A test skill'); expect(skills[0].license).toBe('MIT'); expect(skills[0].body).toBe('Skill instructions here.'); }); test('should read skill with user-invocable flag', () => { const skillContent = `--- name: audit description: Run technical quality checks user-invocable: true --- Audit the code.`; const skillDir = path.join(testRootDir, 'skill'); ensureDir(skillDir); fs.writeFileSync(path.join(skillDir, 'SKILL.src.md'), skillContent); const { skills } = readSourceFiles(testRootDir); expect(skills).toHaveLength(1); expect(skills[0].userInvocable).toBe(true); }); test('should read skill with quoted user-invocable flag', () => { const skillContent = `--- name: audit description: Run technical quality checks user-invocable: 'true' --- Audit the code.`; const skillDir = path.join(testRootDir, 'skill'); ensureDir(skillDir); fs.writeFileSync(path.join(skillDir, 'SKILL.src.md'), skillContent); const { skills } = readSourceFiles(testRootDir); expect(skills).toHaveLength(1); expect(skills[0].userInvocable).toBe(true); }); test('should read skill with reference files', () => { const skillContent = `--- name: impeccable description: Impeccable design skill --- Impeccable design instructions.`; const skillDir = path.join(testRootDir, 'skill'); ensureDir(skillDir); fs.writeFileSync(path.join(skillDir, 'SKILL.src.md'), skillContent); const refDir = path.join(skillDir, 'reference'); ensureDir(refDir); fs.writeFileSync(path.join(refDir, 'typography.md'), 'Typography reference content.'); fs.writeFileSync(path.join(refDir, 'color.md'), 'Color reference content.'); const { skills } = readSourceFiles(testRootDir); expect(skills).toHaveLength(1); expect(skills[0].references).toHaveLength(2); // References may not be in a specific order due to fs.readdirSync const refNames = skills[0].references.map(r => r.name).sort(); expect(refNames).toEqual(['color', 'typography']); }); test('should fall back to "impeccable" when frontmatter has no name', () => { const skillDir = path.join(testRootDir, 'skill'); ensureDir(skillDir); fs.writeFileSync(path.join(skillDir, 'SKILL.src.md'), 'Just body, no frontmatter.'); const { skills } = readSourceFiles(testRootDir); expect(skills).toHaveLength(1); expect(skills[0].name).toBe('impeccable'); }); test('should ignore non-md files in skill/reference', () => { const skillDir = path.join(testRootDir, 'skill'); ensureDir(skillDir); fs.writeFileSync(path.join(skillDir, 'SKILL.src.md'), '---\nname: test-skill\n---\nBody'); const refDir = path.join(skillDir, 'reference'); ensureDir(refDir); fs.writeFileSync(path.join(refDir, 'readme.txt'), 'Not a markdown file'); fs.writeFileSync(path.join(refDir, 'typography.md'), 'Valid reference'); const { skills } = readSourceFiles(testRootDir); expect(skills).toHaveLength(1); expect(skills[0].references).toHaveLength(1); expect(skills[0].references[0].name).toBe('typography'); }); test('should read nested skill script files with portable relative names', () => { const skillDir = path.join(testRootDir, 'skill'); ensureDir(skillDir); fs.writeFileSync(path.join(skillDir, 'SKILL.src.md'), '---\nname: test-skill\n---\nBody'); const scriptsDir = path.join(skillDir, 'scripts'); ensureDir(path.join(scriptsDir, 'live')); fs.writeFileSync(path.join(scriptsDir, 'context.mjs'), 'export const context = true;\n'); fs.writeFileSync(path.join(scriptsDir, 'live/session-store.mjs'), 'export const nested = true;\n'); fs.writeFileSync(path.join(scriptsDir, 'config.json'), '{"local":true}\n'); const { skills } = readSourceFiles(testRootDir); const scripts = skills[0].scripts; const scriptNames = scripts.map(script => script.name).sort(); expect(scriptNames).toEqual(['context.mjs', 'live/session-store.mjs']); expect(scripts.find(script => script.name === 'live/session-store.mjs').content).toContain('nested = true'); }); test('should handle missing skill directory', () => { const { skills } = readSourceFiles(testRootDir); expect(skills).toEqual([]); }); test('should parse all frontmatter fields correctly', () => { const skillContent = `--- name: test-skill description: A comprehensive test skill license: Apache-2.0 compatibility: claude-code user-invocable: true allowed-tools: Bash,Edit --- Body content.`; const skillDir = path.join(testRootDir, 'skill'); ensureDir(skillDir); fs.writeFileSync(path.join(skillDir, 'SKILL.src.md'), skillContent); const { skills } = readSourceFiles(testRootDir); expect(skills[0].name).toBe('test-skill'); expect(skills[0].description).toBe('A comprehensive test skill'); expect(skills[0].license).toBe('Apache-2.0'); expect(skills[0].compatibility).toBe('claude-code'); expect(skills[0].userInvocable).toBe(true); expect(skills[0].allowedTools).toBe('Bash,Edit'); }); }); describe('readPatterns', () => { const testRootDir = TEST_DIR; beforeEach(() => { ensureDir(testRootDir); }); afterEach(() => { if (fs.existsSync(testRootDir)) { fs.rmSync(testRootDir, { recursive: true, force: true }); } }); test('returns the curated DO/DON\'T pattern categories', () => { // v3.0: readPatterns no longer parses SKILL.md. It returns a hand-curated // catalog (CURATED_CATEGORIES) that powers the homepage Antidote teaser. const { patterns, antipatterns } = readPatterns(testRootDir); expect(patterns).toHaveLength(6); expect(antipatterns).toHaveLength(6); expect(patterns[0].name).toBe('Typography'); expect(patterns[0].items.length).toBeGreaterThan(0); expect(antipatterns[0].name).toBe('Typography'); expect(antipatterns[0].items.length).toBeGreaterThan(0); }); test('returns categories in the curated order', () => { const { patterns } = readPatterns(testRootDir); expect(patterns.map((p) => p.name)).toEqual([ 'Typography', 'Color & Contrast', 'Layout & Space', 'Visual Details', 'Motion', 'Interaction', ]); }); test('ignores its arguments (curated, not extracted from any SKILL.md)', () => { const fromBogusRoot = readPatterns('/nonexistent/path/xyz'); const fromRealRoot = readPatterns(testRootDir); expect(fromBogusRoot.patterns.map((p) => p.name)).toEqual( fromRealRoot.patterns.map((p) => p.name) ); expect(fromBogusRoot.patterns).toHaveLength(6); }); }); describe('replacePlaceholders', () => { test('should replace {{model}} with provider-specific value', () => { expect(replacePlaceholders('Ask {{model}} for help.', 'claude-code')).toBe('Ask Claude for help.'); expect(replacePlaceholders('Ask {{model}} for help.', 'gemini')).toBe('Ask Gemini for help.'); expect(replacePlaceholders('Ask {{model}} for help.', 'codex')).toBe('Ask GPT for help.'); expect(replacePlaceholders('Ask {{model}} for help.', 'cursor')).toBe('Ask the model for help.'); expect(replacePlaceholders('Ask {{model}} for help.', 'agents')).toBe('Ask the model for help.'); expect(replacePlaceholders('Ask {{model}} for help.', 'kiro')).toBe('Ask Claude for help.'); }); test('should replace {{config_file}} with provider-specific value', () => { expect(replacePlaceholders('See {{config_file}}.', 'claude-code')).toBe('See CLAUDE.md.'); expect(replacePlaceholders('See {{config_file}}.', 'cursor')).toBe('See .cursorrules.'); expect(replacePlaceholders('See {{config_file}}.', 'gemini')).toBe('See GEMINI.md.'); expect(replacePlaceholders('See {{config_file}}.', 'codex')).toBe('See AGENTS.md.'); expect(replacePlaceholders('See {{config_file}}.', 'agents')).toBe('See .github/copilot-instructions.md.'); expect(replacePlaceholders('See {{config_file}}.', 'kiro')).toBe('See .kiro/settings.json.'); }); test('should replace {{ask_instruction}} with provider-specific value', () => { const result = replacePlaceholders('{{ask_instruction}}', 'claude-code'); expect(result).toBe('STOP and call the AskUserQuestion tool to clarify.'); const cursorResult = replacePlaceholders('{{ask_instruction}}', 'cursor'); expect(cursorResult).toBe('ask the user directly to clarify what you cannot infer.'); }); test('should replace {{available_commands}} with command list', () => { const result = replacePlaceholders('Commands: {{available_commands}}', 'claude-code', ['audit', 'polish', 'optimize']); expect(result).toBe('Commands: /audit, /polish, /optimize'); }); test('should exclude impeccable from {{available_commands}}', () => { const result = replacePlaceholders('Commands: {{available_commands}}', 'claude-code', ['audit', 'impeccable', 'polish']); expect(result).toBe('Commands: /audit, /polish'); }); test('should exclude legacy teach-impeccable from {{available_commands}}', () => { const result = replacePlaceholders('Commands: {{available_commands}}', 'claude-code', ['audit', 'teach-impeccable', 'polish']); expect(result).toBe('Commands: /audit, /polish'); }); test('lists /impeccable sub-commands for {{available_commands}} when no command names are passed', () => { // v3.0 single-skill architecture: with no command names, the list falls back // to the IMPECCABLE_SUB_COMMANDS sub-commands rendered as `/impeccable `. const result = replacePlaceholders('Commands: {{available_commands}}', 'claude-code', []); expect(result.startsWith('Commands: /impeccable ')).toBe(true); expect(result).toContain('/impeccable audit'); expect(result).toContain('/impeccable polish'); }); test('should replace multiple placeholders in the same string', () => { const result = replacePlaceholders('{{model}} uses {{config_file}} and {{ask_instruction}}', 'claude-code'); expect(result).toBe('Claude uses CLAUDE.md and STOP and call the AskUserQuestion tool to clarify.'); }); test('should replace multiple occurrences of the same placeholder', () => { const result = replacePlaceholders('{{model}} and {{model}} again.', 'gemini'); expect(result).toBe('Gemini and Gemini again.'); }); test('should fall back to cursor placeholders for unknown provider', () => { const result = replacePlaceholders('{{model}} {{config_file}}', 'unknown-provider'); expect(result).toBe('the model .cursorrules'); }); test('should replace Codex command invocations without rewriting paths', () => { const source = [ 'Run /impeccable audit.', 'Use `/impeccable polish` next.', '.github/hooks/impeccable.json', '.codex/skills/impeccable/scripts/context.mjs', 'https://example.com/impeccable', ].join('\n'); const result = replacePlaceholders(source, 'codex', [], ['impeccable']); expect(result).toContain('Run $impeccable audit.'); expect(result).toContain('Use `$impeccable polish` next.'); expect(result).toContain('.github/hooks/impeccable.json'); expect(result).toContain('.codex/skills/impeccable/scripts/context.mjs'); expect(result).toContain('https://example.com/impeccable'); }); }); describe('replaceScriptProviderMarker', () => { test('renders only the explicit command-prefix declaration', () => { const source = [ "export const IMPECCABLE_COMMAND_PREFIX = '/'; // @impeccable-provider-command-prefix", 'const regex = /impeccable\\b/gi;', "const runtime = '/src/lib/impeccable/__runtime.js';", "const text = 'Run /impeccable audit';", ].join('\n'); const result = replaceScriptProviderMarker(source, 'codex'); expect(result).toContain('export const IMPECCABLE_COMMAND_PREFIX = "$";'); expect(result).toContain('const regex = /impeccable\\b/gi;'); expect(result).toContain("const runtime = '/src/lib/impeccable/__runtime.js';"); expect(result).toContain("const text = 'Run /impeccable audit';"); }); });