Files
pbakaus_impeccable/tests/lib/utils.test.js
T
Paul BakausandGitHub 482368511a Fix Codex skill version metadata (#703)
Move Codex and .agents skill versions under metadata while keeping all version readers compatible with legacy top-level frontmatter.\n\nAI assistance: prepared with Codex under maintainer direction.
2026-09-01 20:21:01 -04:00

706 lines
23 KiB
JavaScript

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: <output> [TARGET=<value>]
---
Body here.`;
const result = parseFrontmatter(content);
expect(result.frontmatter.name).toBe('test-skill');
expect(result.frontmatter['argument-hint']).toBe('<output> [TARGET=<value>]');
});
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': '<output> [TARGET=<value>]'
};
const result = generateYamlFrontmatter(data);
expect(result).toContain('argument-hint: <output> [TARGET=<value>]');
});
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 generate nested metadata', () => {
const result = generateYamlFrontmatter({
name: 'test',
metadata: { version: '1.2.3' },
});
expect(result).toContain('metadata:\n version: 1.2.3');
});
test('should roundtrip: generate and parse back', () => {
const original = {
name: 'roundtrip-test',
description: 'Testing roundtrip',
'argument-hint': '<arg1>'
};
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('<arg1>');
});
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 <sub>`.
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 provider declarations', () => {
const source = [
"export const IMPECCABLE_COMMAND_PREFIX = '/'; // @impeccable-provider-command-prefix",
"export const IMPECCABLE_PROVIDER_ID = 'source'; // @impeccable-provider-id",
'const regex = /impeccable\\b/gi;',
"const runtime = '/src/lib/impeccable/__runtime.js';",
"const text = 'Run /impeccable audit';",
].join('\n');
const result = replaceScriptProviderMarker(source, 'codex', 'agents');
expect(result).toContain('export const IMPECCABLE_COMMAND_PREFIX = "$";');
expect(result).toContain('export const IMPECCABLE_PROVIDER_ID = "agents";');
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';");
});
});