Skip to content
Back to skills

Release

ASecurity

Cuts a Kraft release - a release candidate, a sweep of the installed rc, a fix loop until the sweep is clean, then the stable release on PyPI, GitHub, Homebrew, Open VSX and the VS Code Marketplace. Use when the maintainer asks to cut a release, start with an rc, or ship X.Y.Z.

  • 12 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added November 11, 2026
ai-agentspythongoshellbashgitdatabasefrontendbackend

Works with

  • claude code
  • vscode
  • cli
  • mcp

Security analysis

A100/100

Pro scans all 5 files and shows the line behind each finding

Scanned November 11, 2026

npx -y skills add itsOmidKarami/kraft --skill release --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Release?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Release
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/itsomidkarami-release/badge)](https://www.skillsdirectory.com/skills/itsomidkarami-release)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: release
description: Cuts a Kraft release - a release candidate, a sweep of the installed rc, a fix loop until the sweep is clean, then the stable release on PyPI, GitHub, Homebrew, Open VSX and the VS Code Marketplace. Use when the maintainer asks to cut a release, start with an rc, or ship X.Y.Z.
disable-model-invocation: true
---

# Release Kraft

This drives the `release` workflow (`.github/workflows/release.yml`) on
`itsOmidKarami/kraft`, checks every output, and stops only for the maintainer's
decisions. `RELEASING.md` and `release.yml` are the source of truth: read both at
the start of every run, and where they disagree with this skill, they win.

Two names, used throughout:

- **`<last-tag>`**: the newest exact `vX.Y.Z` tag, the one this release follows.
- **`X.Y.Z`**: the version being released. The workflow computes it from the
  largest `release::` label among the PRs since `<last-tag>`; read it from the
  dry run. Never pick a number yourself.

Run the scripts from the repo root: `.claude/skills/release/scripts/…`.

**Never hand-edit** `CHANGELOG.md`, `vscode/CHANGELOG.md`, plugin manifest
versions, or `_Added in next_` notes. The release writes them.

**Stop and ask the maintainer before:**

1. Changing a label on any PR but your own fix PRs (for example adding
   `notes::highlight`).
2. The stable (non-rc) run. PyPI publishes cannot be undone.
3. Any fix that changes code, unless they have already said "fix what you find".
4. Anything after a real run that did not conclude `success`: no rerun, no
   publishing by hand, no yanking.
5. Treating a Phase 3 finding as not blocking the release: report it with your
   view and wait for their ruling.

An rc run needs no extra confirmation once they have asked for one, unless its
dry run plans a version they would not expect (a major, or a number other than
the one the last rc led to).

**Tools.** The steps use `gh`. When `gh auth status` fails (a cloud session has
`gh` but no valid token), use the GitHub MCP tools instead:
`references/github-mcp.md` gives each step's MCP form and its gotchas.

```
preflight -> rc dry run -> rc -> rc sweep --clean--> ask -> stable dry run -> go? -> stable -> stamp PR
    ^                               |finding
    +------ fix on main, next rc ---+
```

## Dispatching a run

Every run, dry or real, goes the same way. `gh workflow run` returns before the
run exists, so the newest run straight after a dispatch is often the previous one.

1. Note the newest `release.yml` run's id: `gh run list --workflow release.yml -L 1 --json databaseId`.
2. Dispatch, always with `sha`, so the release is the commit that passed CI even
   if someone merges in the meantime:
   `gh workflow run release.yml --ref main -f dry_run=<true|false> -f pre=<rc|none> -f sha=<sha>`.
3. Poll until a `workflow_dispatch` run with a larger id appears, created after
   your dispatch by your account (another dispatch in the same minute would
   otherwise pass for yours), then wait until it's `completed`
   (`gh run watch <id>` in the background).
4. Read its job log (`gh run view <id> --log`). It prints the notes (Highlights if any,
   New, Fixes, each line ending in its PR number), then one of:
   - `dry run: would release vX.Y.Z…`: a dry run's plan.
   - `releasing vX.Y.Z…`: a real run. A real run whose log lacks this line is the
     wrong run.
   - `nothing since <last-tag> ships; not releasing`: every PR is
     `release::none`. Stop and report.
   - an error naming a PR: a label problem (no `release::` label or two, a
     misspelled `notes::` label, `notes::highlight` on a `release::none` PR).
     Report it; fixing someone's label is the maintainer's call.

## Phase 1: Preflight

Is there something to release, and is main's tip safe to release from?

1. Read `RELEASING.md` and the top of `release.yml`.
2. `git fetch origin main --tags`, then find `<last-tag>` the way the workflow does:
   `git tag --list | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$' | sort -V | tail -1`.
3. List what ships: `git log --first-parent --oneline <last-tag>..origin/main`.
   Each squash commit ends in its PR number. Note the likely headline.
4. Check open PRs for anything that looks meant for this release, and mention it
   (`gh pr list`).
5. Main's tip must have a `test` run on a push that is `completed` / `success`,
   or the workflow refuses it:
   `gh run list --workflow test --branch main --event push --commit <sha>`.
   Wait for it if it's still running.

Record the tip sha. Every later run is pinned to it.

Do not rerun the tests locally. That `test` run covers lint, the docs checks,
intent pins, the backend suite, the frontend and the demo smoke, and the workflow
will not release a sha without it. What CI cannot see is the published wheel,
which Phase 3 installs.

## Phase 2: Cut the release candidate

An rc goes to PyPI and a GitHub pre-release, and nothing else: no Homebrew, no
stamp PR, no Marketplace, no Open VSX.

1. **Dry run** with `pre=rc` (see "Dispatching a run"). Show the maintainer the
   notes and the planned `vX.Y.ZrcN`, then go on: stop only for a version they
   wouldn't expect (above).
2. **Real run**, the same inputs with `dry_run=false`.
3. If it concluded `success`, wait in the background:
   `.claude/skills/release/scripts/wait_published.sh X.Y.ZrcN`. Then check the
   GitHub Release (`gh release view vX.Y.ZrcN`): a pre-release,
   the install line at the top of its notes, the wheel and `kraft-X.Y.ZrcN.vsix`
   attached. If it did not conclude `success`, see "A real run that failed".

## Phase 3: Sweep the release candidate

Test what users will install: the wheel from PyPI, in a clean venv on Python
3.12 (the floor), with its own `KRAFT_HOME` and port.

1. `.claude/skills/release/scripts/smoke.sh X.Y.ZrcN` (a second argument picks
   the port, default 8799). It prints:
   - the version, which must be the rc
   - `kraft admin health`: `status ok` and the rc version
   - `kraft admin doctor`: every check `ok`, except the expected failures below
   - `kraft admin templates lint`: no errors
   - the web UI's `<title>Kraft`
2. **Exercise the headline feature by hand** on the installed rc. smoke.sh
   proves the wheel installs and starts, not that the release's features work,
   so this step and the next are required. The script stopped its server; run
   every command through `scripts/kraft.sh <work dir> <port> …`, which gives it
   the same isolation in any shell:
   ```bash
   S=.claude/skills/release/scripts/kraft.sh; W=<the work dir it printed>; P=8799
   $S $W $P admin start --port $P > $W/server2.log 2>&1 &   # $S $W $P admin stop when done
   ```
   Feed it inputs that should pass and one that should fail, and check that the
   CLI tells them apart. For a chain feature, say: a chain that lints clean and
   one that `admin templates lint` must name.
3. Pick one or two more features from the notes that a smoke run can reach, and
   exercise them the same way.
4. Delete the work dir when you're done with it.

**Expected doctor results** (not regressions): `kraft on PATH` always fails,
since the smoke venv is never on `PATH`; `mcp server` fails or warns where
Kraft's MCP isn't registered in Claude Code's settings (it reads only the
registration, so it can pass while the MCP server is down). Anything else
failing or warning is a finding.

