mirror of
https://github.com/pbakaus/impeccable.git
synced 2026-09-12 06:06:37 +03:00
Merge branch 'main' into add-trae-support
This commit is contained in:
+6
-6
@@ -166,7 +166,7 @@ This is a test skill body.`;
|
||||
const skillContent = `---
|
||||
name: audit
|
||||
description: Run technical quality checks
|
||||
user-invokable: true
|
||||
user-invocable: true
|
||||
args:
|
||||
- name: target
|
||||
description: Target element
|
||||
@@ -188,31 +188,31 @@ Please audit {{target}} for technical quality. Ask {{model}} for help.`;
|
||||
transformers.transformGemini(skills, DIST_DIR, patterns);
|
||||
transformers.transformCodex(skills, DIST_DIR, patterns);
|
||||
|
||||
// Verify Cursor: full frontmatter with user-invokable
|
||||
// Verify Cursor: full frontmatter with user-invocable
|
||||
const cursorContent = fs.readFileSync(path.join(DIST_DIR, 'cursor/.cursor/skills/audit/SKILL.md'), 'utf-8');
|
||||
expect(cursorContent).toContain('---');
|
||||
expect(cursorContent).toContain('name: audit');
|
||||
expect(cursorContent).toContain('{{target}}');
|
||||
expect(cursorContent).toContain('the model');
|
||||
|
||||
// Verify Claude Code: full frontmatter with user-invokable and args
|
||||
// Verify Claude Code: full frontmatter with user-invocable and args
|
||||
const claudeContent = fs.readFileSync(path.join(DIST_DIR, 'claude-code/.claude/skills/audit/SKILL.md'), 'utf-8');
|
||||
expect(claudeContent).toContain('---');
|
||||
expect(claudeContent).toContain('name: audit');
|
||||
expect(claudeContent).toContain('user-invokable: true');
|
||||
expect(claudeContent).toContain('user-invocable: true');
|
||||
expect(claudeContent).toContain('{{target}}');
|
||||
expect(claudeContent).toContain('Claude');
|
||||
|
||||
// Verify Gemini: skill in skills directory
|
||||
expect(fs.existsSync(path.join(DIST_DIR, 'gemini/.gemini/skills/audit/SKILL.md'))).toBe(true);
|
||||
const geminiContent = fs.readFileSync(path.join(DIST_DIR, 'gemini/.gemini/skills/audit/SKILL.md'), 'utf-8');
|
||||
expect(geminiContent).toContain('{{args}}'); // Replaced for user-invokable in Gemini
|
||||
expect(geminiContent).toContain('{{args}}'); // Replaced for user-invocable in Gemini
|
||||
expect(geminiContent).toContain('Gemini');
|
||||
|
||||
// Verify Codex: skill in skills directory
|
||||
expect(fs.existsSync(path.join(DIST_DIR, 'codex/.codex/skills/audit/SKILL.md'))).toBe(true);
|
||||
const codexContent = fs.readFileSync(path.join(DIST_DIR, 'codex/.codex/skills/audit/SKILL.md'), 'utf-8');
|
||||
expect(codexContent).toContain('$TARGET'); // Replaced for user-invokable in Codex
|
||||
expect(codexContent).toContain('$TARGET'); // Replaced for user-invocable in Codex
|
||||
expect(codexContent).toContain('GPT');
|
||||
});
|
||||
|
||||
|
||||
@@ -47,12 +47,12 @@ describe('transformAgents', () => {
|
||||
expect(parsed.body).toBe('Skill instructions.');
|
||||
});
|
||||
|
||||
test('should add user-invokable flag for user-invokable skills', () => {
|
||||
test('should add user-invocable flag for user-invocable skills', () => {
|
||||
const skills = [
|
||||
{
|
||||
name: 'audit',
|
||||
description: 'Audit command',
|
||||
userInvokable: true,
|
||||
userInvocable: true,
|
||||
body: 'Audit the code.'
|
||||
}
|
||||
];
|
||||
@@ -62,10 +62,10 @@ describe('transformAgents', () => {
|
||||
const content = fs.readFileSync(path.join(TEST_DIR, 'agents/.agents/skills/audit/SKILL.md'), 'utf-8');
|
||||
const parsed = parseFrontmatter(content);
|
||||
|
||||
expect(parsed.frontmatter['user-invokable']).toBe(true);
|
||||
expect(parsed.frontmatter['user-invocable']).toBe(true);
|
||||
});
|
||||
|
||||
test('should not add user-invokable flag for non-user-invokable skills', () => {
|
||||
test('should not add user-invocable flag for non-user-invocable skills', () => {
|
||||
const skills = [
|
||||
{
|
||||
name: 'helper',
|
||||
@@ -79,7 +79,7 @@ describe('transformAgents', () => {
|
||||
const content = fs.readFileSync(path.join(TEST_DIR, 'agents/.agents/skills/helper/SKILL.md'), 'utf-8');
|
||||
const parsed = parseFrontmatter(content);
|
||||
|
||||
expect(parsed.frontmatter['user-invokable']).toBeUndefined();
|
||||
expect(parsed.frontmatter['user-invocable']).toBeUndefined();
|
||||
});
|
||||
|
||||
test('should create argument-hint for required args', () => {
|
||||
@@ -87,7 +87,7 @@ describe('transformAgents', () => {
|
||||
{
|
||||
name: 'with-args',
|
||||
description: 'Command with args',
|
||||
userInvokable: true,
|
||||
userInvocable: true,
|
||||
args: [
|
||||
{ name: 'target', description: 'Target', required: true },
|
||||
{ name: 'format', description: 'Format', required: false }
|
||||
@@ -109,7 +109,7 @@ describe('transformAgents', () => {
|
||||
{
|
||||
name: 'no-args',
|
||||
description: 'No args',
|
||||
userInvokable: true,
|
||||
userInvocable: true,
|
||||
args: [],
|
||||
body: 'Simple body.'
|
||||
}
|
||||
@@ -123,12 +123,12 @@ describe('transformAgents', () => {
|
||||
expect(parsed.frontmatter['argument-hint']).toBeUndefined();
|
||||
});
|
||||
|
||||
test('should not add argument-hint for non-user-invokable skills with args', () => {
|
||||
test('should not add argument-hint for non-user-invocable skills with args', () => {
|
||||
const skills = [
|
||||
{
|
||||
name: 'internal',
|
||||
description: 'Internal skill',
|
||||
userInvokable: false,
|
||||
userInvocable: false,
|
||||
args: [{ name: 'target', description: 'Target', required: true }],
|
||||
body: 'Body.'
|
||||
}
|
||||
@@ -188,8 +188,8 @@ describe('transformAgents', () => {
|
||||
|
||||
test('should replace {{available_commands}} placeholder', () => {
|
||||
const skills = [
|
||||
{ name: 'audit', description: 'Audit', userInvokable: true, body: 'Available: {{available_commands}}' },
|
||||
{ name: 'polish', description: 'Polish', userInvokable: true, body: 'Polish body.' }
|
||||
{ name: 'audit', description: 'Audit', userInvocable: true, body: 'Available: {{available_commands}}' },
|
||||
{ name: 'polish', description: 'Polish', userInvocable: true, body: 'Polish body.' }
|
||||
];
|
||||
|
||||
transformAgents(skills, TEST_DIR);
|
||||
@@ -241,7 +241,7 @@ describe('transformAgents', () => {
|
||||
|
||||
test('should support prefix option', () => {
|
||||
const skills = [
|
||||
{ name: 'audit', description: 'Audit', userInvokable: true, body: 'Audit body' }
|
||||
{ name: 'audit', description: 'Audit', userInvocable: true, body: 'Audit body' }
|
||||
];
|
||||
|
||||
transformAgents(skills, TEST_DIR, null, { prefix: 'i-', outputSuffix: '-prefixed' });
|
||||
@@ -255,8 +255,8 @@ describe('transformAgents', () => {
|
||||
|
||||
test('should prefix skill references in body when prefix is set', () => {
|
||||
const skills = [
|
||||
{ name: 'audit', description: 'Audit', userInvokable: true, body: 'Run /polish after the audit skill.' },
|
||||
{ name: 'polish', description: 'Polish', userInvokable: true, body: 'Polish body.' }
|
||||
{ name: 'audit', description: 'Audit', userInvocable: true, body: 'Run /polish after the audit skill.' },
|
||||
{ name: 'polish', description: 'Polish', userInvocable: true, body: 'Polish body.' }
|
||||
];
|
||||
|
||||
transformAgents(skills, TEST_DIR, null, { prefix: 'i-', outputSuffix: '-prefixed' });
|
||||
@@ -284,8 +284,8 @@ describe('transformAgents', () => {
|
||||
console.log = consoleMock;
|
||||
|
||||
const skills = [
|
||||
{ name: 'skill1', description: 'Test', userInvokable: true, body: 'body' },
|
||||
{ name: 'skill2', description: 'Test', userInvokable: false, body: 'body' }
|
||||
{ name: 'skill1', description: 'Test', userInvocable: true, body: 'body' },
|
||||
{ name: 'skill2', description: 'Test', userInvocable: false, body: 'body' }
|
||||
];
|
||||
|
||||
transformAgents(skills, TEST_DIR);
|
||||
@@ -294,7 +294,7 @@ describe('transformAgents', () => {
|
||||
|
||||
expect(consoleMock).toHaveBeenCalledWith(expect.stringContaining('✓ Agents:'));
|
||||
expect(consoleMock).toHaveBeenCalledWith(expect.stringContaining('2 skills'));
|
||||
expect(consoleMock).toHaveBeenCalledWith(expect.stringContaining('1 user-invokable'));
|
||||
expect(consoleMock).toHaveBeenCalledWith(expect.stringContaining('1 user-invocable'));
|
||||
});
|
||||
|
||||
test('should log reference file count', () => {
|
||||
|
||||
@@ -51,12 +51,12 @@ describe('transformClaudeCode', () => {
|
||||
expect(parsed.body).toBe('Skill instructions.');
|
||||
});
|
||||
|
||||
test('should add user-invokable flag for user-invokable skills', () => {
|
||||
test('should add user-invocable flag for user-invocable skills', () => {
|
||||
const skills = [
|
||||
{
|
||||
name: 'audit',
|
||||
description: 'Audit command',
|
||||
userInvokable: true,
|
||||
userInvocable: true,
|
||||
body: 'Audit the code.'
|
||||
}
|
||||
];
|
||||
@@ -66,15 +66,15 @@ describe('transformClaudeCode', () => {
|
||||
const content = fs.readFileSync(path.join(TEST_DIR, 'claude-code/.claude/skills/audit/SKILL.md'), 'utf-8');
|
||||
const parsed = parseFrontmatter(content);
|
||||
|
||||
expect(parsed.frontmatter['user-invokable']).toBe(true);
|
||||
expect(parsed.frontmatter['user-invocable']).toBe(true);
|
||||
});
|
||||
|
||||
test('should include args in frontmatter for user-invokable skills', () => {
|
||||
test('should include args in frontmatter for user-invocable skills', () => {
|
||||
const skills = [
|
||||
{
|
||||
name: 'test-command',
|
||||
description: 'A test command',
|
||||
userInvokable: true,
|
||||
userInvocable: true,
|
||||
args: [
|
||||
{ name: 'target', description: 'The target', required: false },
|
||||
{ name: 'output', description: 'Output format', required: true }
|
||||
@@ -100,7 +100,7 @@ describe('transformClaudeCode', () => {
|
||||
{
|
||||
name: 'simple-skill',
|
||||
description: 'Simple skill',
|
||||
userInvokable: true,
|
||||
userInvocable: true,
|
||||
args: [],
|
||||
body: 'Simple body.'
|
||||
}
|
||||
@@ -162,7 +162,7 @@ describe('transformClaudeCode', () => {
|
||||
{
|
||||
name: 'with-placeholder',
|
||||
description: 'Has placeholder',
|
||||
userInvokable: true,
|
||||
userInvocable: true,
|
||||
args: [{ name: 'target', description: 'Target', required: false }],
|
||||
body: 'Process {{target}} and generate output.'
|
||||
}
|
||||
@@ -203,8 +203,8 @@ describe('transformClaudeCode', () => {
|
||||
console.log = consoleMock;
|
||||
|
||||
const skills = [
|
||||
{ name: 'skill1', description: 'Test', license: '', userInvokable: true, body: 'body' },
|
||||
{ name: 'skill2', description: 'Test', license: '', userInvokable: false, body: 'body' }
|
||||
{ name: 'skill1', description: 'Test', license: '', userInvocable: true, body: 'body' },
|
||||
{ name: 'skill2', description: 'Test', license: '', userInvocable: false, body: 'body' }
|
||||
];
|
||||
|
||||
transformClaudeCode(skills, TEST_DIR);
|
||||
@@ -213,7 +213,7 @@ describe('transformClaudeCode', () => {
|
||||
|
||||
expect(consoleMock).toHaveBeenCalledWith(expect.stringContaining('✓ Claude Code:'));
|
||||
expect(consoleMock).toHaveBeenCalledWith(expect.stringContaining('2 skills'));
|
||||
expect(consoleMock).toHaveBeenCalledWith(expect.stringContaining('1 user-invokable'));
|
||||
expect(consoleMock).toHaveBeenCalledWith(expect.stringContaining('1 user-invocable'));
|
||||
});
|
||||
|
||||
test('should handle empty arrays', () => {
|
||||
@@ -228,7 +228,7 @@ describe('transformClaudeCode', () => {
|
||||
{
|
||||
name: 'test',
|
||||
description: 'Test command',
|
||||
userInvokable: true,
|
||||
userInvocable: true,
|
||||
args: [
|
||||
{ name: 'arg1', description: 'First arg', required: true },
|
||||
{ name: 'arg2', description: 'Second arg', required: false }
|
||||
@@ -279,7 +279,7 @@ Second paragraph with details.
|
||||
|
||||
test('should support prefix option', () => {
|
||||
const skills = [
|
||||
{ name: 'audit', description: 'Audit', license: '', userInvokable: true, body: 'Audit body' }
|
||||
{ name: 'audit', description: 'Audit', license: '', userInvocable: true, body: 'Audit body' }
|
||||
];
|
||||
|
||||
transformClaudeCode(skills, TEST_DIR, null, { prefix: 'i-', outputSuffix: '-prefixed' });
|
||||
|
||||
@@ -55,7 +55,7 @@ describe('transformCodex', () => {
|
||||
{
|
||||
name: 'with-args',
|
||||
description: 'Command with args',
|
||||
userInvokable: true,
|
||||
userInvocable: true,
|
||||
args: [
|
||||
{ name: 'target', description: 'Target', required: true },
|
||||
{ name: 'output', description: 'Output', required: true }
|
||||
@@ -77,7 +77,7 @@ describe('transformCodex', () => {
|
||||
{
|
||||
name: 'optional-args',
|
||||
description: 'Command with optional args',
|
||||
userInvokable: true,
|
||||
userInvocable: true,
|
||||
args: [
|
||||
{ name: 'format', description: 'Format', required: false }
|
||||
],
|
||||
@@ -98,7 +98,7 @@ describe('transformCodex', () => {
|
||||
{
|
||||
name: 'mixed-args',
|
||||
description: 'Mixed args',
|
||||
userInvokable: true,
|
||||
userInvocable: true,
|
||||
args: [
|
||||
{ name: 'input', description: 'Input', required: true },
|
||||
{ name: 'format', description: 'Format', required: false },
|
||||
@@ -116,12 +116,12 @@ describe('transformCodex', () => {
|
||||
expect(parsed.frontmatter['argument-hint']).toBe('<input> [FORMAT=<value>] <output>');
|
||||
});
|
||||
|
||||
test('should transform {{argname}} to $ARGNAME for user-invokable skills', () => {
|
||||
test('should transform {{argname}} to $ARGNAME for user-invocable skills', () => {
|
||||
const skills = [
|
||||
{
|
||||
name: 'normalize',
|
||||
description: 'Normalize',
|
||||
userInvokable: true,
|
||||
userInvocable: true,
|
||||
args: [{ name: 'target', description: 'Target', required: false }],
|
||||
body: 'Please normalize {{target}} to match the design system.'
|
||||
}
|
||||
@@ -141,7 +141,7 @@ describe('transformCodex', () => {
|
||||
{
|
||||
name: 'multi-arg',
|
||||
description: 'Multiple args',
|
||||
userInvokable: true,
|
||||
userInvocable: true,
|
||||
args: [],
|
||||
body: 'Process {{input}} and output to {{output}} with {{format}}.'
|
||||
}
|
||||
@@ -198,8 +198,8 @@ describe('transformCodex', () => {
|
||||
console.log = consoleMock;
|
||||
|
||||
const skills = [
|
||||
{ name: 'skill1', description: 'Test', license: '', userInvokable: true, body: 'body' },
|
||||
{ name: 'skill2', description: 'Test', license: '', userInvokable: false, body: 'body' }
|
||||
{ name: 'skill1', description: 'Test', license: '', userInvocable: true, body: 'body' },
|
||||
{ name: 'skill2', description: 'Test', license: '', userInvocable: false, body: 'body' }
|
||||
];
|
||||
|
||||
transformCodex(skills, TEST_DIR);
|
||||
@@ -208,7 +208,7 @@ describe('transformCodex', () => {
|
||||
|
||||
expect(consoleMock).toHaveBeenCalledWith(expect.stringContaining('✓ Codex:'));
|
||||
expect(consoleMock).toHaveBeenCalledWith(expect.stringContaining('2 skills'));
|
||||
expect(consoleMock).toHaveBeenCalledWith(expect.stringContaining('1 user-invokable'));
|
||||
expect(consoleMock).toHaveBeenCalledWith(expect.stringContaining('1 user-invocable'));
|
||||
});
|
||||
|
||||
test('should handle empty arrays', () => {
|
||||
@@ -218,12 +218,12 @@ describe('transformCodex', () => {
|
||||
expect(skillDirs).toHaveLength(0);
|
||||
});
|
||||
|
||||
test('should handle user-invokable skills without args', () => {
|
||||
test('should handle user-invocable skills without args', () => {
|
||||
const skills = [
|
||||
{
|
||||
name: 'no-args',
|
||||
description: 'No args command',
|
||||
userInvokable: true,
|
||||
userInvocable: true,
|
||||
args: [],
|
||||
body: 'Body content'
|
||||
}
|
||||
@@ -263,7 +263,7 @@ Second line after blank.
|
||||
|
||||
test('should support prefix option', () => {
|
||||
const skills = [
|
||||
{ name: 'audit', description: 'Audit', license: '', userInvokable: true, body: 'Audit body' }
|
||||
{ name: 'audit', description: 'Audit', license: '', userInvocable: true, body: 'Audit body' }
|
||||
];
|
||||
|
||||
transformCodex(skills, TEST_DIR, null, { prefix: 'i-', outputSuffix: '-prefixed' });
|
||||
|
||||
@@ -123,8 +123,8 @@ describe('transformCursor', () => {
|
||||
console.log = consoleMock;
|
||||
|
||||
const skills = [
|
||||
{ name: 'skill1', description: '', license: '', userInvokable: true, body: 'body1' },
|
||||
{ name: 'skill2', description: '', license: '', userInvokable: false, body: 'body2' }
|
||||
{ name: 'skill1', description: '', license: '', userInvocable: true, body: 'body1' },
|
||||
{ name: 'skill2', description: '', license: '', userInvocable: false, body: 'body2' }
|
||||
];
|
||||
|
||||
transformCursor(skills, TEST_DIR);
|
||||
@@ -133,7 +133,7 @@ describe('transformCursor', () => {
|
||||
|
||||
expect(consoleMock).toHaveBeenCalledWith(expect.stringContaining('✓ Cursor:'));
|
||||
expect(consoleMock).toHaveBeenCalledWith(expect.stringContaining('2 skills'));
|
||||
expect(consoleMock).toHaveBeenCalledWith(expect.stringContaining('1 user-invokable'));
|
||||
expect(consoleMock).toHaveBeenCalledWith(expect.stringContaining('1 user-invocable'));
|
||||
});
|
||||
|
||||
test('should handle empty skills array', () => {
|
||||
|
||||
@@ -62,12 +62,12 @@ describe('transformGemini', () => {
|
||||
expect(fs.existsSync(path.join(TEST_DIR, 'gemini/.gemini/skills/skill3/SKILL.md'))).toBe(true);
|
||||
});
|
||||
|
||||
test('should handle user-invokable skills with args', () => {
|
||||
test('should handle user-invocable skills with args', () => {
|
||||
const skills = [
|
||||
{
|
||||
name: 'normalize',
|
||||
description: 'Normalize design',
|
||||
userInvokable: true,
|
||||
userInvocable: true,
|
||||
args: [{ name: 'target', description: 'Target', required: false }],
|
||||
body: 'Please normalize {{target}} to match the design system.'
|
||||
}
|
||||
@@ -76,17 +76,17 @@ describe('transformGemini', () => {
|
||||
transformGemini(skills, TEST_DIR);
|
||||
|
||||
const content = fs.readFileSync(path.join(TEST_DIR, 'gemini/.gemini/skills/normalize/SKILL.md'), 'utf-8');
|
||||
// For user-invokable skills, {{arg}} placeholders become {{args}}
|
||||
// For user-invocable skills, {{arg}} placeholders become {{args}}
|
||||
expect(content).toContain('{{args}}');
|
||||
expect(content).not.toContain('{{target}}');
|
||||
});
|
||||
|
||||
test('should replace multiple different placeholders with {{args}} for user-invokable skills', () => {
|
||||
test('should replace multiple different placeholders with {{args}} for user-invocable skills', () => {
|
||||
const skills = [
|
||||
{
|
||||
name: 'multi-arg',
|
||||
description: 'Multiple args',
|
||||
userInvokable: true,
|
||||
userInvocable: true,
|
||||
args: [],
|
||||
body: 'Process {{input}} and output to {{output}} with {{format}}.'
|
||||
}
|
||||
@@ -99,12 +99,12 @@ describe('transformGemini', () => {
|
||||
expect(argsMatches).toHaveLength(3);
|
||||
});
|
||||
|
||||
test('should not replace placeholders for non-user-invokable skills', () => {
|
||||
test('should not replace placeholders for non-user-invocable skills', () => {
|
||||
const skills = [
|
||||
{
|
||||
name: 'passive-skill',
|
||||
description: 'Passive skill',
|
||||
userInvokable: false,
|
||||
userInvocable: false,
|
||||
body: 'Process {{target}} normally.'
|
||||
}
|
||||
];
|
||||
@@ -143,8 +143,8 @@ describe('transformGemini', () => {
|
||||
console.log = consoleMock;
|
||||
|
||||
const skills = [
|
||||
{ name: 'skill1', description: 'Test', license: '', userInvokable: true, body: 'body' },
|
||||
{ name: 'skill2', description: 'Test', license: '', userInvokable: false, body: 'body' }
|
||||
{ name: 'skill1', description: 'Test', license: '', userInvocable: true, body: 'body' },
|
||||
{ name: 'skill2', description: 'Test', license: '', userInvocable: false, body: 'body' }
|
||||
];
|
||||
|
||||
transformGemini(skills, TEST_DIR);
|
||||
@@ -153,7 +153,7 @@ describe('transformGemini', () => {
|
||||
|
||||
expect(consoleMock).toHaveBeenCalledWith(expect.stringContaining('✓ Gemini:'));
|
||||
expect(consoleMock).toHaveBeenCalledWith(expect.stringContaining('2 skills'));
|
||||
expect(consoleMock).toHaveBeenCalledWith(expect.stringContaining('1 user-invokable'));
|
||||
expect(consoleMock).toHaveBeenCalledWith(expect.stringContaining('1 user-invocable'));
|
||||
});
|
||||
|
||||
test('should handle empty arrays', () => {
|
||||
|
||||
@@ -80,12 +80,12 @@ describe('transformKiro', () => {
|
||||
expect(content).toContain('metadata: some-metadata');
|
||||
});
|
||||
|
||||
test('should not include user-invokable in frontmatter (Kiro does not use it)', () => {
|
||||
test('should not include user-invocable in frontmatter (Kiro does not use it)', () => {
|
||||
const skills = [
|
||||
{
|
||||
name: 'test',
|
||||
description: 'Test',
|
||||
userInvokable: true,
|
||||
userInvocable: true,
|
||||
body: 'Body'
|
||||
}
|
||||
];
|
||||
@@ -93,7 +93,7 @@ describe('transformKiro', () => {
|
||||
transformKiro(skills, TEST_DIR);
|
||||
|
||||
const content = fs.readFileSync(path.join(TEST_DIR, 'kiro/.kiro/skills/test/SKILL.md'), 'utf-8');
|
||||
expect(content).not.toContain('user-invokable');
|
||||
expect(content).not.toContain('user-invocable');
|
||||
});
|
||||
|
||||
test('should handle multiple skills', () => {
|
||||
@@ -142,8 +142,8 @@ describe('transformKiro', () => {
|
||||
|
||||
test('should replace {{available_commands}} placeholder', () => {
|
||||
const skills = [
|
||||
{ name: 'audit', description: 'Audit', userInvokable: true, body: 'Available: {{available_commands}}' },
|
||||
{ name: 'polish', description: 'Polish', userInvokable: true, body: 'Polish body.' }
|
||||
{ name: 'audit', description: 'Audit', userInvocable: true, body: 'Available: {{available_commands}}' },
|
||||
{ name: 'polish', description: 'Polish', userInvocable: true, body: 'Polish body.' }
|
||||
];
|
||||
|
||||
transformKiro(skills, TEST_DIR);
|
||||
@@ -180,7 +180,7 @@ describe('transformKiro', () => {
|
||||
{
|
||||
name: 'test',
|
||||
description: 'Test',
|
||||
userInvokable: true,
|
||||
userInvocable: true,
|
||||
body: 'Body with {{available_commands}}.',
|
||||
references: [
|
||||
{ name: 'ref', content: 'Use {{model}} with {{config_file}}. Commands: {{available_commands}}.', filePath: '/fake/ref.md' }
|
||||
@@ -198,7 +198,7 @@ describe('transformKiro', () => {
|
||||
|
||||
test('should support prefix option', () => {
|
||||
const skills = [
|
||||
{ name: 'audit', description: 'Audit', userInvokable: true, body: 'Audit body' }
|
||||
{ name: 'audit', description: 'Audit', userInvocable: true, body: 'Audit body' }
|
||||
];
|
||||
|
||||
transformKiro(skills, TEST_DIR, null, { prefix: 'i-', outputSuffix: '-prefixed' });
|
||||
@@ -212,8 +212,8 @@ describe('transformKiro', () => {
|
||||
|
||||
test('should prefix skill references in body when prefix is set', () => {
|
||||
const skills = [
|
||||
{ name: 'audit', description: 'Audit', userInvokable: true, body: 'Run /polish after the audit skill.' },
|
||||
{ name: 'polish', description: 'Polish', userInvokable: true, body: 'Polish body.' }
|
||||
{ name: 'audit', description: 'Audit', userInvocable: true, body: 'Run /polish after the audit skill.' },
|
||||
{ name: 'polish', description: 'Polish', userInvocable: true, body: 'Polish body.' }
|
||||
];
|
||||
|
||||
transformKiro(skills, TEST_DIR, null, { prefix: 'i-', outputSuffix: '-prefixed' });
|
||||
@@ -241,8 +241,8 @@ describe('transformKiro', () => {
|
||||
console.log = consoleMock;
|
||||
|
||||
const skills = [
|
||||
{ name: 'skill1', description: 'Test', userInvokable: true, body: 'body' },
|
||||
{ name: 'skill2', description: 'Test', userInvokable: false, body: 'body' }
|
||||
{ name: 'skill1', description: 'Test', userInvocable: true, body: 'body' },
|
||||
{ name: 'skill2', description: 'Test', userInvocable: false, body: 'body' }
|
||||
];
|
||||
|
||||
transformKiro(skills, TEST_DIR);
|
||||
@@ -251,7 +251,7 @@ describe('transformKiro', () => {
|
||||
|
||||
expect(consoleMock).toHaveBeenCalledWith(expect.stringContaining('✓ Kiro:'));
|
||||
expect(consoleMock).toHaveBeenCalledWith(expect.stringContaining('2 skills'));
|
||||
expect(consoleMock).toHaveBeenCalledWith(expect.stringContaining('1 user-invokable'));
|
||||
expect(consoleMock).toHaveBeenCalledWith(expect.stringContaining('1 user-invocable'));
|
||||
});
|
||||
|
||||
test('should log reference file count', () => {
|
||||
|
||||
@@ -48,12 +48,12 @@ describe('transformOpenCode', () => {
|
||||
expect(parsed.body).toBe('Skill instructions.');
|
||||
});
|
||||
|
||||
test('should add user-invokable flag for user-invokable skills', () => {
|
||||
test('should add user-invocable flag for user-invocable skills', () => {
|
||||
const skills = [
|
||||
{
|
||||
name: 'audit',
|
||||
description: 'Audit command',
|
||||
userInvokable: true,
|
||||
userInvocable: true,
|
||||
body: 'Audit the code.'
|
||||
}
|
||||
];
|
||||
@@ -63,10 +63,10 @@ describe('transformOpenCode', () => {
|
||||
const content = fs.readFileSync(path.join(TEST_DIR, 'opencode/.opencode/skills/audit/SKILL.md'), 'utf-8');
|
||||
const parsed = parseFrontmatter(content);
|
||||
|
||||
expect(parsed.frontmatter['user-invokable']).toBe(true);
|
||||
expect(parsed.frontmatter['user-invocable']).toBe(true);
|
||||
});
|
||||
|
||||
test('should not add user-invokable flag for non-user-invokable skills', () => {
|
||||
test('should not add user-invocable flag for non-user-invocable skills', () => {
|
||||
const skills = [
|
||||
{
|
||||
name: 'helper',
|
||||
@@ -80,7 +80,7 @@ describe('transformOpenCode', () => {
|
||||
const content = fs.readFileSync(path.join(TEST_DIR, 'opencode/.opencode/skills/helper/SKILL.md'), 'utf-8');
|
||||
const parsed = parseFrontmatter(content);
|
||||
|
||||
expect(parsed.frontmatter['user-invokable']).toBeUndefined();
|
||||
expect(parsed.frontmatter['user-invocable']).toBeUndefined();
|
||||
});
|
||||
|
||||
test('should include args in frontmatter', () => {
|
||||
@@ -88,7 +88,7 @@ describe('transformOpenCode', () => {
|
||||
{
|
||||
name: 'with-args',
|
||||
description: 'Command with args',
|
||||
userInvokable: true,
|
||||
userInvocable: true,
|
||||
args: [
|
||||
{ name: 'target', description: 'Target element', required: false }
|
||||
],
|
||||
@@ -111,7 +111,7 @@ describe('transformOpenCode', () => {
|
||||
{
|
||||
name: 'no-args',
|
||||
description: 'No args',
|
||||
userInvokable: true,
|
||||
userInvocable: true,
|
||||
args: [],
|
||||
body: 'Simple body.'
|
||||
}
|
||||
@@ -234,8 +234,8 @@ describe('transformOpenCode', () => {
|
||||
|
||||
test('should replace {{available_commands}} placeholder', () => {
|
||||
const skills = [
|
||||
{ name: 'audit', description: 'Audit', userInvokable: true, body: 'Available: {{available_commands}}' },
|
||||
{ name: 'polish', description: 'Polish', userInvokable: true, body: 'Polish body.' }
|
||||
{ name: 'audit', description: 'Audit', userInvocable: true, body: 'Available: {{available_commands}}' },
|
||||
{ name: 'polish', description: 'Polish', userInvocable: true, body: 'Polish body.' }
|
||||
];
|
||||
|
||||
transformOpenCode(skills, TEST_DIR);
|
||||
@@ -272,7 +272,7 @@ describe('transformOpenCode', () => {
|
||||
{
|
||||
name: 'test',
|
||||
description: 'Test',
|
||||
userInvokable: true,
|
||||
userInvocable: true,
|
||||
body: 'Body with {{available_commands}}.',
|
||||
references: [
|
||||
{ name: 'ref', content: 'Use {{model}} with {{config_file}}. Commands: {{available_commands}}.', filePath: '/fake/ref.md' }
|
||||
@@ -290,7 +290,7 @@ describe('transformOpenCode', () => {
|
||||
|
||||
test('should support prefix option', () => {
|
||||
const skills = [
|
||||
{ name: 'audit', description: 'Audit', userInvokable: true, body: 'Audit body' }
|
||||
{ name: 'audit', description: 'Audit', userInvocable: true, body: 'Audit body' }
|
||||
];
|
||||
|
||||
transformOpenCode(skills, TEST_DIR, null, { prefix: 'i-', outputSuffix: '-prefixed' });
|
||||
@@ -304,8 +304,8 @@ describe('transformOpenCode', () => {
|
||||
|
||||
test('should prefix skill references in body when prefix is set', () => {
|
||||
const skills = [
|
||||
{ name: 'audit', description: 'Audit', userInvokable: true, body: 'Run /polish after the audit skill.' },
|
||||
{ name: 'polish', description: 'Polish', userInvokable: true, body: 'Polish body.' }
|
||||
{ name: 'audit', description: 'Audit', userInvocable: true, body: 'Run /polish after the audit skill.' },
|
||||
{ name: 'polish', description: 'Polish', userInvocable: true, body: 'Polish body.' }
|
||||
];
|
||||
|
||||
transformOpenCode(skills, TEST_DIR, null, { prefix: 'i-', outputSuffix: '-prefixed' });
|
||||
@@ -333,8 +333,8 @@ describe('transformOpenCode', () => {
|
||||
console.log = consoleMock;
|
||||
|
||||
const skills = [
|
||||
{ name: 'skill1', description: 'Test', userInvokable: true, body: 'body' },
|
||||
{ name: 'skill2', description: 'Test', userInvokable: false, body: 'body' }
|
||||
{ name: 'skill1', description: 'Test', userInvocable: true, body: 'body' },
|
||||
{ name: 'skill2', description: 'Test', userInvocable: false, body: 'body' }
|
||||
];
|
||||
|
||||
transformOpenCode(skills, TEST_DIR);
|
||||
@@ -343,7 +343,7 @@ describe('transformOpenCode', () => {
|
||||
|
||||
expect(consoleMock).toHaveBeenCalledWith(expect.stringContaining('✓ OpenCode:'));
|
||||
expect(consoleMock).toHaveBeenCalledWith(expect.stringContaining('2 skills'));
|
||||
expect(consoleMock).toHaveBeenCalledWith(expect.stringContaining('1 user-invokable'));
|
||||
expect(consoleMock).toHaveBeenCalledWith(expect.stringContaining('1 user-invocable'));
|
||||
});
|
||||
|
||||
test('should log reference file count', () => {
|
||||
|
||||
@@ -80,12 +80,12 @@ describe('transformPi', () => {
|
||||
expect(content).toContain('metadata: some-metadata');
|
||||
});
|
||||
|
||||
test('should not include user-invokable in frontmatter (Pi does not use it)', () => {
|
||||
test('should not include user-invocable in frontmatter (Pi does not use it)', () => {
|
||||
const skills = [
|
||||
{
|
||||
name: 'test',
|
||||
description: 'Test',
|
||||
userInvokable: true,
|
||||
userInvocable: true,
|
||||
body: 'Body'
|
||||
}
|
||||
];
|
||||
@@ -93,7 +93,7 @@ describe('transformPi', () => {
|
||||
transformPi(skills, TEST_DIR);
|
||||
|
||||
const content = fs.readFileSync(path.join(TEST_DIR, 'pi/.pi/skills/test/SKILL.md'), 'utf-8');
|
||||
expect(content).not.toContain('user-invokable');
|
||||
expect(content).not.toContain('user-invocable');
|
||||
});
|
||||
|
||||
test('should handle multiple skills', () => {
|
||||
@@ -142,8 +142,8 @@ describe('transformPi', () => {
|
||||
|
||||
test('should replace {{available_commands}} placeholder', () => {
|
||||
const skills = [
|
||||
{ name: 'audit', description: 'Audit', userInvokable: true, body: 'Available: {{available_commands}}' },
|
||||
{ name: 'polish', description: 'Polish', userInvokable: true, body: 'Polish body.' }
|
||||
{ name: 'audit', description: 'Audit', userInvocable: true, body: 'Available: {{available_commands}}' },
|
||||
{ name: 'polish', description: 'Polish', userInvocable: true, body: 'Polish body.' }
|
||||
];
|
||||
|
||||
transformPi(skills, TEST_DIR);
|
||||
@@ -180,7 +180,7 @@ describe('transformPi', () => {
|
||||
{
|
||||
name: 'test',
|
||||
description: 'Test',
|
||||
userInvokable: true,
|
||||
userInvocable: true,
|
||||
body: 'Body with {{available_commands}}.',
|
||||
references: [
|
||||
{ name: 'ref', content: 'Use {{model}} with {{config_file}}. Commands: {{available_commands}}.', filePath: '/fake/ref.md' }
|
||||
@@ -198,7 +198,7 @@ describe('transformPi', () => {
|
||||
|
||||
test('should support prefix option', () => {
|
||||
const skills = [
|
||||
{ name: 'audit', description: 'Audit', userInvokable: true, body: 'Audit body' }
|
||||
{ name: 'audit', description: 'Audit', userInvocable: true, body: 'Audit body' }
|
||||
];
|
||||
|
||||
transformPi(skills, TEST_DIR, null, { prefix: 'i-', outputSuffix: '-prefixed' });
|
||||
@@ -212,8 +212,8 @@ describe('transformPi', () => {
|
||||
|
||||
test('should prefix skill references in body when prefix is set', () => {
|
||||
const skills = [
|
||||
{ name: 'audit', description: 'Audit', userInvokable: true, body: 'Run /polish after the audit skill.' },
|
||||
{ name: 'polish', description: 'Polish', userInvokable: true, body: 'Polish body.' }
|
||||
{ name: 'audit', description: 'Audit', userInvocable: true, body: 'Run /polish after the audit skill.' },
|
||||
{ name: 'polish', description: 'Polish', userInvocable: true, body: 'Polish body.' }
|
||||
];
|
||||
|
||||
transformPi(skills, TEST_DIR, null, { prefix: 'i-', outputSuffix: '-prefixed' });
|
||||
@@ -241,8 +241,8 @@ describe('transformPi', () => {
|
||||
console.log = consoleMock;
|
||||
|
||||
const skills = [
|
||||
{ name: 'skill1', description: 'Test', userInvokable: true, body: 'body' },
|
||||
{ name: 'skill2', description: 'Test', userInvokable: false, body: 'body' }
|
||||
{ name: 'skill1', description: 'Test', userInvocable: true, body: 'body' },
|
||||
{ name: 'skill2', description: 'Test', userInvocable: false, body: 'body' }
|
||||
];
|
||||
|
||||
transformPi(skills, TEST_DIR);
|
||||
@@ -251,7 +251,7 @@ describe('transformPi', () => {
|
||||
|
||||
expect(consoleMock).toHaveBeenCalledWith(expect.stringContaining('✓ Pi:'));
|
||||
expect(consoleMock).toHaveBeenCalledWith(expect.stringContaining('2 skills'));
|
||||
expect(consoleMock).toHaveBeenCalledWith(expect.stringContaining('1 user-invokable'));
|
||||
expect(consoleMock).toHaveBeenCalledWith(expect.stringContaining('1 user-invocable'));
|
||||
});
|
||||
|
||||
test('should log reference file count', () => {
|
||||
|
||||
+15
-16
@@ -89,29 +89,29 @@ Skill body.`;
|
||||
expect(result.frontmatter.license).toBe('MIT');
|
||||
});
|
||||
|
||||
test('should parse user-invokable boolean', () => {
|
||||
test('should parse user-invocable boolean', () => {
|
||||
const content = `---
|
||||
name: test-skill
|
||||
user-invokable: true
|
||||
user-invocable: true
|
||||
---
|
||||
|
||||
Body.`;
|
||||
|
||||
const result = parseFrontmatter(content);
|
||||
expect(result.frontmatter['user-invokable']).toBe(true);
|
||||
expect(result.frontmatter['user-invocable']).toBe(true);
|
||||
});
|
||||
|
||||
test('should parse user-invokable as string true (code behavior)', () => {
|
||||
test('should parse user-invocable as string true (code behavior)', () => {
|
||||
const content = `---
|
||||
name: test-skill
|
||||
user-invokable: 'true'
|
||||
user-invocable: 'true'
|
||||
---
|
||||
|
||||
Body.`;
|
||||
|
||||
const result = parseFrontmatter(content);
|
||||
// The parseFrontmatter function doesn't strip quotes from YAML string values
|
||||
expect(result.frontmatter['user-invokable']).toBe("'true'");
|
||||
expect(result.frontmatter['user-invocable']).toBe("'true'");
|
||||
});
|
||||
|
||||
test('should parse allowed-tools field', () => {
|
||||
@@ -162,11 +162,11 @@ describe('generateYamlFrontmatter', () => {
|
||||
const data = {
|
||||
name: 'test',
|
||||
description: 'Test',
|
||||
'user-invokable': true
|
||||
'user-invocable': true
|
||||
};
|
||||
|
||||
const result = generateYamlFrontmatter(data);
|
||||
expect(result).toContain('user-invokable: true');
|
||||
expect(result).toContain('user-invocable: true');
|
||||
});
|
||||
|
||||
test('should roundtrip: generate and parse back', () => {
|
||||
@@ -372,11 +372,11 @@ Skill instructions here.`;
|
||||
expect(skills[0].body).toBe('Skill instructions here.');
|
||||
});
|
||||
|
||||
test('should read skill with user-invokable flag', () => {
|
||||
test('should read skill with user-invocable flag', () => {
|
||||
const skillContent = `---
|
||||
name: audit
|
||||
description: Run technical quality checks
|
||||
user-invokable: true
|
||||
user-invocable: true
|
||||
---
|
||||
|
||||
Audit the code.`;
|
||||
@@ -388,7 +388,7 @@ Audit the code.`;
|
||||
const { skills } = readSourceFiles(testRootDir);
|
||||
|
||||
expect(skills).toHaveLength(1);
|
||||
expect(skills[0].userInvokable).toBe(true);
|
||||
expect(skills[0].userInvocable).toBe(true);
|
||||
});
|
||||
|
||||
test('should read skill with reference files', () => {
|
||||
@@ -478,7 +478,7 @@ name: test-skill
|
||||
description: A comprehensive test skill
|
||||
license: Apache-2.0
|
||||
compatibility: claude-code
|
||||
user-invokable: true
|
||||
user-invocable: true
|
||||
allowed-tools: Bash,Edit
|
||||
---
|
||||
|
||||
@@ -494,7 +494,7 @@ Body content.`;
|
||||
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].userInvokable).toBe(true);
|
||||
expect(skills[0].userInvocable).toBe(true);
|
||||
expect(skills[0].allowedTools).toBe('Bash,Edit');
|
||||
});
|
||||
});
|
||||
@@ -677,7 +677,7 @@ describe('prefixSkillReferences', () => {
|
||||
const result = prefixSkillReferences('Run /audit then /polish. The audit skill is great.', 'i-', ['audit', 'polish']);
|
||||
expect(result).toContain('/i-audit');
|
||||
expect(result).toContain('/i-polish');
|
||||
expect(result).toContain('the i-audit skill');
|
||||
expect(result).toContain('The i-audit skill');
|
||||
});
|
||||
|
||||
test('should not partially match longer skill names', () => {
|
||||
@@ -687,8 +687,7 @@ describe('prefixSkillReferences', () => {
|
||||
|
||||
test('should handle case-insensitive "the X skill" matching', () => {
|
||||
const result = prefixSkillReferences('The audit skill is useful.', 'i-', ['audit']);
|
||||
// The regex replaces case-insensitively, so "The" becomes "the" in the replacement
|
||||
expect(result).toBe('the i-audit skill is useful.');
|
||||
expect(result).toBe('The i-audit skill is useful.');
|
||||
});
|
||||
|
||||
test('should return content unchanged with empty prefix', () => {
|
||||
|
||||
Reference in New Issue
Block a user