The Problem Before the Tool
At Serokell, we have an internal wiki hosted in Notion, where we keep our accumulated technical knowledge, guidelines, and conventions. These guides cover an entire project lifecycle: from bootstrapping the repository, choosing a license, writing tests, applying Haskell styles, among others, to committing on Git, opening pull requests, and performing reviews.
Over the years, our engineers were instructed to always keep these in mind and apply them while programming and performing reviews. With experience, they eventually come naturally, though slips can still happen even to experienced engineers. New hires, in particular, often found them puzzling.
Now, with AI agents doing ever more work, we found that not only would they have to know about our internal guidelines to perform their work, but also to effectively collaborate with the engineers. To fill the gap, we ported many of our guidelines as Claude skills, producing a plugin that is now being adopted by the Serokell engineers who use Claude Code.
In this post, we want to talk about the process to produce these skills, and perhaps inspire people outside Serokell to check the plugin out, who may want to adapt them to their own guidelines.
What Claude Code Skills Actually Are
Claude supports a feature called skills, an open
format now used across several AI tools; Claude Code’s own take on it is
documented here. A skill is a
directory with a SKILL.md file describing what to do in plain English, plus
some optional supporting files. These support files could be anything: Bash
scripts complementing its instructions, other markdown files with gotchas or
solutions to problems it might run into, etc.
Each skill may refer to other skills as a dependency. For example, one of the
skills we created is
bootstrap-repo,
acting as the entrypoint for repository creation. This skill currently calls 9
out of the other 14 skills, such as the skills on how to choose a license,
configure CI, or create a .gitignore, among others.
Example `bootstrap-repo` skill:
--- # SPDX-FileCopyrightText: 2026 SerokellBootstrap a new repository
Skill bundle: this skill delegates to
pull-requests,license-choice,reuse-headers,readme,gitignore,haskell-style,setup-ci,repository-settings, andchangelog. All of them must be available. Install theserokell-globalplugin fromserokell/claude-plugins— it ships all sibling skills atplugins/serokell-global/skills/.
Fork or new repo?
- Temporary fork — fork into the
serokellGitHub org so colleagues inherit access. Add a one-paragraph “why this fork exists” both to the README onmasterand to the GitHub “About” section — an agent should do both for maximum visibility. Stop here. No further steps apply. - A normal new repo — follow the sequence below.
The sequence (non-fork repos)
- Create the new repo from
metatemplates. On GitHub:gh repo create <owner>/<name> --template serokell/metatemplates(use--privateor--publicfor the visibility you need now — a repo that’s private today but may open later still starts--private). GitLab has no template feature — clonemetatemplatesand copy its contents into a fresh project. - Open a bootstrap PR. Don’t push customisation directly to
master— even initialisation goes through review. →pull-requestsskill. - Customise inherited files (any order — these are independent):
- Fill
PROJECT.mdwith this repo’s issue tracker, YouTrack project key, and team lead — several skills read it. - License +
LICENSES/+ rootLICENSE→license-choiceskill. - SPDX headers on every file →
reuse-headersskill. - README rewritten to Standard Readme →
readmeskill. .gitignorefor the project’s stack →gitignoreskill..editorconfig: keep and adapt it (or remove it if the team doesn’t use editor integration).- Haskell configs in
haskell/(or delete the directory) →haskell-styleskill. - PR/MR template: always keep it. Keep the directory for your host
(
.github/or.gitlab/) and delete the other. CONTRIBUTING.md: keep and adapt it if it carries repo-specific contribution info; otherwise remove it.- Agent instruction files: review
.github/copilot-instructions.mdandplugins/serokell-global/skills/(or the equivalent skill directory for your plugin); customise or delete what’s irrelevant. - Strip meta-comments and placeholders (see Gotchas).
- Fill
- Bootstrap CI →
setup-ciskill. Must complete before step 5. - Apply repo settings →
repository-settingsskill. Branch protection’s status-check requirement needs the CI from step 4. - Optionally set up a changelog →
changelogskill. Worth doing now for libraries or anything user-visible. - Merge the bootstrap PR →
pull-requestsskill.
Bootstrap-specific gotchas
- The template README is not Standard-Readme-compliant. Don’t mirror its shape; rewrite from scratch to spec.
- The template
.gitignoreis intentionally minimal. Replace it. - The root
LICENSEmust exist regardless of where your real license metadata lives — GitHub and GitLab key off it. - Placeholders are scattered across files:
mypackage,KEK,Patak,LicenseRef-ReplaceMe. Search-and-replace them all before merging. - Strip every
[//]: # (...)meta-comment from inherited files — they exist to guide the human creator, not to ship. Verify removal with:(The exclusions avoid false positives from skill files and the PR template, which contain these patterns intentionally.)git grep '\[\/\/\]:' -- ':!.claude/' ':!.github/pull_request_template.md'
The skill starts out with REUSE headers and metadata: name, description, and other required skills. Then the next session instructs the agent to delegate skills and install the Claude plugin.
In the next sections, the meat of the skill comes into play, where it goes through the entire sequence of steps to bootstrap the repo and produce a pull request. Some gotchas are documented to steer the agent into the correct direction around some common mistakes.
The end result should be the configured repository plus a pull request adding the infrastructure files in accordance to our guidelines, ready to be reviewed.
From metatemplates to a Real Plugin
metatemplates to a Real PluginOur serokell/metatemplates
template repository contains templates for various kinds of projects we want to
create. A plugin, in Claude Code terms, is a bundle of skills that installs
once, from a marketplace repository, and stays in sync everywhere it’s used, no
copying files by hand into every new repo. Our skills weren’t always a plugin,
though: they started out living directly in metatemplates under
.claude/skills/, meant to be copied into every new repository created from
the template.
That’s exactly the kind of thing that drifts, and we didn’t have to wait long
to see it happen. branch-lint, a
public repository created to test these skills, was forked from metatemplates
early on and got a full local copy of the skills baked in at creation. Shortly
after, a more urgent task pulled me away, and I only came back to it a month
later. Around that time, metatemplates replaced the local copies with the
actual
serokell-global
plugin from
serokell/claude-plugins, and
branch-lint’s copy had already started drifting from the source of truth. We
had to migrate it separately, by hand, the next day.
env-check, created a little later,
never had the problem: it was bootstrapped straight from the plugin, with no
static copy to fall behind in the first place.
The 15 Skills
We ended up with 15 skills, covering a repository’s whole lifecycle. Rather than walking through all of them, here’s the shape in four groups.
Bootstrap, Settings, and CI
bootstrap-repois the sequencer: one PR that takes a new repo frommetatemplatesthrough licensing, infrastructure files, CI, and repo settings, then merges.repository-settingsuses the GitHub API to require signed commits and PR review, disable rebase merging, and set the standard access roles (admin for Operations, write for developers, read for everyone else).setup-ciwires up GitHub Actions or GitLab CI, preferring the Nix-based templates, and feeds the resulting status checks into branch protection. Every pipeline runsreuse lint,xrefcheck, andshellcheck, plus Haddock andhlintfor Haskell projects.nix-binary-cachechecks whether the cache is already configured and, if not, walks through requesting read-only credentials from Operations and adding the substituter, so Nix fetches pre-built derivations instead of rebuilding GHC from scratch.
Infrastructure Files
readmewrites a Standard-Readme-compliant README: the required sections in spec order, a promo blurb when the repo is public, and a separate README per component in a multi-package repo.reuse-headersadds the two required SPDX lines to every new source file (or registers it inREUSE.tomlwhen a file can’t carry a header) and keepsreuse lintpassing.gitignorescopes the.gitignoreto what the project’s build tools actually produce, leaving editor and OS files for the user’s own global ignore instead.license-choicedefaults new Serokell-owned projects to MPL-2.0, switching to GPL or AGPL for specific commercial situations, or to aLicenseRef-Proprietarylicense for closed-source work.
Quality
haskell-stylerunsstylish-haskellandhlintwith the repo’s shared configs, enables-Weverythingwith a curated disable list, and requires a Haddock comment on every exported name.code-testingrequires a regression test for every bug fix, an end-to-end test that actually executes the binary for any CLI tool, and a test for each edge case even when it passes trivially.changelogadds one bullet per user-visible change, tagged with its PR or issue number, under Keep a Changelog categories like Added, Changed, and Fixed.
Shipping and Tracking
committing-workhas the agent sign every commit using GPG keys and use the “Problem:/Solution:” format for commit messages.pull-requestsrequires every PR template checkbox to be ticked, even the irrelevant ones (to show they were actually considered), and only allows merge commits, never squash or rebase.code-reviewpicks a verdict (approve, request changes, or comment), and when the PR under review is the agent’s own, hands the review to a separate agent instance with no memory of why the changes were made, so it can’t rubber-stamp itself.youtrack-issueswrites the required Bug/Task structure (title, repro steps, and acceptance criteria) and moves the issue through its states (Open→In Progress→Review→Done) as the work happens, keeping the assignee field current along the way.
A Concrete Taste
To test our skills, I’ve bootstrapped two simple demo repos: branch-lint and
env-check.
I’m not the kind of person who enjoys having AI produce code (or vibecode) and art. Haskell lives in a warm spot in my heart, and I like to get my hands dirty writing code. Writing functional code is legitimately fun for me, and having the AI do it is like watching the other kids having fun in the playground while you’re grounded in your room. Having said that, these were tools with just 149 LOC and 65 LOC, respectively. They were created just to test and demonstrate these skills and, given what they were for, I had no choice other than having the agent do it this time.
branch-lint
branch-lintThe first bootstrap created a Haskell CLI application meant to validate a branch name against Serokell guidelines. It was created as a public repo using GitHub issues, and it has served its purpose as a test project and was archived shortly after.
When I started this repository, our Claude skills were not yet a plugin, but
rather a collection of 14 skills files in metatemplates that got cloned into
this repository. The 15th skill, nix-binary-cache, wasn’t part of this
original copy. It only arrived after the repo moved to the plugin.
I asked it to load the skills and bootstrap the repository. Claude filled
PROJECT.md, wrote the README, created a .gitignore tailored for Haskell and
Nix, picked MPL-2.0 for the license, migrated to REUSE.toml, removed
GitLab-specific files, and added the Haskell tooling configuration files. The
agent also produced the code and wrote tests for it.
All of this landed as one pull request
(#3), with the bootstrap
changes themselves in a single commit
(66fca65).
The agent then spawned a subagent that performed a review with a pair of fresh
eyes, which caught some defects. I also reviewed it myself and found some
things, which Claude fixed.
Claude also configured the repository settings: branch protection, access,
topics, and description. I didn’t just trust the agent’s claim on this and
asked it to perform a GET request to GitHub, where Claude and I could see
that the settings were applied. I also confirmed on GitHub itself. All the
agent did matched what the skill said it applied, and no manual clicking
through GitHub’s settings UI was required.
env-check
env-checkThe second bootstrap created another basic Haskell CLI application that reads a TOML file that lists which environment variables must be present. If any variable was not set, it would quit with non-zero error code and list each violation. An example TOML might look like the following:
[vars]
DATABASE_URL = true
PORT = false
This project was initially private, using YouTrack for issues rather than
GitHub issues, but made public before its archival. Like branch-lint, this
repository was archived shortly after its conception.
The biggest difference was that the issue tracker was YouTrack. I created a YouTrack project and had Claude fill an issue to bootstrap the repo according to the project description.
By the time I bootstrapped this repository, our Claude skills were already
consolidated into a plugin. The overall process was similar to branch-lint’s,
except there was no local copy to fall out of sync, so no separate migration
commit was needed later. Bootstrapping just meant installing the plugin and
running the skill.
Where This Is Going
About a week after the creation of our Claude plugins repository, I presented a small seminar to Serokell’s engineers, where I briefly described these skills, some interactions between Claude and me, as well as some shortcomings I’ve faced. At the end, I motivated the team to install the plugin with auto-updates enabled, test it with real projects, and create PRs for any problems they encounter.
In some sense, the plugin is still experimental. Even though it was demonstrated to work on smaller projects, I still hope it will be useful in bigger contexts and can evolve from here. We are working to make it happen. With this, my hope is to also motivate external contributors to give our plugin a try, and perhaps inspire them to fork it to match their workflows, contributing back to what could benefit us collectively.
There are some skills that involve internal workflows, namely
youtrack-issues, repository-settings, and nix-binary-cache, that should
be adapted accordingly. youtrack-issues is tied to our internal
issues.serokell.io, and repository-settings names Serokell’s specific team
names. nix-binary-cache describes the flow around Serokell’s own Nix cache
and its AWS credentials, which are not made public.
Even though the code files in this repository are licensed under the MPL-2.0, the skills themselves are licensed under CC0-1.0.
Our work on these skills does not only affect Claude. We also created a small corresponding instructions file for GitHub Copilot, which, although not as in-depth as the skills for Claude, should still prove useful.
These skills are still code, although written in plain English, and are interpreted by the agent. They are still prone to bugs, drift, edge cases, and code duplication. That’s a topic for another post.
For now, though, the plugin is public. If any of this looks useful,
serokell/claude-plugins is right there to clone, read, or steal ideas from.