The sweep is **clean** when smoke.sh shows everything listed in step 1, and
steps 2 and 3 turned up nothing the maintainer rules blocking.

## Fix loop

A finding from Phase 3 is fixed on main and shipped as the next rc; an rc is
never patched in place.

1. **Report the finding**: what failed, the command, the output, and whether it
   blocks the release. One the maintainer rules non-blocking is noted for the
   final report and doesn't hold up Phase 4.
2. **Fix it** on a branch, following `CONTRIBUTING.md` and `/steward`: one PR per fix, with a
   `## Changelog` section and a `release::` label you choose and tell the
   maintainer about. A fix to a feature no stable release has shipped yet is
   `release::none`, since the feature's own entry covers it. A fix to something
   users already have is `release::patch`.
3. **Prove it** before pushing: the checks steward lists for the paths you
   touched, and the step that failed again.
4. **Open the PR, watch it to green**, and ask the maintainer to merge it (or
   merge it if they have said to).
5. **Back to Phase 1** on the new tip, then the next rc. The workflow numbers it
   `rcN+1` itself.

Repeat until a full Phase 3 sweep comes back clean (see its last line). Only
then go on to Phase 4.

## Phase 4: Stable release

The stable run publishes everywhere and cannot be undone.

1. **Ask in one prompt** (`AskUserQuestion`, with a recommended option):
   - **Highlight:** if the rc notes had no Highlights section, propose the
     headline PR. Check first that its `## Changelog` line reads as a headline.
   - **Credentials:** you cannot check them. Ask the maintainer to confirm the two
     under "Before a stable run" in `RELEASING.md`. If the Marketplace identity
     or its federated credential is gone, the publish fails and the run goes red.
