adding-new-ai-format
Step-by-step guide for adding support for a new AI editor format to PRPM - covers types, converters, schemas, CLI, webapp, and testing
Best use case
adding-new-ai-format is best used when you need a repeatable AI agent workflow instead of a one-off prompt.
Step-by-step guide for adding support for a new AI editor format to PRPM - covers types, converters, schemas, CLI, webapp, and testing
Teams using adding-new-ai-format should expect a more consistent output, faster repeated execution, less prompt rewriting.
When to use this skill
- You want a reusable workflow that can be run more than once with consistent structure.
When not to use this skill
- You only need a quick one-off answer and do not need a reusable workflow.
- You cannot install or maintain the underlying files, dependencies, or repository context.
Installation
Claude Code / Cursor / Codex
Manual Installation
- Download SKILL.md from GitHub
- Place it in
.claude/skills/adding-new-ai-format/SKILL.mdinside your project - Restart your AI agent — it will auto-discover the skill
How adding-new-ai-format Compares
| Feature / Agent | adding-new-ai-format | Standard Approach |
|---|---|---|
| Platform Support | Not specified | Limited / Varies |
| Context Awareness | High | Baseline |
| Installation Complexity | Unknown | N/A |
Frequently Asked Questions
What does this skill do?
Step-by-step guide for adding support for a new AI editor format to PRPM - covers types, converters, schemas, CLI, webapp, and testing
Where can I find the source code?
You can find the source code on GitHub using the link provided at the top of the page.
SKILL.md Source
# Adding a New AI Format to PRPM
Complete process for adding support for a new AI editor format (like OpenCode, Cursor, Claude, etc.) to PRPM.
## Overview
This skill documents the systematic process for adding a new AI format to PRPM, based on the OpenCode integration. Follow these steps in order to ensure complete integration across all packages.
## Prerequisites
- Format documentation (understand file structure, frontmatter, directory conventions)
- Example files from the format
- Understanding of format-specific features (tools, agents, commands, etc.)
## Step 1: Types Package (`packages/types/`)
**File**: `src/package.ts`
Add the format to the Format type and FORMATS array:
```typescript
export type Format =
| 'cursor'
| 'claude'
| 'continue'
| 'windsurf'
| 'copilot'
| 'kiro'
| 'agents.md'
| 'gemini.md'
| 'claude.md'
| 'gemini'
| 'opencode' // Add new format here
| 'ruler'
| 'generic'
| 'mcp';
export const FORMATS: readonly Format[] = [
'cursor',
'claude',
// ... other formats
'opencode', // Add here too
'ruler',
'generic',
'mcp',
] as const;
```
**Build and verify**:
```bash
npm run build --workspace=@pr-pm/types
```
## Step 2: Converters Package - Schema (`packages/converters/schemas/`)
Create JSON schema file: `{format}.schema.json`
Example structure:
```json
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "https://registry.prpm.dev/api/v1/schemas/opencode.json",
"title": "OpenCode Agent Format",
"description": "JSON Schema for OpenCode Agents",
"type": "object",
"required": ["frontmatter", "content"],
"properties": {
"frontmatter": {
"type": "object",
"required": ["description"],
"properties": {
"description": { "type": "string" },
// Format-specific fields
},
"additionalProperties": false
},
"content": {
"type": "string",
"description": "Body content as markdown"
}
}
}
```
**CRITICAL Schema Requirements:**
- `$id` must use new URL pattern: `https://registry.prpm.dev/api/v1/schemas/{format}.json` for base schemas
- For subtypes: `https://registry.prpm.dev/api/v1/schemas/{format}/{subtype}.json`
- Add `"additionalProperties": false` to frontmatter object to catch invalid fields
- String fields requiring slugs (like `name`) should use pattern: `"pattern": "^[a-z0-9-]+$"`
If the format has subtypes (like Claude with agents/skills/commands), create separate schema files:
- `{format}-agent.schema.json`
- `{format}-skill.schema.json`
- `{format}-slash-command.schema.json`
- etc.
**IMPORTANT**: When creating subtype schemas, you MUST update the validation logic to map them.
## Step 3: Converters Package - Format Documentation (`packages/converters/docs/`)
**CRITICAL**: Create comprehensive format documentation file: `{format}.md`
This documentation serves as the source of truth for:
- Package authors creating packages in this format
- PRPM contributors implementing converters
- Users understanding format capabilities and limitations
**Required sections**:
```markdown
# {Format Name} Format Specification
**File Locations:**
- {Type 1}: `{path}`
- {Type 2}: `{path}`
**Format:** {Markdown/JSON/etc.} with {YAML frontmatter/etc.}
**Official Docs:** {link to official documentation}
## Overview
Brief description of the format and its purpose.
## Frontmatter Fields
### Required Fields
- **`field-name`** (type): Description
### Optional Fields
- **`field-name`** (type): Description
## Content Format
Describe the body/content structure.
## Best Practices
1. Practice 1
2. Practice 2
## Conversion Notes
### From {Format} to Canonical
How the converter parses this format.
### From Canonical to {Format}
How the converter generates this format.
## Limitations
- Limitation 1
- Limitation 2
## Examples
### Example 1
```markdown
{example content}
```
## Related Documentation
- [Official Docs]({url})
- [PRPM Format Guide](../../docs/formats.mdx)
## Changelog
- **{Date}**: Initial format support
```
**Add to README.md**:
1. **Format Matrix table**: Add row(s) with subtypes, official docs, and OpenCode docs links
2. **Available Formats table**: Add row with link to your new `.md` file
3. **Schema Validation section**: Add schema filename(s) to appropriate list
4. **Frontmatter Support table**: Add row with frontmatter requirements
5. **File Organization table**: Add row with file paths and structure
See `packages/converters/docs/README.md` for examples of how other formats are documented.
## Step 4: Converters Package - Canonical Types
**File**: `packages/converters/src/types/canonical.ts`
### 3a. Add format to CanonicalPackage.format union:
```typescript
format: 'cursor' | 'claude' | ... | 'opencode' | 'ruler' | 'generic' | 'mcp';
```
### 3b. Add format-specific metadata (if needed):
```typescript
// In CanonicalPackage.metadata
metadata?: {
// ... existing configs
opencode?: {
mode?: 'subagent' | 'primary' | 'all';
model?: string;
temperature?: number;
permission?: Record<string, any>;
disable?: boolean;
};
};
```
### 3c. Add to MetadataSection.data (if storing format-specific data):
```typescript
export interface MetadataSection {
type: 'metadata';
data: {
title: string;
description: string;
// ... existing fields
opencode?: {
// Same structure as above
};
};
}
```
### 3d. Add to formatScores and sourceFormat:
```typescript
formatScores?: {
cursor?: number;
// ... others
opencode?: number;
};
sourceFormat?: 'cursor' | 'claude' | ... | 'opencode' | ... | 'generic';
```
## Step 5: Converters Package - From Converter
**File**: `packages/converters/src/from-{format}.ts`
Create converter that parses format → canonical:
```typescript
import type {
CanonicalPackage,
PackageMetadata,
Section,
MetadataSection,
ToolsSection,
} from './types/canonical.js';
import { setTaxonomy } from './taxonomy-utils.js';
import yaml from 'js-yaml'; // If using YAML frontmatter
// Define format-specific interfaces
interface FormatFrontmatter {
// Format-specific frontmatter structure
}
// Parse frontmatter if needed
function parseFrontmatter(content: string): {
frontmatter: Record<string, any>;
body: string
} {
const match = content.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
if (!match) {
return { frontmatter: {}, body: content };
}
const frontmatter = yaml.load(match[1]) as Record<string, any>;
const body = match[2];
return { frontmatter, body };
}
export function fromFormat(
content: string,
metadata: Partial<PackageMetadata> & Pick<PackageMetadata, 'id' | 'name' | 'version' | 'author'>
): CanonicalPackage {
const { frontmatter, body } = parseFrontmatter(content);
const fm = frontmatter as FormatFrontmatter;
const sections: Section[] = [];
// 1. Create metadata section
const metadataSection: MetadataSection = {
type: 'metadata',
data: {
title: metadata.name || metadata.id,
description: fm.description || metadata.description || '',
version: metadata.version || '1.0.0',
author: metadata.author,
},
};
// Store format-specific data for roundtrip
if (/* has format-specific fields */) {
metadataSection.data.formatName = {
// Format-specific data
};
}
sections.push(metadataSection);
// 2. Extract tools (if applicable)
if (fm.tools) {
const enabledTools = Object.entries(fm.tools)
.filter(([_, enabled]) => enabled === true)
.map(([tool, _]) => {
// Normalize tool names to canonical format
return normalizeToolName(tool);
});
if (enabledTools.length > 0) {
sections.push({
type: 'tools',
tools: enabledTools,
});
}
}
// 3. Add body as instructions
if (body.trim()) {
sections.push({
type: 'instructions',
title: 'Instructions',
content: body.trim(),
});
}
// 4. Build canonical package
const canonicalContent: CanonicalPackage['content'] = {
format: 'canonical',
version: '1.0',
sections
};
const pkg: CanonicalPackage = {
...metadata,
id: metadata.id,
name: metadata.name || metadata.id,
version: metadata.version,
author: metadata.author,
description: metadata.description || fm.description || '',
tags: metadata.tags || [],
format: 'formatname',
subtype: 'agent', // Or detect from content
content: canonicalContent,
};
setTaxonomy(pkg, 'formatname', 'agent');
return pkg;
}
```
**Key points**:
- Import yaml if format uses YAML frontmatter
- Extract all format-specific metadata for roundtrip conversion
- Normalize tool names to canonical format (Write, Edit, Bash, etc.)
- Always include `format: 'canonical'` and `version: '1.0'` in content
- InstructionsSection requires `title` field
- Call `setTaxonomy()` before returning
## Step 6: Converters Package - To Converter
**File**: `packages/converters/src/to-{format}.ts`
Create converter that converts canonical → format:
```typescript
import type {
CanonicalPackage,
ConversionResult,
} from './types/canonical.js';
import yaml from 'js-yaml';
export function toFormat(pkg: CanonicalPackage): ConversionResult {
const warnings: string[] = [];
let qualityScore = 100;
try {
const content = convertContent(pkg, warnings);
const lossyConversion = warnings.some(w =>
w.includes('not supported') || w.includes('skipped')
);
if (lossyConversion) {
qualityScore -= 10;
}
return {
content,
format: 'formatname',
warnings: warnings.length > 0 ? warnings : undefined,
lossyConversion,
qualityScore,
};
} catch (error) {
warnings.push(`Conversion error: ${error instanceof Error ? error.message : String(error)}`);
return {
content: '',
format: 'formatname',
warnings,
lossyConversion: true,
qualityScore: 0,
};
}
}
function convertContent(pkg: CanonicalPackage, warnings: string[]): string {
const lines: string[] = [];
// Extract sections
const metadata = pkg.content.sections.find(s => s.type === 'metadata');
const tools = pkg.content.sections.find(s => s.type === 'tools');
const instructions = pkg.content.sections.find(s => s.type === 'instructions');
// Build frontmatter
const frontmatter: Record<string, any> = {};
if (metadata?.type === 'metadata') {
frontmatter.description = metadata.data.description;
}
// Restore format-specific metadata (for roundtrip)
const formatData = metadata?.type === 'metadata' ? metadata.data.formatName : undefined;
if (formatData) {
Object.assign(frontmatter, formatData);
}
// Convert tools
if (tools?.type === 'tools' && tools.tools.length > 0) {
frontmatter.tools = convertToolsToFormatStructure(tools.tools);
}
// Generate YAML frontmatter (if applicable)
lines.push('---');
lines.push(yaml.dump(frontmatter, { indent: 2, lineWidth: -1 }).trim());
lines.push('---');
lines.push('');
// Add body content
if (instructions?.type === 'instructions') {
lines.push(instructions.content);
}
return lines.join('\n').trim() + '\n';
}
```
**Section type handling**:
- **PersonaSection**: `section.data.role` (NOT `section.content`)
- **RulesSection**: `section.items` (NOT `section.rules`), each item has `rule.content`
- **InstructionsSection**: `section.content` and `section.title`
- **ExamplesSection**: `section.examples` array with `description` and `code`
## Step 7: Converters Package - Exports and Validation
**File**: `packages/converters/src/index.ts`
Add to exports:
```typescript
// From converters
export { fromFormat } from './from-format.js';
// To converters
export { toFormat } from './to-format.js';
```
**File**: `packages/converters/src/validation.ts`
### 7a. Add to FormatType:
```typescript
export type FormatType =
| 'cursor'
| 'claude'
// ... others
| 'opencode'
| 'canonical';
```
### 7b. Add to base schema map:
```typescript
const schemaMap: Record<FormatType, string> = {
'cursor': 'cursor.schema.json',
// ... others
'opencode': 'opencode.schema.json',
'canonical': 'canonical.schema.json',
};
```
### 7c. **CRITICAL**: Add subtype schemas to subtypeSchemaMap:
```typescript
const subtypeSchemaMap: Record<string, string> = {
'claude:agent': 'claude-agent.schema.json',
'claude:skill': 'claude-skill.schema.json',
'claude:slash-command': 'claude-slash-command.schema.json',
'claude:hook': 'claude-hook.schema.json',
'cursor:slash-command': 'cursor-command.schema.json',
'kiro:hook': 'kiro-hooks.schema.json',
'kiro:agent': 'kiro-agent.schema.json',
'droid:skill': 'droid-skill.schema.json',
'droid:slash-command': 'droid-slash-command.schema.json',
'droid:hook': 'droid-hook.schema.json',
'opencode:slash-command': 'opencode-slash-command.schema.json', // Add your subtypes here
};
```
**Why this matters**: Without adding subtypes to `subtypeSchemaMap`, validation will fall back to the base format schema and won't validate subtype-specific fields. This causes validation to fail or pass incorrectly.
**File**: `packages/converters/src/taxonomy-utils.ts`
Add to Format type:
```typescript
export type Format = 'cursor' | 'claude' | ... | 'opencode' | ... | 'mcp';
```
Add to normalizeFormat:
```typescript
export function normalizeFormat(sourceFormat: string): Format {
const normalized = sourceFormat.toLowerCase();
if (normalized.includes('cursor')) return 'cursor';
// ... others
if (normalized.includes('opencode')) return 'opencode';
return 'generic';
}
```
**Build converters**:
```bash
npm run build --workspace=@pr-pm/converters
```
## Step 8: CLI Package - Filesystem
**File**: `packages/cli/src/core/filesystem.ts`
### 7a. Add to getDestinationDir:
```typescript
export function getDestinationDir(format: Format, subtype: Subtype, name?: string): string {
const packageName = stripAuthorNamespace(name);
switch (format) {
// ... existing cases
case 'opencode':
// OpenCode supports agents, slash commands, and custom tools
// Agents: .opencode/agent/*.md
// Commands: .opencode/command/*.md
// Tools: .opencode/tool/*.ts or *.js
if (subtype === 'agent') return '.opencode/agent';
if (subtype === 'slash-command') return '.opencode/command';
if (subtype === 'tool') return '.opencode/tool';
return '.opencode/agent'; // Default
// ... rest
}
}
```
### 7b. Add to autoDetectFormat:
```typescript
const formatDirs: Array<{ format: Format; dir: string }> = [
{ format: 'cursor', dir: '.cursor' },
// ... others
{ format: 'opencode', dir: '.opencode' },
{ format: 'agents.md', dir: '.agents' },
];
```
## Step 9: CLI Package - Format Mappings
**Files**: `packages/cli/src/commands/search.ts` and `packages/cli/src/commands/install.ts`
Add to both files:
### 8a. formatIcons:
```typescript
const formatIcons: Record<Format, string> = {
'claude': '🤖',
'cursor': '📋',
// ... others
'opencode': '⚡', // Choose appropriate emoji
'gemini.md': '✨', // Don't forget format aliases
'claude.md': '🤖',
'ruler': '📏',
'generic': '📦',
};
```
### 8b. formatLabels:
```typescript
const formatLabels: Record<Format, string> = {
'claude': 'Claude',
'cursor': 'Cursor',
// ... others
'opencode': 'OpenCode',
'gemini.md': 'Gemini', // Format aliases
'claude.md': 'Claude',
'ruler': 'Ruler',
'generic': '',
};
```
## Step 10: Webapp - Format Subtypes and Filter Dropdown
**File**: `packages/webapp/src/app/(app)/search/SearchClient.tsx`
### 9a. Add to FORMAT_SUBTYPES:
```typescript
const FORMAT_SUBTYPES: Record<Format, Subtype[]> = {
'cursor': ['rule', 'agent', 'slash-command', 'tool'],
'claude': ['skill', 'agent', 'slash-command', 'tool', 'hook'],
'claude.md': ['agent'], // Format aliases
'gemini.md': ['slash-command'],
// ... others
'opencode': ['agent', 'slash-command', 'tool'], // List all supported subtypes
'ruler': ['rule', 'agent', 'tool'],
'generic': ['rule', 'agent', 'skill', 'slash-command', 'tool', 'chatmode', 'hook'],
};
```
### 9b. Add to format filter dropdown (around line 1195):
```typescript
<select
value={selectedFormat}
onChange={(e) => setSelectedFormat(e.target.value as Format | '')}
className="w-full px-3 py-2 bg-prpm-dark border border-prpm-border rounded text-white focus:outline-none focus:border-prpm-accent"
>
<option value="">All Formats</option>
<option value="cursor">Cursor</option>
<option value="claude">Claude</option>
<option value="continue">Continue</option>
<option value="windsurf">Windsurf</option>
<option value="copilot">GitHub Copilot</option>
<option value="kiro">Kiro</option>
<option value="gemini">Gemini CLI</option>
<option value="droid">Droid</option>
<option value="opencode">OpenCode</option> {/* Add your format here */}
<option value="mcp">MCP</option>
<option value="agents.md">Agents.md</option>
<option value="generic">Generic</option>
</select>
```
### 9c. Add compatibility info section (after the dropdown):
```typescript
{selectedFormat === 'opencode' && (
<div className="mt-3 p-3 bg-gray-500/10 border border-gray-500/30 rounded-lg">
<p className="text-xs text-gray-400">
Tool-specific format for <strong>OpenCode AI</strong>
</p>
</div>
)}
```
## Step 11: Registry - Fastify Route Schemas
**CRITICAL**: Add the format to all Fastify route validation schemas to prevent 400 errors.
### 10a. **File**: `packages/registry/src/routes/download.ts`
Add to format enum in schema (2-3 places):
```typescript
// Download route schema (line ~46)
format: {
type: 'string',
enum: ['cursor', 'claude', 'continue', 'windsurf', 'copilot', 'kiro', 'ruler', 'agents.md', 'gemini', 'droid', 'opencode', 'generic'],
description: 'Target format for conversion (optional)',
},
// Compatibility check route schema (lines ~201, 205)
from: {
type: 'string',
enum: ['cursor', 'claude', 'continue', 'windsurf', 'copilot', 'kiro', 'ruler', 'agents.md', 'gemini', 'droid', 'opencode', 'generic'],
},
to: {
type: 'string',
enum: ['cursor', 'claude', 'continue', 'windsurf', 'copilot', 'kiro', 'ruler', 'agents.md', 'gemini', 'droid', 'opencode', 'generic'],
},
```
### 10b. **File**: `packages/registry/src/routes/search.ts`
Add to FORMAT_ENUM constant (line ~12):
```typescript
const FORMAT_ENUM = ['cursor', 'claude', 'continue', 'windsurf', 'copilot', 'kiro', 'agents.md', 'gemini', 'ruler', 'droid', 'opencode', 'generic', 'mcp'] as const;
```
### 10c. **File**: `packages/registry/src/routes/analytics.ts`
Add to both Zod schema and Fastify schema:
```typescript
// Zod schema (line ~15)
const TrackDownloadSchema = z.object({
packageId: z.string(),
version: z.string().optional(),
format: z.enum(['cursor', 'claude', 'continue', 'windsurf', 'copilot', 'kiro', 'agents.md', 'gemini', 'ruler', 'droid', 'opencode', 'generic', 'mcp']).optional(),
client: z.enum(['cli', 'web', 'api']).optional(),
});
// Fastify schema (line ~45)
format: {
type: 'string',
enum: ['cursor', 'claude', 'continue', 'windsurf', 'copilot', 'kiro', 'agents.md', 'gemini', 'ruler', 'droid', 'opencode', 'generic', 'mcp'],
description: 'Download format'
},
```
**Why this matters**: Without these additions, the registry will reject API requests with 400 validation errors when users try to download or filter by the new format.
## Step 12: Testing and Validation
### 11a. Build types package first:
```bash
npm run build --workspace=@pr-pm/types
```
This is critical because other packages depend on the updated Format type.
### 11b. Build registry and webapp:
```bash
npm run build --workspace=@pr-pm/registry
npm run build --workspace=@pr-pm/webapp
```
### 11c. Run typecheck:
```bash
npm run typecheck
```
Fix any TypeScript errors:
- Missing format in type unions
- Format aliases ('gemini.md', 'claude.md')
- Section structure (use correct field names)
### 11d. Build all packages:
```bash
npm run build
```
### 11e. Run converter tests:
```bash
npm test --workspace=@pr-pm/converters
```
### 11f. Create test fixtures (recommended):
```typescript
// packages/converters/src/__tests__/to-opencode.test.ts
import { describe, it, expect } from 'vitest';
import { toOpencode } from '../to-opencode.js';
import { validateMarkdown } from '../validation.js';
import type { CanonicalPackage } from '../types/canonical.js';
describe('OpenCode Format', () => {
it('should convert from OpenCode to canonical', () => {
const opencodeContent = `---
description: Test agent
mode: subagent
---
Test instructions`;
const result = fromOpencode(opencodeContent, {
id: 'test',
name: 'test',
version: '1.0.0',
author: 'test',
});
expect(result.format).toBe('opencode');
expect(result.subtype).toBe('agent');
});
it('should convert canonical to OpenCode', () => {
const canonical: CanonicalPackage = {
// ... build test package
};
const result = toOpencode(canonical);
expect(result.format).toBe('opencode');
expect(result.content).toContain('---');
});
// CRITICAL: Add schema validation tests!
describe('JSON Schema Validation', () => {
it('should generate schema-compliant agent output', () => {
const agentPackage: CanonicalPackage = {
// ... build agent test package with subtype: 'agent'
};
const result = toOpencode(agentPackage);
const validation = validateMarkdown('opencode', result.content, 'agent');
if (!validation.valid) {
console.error('Validation errors:', validation.errors);
}
expect(validation.valid).toBe(true);
expect(validation.errors).toHaveLength(0);
});
});
});
```
**Why Schema Validation Tests Matter:**
- Catch mismatches between converter implementation and schema
- Ensure converters generate compliant output
- Reveal missing required fields or incorrect field names
- Example: We discovered Claude agent schema was missing required `mode` field via validation tests
## Step 13: Additional Documentation
Beyond the format documentation created in Step 3:
- **User-facing**: Add to Mintlify docs if the format needs special installation instructions
- **Internal**: Add notes to `docs/development/` if there are special considerations
- **Decision logs**: Document any architectural decisions in `docs/decisions/`
## Common Pitfalls
### 1. Missing Format Aliases
Formats like 'gemini.md' and 'claude.md' are aliases that MUST be included in all format mappings.
### 2. Incorrect Section Structure
- PersonaSection uses `data.role`, not `content`
- RulesSection uses `items`, not `rules`
- InstructionsSection requires `title` field
- Each Rule has `content`, not `description`
### 3. CanonicalContent Requirements
Must always include:
```typescript
{
format: 'canonical',
version: '1.0',
sections: [...]
}
```
### 4. setTaxonomy Signature
```typescript
setTaxonomy(pkg, 'formatname', 'subtype'); // Returns void
return pkg; // Return the package separately
```
### 5. Tool Name Normalization
Map format-specific tool names to canonical:
- `write` → `Write`
- `edit` → `Edit`
- `bash` → `Bash`
### 6. YAML Import
If using YAML frontmatter:
```typescript
import yaml from 'js-yaml'; // Top-level import
// NOT: const yaml = await import('js-yaml');
```
## Checklist
Before submitting:
**Types Package:**
- [ ] Added format to types/src/package.ts (Format type and FORMATS array)
- [ ] Built types package
**Converters Package:**
- [ ] Created schema file(s) in converters/schemas/
- [ ] If format has subtypes, created separate schema files for each subtype (e.g., {format}-agent.schema.json, {format}-slash-command.schema.json)
- [ ] Created format documentation in converters/docs/{format}.md
- [ ] Updated converters/docs/README.md (Format Matrix, Available Formats, Schema Validation, Frontmatter Support, File Organization tables)
- [ ] Updated converters/src/types/canonical.ts (all 4 places: format union, metadata, MetadataSection.data, formatScores, sourceFormat)
- [ ] Created from-{format}.ts converter
- [ ] Created to-{format}.ts converter
- [ ] Updated converters/src/index.ts exports
- [ ] Updated converters/src/validation.ts (FormatType, schemaMap, and **CRITICAL**: subtypeSchemaMap for each subtype)
- [ ] Updated converters/src/taxonomy-utils.ts (Format type and normalizeFormat)
- [ ] Copied all schemas to packages/cli/dist/schemas/ for runtime use
**CLI Package:**
- [ ] Updated cli/src/core/filesystem.ts (getDestinationDir and autoDetectFormat)
- [ ] Updated cli/src/commands/search.ts (formatIcons and formatLabels, including aliases)
- [ ] Updated cli/src/commands/install.ts (formatIcons and formatLabels, including aliases)
**Webapp Package:**
- [ ] Updated webapp SearchClient.tsx (FORMAT_SUBTYPES, including aliases)
- [ ] Added to format filter dropdown
- [ ] Added compatibility info section
**Registry Package:**
- [ ] Updated registry/src/routes/download.ts (format enum in 2-3 places)
- [ ] Updated registry/src/routes/search.ts (FORMAT_ENUM constant)
- [ ] Updated registry/src/routes/analytics.ts (Zod schema and Fastify schema)
- [ ] Built registry package
**Testing:**
- [ ] Ran typecheck successfully
- [ ] Built all packages successfully
- [ ] Wrote tests for converters
- [ ] Documented the integration
## Example: OpenCode Integration
See the following files for reference:
- `packages/converters/src/from-opencode.ts`
- `packages/converters/src/to-opencode.ts`
- `packages/converters/schemas/opencode.schema.json`
- Git commit history for the OpenCode integration PR
## Summary
Adding a new format requires changes across 6 packages:
1. **types** - Add to Format type (build first!)
2. **converters** - Schema, from/to converters, canonical types, validation, taxonomy
3. **cli** - Filesystem and format mappings
4. **webapp** - Format subtypes, filter dropdown, compatibility info
5. **registry** - Fastify route schemas (download, search, analytics)
6. **tests** - Verify everything works
**Build order matters**: types → converters → cli → webapp → registry
Follow the steps systematically, use existing format implementations as reference, and always run typecheck and tests before submitting.Related Skills
adf-format-json-schema
Query Atlassian Document Format (ADF) JSON schema definitions to understand ADF node and mark types. Use this skill when implementing ADF dataclass nodes/marks, or when user asks about ADF structure, ADF nodes, ADF marks, or Atlassian Document Format implementation.
adding-tweets
Add tweets to the Second Brain. Use when the user provides a Twitter/X URL and pasted tweet content, asking to "add a tweet", "save this tweet", or "capture this tweet".
adding-todos
Use this skill to capture an idea, task, or issue that surfaces during a Kata session as a structured todo for later work. This skill creates markdown todo files in the .planning/todos/pending directory with relevant metadata and content extracted from the conversation. Triggers include "add todo", "capture todo", "new todo", and "create todo".
adding-stacks
Use when adding a new framework/stack to create-faster CLI tool - addresses copy-first mentality, incomplete implementations, and missing dependencies
adding-phases
Use this skill to add planned work discovered during execution to the end of the current milestone in the roadmap. This skill appends sequential phases to the current milestone's phase list, automatically calculating the next phase number. Triggers include "add phase", "append phase", "new phase", and "create phase". This skill updates ROADMAP.md and STATE.md accordingly.
adding-persistent-event
Adds a new type of event that gets persisted to the event log. Use this when adding new kinds of write operations to the system or when adding new events to existing code.
adding-notes
Add new notes to the Second Brain knowledge base. Use when the user provides a resource (URL, book, podcast, article, GitHub repo, Reddit thread) and asks to "add a note", "create a note", "save this", "add to my notes", "take notes on", or "capture this".
/mnt/data2/nhlstats/.github/skills/adding-new-sport/SKILL.md
```markdown
adding-new-metric
Guides systematic implementation of new sustainability metrics in OSS Sustain Guard using the plugin-based metric system. Use when adding metric functions to evaluate project health aspects like issue responsiveness, test coverage, or security response time.
adding-nango-provider-support
Use when adding support for a new Nango provider - configures provider in providers.yaml, creates documentation (main page, setup guide, connect guide), and updates docs.json following established patterns
adding-models
Guide for adding new LLM models to Letta Code. Use when the user wants to add support for a new model, needs to know valid model handles, or wants to update the model configuration. Covers models.json configuration, CI test matrix, and handle validation.
adding-mod-parsers
Use when adding new mod parsers to convert game mod strings to typed Mod objects - guides the template-based parsing pattern (project)