Seven reusable prompts, written once as Agent Skills and shared across
Claude Code, Codex, and Cursor. One folder per prompt, one SKILL.md each,
no build step.
There is no promptalias command. The name comes
from a CLI that was designed and then killed before implementation — a compiler from one
terse YAML file into per-tool command files. The three tools had already converged on a
single format, so there was nothing left to compile. The name stuck; the compiler never
happened.
# all seven prompts, every agent, from this repo
npx skills add kishormorol/promptalias --global --all
Two caveats that were measured rather than assumed — add copies
instead of linking back, and it reported success for two tools it never wrote to. The
symlink chain below is the fix for both.
The folder name is the trigger, so u/ gives you /u. Everything
that decides whether the prompt fires lives in two lines of frontmatter.
u/SKILL.md
---
name: u
description: Update an existing feature while keeping its tests green.
Use when the user asks to change, extend, modify, or adjust behaviour
that already exists — "update the X", "make X also do Y", "tweak X" …
---
# Update a feature
Update this feature: $ARGUMENTS.
Keep existing tests passing.
…
Must match the folder. The validator fails the build if it drifts.
The only text matched against what you typed. Written as “Do X. Use when Y.”
A description without this clause installs fine, lists fine, and then never fires. Nothing anywhere reports it — so the validator does.
The prompt itself. $ARGUMENTS takes whatever followed the trigger.
/n and /u divide on whether the thing exists yet.
/ex and /p are the two that write no code at all.
The portable path works in all three tools and is the one to lean on. The hook exists for the case the first path keeps missing: your own wording.
What that looks like from a terminal. Path 1 needs nothing shown here — the agent reads the descriptions itself. Path 2 is the hook, and this is the whole of it: it prints the prompt it matched, hands the agent one line of context, and says nothing when nothing fits.
Listing more of the ways a request actually gets worded costs nothing and travels everywhere. This is the portable half, and the one to exhaust before reaching for anything else.
Selecting a prompt from your own keywords needs the input string, which only a hook sees. Codex and Cursor expose no equivalent — so a phrase that works only here does not travel. That cost is paid on purpose.
Every phrasing is run back through the resolver, alone and inside five carrier sentences,
and has to come back pointing at the prompt that claims it. These rows come from
scripts/probes.json — written from scratch, then checked on every push.
| What you type | Matched on | Runs |
|---|---|---|
| make the parser also handle tabs | “make X also handle Y” | /u |
| I need something that watches the log | “I need something that Y” | /n |
| cover the parser with tests | “cover X with tests” | /t |
| why is the cache broken | “why is X broken” | /d |
| did I break anything with that change | “did I break anything” | /rv |
| walk me through resolve.py | “walk me through X” | /ex |
| what would it take to support Windows | “what would it take to X” | /p |
| deploy to production | — | silence |
The first row is the interesting one. /u’s description lists
“make X also do Y”, which does not match “also handle tabs” — exactly the
kind of miss the hook’s vocabulary.json exists to close.
The table above measures the hook — the matcher whose decision is visible from outside. The agent is the other half: it reads the whole description and decides with judgement rather than a regex, so it can fire on wording no example lists, and skip wording every example lists.
That half was measured too. Nine ordinary sentences, one session each, hooks disabled so the hook could not supply the answer it was being compared against. Seven asked for a prompt and got it; two asked for neither and got silence.
One session each is not a rate, and the one sentence since run repeatedly shows why:
“why is the resolver broken” reached for /d six times out of seven,
and once reached for nothing at all. The same words, the same descriptions, a different
day.
So it is evidence, not proof — non-interactive sessions, written by the same hand as the descriptions. The phrasings stay recorded as unambiguous rather than as proven, and the method and its limits are written down next to the result. Everything here — the seven prompts, the phrasings, the vocabulary — is a starting point to fork and change, not a fixed list.
npx skills add copies rather than links, and reported success for two tools it
never wrote to — both measured, both documented. Pointing the hub at the repo and each tool
at the hub makes an edit here live everywhere, immediately.
# every prompt, every tool, resolving to this repo
mkdir -p ~/.agents/skills ~/.claude/skills ~/.codex/skills ~/.cursor/skills-cursor
for s in u n rv t d ex p; do
rm -rf ~/.agents/skills/$s
ln -s ~/promptalias/$s ~/.agents/skills/$s
ln -sfn ../../.agents/skills/$s ~/.claude/skills/$s
ln -sfn ~/.agents/skills/$s ~/.codex/skills/$s
ln -sfn ~/.agents/skills/$s ~/.cursor/skills-cursor/$s
done
The tradeoff: npx skills no longer manages these entries, so a future
add may replace the links with copies again.
Turn the hook on by copying one block into ~/.claude/settings.json. It runs on
every prompt you submit, so the properties that matter are negative: it never blocks, never
rewrites, and exits silently on any failure.
{ "hooks": { "UserPromptSubmit": [ { "hooks": [
{ "type": "command", "command": "~/promptalias/hooks/resolve.py", "timeout": 5 }
] } ] } }
Then add how you actually phrase things. A lone capital stands for whatever you really say, and your own wording outranks anything lifted from a description.
hooks/vocabulary.json
{ "prompts": {
"u": ["make X also handle Y", "wire X up to Y"],
"d": ["track down X", "what's up with X"],
"rv": ["eyeball this", "poke holes in this"]
} }
Ask it what it would do with a sentence before trusting it with one:
Two prompts that match equally well also produce no match. A silent hook is the designed outcome, not a broken one — the worst it can do is fail to suggest something.
The folder name is the trigger, so keep it short. Bodies stay readable in one screen; if a prompt needs more than that, it wants to be a real skill with reference files.
mkdir mytrigger && $EDITOR mytrigger/SKILL.md
---
name: mytrigger
description: Do X. Use when the user asks to …
---
Body of the prompt. $ARGUMENTS takes whatever followed the trigger.
The rule that matters more than the rest: write the description as
“Do X. Use when Y.” A bare label like "Update a feature" installs
fine, appears in skills list, and then never fires on its own. Nothing anywhere
reports that as broken — so the validator does:
validate.py --strict — every prompt is a well-formed skill, and no
description is missing its Use when clause.unittest — 67 tests across the validator, the resolver, and the numbers
these pages quote, most of them on the hook’s silences.check_phrasings.py — 77 phrasings and 24 probes still select the prompts
that claim them.check_phrasings.py --check — the published results page has not gone
stale.check_page.py — this page still states the counts and routing outcomes the
repo actually has. It is the only file here that talks to people who cannot see the repo,
so it is the one that rots unnoticed.measure_invocation.py --check — every rate quoted here and in the notes
still matches scripts/repeat_runs.json, the file the repeated runs were
written to. It measures nothing itself; it opens files, so it is free to run.Python 3, no dependencies, nothing to install. The same six commands run locally.