Skill Management
How pair resolves, transforms, and renames skills, path resolution, the transformation pipeline, and naming conventions.
pair skills are SKILL.md files that live in the Knowledge Base and get distributed to AI tool directories. This page explains the three mechanisms that make this work: resolution, transformation, and renaming.
Skill Resolution
The Problem
Skills are authored in a category-based directory structure (capability/verify-quality/SKILL.md) but AI tools expect them in a flat target directory (.claude/skills/). The CLI needs to locate the right source files and map them to the right targets.
How It Works
Resolution is registry-based, not runtime file scanning. The config.json declares the source directory, target directories, and transform rules:
Source Types
Skills come from three sources:
| Source | Location | Example |
|---|---|---|
| Installed KB | KB dataset .skills/ directory | .skills/capability/verify-quality/SKILL.md |
| Local override | Project .pair/ directory | Custom skills added by the team |
| Composed | Referenced inside another SKILL.md | /pair-capability-verify-quality invoked by /pair-process-implement |
Resolution Order
- CLI reads
config.jsonto find the skills registry entry - Scans the source directory (
.skills/) for allSKILL.mdfiles - Applies naming transforms (flatten + prefix)
- Copies to canonical target (
.claude/skills/) - Creates symlinks for secondary targets (
.github/skills/,.cursor/skills/, etc.)
Transformation Pipeline
The Problem
A raw SKILL.md file contains relative links, short skill names, and category-based paths. After installation, these need to work in a flat directory structure where all skills are siblings.
Pipeline Stages
The pipeline runs during pair-cli install and pair-cli update:
Stage 1: Copy with naming transforms
Stage 2: Frontmatter sync
Stage 3: Link rewriting
Stage 4: Skill reference rewriting
Stage 5: Multi-target distribution
Stage 6: Cross-registry propagation
After all registries are installed, skill references in non-skills files (like AGENTS.md) are also rewritten:
Concrete Example
Here's a full before/after for the implement skill:
Before (source):
After (installed):
Renaming Conventions
The Problem
AI tools invoke skills by directory name (e.g., /implement). Without namespacing, skill names could collide with other tools' commands. Prefixing with pair- and the category creates unique, discoverable names.
Naming Rules
The transform is deterministic: {prefix}-{category}-{skill-name}
With the default config (prefix: "pair", flatten: true):
| Source Path | Installed Directory | Slash Command |
|---|---|---|
next/ (top-level) | pair-next/ | /pair-next |
process/implement/ | pair-process-implement/ | /pair-process-implement |
process/review/ | pair-process-review/ | /pair-process-review |
capability/verify-quality/ | pair-capability-verify-quality/ | /pair-capability-verify-quality |
capability/record-decision/ | pair-capability-record-decision/ | /pair-capability-record-decision |
process/review/references/ (sub-dir of a skill) | pair-process-review/references/ | none, content, not a skill |
Collision Detection
If two different source paths flatten to the same installed name (e.g. a/b/ and a-b/ both becoming a-b/), install fails outright with an explicit error listing the collision instead of silently overwriting one skill with the other.
Three Namespace Prefixes
| Prefix | Count | Purpose |
|---|---|---|
pair- | 1 | Top-level skills (only next) |
pair-process- | 9 | Workflow orchestration skills |
pair-capability- | 25 | Atomic operation skills |
Transform Functions
The naming transform applies two functions in order:
-
Flatten: Replace
/with-, bounded to the registry's entry depth (flattenDepth: 2for skills: see Configuration)capability/verify-quality→capability-verify-qualityprocess/review/references→process-review/references(a skill's own sub-directory is preserved, not flattened into a sibling)
-
Prefix: Prepend the prefix with
-separator (only the top-level segment)capability-verify-quality→pair-capability-verify-qualityprocess-review/references→pair-process-review/references
Reference Rewriting Rules
Skill references (slash commands in markdown) are rewritten using boundary-aware matching:
- Only references with a leading
/are rewritten (e.g.,/nextbut notnext) - Longest matches are processed first to avoid partial replacements
- Boundaries include: start/end of line, whitespace, backticks, quotes, parentheses, pipes
Examples:
| Input | Output |
|---|---|
`/next` | `/pair-next` |
Composes /verify-quality and /record-decision. | Composes /pair-capability-verify-quality and /pair-capability-record-decision. |
invoke /implement immediately | invoke /pair-process-implement immediately |
name: pair-next (no leading slash) | name: pair-next (unchanged) |
path/next/page (mid-path) | path/next/page (unchanged) |
Fenced Code Blocks Are Skipped
Rewriting is applied line-by-line and tracks fenced code blocks (``` or ~~~). Content inside a fence is left untouched: a skill name quoted as example text isn't a live invocation. Inline single-backtick code spans (`/next`) are still rewritten; only triple-backtick/tilde blocks are excluded.
The relative-link rewriter (Stage 3) gets this for free: it walks a real markdown AST, so links inside fenced or inline code were never extracted as links in the first place.
Idempotent Updates and Prefix Changes
The Problem
pair-cli update re-derives skill bodies from source on every run, so a rename (e.g. a prefix change in pair.config.json) always produces the correct new skill files. But two kinds of content are not re-derived from source every run:
- Files copied with
addbehavior (e.g. adoption docs): never overwritten once they exist, so a stale reference baked in during an earlier install (/pair-next) is never revisited by a plain copy-and-rewrite pass. - Anything a previous run wrote using the old prefix, where the current config only knows the new one.
Guessing at the old prefix by string-matching ("pair-" → "foo-") is explicitly avoided: it produces false positives on unrelated content that happens to start with the same string.
The Fix: A Recorded Mapping
Each install/update writes a small manifest (.pair/.skill-name-map.json) recording this run's {shortName: installedName} map. The next run reads it back and computes an exact transition map (oldInstalledName -> newInstalledName) for every skill whose installed name changed, then applies that transition on top of the normal short-name rewrite when reconciling non-skill registries. The manifest is then rewritten with the current mapping.
This is why install -> update -> update is byte-stable, and why changing prefix between two runs correctly:
- removes the old flattened directory (e.g.
pair-catalog-next/) instead of leaving it orphaned alongside the new one, - rewrites already-installed references (including ones living in
add-behavior files) to the new name.
Removed or Disabled Skills
If a skill disappears from the source registry between runs, its old installed name has no valid new target. Any remaining /oldName invocation is left exactly as-is, and the install/update report logs a warning so it can be cleaned up by hand.
External KBs
The same registry-build, rewrite, and manifest pipeline runs regardless of where the dataset comes from: the official KB, a project override, or an external KB installed via --source. No source-side restructuring is required or supported; the nested .skills/<type>/<name>/ layout is flattened, prefixed, and rewritten identically either way.
Windows Compatibility
On Windows, symlink targets fall back to copy mode because symlinks are not reliably supported. This means secondary targets (.github/skills/, .cursor/skills/) get independent copies instead of symlinks.
Migration Notes
map-subdomains, map-contexts: process → capability
Reclassified so they can be invoked scoped by any planning/refinement skill, not just as a standalone lifecycle step:
| Before | After | |
|---|---|---|
| Source path | .skills/process/map-subdomains/, .skills/process/map-contexts/ | .skills/capability/map-subdomains/, .skills/capability/map-contexts/ |
| Installed command | /pair-process-map-subdomains, /pair-process-map-contexts | /pair-capability-map-subdomains, /pair-capability-map-contexts |
$scope | Optional (all default, or single) | Required, items/areas the caller touched; all is bootstrap-only |
| No DDD artifacts | Prerequisite check HALTs | Falls back to "system areas" (services/modules): no HALT |
No code change is required in apps/pair-cli: the skills registry (config.json) is category-agnostic; moving a skill between process/ and capability/ in the dataset is picked up automatically by the flatten+prefix transform on the next pair-cli install/pair-cli update.
Related
- Skills Catalog: All 51 pair skills
- Configuration Reference: config.json registry settings
- KB Structure Reference: Directory layout