setup-ci
Skill5 starsChanged 7 days agoMITView on GitHub
Set up CI for a project, and offer to generate its own /release skill. Detects the stack, writes .github/workflows/ci.yml from the guide's baseline and generates the project's own .claude/skills/release/SKILL.md (release process: version bump, tag, CI watch, registry check). Use when the user says \"set up CI\", \"add a release skill\", \"add a release process\", \"set up releases\", or when a project has no .github/workflows/ci.yml or release skill.
- Reads credentials
- Installs packages
- Commits and pushes
6 other projects use a version of this.
Adopted by
Most recently taken up by
- kvothesson/ar-pluginstheir own version, first seen Nov 1, 2026
- Dmkdok/product-factorytheir own version, first seen Oct 25, 2026
- wyeworks/accountable-reviewtheir own version, first seen Oct 2, 2026
- aprameyak/coding-skillstheir own version, first seen Oct 2, 2026
- talhaMah56/claude_skillstheir own version, first seen Sep 12, 2026
- stackql/stackql-skillstheir own version, first seen Mar 25, 2026
Other versions6 projects use a version of this.
Word for word · 0
None found.
Edited · 0
None found.
Wrote their own version · 6
- wyeworks/accountable-review★ 42same name
- kvothesson/ar-plugins★ 5same name
- aprameyak/coding-skills★ 1same name
- Dmkdok/product-factory★ 1same name
- stackql/stackql-skills★ 0same name
- talhaMah56/claude_skills★ 0same name
How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.
The file
Show its rules and text
We split this skill into the rules it gives an agent
14 rules. 0 are common, 4 are distinctive, and 10 are very specific to this project.
0
Common
Most of the 5 edited versions we read keep these.
4
Distinctive
This author's own choices. Worth a look.
10
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 8
Run from the project root.
- Very specific: line 12
If .github/workflows/ci.yml or .claude/skills/release/SKILL.md already exists, show what exists and ask before overwriting either.
- Distinctive: line 19
Ask for the package name and the minimum Python version.
- Very specific: line 24
Add one smoke test in tests/ that imports the package.
- Very specific: line 25
Run ruff check, ruff format --check, pyright and pytest, then commit the skeleton on its own (chore: add project skeleton).
- Very specific: line 28
Ask for the package name, the minimum Node major, and the package manager (npm or pnpm).
- Very specific: line 31
Write tsconfig.json with "strict": true, "target": "ES2022", "module": "NodeNext", "outDir": "dist" and "include": ["src", "tests"].
- Very specific: line 34
Add src/index.ts containing export {}.
- Very specific: line 35
Run the four scripts with the chosen package manager, then commit the skeleton on its own (chore: add project skeleton).
- Distinctive: line 67
Ask the user only about what is still unknown, plus the publish target, which is always asked.
- Very specific: line 73
No publish target: delete the block between BEGIN PUBLISH and END PUBLISH, and the manual-step note at the top that refers to it.
- Very specific: line 76
Keep the needs: list on publish equal to every test job in the file.
- Very specific: line 84
Write the template below to .claude/skills/release/SKILL.md, with these changes:
- Distinctive: line 90
Keep the integration-branch read as a command to run at release time.
“Common” is measured: kept by at least 6 in 10 of the 5 edited versions of this file we could read. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Set up CI and release
Run from the project root. Detect first, ask only about what can't be detected, then write two files. Both belong to the project afterward and are edited by hand. This skill does not maintain them.
Baselines live in the guide at project-documents/ai-project-guide/project-guides/ci/<lang>/ci.yml (python, typescript). If the stack is another language, say there is no baseline yet and stop. Do not improvise one.
If .github/workflows/ci.yml or .claude/skills/release/SKILL.md already exists, show what exists and ask before overwriting either.
0. Prepare the project
No manifest. No pyproject.toml and no package.json is normal right after onboarding. Offer to create a minimal skeleton and continue with step 1 once it is committed. If the user declines, stop.
Python (uv):
- Ask for the package name and the minimum Python version. Do not pick either.
uv init --package --name <pkg> --python <floor> --vcs none .- Replace the placeholder
description. Remove the hello-world[project.scripts]entry and the generatedmain(). uv add --dev ruff pyright pytest- Copy the guide's
project-guides/lint/python/ruff.tomlto the project root as is. Inpyproject.toml, add the[tool.pyright]block fromrules/python.mdwithpythonVersionset to the floor, and[tool.pytest.ini_options]withtestpaths = ["tests"]. - Add one smoke test in
tests/that imports the package. - Run
ruff check,ruff format --check,pyrightandpytest, then commit the skeleton on its own (chore: add project skeleton).
TypeScript (ESM, Vitest):
- Ask for the package name, the minimum Node major, and the package manager (npm or pnpm). Do not pick any of them. The
targetbelow needs Node 18 or newer; if the floor is lower, ask. npm init -y(npm) orpnpm init(pnpm), then inpackage.jsonsetname,"type": "module",engines.nodeto>=<floor>,"exports": "./dist/index.js"and"files": ["dist"]. For pnpm also set"packageManager": "pnpm@<version>"frompnpm --version. Replace the placeholderdescriptionand the defaulttestscript, and removemain.npm i -D(npm) orpnpm add -D(pnpm) withtypescript vitest eslint @eslint/js typescript-eslint @types/node- Write
tsconfig.jsonwith"strict": true,"target": "ES2022","module": "NodeNext","outDir": "dist"and"include": ["src", "tests"]. Writetsconfig.build.jsonthat extends it, sets"rootDir": "src"(TypeScript 6 requires it, and it keepsdist/index.jsflat), and includes onlysrc. Lint needs the first to cover the tests. - Copy the guide's
project-guides/lint/typescript/eslint.config.mjsto the project root as is. - Set the scripts:
lintiseslint .,typecheckistsc --noEmit,testisvitest run,buildistsc -p tsconfig.build.json. The CI baseline runs these four. - Add
src/index.tscontainingexport {};andtests/smoke.test.tsthat imports it. NodeNext requires the.jsextension on relative imports even in.tsfiles (../src/index.js); that is correct, so leave a comment saying so. - Run the four scripts with the chosen package manager, then commit the skeleton on its own (
chore: add project skeleton).
.gitignore. If .gitignore is missing, or lacks the stack's basics (python: .venv/, __pycache__/, .env; typescript: node_modules/, dist/, .env), offer the standard template for the stack from github/gitignore (Python.gitignore or Node.gitignore), plus .env. Merge into an existing file; never drop the user's own lines.
1. Detect
| Slot | How |
| Language | pyproject.toml means python; package.json plus tsconfig.json means typescript |
| Version files | Grep the current version string in pyproject.toml, package.json, __init__.py/_version.py, and the project's other manifests and source files. Skip the changelog, the devlog and project-documents/: they record versions rather than define them. List every remaining hit. |
| Lock refresh | python: uv lock if uv.lock exists. npm: npm install --package-lock-only; pnpm: pnpm install --lockfile-only. None if no lockfile. |
| Package manager | typescript only. pnpm-lock.yaml or a packageManager field starting pnpm@ means pnpm; package-lock.json means npm. Both or neither: ask. |
| Tag format | git tag --sort=-creatordate | head. Copy the pattern (v1.2.3, 1.2.3). No tags: vX.Y.Z. |
| Tag | <tag> in the release skill: the tag format written with the literal placeholder X.Y.Z (so vX.Y.Z or X.Y.Z). The release fills in the real number at release time. |
| Changelog | CHANGELOG.md (check the root, then docs/). Note whether it has ## [Unreleased]. None: the release skill skips the changelog step. |
| Devlog | DEVLOG.md at the root only. Ignore any other devlog, including project-documents/DEVLOG.md. None at the root: skip the devlog step without asking. |
| Publish target | Always ask: PyPI, npm or none. A manifest name does not mean the project is published. If a workflow already has a publish job, offer its target as the default. |
| Package name | name from the manifest. It can differ from the repo name. |
| Project | <project> in the release skill: the package name, or the repo directory name when there is none. |
| Registry | <registry> in the release skill: PyPI or npm, from the publish target. None: the [registry] text is removed. |
| Registry check | PyPI: https://pypi.org/pypi/<pkg>/<ver>/json. npm: npm view <pkg>@<ver> version. None: the release stops at green CI. |
| Python versions | Every minor listed in the Programming Language :: Python :: 3.N trove classifiers, as a quoted YAML list. No classifiers: every minor from the requires-python floor through its upper bound. No upper bound, or no requires-python: ask, offering the interpreters from uv python list --only-installed as the choices. Never fill the top of the range from memory. |
| Node versions | The majors engines.node allows, as a quoted YAML list. If engines.node is missing or has no upper bound, ask which majors to test. Never fill the range from memory. |
| Publish runtime | __PUBLISH_PYTHON_VERSION__ / __PUBLISH_NODE_VERSION__: the lowest version in the matrix. |
| Tag glob | __TAG_GLOB__, derived from the tag format: v* for v-prefixed tags, [0-9]* for bare ones. |
| Commit type | <commit type> in the release skill: package if the project uses the guide's commit types, else chore. |
| Check commands | python: ruff check, ruff format --check, pyright (or the project's type checker), pytest. typescript: the lint, typecheck, test, build scripts in package.json. Fill __*_SCRIPT__ only with scripts that exist. If one is missing, say so and drop that step rather than inventing it. |
| Integration branch | cf config get git.integration_branch, only to fill the CI trigger branches (main, plus this value if set). Never bake it into the release skill. |
| cf project | Does cf exist and does project-documents/ exist? Decides whether the release skill keeps the cf steps. |
| Frontmatter block | Enable the OPTIONAL cf block in ci.yml when project-documents/ exists and its markdown files carry YAML frontmatter. Otherwise remove it. |
| Fetch main | Enable the OPTIONAL git fetch origin main:main line when any test file reads main from the project's own checkout: origin/main, a main.. range, or --diff main and similar ref arguments. A branch named main in a temporary repo the test creates itself (git init -b main) does not count. Tag builds go red without it. No such reference: remove it. Unsure: ask. |
Ask the user only about what is still unknown, plus the publish target, which is always asked. Present everything detected in one short list and let the user correct it before writing.
2. Write ci.yml
Copy the baseline for the language to .github/workflows/ci.yml and replace every __SLOT__. Then:
- No publish target: delete the block between
BEGIN PUBLISHandEND PUBLISH, and the manual-step note at the top that refers to it. - TypeScript with pnpm: in both jobs, add
- uses: pnpm/action-setup@v4before eachactions/setup-nodestep (it reads the version frompackageManager), changecache: npmtocache: pnpm, replacenpm ciwithpnpm install --frozen-lockfileandnpm runwithpnpm run. Leavenpm publishas is; the runner has npm. - TypeScript: drop
--access publicfrom the publish step when the manifest'spublishConfig.accessisrestricted. - Keep the
needs:list onpublishequal to every test job in the file. - Each OPTIONAL block is decided by its probe in step 1: uncomment it when the probe says enable, delete it otherwise. Do not leave commented blocks behind.
- Grep the result for
__[A-Z]to confirm no slot is left unfilled. A leftover slot is an error, not something to guess.
Tell the user the one-time manual step for the publish target (PyPI trusted publishing, or an NPM_TOKEN secret). The first tag fails without it.
3. Write the release skill
Write the template below to .claude/skills/release/SKILL.md, with these changes:
- Fill the
<...>slots from step 1. - Markers are
[cf],[changelog],[devlog],[registry], and the inverse form[no X]for each ([no registry],[no changelog]). A marker on a heading governs the whole step; an inline marker governs everything after it up to the next marker or the end of that list item or paragraph. Keep the marked text only if that thing exists (cf present, file present, registry present;[no registry]is kept only when there is none). Remove the markers themselves. - In step 4, the check commands are exactly the commands in the ci.yml just written, so the two cannot drift. They stay check-only (
ruff format --check, notruff format): the release commit touches version files and nothing else. - Renumber after removals.
- Keep the integration-branch read as a command to run at release time. Do not write its value into the skill.
Template
---
name: release
description: Cut a <project> release — bump the version, update the changelog, tag, push, then watch CI until it is green [registry] and <registry> shows the new version. Use when the user says "release", "cut a release", "ship it", or "bump and tag".
---
# Release <project>
Run from the repo root. Stop and report on any failed command; don't work around it.
## 1. Check cf state [cf]
Run `cf next`. If it reports something unfinished (open review gate, unchecked tasks, failing check), stop and show it. Otherwise continue.
## 2. Merge [cf]
Read the target with `cf config get git.integration_branch` (empty means `main`). If the current branch is a slice branch not yet merged into the target, follow the merge steps in CLAUDE.md (Git Rules). If already on the target with the work merged, skip this.
## 3. Pick the version
The working tree must be clean before continuing; if it isn't, stop and show `git status`.
Current version: `<version file>`. Read the unreleased changes. [changelog] Use `## [Unreleased]` in <changelog path>. [no changelog] Use `git log` since the last tag.
- **Patch** is the default: fixes, refinements, and new flags, options, parameters, config keys or checks. A non-empty changelog, or entries under `### Added`, does not make a release minor.
- **Minor** only for a new user-facing feature or a significant new AI capability. Propose it in one line with the reason and wait for the user to confirm before changing any files.
- **Major** only when the user explicitly asks for it.
For a patch, state the chosen version in one line and proceed.
## 4. Bump and document
1. Set the version in <each version file>, then run <lock refresh command>.
2. [changelog] In <changelog path>, insert `## [X.Y.Z] - YYYYMMDD` directly under `## [Unreleased]`, so the unreleased entries sit under the new version and `[Unreleased]` is empty. Entries stay short and user-facing.
3. [devlog] In <devlog path>, under today's heading (create it at the top if missing), add `### Release X.Y.Z` with a one-line contents summary.
4. Run <check commands from ci.yml>. Any failure stops the release; CI would block the publish anyway. These only check: if formatting or lint fails, stop, fix it in its own commit, and start the release again.
5. Commit: `<commit type>: bump version to X.Y.Z`.
## 5. Tag and push
Invoking this skill is the user's request to push.
```
git tag -a <tag> -m "<tag>"
git push
git push --tags
```
## 6. Watch CI
[registry] The `publish` job in `.github/workflows/ci.yml` runs only on the tag and needs every test job to pass. A red test job means nothing publishes, silently. A tag alone is not a release: it is not done until <registry> shows the new version.
[no registry] CI must be green on the tagged commit.
Start a loop via the `loop` skill with a 5 minute interval and this check:
1. `gh run list --workflow ci.yml --branch <tag> --json databaseId,status,conclusion,headSha` — match `headSha` to the tagged commit. (`--commit` is unreliable right after a push.)
2. Still running: report status and wait for the next tick.
3. Failed: pull the log (`gh run view <id> --log-failed`), report the actual error, stop the loop.
4. Passed: [registry] confirm with `<registry check command>`. If it returns, report "X.Y.Z live on <registry>" and stop the loop. If not yet, wait for the next tick. [no registry] Report green CI and stop the loop.
Always stop the loop yourself when it resolves either way.4. Report
Print the two paths written, the detected values the user should double-check (version files, tag format), and the one-time manual step. If the integration branch was added to the CI triggers, say that it was copied in at generation time: if git.integration_branch changes later, the branches: lists in ci.yml must be updated by hand. Do not commit unless the user asks.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

