Skip to content
← Selected projects

Skillpack

2026 · open source · claude code + codex

A skill is a markdown file that tells a coding agent when to act and how. I had the same eight copied into several repos, and each copy drifted. Skillpack is the one folder they live in now. Every repo pulls it as a git subtree, and a hook pushes edits back. Code on GitHub.

fig. 1 — the sync loop. ① Upstream holds the skills, a catalog that regenerates the README, and the plugin manifest. ② A consumer repo carries the folder as a squashed subtree and symlinks it into the paths Claude Code and Codex read. A SessionStart hook merges the last background fetch. ③ A Stop hook commits edits inside the folder, merges upstream if it moved, and pushes. ④ The plugin marketplace installs the same repo read-only.

Edit where you use it

The plugin route is fine for reading: install once and forget it. But a skill only improves while it is in use, and the plugin copy is not the one in use. Every fix I made landed in one repo and stayed there.

So the subtree is the canonical copy: a normal folder in the consumer repo, edited in place. A hook carries each change upstream before the session ends. The next repo to start a session merges it, no pull request. Both agents read the same files through symlinks, so there is no Claude version and no Codex version.

Two hooks, one script

sync.sh --start runs at SessionStart. It merges what the last background fetch brought in, no network needed, then fetches again in the background. The session never waits on GitHub.

sync.sh --stop runs at Stop. It commits only the skills folder, so a merge never runs over unrelated uncommitted work. Then it merges upstream if it moved, and pushes. State is two hashes in .git/skills-sync: the folder tree last synced and the upstream commit it matched. When they match, the script exits without touching git.

a normal session

$ sync.sh --startskills-sync: merged upstream changes into .claude/shared-skills # ... a session that edits skills/unslop/SKILL.md ... $ sync.sh --stopskills-sync: pushed .claude/shared-skills to upstream

same line edited on both sides

$ sync.sh --stopskills-sync: CONFLICT merging upstream into .claude/shared-skills  (in: skills/handoff/SKILL.md). Resolve: git subtree merge --squash ...$ echo $?2
fig. 2 — one session as the hooks see it, then the conflict path. A conflict aborts the merge, leaves the tree clean and exits 2, the one exit code the hook treats as a reason to wake the agent. A missing network or an empty upstream is a line on stderr and exit 0.

Bootstrapping was the hard part. A folder made by hand has no subtree history, and git subtree push fails one way against an empty upstream and another way against a missing branch. The test script builds a bare upstream in a temp directory, wires three consumer repos to it, and walks fifteen scenarios through them: fresh install, edits from either side, concurrent edits to different files, the same line changed on both sides, an empty upstream, an unreachable one. About twenty seconds, no network.

The catalog is a map

catalog.yaml puts each skill in exactly one category and lists, per category, other people's skills I rate. A script regenerates the README tables from it plus each skill's frontmatter, and exits 1 if a folder on disk is missing from the catalog or the catalog names a folder that does not exist. The README cannot describe a tree that is not there.

Three categories hold none of my skills yet and exist for the links. Deliberate: someone looking for an interview drill should find jobforge, and the coach it derives from, in the same place they found unslop.

skills
8 · one SKILL.md each
catalog
8 categories · 5 hold my skills, all link out
install paths
2 · plugin marketplace, git subtree
agents
Claude Code, Codex
sync
157 lines of bash · both directions
sync tests
15 scenarios, sandboxed, no network
catalog build
exits 1 when tree and catalog disagree
status
MIT · github.com/ong6/skillpack

What stays out

Only generic skills go in. Anything naming a private path, an account or a price stays in the consumer repo beside the subtree. The store this grew out of keeps more of those than it publishes. The eight here survived being pulled out of that context: writing, web extraction, diagrams, a PDF builder, a session handoff, and the feedback loop that patches whichever skill or rule earned the complaint.

Ong Jun Xiong

ENGINEER · BUILDER · SINGAPORE

EmailHobbiesArchiveNotesGitHubLinkedInSource

© 2026 Ong Jun Xiong