Merge pull request #57 from pbakaus/fix/user-invocable-spelling

Fix user-invokable -> user-invocable spelling across entire codebase
This commit is contained in:
Paul Bakaus
2026-03-20 23:36:47 -07:00
committed by GitHub
46 changed files with 210 additions and 207 deletions
+1 -1
View File
@@ -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",
+3
View File
@@ -22,3 +22,6 @@ Thumbs.db
# Cloudflare
.wrangler/
# User design context
.impeccable.md
+1 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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);
+8 -8
View File
@@ -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}`);
}
+6 -6
View File
@@ -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}`);
}
+11 -11
View File
@@ -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}`);
}
+5 -5
View File
@@ -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}`);
}
+8 -8
View File
@@ -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}`);
}
+4 -4
View File
@@ -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}`);
}
+6 -6
View File
@@ -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}`);
}
+4 -4
View File
@@ -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}`);
}
+2 -2
View File
@@ -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,
+3 -3
View File
@@ -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
+1 -1
View File
@@ -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.
+1 -1
View File
@@ -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.
+1 -1
View File
@@ -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.
+1 -1
View File
@@ -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.
+1 -1
View File
@@ -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.
+1 -1
View File
@@ -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.
+1 -1
View File
@@ -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.
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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.
+1 -1
View File
@@ -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.
+1 -1
View File
@@ -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.
+1 -1
View File
@@ -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.
+1 -1
View File
@@ -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.
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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.
+1 -1
View File
@@ -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:
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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 -1
View File
@@ -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.
+1 -1
View File
@@ -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
View File
@@ -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');
});
+17 -17
View File
@@ -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', () => {
+12 -12
View File
@@ -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' });
+12 -12
View File
@@ -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' });
+3 -3
View File
@@ -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', () => {
+10 -10
View File
@@ -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', () => {
+12 -12
View File
@@ -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', () => {
+16 -16
View File
@@ -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', () => {
+12 -12
View File
@@ -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
View File
@@ -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');
});
});