Skills registry
The .ai/ skills registry, the multi-provider adapter pattern, and the sync script that fans skills out to Claude, Codex, and Cursor.
Skills are the reusable instructions that AI agents invoke to perform well-defined tasks in this repo — propose a change, archive a change, run explore mode, and so on. They live in one canonical location and are fanned out to each supported provider by a small sync script.
The goal is provider-agnostic authoring. You write a skill once under .ai/skills/, register it in .ai/manifest.yaml, run pnpm ai:sync, and the same skill becomes available in Claude Code, Codex, and Cursor — each with the filename conventions and frontmatter that provider expects.
Canonical skill sources live under .ai/skills/ and are registered in .ai/manifest.yaml. Everything under .claude/skills/, .codex/skills/, and .cursor/skills/ is generated output — never edit those files directly. If you change a skill, edit the source in .ai/skills/<name>/skill.md and re-run pnpm ai:sync.
Layout
.ai/
├── manifest.yaml # registry: which skills and commands are published, and to which providers
├── skills/ # canonical skill sources (one directory per skill)
│ └── <skill-name>/
│ └── skill.md
├── commands/ # optional slash-command wrappers (opsx/propose, opsx/apply, …)
│ └── opsx/
│ └── <command>.md
└── providers/ # per-provider adapter configs
├── claude/adapter.yaml
├── codex/adapter.yaml
└── cursor/adapter.yaml
A skill source is just a markdown file with YAML frontmatter (name, description, and optional metadata) followed by the instructions the agent should follow. The sync script reads the manifest, looks up the source, applies the provider's adapter rules (filename case, frontmatter transform, output directory), and writes the result into .claude/, .codex/, or .cursor/ as appropriate.
The manifest
.ai/manifest.yaml is the single source of truth for which skills exist and where they publish. A minimal entry looks like:
version: 1 skills: openspec-propose: source: skills/openspec-propose/skill.md providers: [claude, codex, cursor] commands: opsx/propose: source: commands/opsx/propose.md providers: [claude, cursor]
Three things to know:
sourceis a path relative to.ai/. It's always the canonical markdown file.providersis the fan-out list. Every provider in the list gets a copy of the skill through its adapter.- Commands are separate from skills. Slash commands (like
opsx:propose) are thin wrappers that invoke a skill. Codex doesn't support slash commands, so command entries typically list[claude, cursor]while their underlying skills list all three.
The provider adapters
Each provider has an adapter.yaml under .ai/providers/<name>/ that tells the sync script where to put files and how to transform them. The shape is deliberately small:
outputDir: .claude mappings: skills: target: "skills/{name}/SKILL.md" transform: uppercase-filename commands: target: "commands/{path}.md" frontmatter: passthrough
For Claude, a skill at .ai/skills/openspec-propose/skill.md becomes .claude/skills/openspec-propose/SKILL.md (uppercase filename — Claude's convention). For Cursor and Codex, the same source becomes the file those tools expect. The sync script does the copy and the transform; the adapter config captures every provider-specific quirk in one place.
The sync script
pnpm ai:sync runs scripts/ai-sync.mjs. It:
- Reads
.ai/manifest.yaml. - For every
skills:entry, loads the source markdown and the listed providers' adapter configs. - For every
(skill, provider)pair, writes the transformed file to the provider'soutputDir. - Does the same for
commands:entries.
Use pnpm ai:sync --check in CI to verify the generated output is up to date with the sources. The opsx:archive skill runs this check before finalising a change, so a forgotten sync won't sneak into an archive.
Registered skills
At time of writing, the canonical skill set is:
| Skill | Purpose |
|---|---|
openspec-propose | Create a new openspec change with proposal, design, specs, and a phased tasks.md in one step. |
openspec-apply-change | Implement the tasks from an existing change — supports parallel team dispatch in worktrees. |
openspec-explore | Thinking-partner mode. Read, investigate, diagram — never implement. Use before or during a change. |
openspec-archive-change | Finalize a completed change, sync delta specs to the main spec tree, and move the change to openspec/changes/archive/. |
docs-component | Scaffold or update a component documentation page in apps/docs with the standard five-section layout (Accessibility, Playground, Props, Variants, Usage). |
All five skills are published to [claude, codex, cursor] via the manifest. The slash-command wrappers opsx:propose, opsx:apply, opsx:explore, and opsx:archive are published to [claude, cursor] (Codex invokes the underlying skill directly).
Authoring a new skill
Create the source
Add .ai/skills/<skill-name>/skill.md with YAML frontmatter (name, description) and the instructions the agent should follow. Keep it provider-agnostic — no Claude-only or Cursor-only syntax.
Register it in the manifest
Add an entry to .ai/manifest.yaml under skills: with the source path and the list of providers that should receive it.
Optionally add a slash command
If the skill should be invokable as a slash command in Claude or Cursor, add a matching entry under .ai/commands/ and register it in .ai/manifest.yaml under commands:.
Sync
Run pnpm ai:sync. The generated files land under .claude/, .codex/, and .cursor/. Commit both the source and the generated output so CI stays green.
Verify with `--check`
Run pnpm ai:sync --check to confirm the generated output is up to date with the sources. This is the same check the archive skill runs.
What's next?
- Read the Openspec workflow page to see the skills in context.
- Read the Surfaces inventory page to understand how proposals verify coverage.
- Browse
.ai/skills/in the repo to see the real skill sources.