mirror of
https://github.com/pbakaus/impeccable.git
synced 2026-09-17 08:36:25 +03:00
Fix user-invokable -> user-invocable spelling across entire codebase
PR #50 fixed the output SKILL.md files but the source files, build scripts, tests, docs, and server code still used the wrong spelling. Claude Code expects `user-invocable` (with c) for slash command autocomplete to work. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
d6b1a56bc5
commit
df1c26ce63
@@ -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",
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
## Design Context
|
||||
|
||||
### Users
|
||||
Frontend and full-stack developers who use AI coding tools (Cursor, Claude Code, Gemini CLI, Codex CLI, etc.) and want better design output from their AI. They land on the site from GitHub, social media, or word of mouth, already aware that AI-generated UIs have quality problems. They're looking for a practical solution, not education about the problem.
|
||||
|
||||
### Brand Personality
|
||||
Expert, opinionated, refined. Impeccable speaks with an authoritative design voice -- confident taste, editorial quality, zero hedging. It's the design director in the room who knows exactly what's wrong and how to fix it.
|
||||
|
||||
### Aesthetic Direction
|
||||
- **Visual tone**: Editorial precision meets fluid motion. Light, high-contrast, typographically bold. The site itself must be proof of what Impeccable enables -- it should practice what it preaches.
|
||||
- **Theme**: Light mode with warm paper tones, vibrant magenta/rose accent, serif display + clean sans body
|
||||
- **Anti-references**: Generic AI tool marketing (dark mode, purple gradients, glassmorphism, neon accents). The site should be the antithesis of everything it critiques.
|
||||
|
||||
### Design Principles
|
||||
1. **Practice what you preach** -- the site must pass its own anti-pattern tests with flying colors
|
||||
2. **Show, don't tell** -- demonstrate design quality through execution, not just words
|
||||
3. **Expert confidence** -- no hedging, no "maybe consider." Direct, opinionated, decisive.
|
||||
4. **Editorial over marketing** -- feels like a design publication, not a SaaS landing page
|
||||
5. **Purposeful restraint** -- every element earns its place; nothing decorative without function
|
||||
@@ -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