2. If they picked a highlight, add the label: `gh pr edit <n> --add-label notes::highlight`.
3. `git fetch origin main`. For each PR number in
   `git log --first-parent --oneline <sha>..origin/main`, read its labels with
   `gh pr view <n> --json labels`. Any that isn't `release::none` isn't in the
   rc you swept: go back to Phase 1 for a new rc, or ask the maintainer whether
   to release the pinned sha anyway.
4. **Dry run** with `pre=none` and the same `sha`. The version must be the last
   rc's without its suffix (`v2.3.0` after `v2.3.0rc2`). If it is not, a label
   changed since the rc: stop and report.
5. **Ask Go / hold**, showing the notes and the version.
6. On Go, the **real run**, the same inputs with `dry_run=false`.
7. If it concluded `success`, wait in the background:
   `.claude/skills/release/scripts/wait_published.sh X.Y.Z pypi brew openvsx`.
   If the script exits 1 (a target still not serving after 30 minutes) on a
   run that concluded `success`, report it and confirm by hand with the
   per-target check under "What a stable run publishes" in `RELEASING.md`:
   after a `success` run it is more likely lag than a missed publish. Then
   check the rest:

   | Target | Check |
   |---|---|
   | GitHub Release | `vX.Y.Z`, not a pre-release, wheel and `.vsix` attached |
   | VS Code Marketplace | the run's "publish the VS Code extension" step succeeded, and its "check the Marketplace lists this release" step ended without its 5-minute warning; if it warned, `npx --prefix vscode vsce show kraft-sdlc.kraft` names the version. A failed publish step usually means the identity's login or membership lapsed, and the extension is not published: report it. |
   | Docs | the `docs` run the release dispatched passed; a warning that it was not dispatched means run `docs` by hand |

8. `.claude/skills/release/scripts/smoke.sh X.Y.Z`, as in Phase 3.

## A real run that failed

Stop and report: the run, the step that failed, and its log. Do not rerun,
publish by hand, or yank anything without the maintainer. What happens next
depends on where it failed. A build before the tag push leaves nothing behind,
and a rerun, once they agree, is safe. After the tag, a rerun plans nothing, and the targets that
did not publish are finished by hand. `RELEASING.md`, "What a stable run
publishes", lists the steps in order and the commands for each.

## Phase 5: After the release

The release is done when the stamp PR has merged.

1. **Stamp PR.** The release bot opens
   `chore: stamp plugin manifests and changelog for vX.Y.Z` (`release::none`) and
   auto-merges it once CI passes. Find it
   with `gh pr list --state all --search "stamp plugin manifests vX.Y.Z"`.
   If it's still open, watch it to green. If a check
   fails, read that check's log and report it. A patch release that ships an
   `_Added in next_` note does not fail it: the stamp step warns and adds a line
   to the PR (`RELEASING.md`, "Docs Added in notes").
2. **What's new.** Open a `release::none` pull request that adds the release's
   section to `docsite/content/6.project/3.whats-new.md`, newest first: its
   `notes::highlight` entries (one with sub-bullets counts as one) and at most
   two more of its most notable changes, a bullet each with a link to the page that describes
   it, under `<!-- not new: shipped in X.Y -->`.
   A patch release adds nothing.
3. **Report**, outcome first: the version, where it is published, what the rc
   and stable smoke runs checked, anything that warned, and the release link.
4. **Offer an announcement.** Build it from `CHANGELOG.md` on main, grouped by
   theme rather than by version, with the install lines
   (`uv tool install kraft-sdlc`, `brew tap itsOmidKarami/kraft && brew install kraft`,
   `kraft admin update`) and the live demo, https://kraft-sdlc.dev/demo/. Keep
   every feature claim to what the changelog says.

## Gotchas

| Gotcha | What to do |
|---|---|
| Foreground `sleep` is blocked | Wait with a `run_in_background` command or a Monitor. Never chain short sleeps. |
| `git pull origin main` on a cloud session's branch | It fast-forwards the branch, and the stop hook reports an unpushed commit. Use `git fetch` only; if it happens, push the branch, which carries only main's commits. |

Files in this skill

  • SKILL.md13.1 KB
  • references/github-mcp.md2.2 KB
  • scripts/kraft.sh546 B
  • scripts/smoke.sh1.8 KB
  • scripts/wait_published.sh1.2 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

View all in ai-agents →

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.