Creating skills (Claude + Cursor + Codex)
Invariants (assert)
- Skill markdown MUST NOT reference files that do not exist in this repo or
in that skill package (except allowlisted consumer/runtime contract paths
such as
.cursor/judge-swarm-bindings.json). Enforced by
scripts/check_skill_file_refs.py.
- A new skill MUST be available to Claude, Cursor, and Codex — never only
the harness the agent happens to be running in.
- Prefer putting portable skills under
product/skills/<name>/ (or mined
lessons under corpus/skills/<name>/) and running ./install.sh. That is
the only path that keeps all three harness roots in sync automatically.
See docs/ecosystem.md. Engine skills
(reflect, create-skill, …) live under engine/skills/ only.
- Claude-only skills MUST be listed in
CLAUDE_ONLY_SKILLS in install.sh
(and nowhere else). Everything not listed MUST install to all three.
- When home-linking a project skill (repo
.cursor/skills/<name> that is
not in catstack), you MUST symlink the same source into all three personal
skill roots in one step — never Claude+Cursor only.
- A new skill MUST ship a
tests/ dir before it's added. Code skills (a
scripts/ dir or any .py/.mjs/.js/.ts/.sh file) need at least
two real test functions. Prose-only skills need a positive fixture and a
negative fixture (e.g. tests/fires_*.md / tests/stays_silent_*.md)
showing a prompt that should, and one that should not, invoke the skill.
Enforced by scripts/check_skill_test_coverage.py. A skill predating this
rule is grandfathered in scripts/skill_test_debt_allowlist.txt, which is
shrink-only (scripts/check_skill_test_debt_no_growth.py) — never add a
new skill to it instead of writing its tests.
Preferred path (catstack / portable)
- Create
product/skills/<name>/SKILL.md (portable) or corpus/skills/<name>/SKILL.md (mined lesson).
- Write its
tests/ dir (positive + negative — see the test-coverage invariant above).
- Run
./install.sh from the catstack repo root.
- Verify:
python3 scripts/check_skill_test_coverage.py
python3 scripts/check_skills_three_harnesses.py
ls -la ~/.claude/skills/<name> ~/.cursor/skills/<name> ~/.codex/skills/<name>
Domain sections (optional, product skills)
Portable product skills MAY add task-type files under domains/ next to
SKILL.md. Install already symlinks the whole skill directory, so those
files travel for free. There is no separate top-level domains package
under product/ (domains live inside each skill directory).
product/skills/<name>/
SKILL.md # generic invariants + domain selector
domains/
coding.md # optional: software / PR / CI bindings
equities.md # optional: holdings / claim-research bindings
Types start as coding and equities. Add a new type only when a real
skill needs it.
Selector (MUST paste into every domain-aware SKILL.md)
After reading SKILL.md, read at most one sibling domains/<type>.md:
- User named the type (
coding, equities, holdings, claim research).
- Else cwd has
.cursor/judge-swarm-bindings.json, or equities trigger
words (holdings, Sheets, research report) → equities; Invoker /
catstack / package.json without those → coding.
- Else none. Do not read both in one turn.
Invariants (assert)
- Generic
SKILL.md MUST NOT name consumer CLIs, absolute paths, or
scripts that are not in this catstack skill tree.
- Domain files MUST NOT restate the generic sequence — only triggers,
consumer binding lookup rules, and do-nots.
- Domain bindings are loaded from a consumer file under cwd (for
equities:
.cursor/judge-swarm-bindings.json). If missing, fail closed.
- Named paths in skill markdown MUST exist in catstack (or the skill
package), except allowlisted consumer contracts — see
scripts/check_skill_file_refs.py.
- Project CLIs that only exist in one repo stay project skills (home-link
with
scripts/link_skill_three_harnesses.sh), not catstack domains.
Project-skill home link (all three)
If the skill must live in a project (e.g. .cursor/skills/wipe-bad-pr):
bash scripts/link_skill_three_harnesses.sh /absolute/path/to/skill-dir
Or manually, same source for each:
src=/absolute/path/to/skill-dir
name=$(basename "$src")
ln -sfn "$src" "$HOME/.claude/skills/$name"
ln -sfn "$src" "$HOME/.cursor/skills/$name"
ln -sfn "$src" "$HOME/.codex/skills/$name"
Then run:
python3 /path/to/catstack/scripts/check_skills_three_harnesses.py --home
Do not
- Follow Cursor built-in create-skill text that only mentions
~/.cursor/skills/.
ln -s into one or two harness roots and call it done.
- Invent a one-off install path that bypasses
install.sh / the link script.
1---2name: create-skill3description: Create or install an agent skill for Claude, Cursor, and Codex together. Use when authoring a new skill, adding SKILL.md, home-linking a project skill, or when the user says create-skill / install a skill. Overrides single-harness Cursor-only install advice.4---56# Creating skills (Claude + Cursor + Codex)78## Invariants (assert)910- Skill markdown MUST NOT reference files that do not exist in this repo or11 in that skill package (except allowlisted consumer/runtime contract paths12 such as `.cursor/judge-swarm-bindings.json`). Enforced by13 `scripts/check_skill_file_refs.py`.14- A new skill MUST be available to **Claude, Cursor, and Codex** — never only15 the harness the agent happens to be running in.16- Prefer putting portable skills under `product/skills/<name>/` (or mined17 lessons under `corpus/skills/<name>/`) and running `./install.sh`. That is18 the only path that keeps all three harness roots in sync automatically.19 See [docs/ecosystem.md](../../../docs/ecosystem.md). Engine skills20 (`reflect`, `create-skill`, …) live under `engine/skills/` only.21- Claude-only skills MUST be listed in `CLAUDE_ONLY_SKILLS` in `install.sh`22 (and nowhere else). Everything not listed MUST install to all three.23- When home-linking a **project** skill (repo `.cursor/skills/<name>` that is24 not in catstack), you MUST symlink the same source into all three personal25 skill roots in one step — never Claude+Cursor only.26- A new skill MUST ship a `tests/` dir before it's added. Code skills (a27 `scripts/` dir or any `.py`/`.mjs`/`.js`/`.ts`/`.sh` file) need at least28 two real test functions. Prose-only skills need a positive fixture and a29 negative fixture (e.g. `tests/fires_*.md` / `tests/stays_silent_*.md`)30 showing a prompt that should, and one that should not, invoke the skill.31 Enforced by `scripts/check_skill_test_coverage.py`. A skill predating this32 rule is grandfathered in `scripts/skill_test_debt_allowlist.txt`, which is33 shrink-only (`scripts/check_skill_test_debt_no_growth.py`) — never add a34 new skill to it instead of writing its tests.3536## Preferred path (catstack / portable)37381. Create `product/skills/<name>/SKILL.md` (portable) or `corpus/skills/<name>/SKILL.md` (mined lesson).392. Write its `tests/` dir (positive + negative — see the test-coverage invariant above).403. Run `./install.sh` from the catstack repo root.414. Verify:4243```bash44python3 scripts/check_skill_test_coverage.py45python3 scripts/check_skills_three_harnesses.py46ls -la ~/.claude/skills/<name> ~/.cursor/skills/<name> ~/.codex/skills/<name>47```4849## Domain sections (optional, product skills)5051Portable product skills MAY add task-type files under `domains/` next to52`SKILL.md`. Install already symlinks the whole skill directory, so those53files travel for free. There is no separate top-level domains package54under `product/` (domains live inside each skill directory).5556```text57product/skills/<name>/58 SKILL.md # generic invariants + domain selector59 domains/60 coding.md # optional: software / PR / CI bindings61 equities.md # optional: holdings / claim-research bindings62```6364Types start as `coding` and `equities`. Add a new type only when a real65skill needs it.6667### Selector (MUST paste into every domain-aware `SKILL.md`)6869After reading `SKILL.md`, read **at most one** sibling `domains/<type>.md`:70711. User named the type (`coding`, `equities`, holdings, claim research).722. Else cwd has `.cursor/judge-swarm-bindings.json`, or equities trigger73 words (holdings, Sheets, research report) → `equities`; Invoker /74 catstack / `package.json` without those → `coding`.753. Else none. Do not read both in one turn.7677### Invariants (assert)7879- Generic `SKILL.md` MUST NOT name consumer CLIs, absolute paths, or80 scripts that are not in this catstack skill tree.81- Domain files MUST NOT restate the generic sequence — only triggers,82 consumer binding lookup rules, and do-nots.83- Domain bindings are loaded from a **consumer** file under cwd (for84 equities: `.cursor/judge-swarm-bindings.json`). If missing, fail closed.85- Named paths in skill markdown MUST exist in catstack (or the skill86 package), except allowlisted consumer contracts — see87 `scripts/check_skill_file_refs.py`.88- Project CLIs that only exist in one repo stay project skills (home-link89 with `scripts/link_skill_three_harnesses.sh`), not catstack domains.9091## Project-skill home link (all three)9293If the skill must live in a project (e.g. `.cursor/skills/wipe-bad-pr`):9495```bash96bash scripts/link_skill_three_harnesses.sh /absolute/path/to/skill-dir97```9899Or manually, same source for each:100101```bash102src=/absolute/path/to/skill-dir103name=$(basename "$src")104ln -sfn "$src" "$HOME/.claude/skills/$name"105ln -sfn "$src" "$HOME/.cursor/skills/$name"106ln -sfn "$src" "$HOME/.codex/skills/$name"107```108109Then run:110111```bash112python3 /path/to/catstack/scripts/check_skills_three_harnesses.py --home113```114115## Do not116117- Follow Cursor built-in create-skill text that only mentions `~/.cursor/skills/`.118- `ln -s` into one or two harness roots and call it done.119- Invent a one-off install path that bypasses `install.sh` / the link script.