mirror of
https://github.com/pbakaus/impeccable.git
synced 2026-09-19 09:36:59 +03:00
Merge pull request #57 from pbakaus/fix/user-invocable-spelling
Fix user-invokable -> user-invocable spelling across entire codebase
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "impeccable",
|
||||
"description": "Design vocabulary and skills for frontend development. Includes 21 skills (20 user-invokable: /polish, /distill, /audit, /typeset, /overdrive, etc.) and an enhanced frontend-design skill with curated anti-patterns.",
|
||||
"description": "Design vocabulary and skills for frontend development. Includes 21 skills (20 user-invocable: /polish, /distill, /audit, /typeset, /overdrive, etc.) and an enhanced frontend-design skill with curated anti-patterns.",
|
||||
"version": "1.5.1",
|
||||
"author": {
|
||||
"name": "Paul Bakaus",
|
||||
|
||||
@@ -22,3 +22,6 @@ Thumbs.db
|
||||
|
||||
# Cloudflare
|
||||
.wrangler/
|
||||
|
||||
# User design context
|
||||
.impeccable.md
|
||||
|
||||
@@ -53,7 +53,7 @@ When bumping the version, update **all** of these locations to keep them in sync
|
||||
|
||||
## Adding New Skills
|
||||
|
||||
When adding a new user-invokable skill, update the command count in **all** of these locations:
|
||||
When adding a new user-invocable skill, update the command count in **all** of these locations:
|
||||
|
||||
- `public/index.html` → meta descriptions, hero box, section lead
|
||||
- `public/cheatsheet.html` → meta description, subtitle, `commandCategories`, `commandRelationships`
|
||||
|
||||
+9
-9
@@ -9,7 +9,7 @@ This repository uses a **feature-rich source format** that transforms into provi
|
||||
### Why This Approach?
|
||||
|
||||
Different providers have different capabilities:
|
||||
- **Claude Code, OpenCode**: Full metadata — args, user-invokable, allowed-tools, license, compatibility
|
||||
- **Claude Code, OpenCode**: Full metadata — args, user-invocable, allowed-tools, license, compatibility
|
||||
- **Codex, Agents**: Args converted to `argument-hint` format
|
||||
- **Gemini**: Minimal frontmatter, `{{arg}}` placeholders become `{{args}}`
|
||||
- **Cursor, Kiro, Pi**: Basic frontmatter (name, description, license/compatibility)
|
||||
@@ -34,8 +34,8 @@ Your skill instructions here...
|
||||
**Frontmatter fields** (based on [Agent Skills spec](https://agentskills.io/specification)):
|
||||
- `name` (required): Skill identifier (1-64 chars, lowercase/numbers/hyphens)
|
||||
- `description` (required): What the skill provides (1-1024 chars)
|
||||
- `user-invokable` (optional): Boolean — if `true`, the skill can be invoked as a slash command
|
||||
- `args` (optional): Array of argument objects (for user-invokable skills)
|
||||
- `user-invocable` (optional): Boolean — if `true`, the skill can be invoked as a slash command
|
||||
- `args` (optional): Array of argument objects (for user-invocable skills)
|
||||
- `name`: Argument identifier
|
||||
- `description`: What it's for
|
||||
- `required`: Boolean (defaults to false)
|
||||
@@ -90,27 +90,27 @@ All providers output skills to `dist/{provider}/.{config}/skills/{name}/SKILL.md
|
||||
|
||||
### Claude Code (Full Featured)
|
||||
- Output: `dist/claude-code/.claude/skills/{name}/SKILL.md`
|
||||
- Frontmatter: name, description, user-invokable, args, license, compatibility, metadata, allowed-tools
|
||||
- Frontmatter: name, description, user-invocable, args, license, compatibility, metadata, allowed-tools
|
||||
- Preserves `{{arg}}` placeholders in body
|
||||
|
||||
### OpenCode (Full Featured)
|
||||
- Output: `dist/opencode/.opencode/skills/{name}/SKILL.md`
|
||||
- Frontmatter: name, description, user-invokable, args, license, compatibility, metadata, allowed-tools
|
||||
- Frontmatter: name, description, user-invocable, args, license, compatibility, metadata, allowed-tools
|
||||
- Same format as Claude Code
|
||||
|
||||
### Gemini CLI
|
||||
- Output: `dist/gemini/.gemini/skills/{name}/SKILL.md`
|
||||
- Frontmatter: name, description
|
||||
- For user-invokable skills: remaining `{{arg}}` placeholders become `{{args}}`
|
||||
- For user-invocable skills: remaining `{{arg}}` placeholders become `{{args}}`
|
||||
|
||||
### Codex CLI
|
||||
- Output: `dist/codex/.codex/skills/{name}/SKILL.md`
|
||||
- Frontmatter: name, description, argument-hint, license
|
||||
- For user-invokable skills: `{{argname}}` → `$ARGNAME` (uppercase)
|
||||
- For user-invocable skills: `{{argname}}` → `$ARGNAME` (uppercase)
|
||||
|
||||
### Agents (VS Code Copilot, Antigravity)
|
||||
- Output: `dist/agents/.agents/skills/{name}/SKILL.md`
|
||||
- Frontmatter: name, description, user-invokable, argument-hint
|
||||
- Frontmatter: name, description, user-invocable, argument-hint
|
||||
- Args converted to `argument-hint` format (e.g., `<target> [FORMAT=<value>]`)
|
||||
|
||||
### Kiro
|
||||
@@ -170,7 +170,7 @@ The build system uses a modular architecture under `scripts/`:
|
||||
- `transformClaudeCode()`: Full metadata with args and allowed-tools
|
||||
- `transformGemini()`: Minimal frontmatter, `{{arg}}` → `{{args}}`
|
||||
- `transformCodex()`: Args → argument-hint, `{{arg}}` → `$ARGNAME`
|
||||
- `transformAgents()`: Args → argument-hint, user-invokable flag
|
||||
- `transformAgents()`: Args → argument-hint, user-invocable flag
|
||||
- `transformKiro()`: Basic frontmatter with license/compatibility/metadata
|
||||
- `transformOpenCode()`: Full metadata (same as Claude Code)
|
||||
- `transformPi()`: Basic frontmatter with license/compatibility/metadata
|
||||
|
||||
+1
-1
@@ -362,7 +362,7 @@
|
||||
<span class="changelog-date">March 4, 2026</span>
|
||||
</div>
|
||||
<ul class="changelog-items">
|
||||
<li>Unified skills architecture — commands are now skills with <code>user-invokable: true</code></li>
|
||||
<li>Unified skills architecture — commands are now skills with <code>user-invocable: true</code></li>
|
||||
<li>Added VS Code Copilot and Google Antigravity support (<code>.agents/skills/</code>)</li>
|
||||
<li>New install flow: <code>npx skills add</code> as primary, universal ZIP as fallback</li>
|
||||
<li>Added universal ZIP containing all 5 provider directories</li>
|
||||
|
||||
+5
-5
@@ -200,12 +200,12 @@ function generateApiData(buildDir, skills, patterns) {
|
||||
id: path.basename(path.dirname(s.filePath)),
|
||||
name: s.name,
|
||||
description: s.description,
|
||||
userInvokable: s.userInvokable,
|
||||
userInvocable: s.userInvocable,
|
||||
}));
|
||||
fs.writeFileSync(path.join(apiDir, 'skills.json'), JSON.stringify(skillsData));
|
||||
|
||||
// commands.json (user-invokable skills only)
|
||||
const commandsData = skillsData.filter(s => s.userInvokable);
|
||||
// commands.json (user-invocable skills only)
|
||||
const commandsData = skillsData.filter(s => s.userInvocable);
|
||||
fs.writeFileSync(path.join(apiDir, 'commands.json'), JSON.stringify(commandsData));
|
||||
|
||||
// patterns.json
|
||||
@@ -337,8 +337,8 @@ async function build() {
|
||||
// Read source files (unified skills architecture)
|
||||
const { skills } = readSourceFiles(ROOT_DIR);
|
||||
const patterns = readPatterns(ROOT_DIR);
|
||||
const userInvokableCount = skills.filter(s => s.userInvokable).length;
|
||||
console.log(`📖 Read ${skills.length} skills (${userInvokableCount} user-invokable) and ${patterns.patterns.length + patterns.antipatterns.length} pattern categories\n`);
|
||||
const userInvocableCount = skills.filter(s => s.userInvocable).length;
|
||||
console.log(`📖 Read ${skills.length} skills (${userInvocableCount} user-invocable) and ${patterns.patterns.length + patterns.antipatterns.length} pattern categories\n`);
|
||||
|
||||
// Transform for each provider
|
||||
transformCursor(skills, DIST_DIR, patterns);
|
||||
|
||||
@@ -5,9 +5,9 @@ import { cleanDir, ensureDir, writeFile, generateYamlFrontmatter, replacePlaceho
|
||||
* Agents Transformer (VS Code Copilot + Antigravity)
|
||||
*
|
||||
* All skills output to .agents/skills/{name}/SKILL.md
|
||||
* Frontmatter: name, description, user-invokable (if true), argument-hint (from args)
|
||||
* Frontmatter: name, description, user-invocable (if true), argument-hint (from args)
|
||||
*
|
||||
* @param {Array} skills - All skills (including user-invokable ones)
|
||||
* @param {Array} skills - All skills (including user-invocable ones)
|
||||
* @param {string} distDir - Distribution output directory
|
||||
* @param {Object} patterns - Design patterns data (unused)
|
||||
* @param {Object} options - Optional settings
|
||||
@@ -21,7 +21,7 @@ export function transformAgents(skills, distDir, patterns = null, options = {})
|
||||
ensureDir(skillsDir);
|
||||
|
||||
const allSkillNames = skills.map(s => s.name);
|
||||
const commandNames = skills.filter(s => s.userInvokable).map(s => `${prefix}${s.name}`);
|
||||
const commandNames = skills.filter(s => s.userInvocable).map(s => `${prefix}${s.name}`);
|
||||
let refCount = 0;
|
||||
for (const skill of skills) {
|
||||
const skillName = `${prefix}${skill.name}`;
|
||||
@@ -32,10 +32,10 @@ export function transformAgents(skills, distDir, patterns = null, options = {})
|
||||
description: skill.description,
|
||||
};
|
||||
|
||||
if (skill.userInvokable) frontmatterObj['user-invokable'] = true;
|
||||
if (skill.userInvocable) frontmatterObj['user-invocable'] = true;
|
||||
|
||||
// Build argument-hint from args array for user-invokable skills
|
||||
if (skill.userInvokable && skill.args && skill.args.length > 0) {
|
||||
// Build argument-hint from args array for user-invocable skills
|
||||
if (skill.userInvocable && skill.args && skill.args.length > 0) {
|
||||
const hints = skill.args.map(arg => {
|
||||
return arg.required ? `<${arg.name}>` : `[${arg.name.toUpperCase()}=<value>]`;
|
||||
});
|
||||
@@ -62,8 +62,8 @@ export function transformAgents(skills, distDir, patterns = null, options = {})
|
||||
}
|
||||
}
|
||||
|
||||
const userInvokableCount = skills.filter(s => s.userInvokable).length;
|
||||
const userInvocableCount = skills.filter(s => s.userInvocable).length;
|
||||
const refInfo = refCount > 0 ? ` (${refCount} reference files)` : '';
|
||||
const prefixInfo = prefix ? ` [${prefix}prefixed]` : '';
|
||||
console.log(`✓ Agents${prefixInfo}: ${skills.length} skills (${userInvokableCount} user-invokable)${refInfo}`);
|
||||
console.log(`✓ Agents${prefixInfo}: ${skills.length} skills (${userInvocableCount} user-invocable)${refInfo}`);
|
||||
}
|
||||
|
||||
@@ -7,11 +7,11 @@ import { cleanDir, ensureDir, writeFile, generateYamlFrontmatter, replacePlaceho
|
||||
* All skills output to .claude/skills/{name}/SKILL.md
|
||||
* User-invokable skills get args support in frontmatter.
|
||||
*
|
||||
* @param {Array} skills - All skills (including user-invokable ones)
|
||||
* @param {Array} skills - All skills (including user-invocable ones)
|
||||
* @param {string} distDir - Distribution output directory
|
||||
* @param {Object} patterns - Design patterns data (unused, kept for interface consistency)
|
||||
* @param {Object} options - Optional settings
|
||||
* @param {string} options.prefix - Prefix to add to user-invokable skill names (e.g., 'i-')
|
||||
* @param {string} options.prefix - Prefix to add to user-invocable skill names (e.g., 'i-')
|
||||
* @param {string} options.outputSuffix - Suffix for output directory (e.g., '-prefixed')
|
||||
*/
|
||||
export function transformClaudeCode(skills, distDir, patterns = null, options = {}) {
|
||||
@@ -23,7 +23,7 @@ export function transformClaudeCode(skills, distDir, patterns = null, options =
|
||||
ensureDir(skillsDir);
|
||||
|
||||
const allSkillNames = skills.map(s => s.name);
|
||||
const commandNames = skills.filter(s => s.userInvokable).map(s => `${prefix}${s.name}`);
|
||||
const commandNames = skills.filter(s => s.userInvocable).map(s => `${prefix}${s.name}`);
|
||||
let refCount = 0;
|
||||
for (const skill of skills) {
|
||||
const skillName = `${prefix}${skill.name}`;
|
||||
@@ -34,7 +34,7 @@ export function transformClaudeCode(skills, distDir, patterns = null, options =
|
||||
description: skill.description,
|
||||
};
|
||||
|
||||
if (skill.userInvokable) frontmatterObj['user-invokable'] = true;
|
||||
if (skill.userInvocable) frontmatterObj['user-invocable'] = true;
|
||||
if (skill.args && skill.args.length > 0) frontmatterObj.args = skill.args;
|
||||
if (skill.license) frontmatterObj.license = skill.license;
|
||||
if (skill.compatibility) frontmatterObj.compatibility = skill.compatibility;
|
||||
@@ -61,8 +61,8 @@ export function transformClaudeCode(skills, distDir, patterns = null, options =
|
||||
}
|
||||
}
|
||||
|
||||
const userInvokableCount = skills.filter(s => s.userInvokable).length;
|
||||
const userInvocableCount = skills.filter(s => s.userInvocable).length;
|
||||
const refInfo = refCount > 0 ? ` (${refCount} reference files)` : '';
|
||||
const prefixInfo = prefix ? ` [${prefix}prefixed]` : '';
|
||||
console.log(`✓ Claude Code${prefixInfo}: ${skills.length} skills (${userInvokableCount} user-invokable)${refInfo}`);
|
||||
console.log(`✓ Claude Code${prefixInfo}: ${skills.length} skills (${userInvocableCount} user-invocable)${refInfo}`);
|
||||
}
|
||||
|
||||
@@ -5,14 +5,14 @@ import { cleanDir, ensureDir, writeFile, generateYamlFrontmatter, replacePlaceho
|
||||
* Codex Transformer (Skills Only)
|
||||
*
|
||||
* All skills output to .codex/skills/{name}/SKILL.md
|
||||
* Frontmatter: name, description, argument-hint (from args for user-invokable)
|
||||
* For user-invokable skills: {{argname}} becomes $ARGNAME in body
|
||||
* Frontmatter: name, description, argument-hint (from args for user-invocable)
|
||||
* For user-invocable skills: {{argname}} becomes $ARGNAME in body
|
||||
*
|
||||
* @param {Array} skills - All skills (including user-invokable ones)
|
||||
* @param {Array} skills - All skills (including user-invocable ones)
|
||||
* @param {string} distDir - Distribution output directory
|
||||
* @param {Object} patterns - Design patterns data (unused)
|
||||
* @param {Object} options - Optional settings
|
||||
* @param {string} options.prefix - Prefix to add to user-invokable skill names (e.g., 'i-')
|
||||
* @param {string} options.prefix - Prefix to add to user-invocable skill names (e.g., 'i-')
|
||||
* @param {string} options.outputSuffix - Suffix for output directory (e.g., '-prefixed')
|
||||
*/
|
||||
export function transformCodex(skills, distDir, patterns = null, options = {}) {
|
||||
@@ -24,7 +24,7 @@ export function transformCodex(skills, distDir, patterns = null, options = {}) {
|
||||
ensureDir(skillsDir);
|
||||
|
||||
const allSkillNames = skills.map(s => s.name);
|
||||
const commandNames = skills.filter(s => s.userInvokable).map(s => `${prefix}${s.name}`);
|
||||
const commandNames = skills.filter(s => s.userInvocable).map(s => `${prefix}${s.name}`);
|
||||
let refCount = 0;
|
||||
for (const skill of skills) {
|
||||
const skillName = `${prefix}${skill.name}`;
|
||||
@@ -35,8 +35,8 @@ export function transformCodex(skills, distDir, patterns = null, options = {}) {
|
||||
description: skill.description,
|
||||
};
|
||||
|
||||
// Build argument-hint from args array for user-invokable skills
|
||||
if (skill.userInvokable && skill.args && skill.args.length > 0) {
|
||||
// Build argument-hint from args array for user-invocable skills
|
||||
if (skill.userInvocable && skill.args && skill.args.length > 0) {
|
||||
const hints = skill.args.map(arg => {
|
||||
return arg.required ? `<${arg.name}>` : `[${arg.name.toUpperCase()}=<value>]`;
|
||||
});
|
||||
@@ -48,8 +48,8 @@ export function transformCodex(skills, distDir, patterns = null, options = {}) {
|
||||
|
||||
let skillBody = replacePlaceholders(skill.body, 'codex', commandNames);
|
||||
if (prefix) skillBody = prefixSkillReferences(skillBody, prefix, allSkillNames);
|
||||
// For user-invokable skills, transform remaining {{argname}} to $ARGNAME
|
||||
if (skill.userInvokable) {
|
||||
// For user-invocable skills, transform remaining {{argname}} to $ARGNAME
|
||||
if (skill.userInvocable) {
|
||||
skillBody = skillBody.replace(/\{\{([^}]+)\}\}/g, (match, argName) => {
|
||||
return `$${argName.toUpperCase()}`;
|
||||
});
|
||||
@@ -72,8 +72,8 @@ export function transformCodex(skills, distDir, patterns = null, options = {}) {
|
||||
}
|
||||
}
|
||||
|
||||
const userInvokableCount = skills.filter(s => s.userInvokable).length;
|
||||
const userInvocableCount = skills.filter(s => s.userInvocable).length;
|
||||
const refInfo = refCount > 0 ? ` (${refCount} reference files)` : '';
|
||||
const prefixInfo = prefix ? ` [${prefix}prefixed]` : '';
|
||||
console.log(`✓ Codex${prefixInfo}: ${skills.length} skills (${userInvokableCount} user-invokable)${refInfo}`);
|
||||
console.log(`✓ Codex${prefixInfo}: ${skills.length} skills (${userInvocableCount} user-invocable)${refInfo}`);
|
||||
}
|
||||
|
||||
@@ -7,11 +7,11 @@ import { cleanDir, ensureDir, writeFile, generateYamlFrontmatter, replacePlaceho
|
||||
* All skills output to .cursor/skills/{name}/SKILL.md
|
||||
* Frontmatter: name, description, license
|
||||
*
|
||||
* @param {Array} skills - All skills (including user-invokable ones)
|
||||
* @param {Array} skills - All skills (including user-invocable ones)
|
||||
* @param {string} distDir - Distribution output directory
|
||||
* @param {Object} patterns - Design patterns data (unused)
|
||||
* @param {Object} options - Optional settings
|
||||
* @param {string} options.prefix - Prefix to add to user-invokable skill names (e.g., 'i-')
|
||||
* @param {string} options.prefix - Prefix to add to user-invocable skill names (e.g., 'i-')
|
||||
* @param {string} options.outputSuffix - Suffix for output directory (e.g., '-prefixed')
|
||||
*/
|
||||
export function transformCursor(skills, distDir, patterns = null, options = {}) {
|
||||
@@ -23,7 +23,7 @@ export function transformCursor(skills, distDir, patterns = null, options = {})
|
||||
ensureDir(skillsDir);
|
||||
|
||||
const allSkillNames = skills.map(s => s.name);
|
||||
const commandNames = skills.filter(s => s.userInvokable).map(s => `${prefix}${s.name}`);
|
||||
const commandNames = skills.filter(s => s.userInvocable).map(s => `${prefix}${s.name}`);
|
||||
let refCount = 0;
|
||||
for (const skill of skills) {
|
||||
const skillName = `${prefix}${skill.name}`;
|
||||
@@ -55,8 +55,8 @@ export function transformCursor(skills, distDir, patterns = null, options = {})
|
||||
}
|
||||
}
|
||||
|
||||
const userInvokableCount = skills.filter(s => s.userInvokable).length;
|
||||
const userInvocableCount = skills.filter(s => s.userInvocable).length;
|
||||
const refInfo = refCount > 0 ? ` (${refCount} reference files)` : '';
|
||||
const prefixInfo = prefix ? ` [${prefix}prefixed]` : '';
|
||||
console.log(`✓ Cursor${prefixInfo}: ${skills.length} skills (${userInvokableCount} user-invokable)${refInfo}`);
|
||||
console.log(`✓ Cursor${prefixInfo}: ${skills.length} skills (${userInvocableCount} user-invocable)${refInfo}`);
|
||||
}
|
||||
|
||||
@@ -6,13 +6,13 @@ import { cleanDir, ensureDir, writeFile, generateYamlFrontmatter, replacePlaceho
|
||||
*
|
||||
* All skills output to .gemini/skills/{name}/SKILL.md
|
||||
* Frontmatter: name, description
|
||||
* For user-invokable skills: {{arg}} placeholders become {{args}} in body
|
||||
* For user-invocable skills: {{arg}} placeholders become {{args}} in body
|
||||
*
|
||||
* @param {Array} skills - All skills (including user-invokable ones)
|
||||
* @param {Array} skills - All skills (including user-invocable ones)
|
||||
* @param {string} distDir - Distribution output directory
|
||||
* @param {Object} patterns - Design patterns data (unused)
|
||||
* @param {Object} options - Optional settings
|
||||
* @param {string} options.prefix - Prefix to add to user-invokable skill names (e.g., 'i-')
|
||||
* @param {string} options.prefix - Prefix to add to user-invocable skill names (e.g., 'i-')
|
||||
* @param {string} options.outputSuffix - Suffix for output directory (e.g., '-prefixed')
|
||||
*/
|
||||
export function transformGemini(skills, distDir, patterns = null, options = {}) {
|
||||
@@ -24,7 +24,7 @@ export function transformGemini(skills, distDir, patterns = null, options = {})
|
||||
ensureDir(skillsDir);
|
||||
|
||||
const allSkillNames = skills.map(s => s.name);
|
||||
const commandNames = skills.filter(s => s.userInvokable).map(s => `${prefix}${s.name}`);
|
||||
const commandNames = skills.filter(s => s.userInvocable).map(s => `${prefix}${s.name}`);
|
||||
let refCount = 0;
|
||||
for (const skill of skills) {
|
||||
const skillName = `${prefix}${skill.name}`;
|
||||
@@ -37,8 +37,8 @@ export function transformGemini(skills, distDir, patterns = null, options = {})
|
||||
|
||||
let skillBody = replacePlaceholders(skill.body, 'gemini', commandNames);
|
||||
if (prefix) skillBody = prefixSkillReferences(skillBody, prefix, allSkillNames);
|
||||
// For user-invokable skills, replace remaining {{arg}} placeholders with {{args}}
|
||||
if (skill.userInvokable) {
|
||||
// For user-invocable skills, replace remaining {{arg}} placeholders with {{args}}
|
||||
if (skill.userInvocable) {
|
||||
skillBody = skillBody.replace(/\{\{[^}]+\}\}/g, '{{args}}');
|
||||
}
|
||||
|
||||
@@ -59,8 +59,8 @@ export function transformGemini(skills, distDir, patterns = null, options = {})
|
||||
}
|
||||
}
|
||||
|
||||
const userInvokableCount = skills.filter(s => s.userInvokable).length;
|
||||
const userInvocableCount = skills.filter(s => s.userInvocable).length;
|
||||
const refInfo = refCount > 0 ? ` (${refCount} reference files)` : '';
|
||||
const prefixInfo = prefix ? ` [${prefix}prefixed]` : '';
|
||||
console.log(`✓ Gemini${prefixInfo}: ${skills.length} skills (${userInvokableCount} user-invokable)${refInfo}`);
|
||||
console.log(`✓ Gemini${prefixInfo}: ${skills.length} skills (${userInvocableCount} user-invocable)${refInfo}`);
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import { cleanDir, ensureDir, writeFile, generateYamlFrontmatter, replacePlaceho
|
||||
* All skills output to .kiro/skills/{name}/SKILL.md
|
||||
* Frontmatter: name, description, license, compatibility, metadata
|
||||
*
|
||||
* @param {Array} skills - All skills (including user-invokable ones)
|
||||
* @param {Array} skills - All skills (including user-invocable ones)
|
||||
* @param {string} distDir - Distribution output directory
|
||||
* @param {Object} patterns - Design patterns data (unused)
|
||||
* @param {Object} options - Optional settings
|
||||
@@ -23,7 +23,7 @@ export function transformKiro(skills, distDir, patterns = null, options = {}) {
|
||||
ensureDir(skillsDir);
|
||||
|
||||
const allSkillNames = skills.map(s => s.name);
|
||||
const commandNames = skills.filter(s => s.userInvokable).map(s => `${prefix}${s.name}`);
|
||||
const commandNames = skills.filter(s => s.userInvocable).map(s => `${prefix}${s.name}`);
|
||||
let refCount = 0;
|
||||
for (const skill of skills) {
|
||||
const skillName = `${prefix}${skill.name}`;
|
||||
@@ -58,8 +58,8 @@ export function transformKiro(skills, distDir, patterns = null, options = {}) {
|
||||
}
|
||||
}
|
||||
|
||||
const userInvokableCount = skills.filter(s => s.userInvokable).length;
|
||||
const userInvocableCount = skills.filter(s => s.userInvocable).length;
|
||||
const refInfo = refCount > 0 ? ` (${refCount} reference files)` : '';
|
||||
const prefixInfo = prefix ? ` [${prefix}prefixed]` : '';
|
||||
console.log(`✓ Kiro${prefixInfo}: ${skills.length} skills (${userInvokableCount} user-invokable)${refInfo}`);
|
||||
console.log(`✓ Kiro${prefixInfo}: ${skills.length} skills (${userInvocableCount} user-invocable)${refInfo}`);
|
||||
}
|
||||
|
||||
@@ -14,11 +14,11 @@ import {
|
||||
* All skills output to .opencode/skills/{name}/SKILL.md
|
||||
* User-invokable skills get args support in frontmatter.
|
||||
*
|
||||
* @param {Array} skills - All skills (including user-invokable ones)
|
||||
* @param {Array} skills - All skills (including user-invocable ones)
|
||||
* @param {string} distDir - Distribution output directory
|
||||
* @param {Object} patterns - Design patterns data (unused, kept for interface consistency)
|
||||
* @param {Object} options - Optional settings
|
||||
* @param {string} options.prefix - Prefix to add to user-invokable skill names (e.g., 'i-')
|
||||
* @param {string} options.prefix - Prefix to add to user-invocable skill names (e.g., 'i-')
|
||||
* @param {string} options.outputSuffix - Suffix for output directory (e.g., '-prefixed')
|
||||
*/
|
||||
export function transformOpenCode(
|
||||
@@ -36,7 +36,7 @@ export function transformOpenCode(
|
||||
|
||||
const allSkillNames = skills.map((s) => s.name);
|
||||
const commandNames = skills
|
||||
.filter((s) => s.userInvokable)
|
||||
.filter((s) => s.userInvocable)
|
||||
.map((s) => `${prefix}${s.name}`);
|
||||
let refCount = 0;
|
||||
for (const skill of skills) {
|
||||
@@ -48,7 +48,7 @@ export function transformOpenCode(
|
||||
description: skill.description,
|
||||
};
|
||||
|
||||
if (skill.userInvokable) frontmatterObj['user-invokable'] = true;
|
||||
if (skill.userInvocable) frontmatterObj['user-invocable'] = true;
|
||||
if (skill.args && skill.args.length > 0) frontmatterObj.args = skill.args;
|
||||
if (skill.license) frontmatterObj.license = skill.license;
|
||||
if (skill.compatibility) frontmatterObj.compatibility = skill.compatibility;
|
||||
@@ -77,8 +77,8 @@ export function transformOpenCode(
|
||||
}
|
||||
}
|
||||
|
||||
const userInvokableCount = skills.filter((s) => s.userInvokable).length;
|
||||
const userInvocableCount = skills.filter((s) => s.userInvocable).length;
|
||||
const refInfo = refCount > 0 ? ` (${refCount} reference files)` : '';
|
||||
const prefixInfo = prefix ? ` [${prefix}prefixed]` : '';
|
||||
console.log(`✓ OpenCode${prefixInfo}: ${skills.length} skills (${userInvokableCount} user-invokable)${refInfo}`);
|
||||
console.log(`✓ OpenCode${prefixInfo}: ${skills.length} skills (${userInvocableCount} user-invocable)${refInfo}`);
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import { cleanDir, ensureDir, writeFile, generateYamlFrontmatter, replacePlaceho
|
||||
* All skills output to .pi/skills/{name}/SKILL.md
|
||||
* Frontmatter: name, description, license, compatibility, metadata
|
||||
*
|
||||
* @param {Array} skills - All skills (including user-invokable ones)
|
||||
* @param {Array} skills - All skills (including user-invocable ones)
|
||||
* @param {string} distDir - Distribution output directory
|
||||
* @param {Object} patterns - Design patterns data (unused)
|
||||
* @param {Object} options - Optional settings
|
||||
@@ -23,7 +23,7 @@ export function transformPi(skills, distDir, patterns = null, options = {}) {
|
||||
ensureDir(skillsDir);
|
||||
|
||||
const allSkillNames = skills.map(s => s.name);
|
||||
const commandNames = skills.filter(s => s.userInvokable).map(s => `${prefix}${s.name}`);
|
||||
const commandNames = skills.filter(s => s.userInvocable).map(s => `${prefix}${s.name}`);
|
||||
let refCount = 0;
|
||||
for (const skill of skills) {
|
||||
const skillName = `${prefix}${skill.name}`;
|
||||
@@ -58,8 +58,8 @@ export function transformPi(skills, distDir, patterns = null, options = {}) {
|
||||
}
|
||||
}
|
||||
|
||||
const userInvokableCount = skills.filter(s => s.userInvokable).length;
|
||||
const userInvocableCount = skills.filter(s => s.userInvocable).length;
|
||||
const refInfo = refCount > 0 ? ` (${refCount} reference files)` : '';
|
||||
const prefixInfo = prefix ? ` [${prefix}prefixed]` : '';
|
||||
console.log(`✓ Pi${prefixInfo}: ${skills.length} skills (${userInvokableCount} user-invokable)${refInfo}`);
|
||||
console.log(`✓ Pi${prefixInfo}: ${skills.length} skills (${userInvocableCount} user-invocable)${refInfo}`);
|
||||
}
|
||||
|
||||
@@ -104,7 +104,7 @@ export function readFilesRecursive(dir, fileList = []) {
|
||||
/**
|
||||
* Read and parse all source files (unified skills architecture)
|
||||
* All source lives in source/skills/{name}/SKILL.md
|
||||
* Returns { skills } where each skill has userInvokable flag
|
||||
* Returns { skills } where each skill has userInvocable flag
|
||||
*/
|
||||
export function readSourceFiles(rootDir) {
|
||||
const skillsDir = path.join(rootDir, 'source/skills');
|
||||
@@ -147,7 +147,7 @@ export function readSourceFiles(rootDir) {
|
||||
compatibility: frontmatter.compatibility || '',
|
||||
metadata: frontmatter.metadata || null,
|
||||
allowedTools: frontmatter['allowed-tools'] || '',
|
||||
userInvokable: frontmatter['user-invokable'] === true || frontmatter['user-invokable'] === 'true',
|
||||
userInvocable: frontmatter['user-invocable'] === true || frontmatter['user-invocable'] === 'true',
|
||||
args: frontmatter.args || [],
|
||||
context: frontmatter.context || null,
|
||||
body,
|
||||
|
||||
@@ -40,17 +40,17 @@ export async function getSkills() {
|
||||
id: entry.name,
|
||||
name: frontmatter.name || entry.name,
|
||||
description: frontmatter.description || "No description available",
|
||||
userInvokable: frontmatter['user-invokable'] === true || frontmatter['user-invokable'] === 'true',
|
||||
userInvocable: frontmatter['user-invocable'] === true || frontmatter['user-invocable'] === 'true',
|
||||
});
|
||||
}
|
||||
|
||||
return skills;
|
||||
}
|
||||
|
||||
// Read commands (user-invokable skills)
|
||||
// Read commands (user-invocable skills)
|
||||
export async function getCommands() {
|
||||
const allSkills = await getSkills();
|
||||
return allSkills.filter(s => s.userInvokable);
|
||||
return allSkills.filter(s => s.userInvocable);
|
||||
}
|
||||
|
||||
// Get command/skill source content
|
||||
|
||||
@@ -8,7 +8,7 @@ args:
|
||||
- name: context
|
||||
description: What to adapt for (mobile, tablet, desktop, print, email, etc.)
|
||||
required: false
|
||||
user-invokable: true
|
||||
user-invocable: true
|
||||
---
|
||||
|
||||
Adapt existing designs to work effectively across different contexts - different screen sizes, devices, platforms, or use cases.
|
||||
|
||||
@@ -5,7 +5,7 @@ args:
|
||||
- name: target
|
||||
description: The feature or component to animate (optional)
|
||||
required: false
|
||||
user-invokable: true
|
||||
user-invocable: true
|
||||
---
|
||||
|
||||
Analyze a feature and strategically add animations and micro-interactions that enhance understanding, provide feedback, and create delight.
|
||||
|
||||
@@ -5,7 +5,7 @@ args:
|
||||
- name: target
|
||||
description: The feature or component to improve layout for (optional)
|
||||
required: false
|
||||
user-invokable: true
|
||||
user-invocable: true
|
||||
---
|
||||
|
||||
Assess and improve layout and spacing that feels monotonous, crowded, or structurally weak — turning generic arrangements into intentional, rhythmic compositions.
|
||||
|
||||
@@ -5,7 +5,7 @@ args:
|
||||
- name: area
|
||||
description: The feature or area to audit (optional)
|
||||
required: false
|
||||
user-invokable: true
|
||||
user-invocable: true
|
||||
---
|
||||
|
||||
Run systematic quality checks and generate a comprehensive audit report with prioritized issues and actionable recommendations. Don't fix issues - document them for other commands to address.
|
||||
|
||||
@@ -5,7 +5,7 @@ args:
|
||||
- name: target
|
||||
description: The feature or component to make bolder (optional)
|
||||
required: false
|
||||
user-invokable: true
|
||||
user-invocable: true
|
||||
---
|
||||
|
||||
Increase visual impact and personality in designs that are too safe, generic, or visually underwhelming, creating more engaging and memorable experiences.
|
||||
|
||||
@@ -5,7 +5,7 @@ args:
|
||||
- name: target
|
||||
description: The feature or component with unclear copy (optional)
|
||||
required: false
|
||||
user-invokable: true
|
||||
user-invocable: true
|
||||
---
|
||||
|
||||
Identify and improve unclear, confusing, or poorly written interface text to make the product easier to understand and use.
|
||||
|
||||
@@ -5,7 +5,7 @@ args:
|
||||
- name: target
|
||||
description: The feature or component to colorize (optional)
|
||||
required: false
|
||||
user-invokable: true
|
||||
user-invocable: true
|
||||
---
|
||||
|
||||
Strategically introduce color to designs that are too monochromatic, gray, or lacking in visual warmth and personality.
|
||||
|
||||
@@ -5,7 +5,7 @@ args:
|
||||
- name: area
|
||||
description: The feature or area to critique (optional)
|
||||
required: false
|
||||
user-invokable: true
|
||||
user-invocable: true
|
||||
---
|
||||
|
||||
## MANDATORY PREPARATION
|
||||
|
||||
@@ -5,7 +5,7 @@ args:
|
||||
- name: target
|
||||
description: The feature or area to add delight to (optional)
|
||||
required: false
|
||||
user-invokable: true
|
||||
user-invocable: true
|
||||
---
|
||||
|
||||
Identify opportunities to add moments of joy, personality, and unexpected polish that transform functional interfaces into delightful experiences.
|
||||
|
||||
@@ -5,7 +5,7 @@ args:
|
||||
- name: target
|
||||
description: The feature or component to distill (optional)
|
||||
required: false
|
||||
user-invokable: true
|
||||
user-invocable: true
|
||||
---
|
||||
|
||||
Remove unnecessary complexity from designs, revealing the essential elements and creating clarity through ruthless simplification.
|
||||
|
||||
@@ -5,7 +5,7 @@ args:
|
||||
- name: target
|
||||
description: The feature, component, or area to extract from (optional)
|
||||
required: false
|
||||
user-invokable: true
|
||||
user-invocable: true
|
||||
---
|
||||
|
||||
Identify reusable patterns, components, and design tokens, then extract and consolidate them into the design system for systematic reuse.
|
||||
|
||||
@@ -5,7 +5,7 @@ args:
|
||||
- name: target
|
||||
description: The feature or area to harden (optional)
|
||||
required: false
|
||||
user-invokable: true
|
||||
user-invocable: true
|
||||
---
|
||||
|
||||
Strengthen interfaces against edge cases, errors, internationalization issues, and real-world usage scenarios that break idealized designs.
|
||||
|
||||
@@ -5,7 +5,7 @@ args:
|
||||
- name: feature
|
||||
description: The page, route, or feature to normalize (optional)
|
||||
required: false
|
||||
user-invokable: true
|
||||
user-invocable: true
|
||||
---
|
||||
|
||||
Analyze and redesign the feature to perfectly match our design system standards, aesthetics, and established patterns.
|
||||
|
||||
@@ -5,7 +5,7 @@ args:
|
||||
- name: target
|
||||
description: The feature or area needing onboarding (optional)
|
||||
required: false
|
||||
user-invokable: true
|
||||
user-invocable: true
|
||||
---
|
||||
|
||||
## MANDATORY PREPARATION
|
||||
|
||||
@@ -5,7 +5,7 @@ args:
|
||||
- name: target
|
||||
description: The feature or area to optimize (optional)
|
||||
required: false
|
||||
user-invokable: true
|
||||
user-invocable: true
|
||||
---
|
||||
|
||||
Identify and fix performance issues to create faster, smoother user experiences.
|
||||
|
||||
@@ -5,7 +5,7 @@ args:
|
||||
- name: target
|
||||
description: The feature or area to push into overdrive (optional)
|
||||
required: false
|
||||
user-invokable: true
|
||||
user-invocable: true
|
||||
---
|
||||
|
||||
Start your response with:
|
||||
|
||||
@@ -5,7 +5,7 @@ args:
|
||||
- name: target
|
||||
description: The feature or area to polish (optional)
|
||||
required: false
|
||||
user-invokable: true
|
||||
user-invocable: true
|
||||
---
|
||||
|
||||
## MANDATORY PREPARATION
|
||||
|
||||
@@ -5,7 +5,7 @@ args:
|
||||
- name: target
|
||||
description: The feature or component to make quieter (optional)
|
||||
required: false
|
||||
user-invokable: true
|
||||
user-invocable: true
|
||||
---
|
||||
|
||||
Reduce visual intensity in designs that are too bold, aggressive, or overstimulating, creating a more refined and approachable aesthetic without losing effectiveness.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
name: teach-impeccable
|
||||
description: One-time setup that gathers design context for your project and saves it to your AI config file. Run once to establish persistent design guidelines.
|
||||
user-invokable: true
|
||||
user-invocable: true
|
||||
---
|
||||
|
||||
Gather design context for this project, then persist it for all future sessions.
|
||||
|
||||
@@ -5,7 +5,7 @@ args:
|
||||
- name: target
|
||||
description: The feature or component to improve typography for (optional)
|
||||
required: false
|
||||
user-invokable: true
|
||||
user-invocable: true
|
||||
---
|
||||
|
||||
Assess and improve typography that feels generic, inconsistent, or poorly structured — turning default-looking text into intentional, well-crafted type.
|
||||
|
||||
+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', () => {
|
||||
|
||||
+13
-13
@@ -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');
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user