Update provider configs from official docs, add HARNESSES.md reference

Research each harness's official documentation to verify and correct
provider frontmatter configs. Remove Codex/Gemini body transforms that
targeted their commands systems, not skills.

- Add compatibility + metadata to Cursor and Agents (Copilot)
- Add allowed-tools to Pi
- Remove Codex $ARGNAME and Gemini {{args}} body transforms
- Add HARNESSES.md as source of truth for harness capabilities

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Paul Bakaus
2026-03-24 10:46:59 -07:00
co-authored by Claude Opus 4.6
parent 6de73abf1b
commit 2d4c7899dd
5 changed files with 87 additions and 129 deletions
+1
View File
@@ -1,6 +1,7 @@
---
name: frontend-design
description: Create distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, artifacts, posters, or applications. Generates creative, polished code that avoids generic AI aesthetics.
license: Apache 2.0. Based on Anthropic's frontend-design skill. See NOTICE.md for attribution.
---
This skill guides creation of distinctive, production-grade frontend interfaces that avoid generic "AI slop" aesthetics. Implement real working code with exceptional attention to aesthetic details and creative choices.
+81
View File
@@ -0,0 +1,81 @@
# Harness Skills Capabilities Reference
Source of truth for what each AI coding harness supports in terms of agent skills.
Used to inform provider configs in `scripts/lib/transformers/providers.js`.
Last verified: 2026-03-24
## Official Documentation
| Harness | Docs URL |
|---------|----------|
| Claude Code | https://code.claude.com/docs/en/skills |
| Cursor | https://cursor.com/docs/context/skills |
| Gemini CLI | https://geminicli.com/docs/cli/skills/ |
| Codex CLI | https://developers.openai.com/codex/skills |
| GitHub Copilot (Agents) | https://code.visualstudio.com/docs/copilot/customization/agent-skills |
| Kiro | https://kiro.dev/docs/skills/ |
| OpenCode | https://opencode.ai/docs/skills/ |
| Pi | https://github.com/badlogic/pi-mono/blob/main/packages/coding-agent/docs/skills.md |
## Spec Compliance
All harnesses follow the [Agent Skills specification](https://agentskills.io/specification) to varying degrees. The spec defines these frontmatter fields: `name`, `description`, `license`, `compatibility`, `metadata`, `allowed-tools`.
Provider-specific extensions beyond the spec: `user-invocable`, `argument-hint`, `disable-model-invocation`, `allowed-tools` (extended syntax), `model`, `effort`, `context`, `agent`, `hooks`, `subtask`, `mcp`.
## Frontmatter Support
Fields marked with * are spec-standard. Others are provider extensions.
| Field | Claude Code | Cursor | Gemini | Codex | Copilot | Kiro | OpenCode | Pi |
|-------|:-----------:|:------:|:------:|:-----:|:-------:|:----:|:--------:|:--:|
| `name`* | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes |
| `description`* | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes |
| `license`* | Yes | Yes | Ignored | No | Yes | Yes | Yes | Yes |
| `compatibility`* | Yes | Yes | Ignored | No | Yes | Yes | Yes | Yes |
| `metadata`* | Yes | Yes | Ignored | No | Yes | Yes | Yes | Yes |
| `allowed-tools`* | Yes | No | Ignored | No | No | No | Yes | Yes |
| `user-invocable` | Yes | No | No | No | Yes | No | Yes | No |
| `argument-hint` | Yes | No | No | No | Yes | No | Yes | No |
| `disable-model-invocation` | Yes | Yes | No | No | Yes | No | Yes | Yes |
| `model` | Yes | No | No | No | No | No | Yes | No |
| `effort` | Yes | No | No | No | No | No | No | No |
| `context` | Yes | No | No | No | No | No | No | No |
| `agent` | Yes | No | No | No | No | No | Yes | No |
| `hooks` | Yes | No | No | No | No | No | No | No |
Notes:
- Gemini CLI validates only `name` and `description`; other spec fields are parsed but ignored.
- Codex CLI uses a separate `agents/openai.yaml` sidecar for extended metadata (icons, branding, MCP tools, invocation control).
- Kiro recognizes `user-invocable` and `disable-model-invocation` per community reports but does not formally document them.
- Unknown fields are silently ignored by all harnesses.
## Skill Directory Structure
| Harness | Native directory | Also reads |
|---------|-----------------|------------|
| Claude Code | `.claude/skills/` | - |
| Cursor | `.cursor/skills/` | `.agents/skills/`, `.claude/skills/`, `.codex/skills/` |
| Gemini CLI | `.gemini/skills/` | `.agents/skills/` |
| Codex CLI | `.agents/skills/` (primary) | - |
| GitHub Copilot | `.github/skills/` | `.agents/skills/`, `.claude/skills/` |
| Kiro | `.kiro/skills/` | - |
| OpenCode | `.opencode/skills/` | `.agents/skills/`, `.claude/skills/` |
| Pi | `.pi/skills/` | `.agents/skills/` |
All harnesses support the `{skill-name}/SKILL.md` directory structure with optional `reference/`, `scripts/`, and `assets/` subdirectories.
## Placeholder / Variable Substitution
Claude Code supports runtime variable substitution directly in SKILL.md bodies: `$ARGUMENTS`, `$0`-`$N`, `${CLAUDE_SKILL_DIR}`, `${CLAUDE_SESSION_ID}`. No other harness supports substitution in skills.
Some harnesses have separate "custom commands" systems (distinct from skills) with their own substitution:
| Harness | Command system | Substitution syntax |
|---------|---------------|-------------------|
| Gemini CLI | `.gemini/commands/` (TOML) | `{{args}}`, `!{shell}`, `@{file}` |
| Codex CLI | `.codex/prompts/` | `$ARGNAME` |
| OpenCode | `.opencode/commands/` | `$ARGUMENTS`, `$1`-`$N`, `` !`shell` `` |
Our build system handles cross-provider placeholders at compile time via `replacePlaceholders()` for `{{model}}`, `{{config_file}}`, `{{ask_instruction}}`, and `{{available_commands}}`.
+3 -15
View File
@@ -13,7 +13,7 @@ export const PROVIDERS = {
provider: 'cursor',
configDir: '.cursor',
displayName: 'Cursor',
frontmatterFields: ['license'],
frontmatterFields: ['license', 'compatibility', 'metadata'],
},
'claude-code': {
provider: 'claude-code',
@@ -26,30 +26,18 @@ export const PROVIDERS = {
configDir: '.gemini',
displayName: 'Gemini',
frontmatterFields: [],
bodyTransform: (body, skill) => {
if (skill.userInvocable) {
return body.replace(/\{\{[^}]+\}\}/g, '{{args}}');
}
return body;
},
},
codex: {
provider: 'codex',
configDir: '.codex',
displayName: 'Codex',
frontmatterFields: ['argument-hint', 'license'],
bodyTransform: (body, skill) => {
if (skill.userInvocable) {
return body.replace(/\{\{([^}]+)\}\}/g, (_, argName) => `$${argName.toUpperCase()}`);
}
return body;
},
},
agents: {
provider: 'agents',
configDir: '.agents',
displayName: 'Agents',
frontmatterFields: ['user-invocable', 'argument-hint'],
frontmatterFields: ['user-invocable', 'argument-hint', 'license', 'compatibility', 'metadata'],
},
kiro: {
provider: 'kiro',
@@ -67,6 +55,6 @@ export const PROVIDERS = {
provider: 'pi',
configDir: '.pi',
displayName: 'Pi',
frontmatterFields: ['license', 'compatibility', 'metadata'],
frontmatterFields: ['license', 'compatibility', 'metadata', 'allowed-tools'],
},
};
+2 -2
View File
@@ -203,13 +203,13 @@ Please audit {{target}} for technical quality. Ask {{model}} for help.`;
// 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-invocable in Gemini
expect(geminiContent).toContain('{{target}}'); // No body transform, placeholder preserved
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-invocable in Codex
expect(codexContent).toContain('{{target}}'); // No body transform, placeholder preserved
expect(codexContent).toContain('GPT');
});
-112
View File
@@ -190,115 +190,3 @@ for (const [key, config] of Object.entries(PROVIDERS)) {
}
});
}
// Provider-specific body transform tests
describe('Codex body transform', () => {
const transform = createTransformer(PROVIDERS.codex);
beforeEach(() => {
if (fs.existsSync(TEST_DIR)) fs.rmSync(TEST_DIR, { recursive: true, force: true });
});
afterEach(() => {
if (fs.existsSync(TEST_DIR)) fs.rmSync(TEST_DIR, { recursive: true, force: true });
});
test('should transform {{argname}} to $ARGNAME for user-invocable skills', () => {
const skills = [{
name: 'test',
description: 'Test',
userInvocable: true,
argumentHint: '[target]',
body: 'Process {{target}} now.'
}];
transform(skills, TEST_DIR);
const content = fs.readFileSync(
path.join(TEST_DIR, `codex/.codex/skills/test/SKILL.md`), 'utf-8'
);
expect(content).toContain('Process $TARGET now.');
});
test('should not transform {{argname}} for non-user-invocable skills', () => {
const skills = [{
name: 'test',
description: 'Test',
userInvocable: false,
body: 'Process {{target}} now.'
}];
transform(skills, TEST_DIR);
const content = fs.readFileSync(
path.join(TEST_DIR, `codex/.codex/skills/test/SKILL.md`), 'utf-8'
);
// {{target}} should have been left as-is (not transformed by replacePlaceholders either,
// since 'target' is not a known placeholder)
expect(content).toContain('Process {{target}} now.');
});
test('should replace system placeholders before body transform', () => {
const skills = [{
name: 'test',
description: 'Test',
userInvocable: true,
body: 'Ask {{model}} about {{target}}.'
}];
transform(skills, TEST_DIR);
const content = fs.readFileSync(
path.join(TEST_DIR, `codex/.codex/skills/test/SKILL.md`), 'utf-8'
);
// {{model}} replaced first by replacePlaceholders, then {{target}} by bodyTransform
expect(content).toContain('Ask GPT about $TARGET.');
});
});
describe('Gemini body transform', () => {
const transform = createTransformer(PROVIDERS.gemini);
beforeEach(() => {
if (fs.existsSync(TEST_DIR)) fs.rmSync(TEST_DIR, { recursive: true, force: true });
});
afterEach(() => {
if (fs.existsSync(TEST_DIR)) fs.rmSync(TEST_DIR, { recursive: true, force: true });
});
test('should transform all remaining {{*}} to {{args}} for user-invocable skills', () => {
const skills = [{
name: 'test',
description: 'Test',
userInvocable: true,
body: 'Process {{target}} and {{format}}.'
}];
transform(skills, TEST_DIR);
const content = fs.readFileSync(
path.join(TEST_DIR, `gemini/.gemini/skills/test/SKILL.md`), 'utf-8'
);
expect(content).toContain('Process {{args}} and {{args}}.');
});
test('should not transform for non-user-invocable skills', () => {
const skills = [{
name: 'test',
description: 'Test',
userInvocable: false,
body: 'Process {{target}}.'
}];
transform(skills, TEST_DIR);
const content = fs.readFileSync(
path.join(TEST_DIR, `gemini/.gemini/skills/test/SKILL.md`), 'utf-8'
);
expect(content).toContain('Process {{target}}.');
});
test('should replace system placeholders before body transform', () => {
const skills = [{
name: 'test',
description: 'Test',
userInvocable: true,
body: 'Ask {{model}} about {{target}}.'
}];
transform(skills, TEST_DIR);
const content = fs.readFileSync(
path.join(TEST_DIR, `gemini/.gemini/skills/test/SKILL.md`), 'utf-8'
);
// {{model}} replaced first, then remaining {{target}} becomes {{args}}
expect(content).toContain('Ask Gemini about {{args}}.');
});
});