[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M754FAFB09T1JGQWBY93VB2T":3,"skill-same:01M754FAFB09T1JGQWBY93VB2T":49},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":16,"categories":17,"keywords":18,"publishedAt":19,"linkOnly":20,"markdownContent":21,"markdownBody":22,"frontMatter":23,"refName":25,"commitSha":26,"skillDirectory":27,"forksCount":28,"repositoryHtmlUrl":29,"repositoryHomepageUrl":30,"ownerHtmlUrl":31,"ownerBlogUrl":32,"ownerDescription":33,"updatedAt":34,"install":35,"resources":48},"01M754FAFB09T1JGQWBY93VB2T","cut-release",null,"Cut a tagged release of mecatl — dispatch the Create Release PR workflow, review and merge the release PR, then verify the tag and the artifacts it publishes (images + Helm charts to GHCR, plus a GitHub Release with signed archives and a Homebrew cask bump). Use when asked to cut\u002Fship\u002Ftag\u002Fpublish a release or bump the version. NOT for general git tagging unrelated to a mecatl release.","stacklok\u002Fmecatl","stacklok","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F110237746?v=4","01M754F1F8WJJZYJ0WZ73C7PBS","Stacklok","01M754F1GGDR6FG8K1RZ8SR2NA",309,"Apache-2.0","https:\u002F\u002Fgithub.com\u002Fstacklok\u002Fmecatl\u002Fblob\u002Fmain\u002F.claude\u002Fskills\u002Fcut-release\u002FSKILL.md",[],[],"2026-11-12 13:55:43",false,"---\nname: cut-release\ndescription: >-\n  Cut a tagged release of mecatl — dispatch the Create Release PR workflow, review and\n  merge the release PR, then verify the tag and the artifacts it publishes (images +\n  Helm charts to GHCR, plus a GitHub Release with signed archives and a Homebrew cask\n  bump). Use when asked to cut\u002Fship\u002Ftag\u002Fpublish a release or bump the version. NOT for\n  general git tagging unrelated to a mecatl release.\nmetadata:\n  author: stacklok\n---\n\n# Cut a mecatl release\n\nA release is a `vX.Y.Z` git tag. Pushing that tag triggers `.github\u002Fworkflows\u002Frelease.yml`,\nwhich publishes signed images (`mecated`, `mecatui`, `mecak8s`, execution provider\u002Fworkload,\nStudio, and the Slack-bot example), Helm charts, and versioned microVM and Brood Box\nartifacts. It also publishes a **GitHub Release** carrying `darwin`\u002F`linux` x\n`amd64`\u002F`arm64` CLI archives, a `checksums.txt`, cosign bundles, SBOMs, and build\nprovenance, and pushes a `mecatl` cask bump to the public `stacklok\u002Fhomebrew-tap`\nrepository. There is **no** version baked into the Go code — the tag IS the release.\n\nTwo consequences of the Homebrew half, before you start:\n\n- **A published tag is immutable in practice.** The cask records the release archives'\n  SHA256 checksums for a specific tag. Deleting and re-pushing a `vX.Y.Z` that already produced\n  a release and a tap commit leaves the tap pointing at checksums that no longer match, which\n  breaks `brew install` for everyone. If a release goes wrong after the tap commit lands,\n  **fix forward with the next patch version.** Re-tagging is only an option when the run failed\n  before publishing anything.\n- **Validate release-only inputs BEFORE tagging**, not by pushing a throwaway tag.\n  The release-PR workflow checks execution-image digest pins before it opens the PR;\n  CI checks the Brood Box build and microVM defaults handoff. A local\n  `task release:snapshot && task release:verify` only checks CLI archives and\n  Homebrew packaging; it does not prove the container publishing jobs work.\n\n**Nothing pushes a commit to `main`.** The release runs through an ordinary pull request:\nyou dispatch a workflow, a bot opens the PR, a human merges it, and a bot tags the merge\ncommit. You never run `git push origin main`, and you never create the tag by hand.\n\n`VERSION` (repo root, **bare** semver — `0.0.34`, not `v0.0.34`) is the source of truth\nfor the release version. The release PR propagates it to the `mecak8s` and\n`mecatl-studio` chart versions and `appVersion`s, and to the `mecak8s` default image\ntag. The workflows verify those values directly rather than maintaining a fixed list of\nfiles that a release PR may change.\n\nIt did not used to be. `mecatequi-reusable.yml` referenced its three sibling composite actions\nby a hardcoded `@vX.Y.Z` literal, so every release had to bump those pins in the same tagged\ncommit or ship version skew. That self-reference — a file naming a tag that does not exist yet —\nis why a release needed a commit on `main` at all. The pins are now `$\u002F` self-repository refs,\nwhich resolve to this repo at the exact ref the workflow is running from, so there is nothing\nleft to bump and skew is impossible rather than merely policed.\n\n## Steps\n\nRun from the repo root.\n\n1. **Confirm what you're shipping.** The release tags whatever is on `main` when the release PR\n   merges. Review what has landed since the last tag:\n   ```sh\n   git tag --sort=-v:refname --list 'v*' | head -1   # e.g. v0.0.33\n   git log \u003Clast-tag>..origin\u002Fmain --oneline\n   ```\n   Confirm the bump type with the operator before dispatch; `patch` is the established\n   release cadence even when the range includes additive changes. Do not infer a\n   `minor` bump from commit subjects alone.\n\n2. **Dispatch the release-PR workflow.** This is the only step that starts a release:\n   ```sh\n   gh workflow run create-release-pr.yml -f bump_type=patch\n   gh run watch \"$(gh run list --workflow=create-release-pr.yml --limit 1 --json databaseId --jq '.[0].databaseId')\"\n   ```\n   The workflow validates the tracked `ARG` defaults in\n   `build\u002Fexecution-provider\u002FDockerfile` and `build\u002Fexecution-workload\u002FDockerfile`\n   on `main`: the Go builder, static provider runtime, and Brood Box workload\n   runtime must have tagged digest pins available for Linux amd64 and arm64.\n   It does not build the images: `execution-images.yml` builds both for amd64\n   and arm64, without publishing, on every push to `main`. The tag workflow repeats the\n   pin and availability validation on the merged commit before tagging. Renovate\n   updates the pins through its native Dockerfile manager; no image repository\n   variables are required.\n   If validation fails, fix the tracked pins through review (or retry a transient\n   registry failure); do not bypass the check. The publishing job validates the\n   tagged checkout again. The workflow bumps `VERSION`, the `mecak8s` and\n   `mecatl-studio` chart versions and app versions, and the `mecak8s` chart's default\n   image tag. It opens `Release vX.Y.Z` from branch `release\u002FvX.Y.Z`, then asserts that\n   the required values are synchronized. **If that verification step fails, do not merge the PR**;\n   close it, delete the branch, and read the job log.\n\n3. **Review the release PR like any other PR** and confirm that all changes belong to the\n   release update:\n   ```sh\n   gh pr list --head \"release\u002FvX.Y.Z\" --json number,url,files\n   gh pr diff \u003Cnumber>\n   ```\n   Wait for CI to go green. The PR is opened by the release GitHub App, so it triggers checks\n   normally.\n\n4. **Squash-merge it.** A human does this — it is the approval gate, and it is the only way\n   `VERSION` changes on `main`:\n   ```sh\n   gh pr merge \u003Cnumber> --squash\n   ```\n   The tagging workflow does not read the commit subject — it asks GitHub which PR produced\n   the commit and requires a merged, bot-opened PR from branch `release\u002FvX.Y.Z` with matching\n   version values. The squash title and the number of updated files do not affect the tag gate.\n\n5. **Watch the tag get created.** Merging fires `create-release-tag.yml`, which re-verifies the\n   commit and pushes the annotated tag. That push fires `release.yml` on its own — the tag is\n   pushed by a GitHub App installation token precisely so the cascade happens, where a\n   `GITHUB_TOKEN`-pushed tag would trigger nothing:\n   ```sh\n   gh run watch \"$(gh run list --workflow=create-release-tag.yml --limit 1 --json databaseId --jq '.[0].databaseId')\"\n   git fetch --tags && git tag --sort=-v:refname --list 'v*' | head -1\n   ```\n\n6. **Confirm the release run started, then wait for every publishing job.** A\n   successful CLI archive or one green image is not a complete release. In\n   particular, check the `mecated`, execution provider\u002Fworkload, Brood Box\n   resolution, microVM, mecak8s\u002Fexecution\u002FStudio chart, and CLI\u002FHomebrew jobs\n   before reporting success:\n   ```sh\n   gh run list --workflow=release.yml --limit 3\n   gh run watch \"$(gh run list --workflow=release.yml --limit 1 --json databaseId --jq '.[0].databaseId')\"\n   ```\n   The Studio chart job skips with a notice when the tag has no chart, so confirm the\n   charts actually landed in GHCR (bare `X.Y.Z`, no `v`):\n   ```sh\n   helm show chart oci:\u002F\u002Fghcr.io\u002Fstacklok\u002Fmecatl\u002Fcharts\u002Fmecak8s --version X.Y.Z\n   helm show chart oci:\u002F\u002Fghcr.io\u002Fstacklok\u002Fmecatl\u002Fcharts\u002Fmecatl-studio --version X.Y.Z\n   ```\n\n7. **Verify the GitHub Release carries every artifact.** It must not be a draft, and it must\n   have four archives plus a checksum file, with a cosign bundle and an SBOM alongside each.\n   Also require `brood-base-index.json`, `brood-platforms.json`, and the\n   `microvm-default-*.json` completion assets for each platform. Missing assets\n   mean the release is incomplete even if the CLI archives are available:\n   ```sh\n   gh release view vX.Y.Z --json isDraft,assets --jq '{draft: .isDraft, assets: [.assets[].name]}'\n   ```\n   Then prove one archive is actually usable rather than trusting the asset list. Use the\n   repo-local `.scratch\u002F` dir, never `\u002Ftmp` (AGENTS.md):\n   ```sh\n   mkdir -p .scratch\u002Frelease-vX.Y.Z\n   gh release download vX.Y.Z -p 'checksums.txt' -p '*darwin_arm64*' -D .scratch\u002Frelease-vX.Y.Z\n   (cd .scratch\u002Frelease-vX.Y.Z \\\n     && shasum -a 256 -c checksums.txt --ignore-missing \\\n     && tar -xzf mecatl_*_darwin_arm64.tar.gz \\\n     && .\u002Fmecatui --version && .\u002Fmecated --version)\n   ```\n   Both `--version` lines must print the tag you just cut. A `dev+\u003Crevision>` output means the\n   release build lost its `BUILD_ID` linker stamp — a release bug, not a cosmetic one, because\n   the public install docs claim a released binary reports its tag.\n\n   **If the run died between \"release created\" and \"assets uploaded\"** it leaves a DRAFT, which\n   a lookup by tag does not return, so a naive re-run fails trying to create the release again.\n   Recover with `gh release delete vX.Y.Z --cleanup-tag=false --yes`, then re-dispatch.\n\n8. **Verify the Homebrew tap got the cask bump:**\n   ```sh\n   gh api repos\u002Fstacklok\u002Fhomebrew-tap\u002Fcommits --jq '.[0].commit.message'\n   gh api repos\u002Fstacklok\u002Fhomebrew-tap\u002Fcontents\u002FCasks\u002Fmecatl.rb --jq '.content' \\\n     | base64 -d | grep -E 'version|url|sha256' | head\n   ```\n   The top commit must name the version you just cut, and the cask's `url` and `sha256`\n   values must match the release assets from step 7.\n\n   **While `stacklok\u002Fmecatl` is private, `brew install stacklok\u002Ftap\u002Fmecatl` fails** even after a\n   correct tap commit: Homebrew's downloader does not authenticate, so it cannot fetch a release\n   archive from a private repository. The tap commit landing is the whole verification until the\n   repository goes public; this is known and accepted. Once it is public, run the real\n   end-to-end check once:\n   ```sh\n   brew update && brew install stacklok\u002Ftap\u002Fmecatl && mecatui --version\n   ```\n\n## The engine module is tagged separately\n\nThe `vX.Y.Z` release above is the **root repo \u002F `mecated` image** release. The importable\ncore, `github.com\u002Fstacklok\u002Fmecatl\u002Fengine`, is its **own Go module** with its own\ntag grammar `engine\u002FvX.Y.Z` (distinct from the root tags). It carries a public-API\ncompatibility contract (`engine\u002FCOMPATIBILITY.md`).\n\n- **Engine tags are cut on their own schedule.** Cutting one is a deliberate maintainer\n  decision — do NOT cut it as part of a routine root release unless asked. The `engine\u002FvX.Y.Z`\n  and root `vX.Y.Z` version lines are independent. Bumps follow `engine\u002FCOMPATIBILITY.md`\n  (pre-v1: minor = additive or breaking, patch = fixes only).\n\n### Cutting an engine tag (mirrors the root flow)\n\nRun from the repo root.\n\n1. **Pick the engine version.** Increment the latest engine tag per `engine\u002FCOMPATIBILITY.md`\n   (pre-v1: Added = minor, Changed\u002FRemoved = minor too; patch = fixes). The latest engine tag:\n   ```sh\n   git tag --sort=-v:refname --list 'engine\u002Fv*' | head -1\n   ```\n\n2. **Pre-flight.** Confirm `engine\u002FCHANGELOG.md` has an `[Unreleased]` entry covering everything\n   since the last engine tag. Then run the advisory `gorelease` check:\n   ```sh\n   task api:release-check\n   ```\n   It never fails: it previews the SemVer classification against the previous engine tag, so\n   compare its verdict with the version you picked. The authoritative guard is the `api-compat` gate\n   (`task api:check`), which already guarantees the committed `engine\u002Fapi\u002F*.txt` snapshots match the\n   surface being tagged.\n\n3. **Create the annotated tag** with a concise summary:\n   ```sh\n   git tag -a engine\u002FvX.Y.Z -m \"engine\u002FvX.Y.Z — \u003Cone-line summary>\"\n   ```\n\n4. **Push the tag:**\n   ```sh\n   git push origin engine\u002FvX.Y.Z\n   ```\n\n**This line is deliberately still manual.** An engine tag adds no commit to `main` and carries\nno pin bump, so it never needed the release-PR flow the root `vX.Y.Z` line uses — pushing the\ntag is the whole release.\n\n**IMPORTANT — an engine tag fires NO image build, NO GitHub Release, and NO Homebrew cask bump.** `release.yml` triggers on `v*` (the root tag\nglob), which does **not** match `engine\u002Fv*`, so cutting an engine tag runs none of the ko build \u002F\ncosign \u002F SBOM \u002F SLSA pipeline. It only publishes the module version, making it resolvable for\n`go get github.com\u002Fstacklok\u002Fmecatl\u002Fengine@engine\u002FvX.Y.Z` consumers. There is no pin\nbump and no `release.yml` run to confirm — the push of the tag is the whole release.\n\n## Notes\n\n- **Two publishing destinations, one tag.** A run can succeed on the GHCR images and still fail\n  on the release or the tap (or vice versa). GoReleaser's cask pipe continues on error and the\n  Release is created before the cask is pushed, so a bad tap token loses the cask but NOT\n  the Release. Steps 7 and 8 are not optional: a green `gh run list` line is not proof that\n  `brew install` works.\n- **Never hand-edit `stacklok\u002Fhomebrew-tap`.** The cask is generated from the tag by the\n  release workflow and carries a `DO NOT EDIT` header. A manual edit is overwritten by the next\n  release and desynchronizes the checksums in the meantime.\n- **Never push to `main`, and never create a root `vX.Y.Z` tag by hand.** Both are the\n  workflows' job. A hand-pushed version bump skips code review, and a hand-created tag can point\n  at a commit whose release metadata the gate rejects. If `VERSION` is edited on `main` outside a\n  release PR, `create-release-tag.yml` refuses to tag it rather than cutting a release from it.\n  `release.yml`'s `guard` job additionally refuses to publish anything from a tag that is\n  not an ancestor of `main`, so a tag cut on a branch builds nothing. (This applies to the ROOT\n  `v*` line only — the `engine\u002Fv*` tags below are still cut by hand, deliberately: they carry\n  no pin bump and add no commit to `main`.)\n- **Annotated tags only** (`git tag -a`), matching prior releases — they carry a tagger + message.\n  `create-release-tag.yml` does this; the tagger is `github-actions[bot]`.\n- **Don't bump illustrative documentation refs** unless asked — the `@vX.Y.Z` examples in\n  `user-docs\u002Fbuilding\u002Fdeployment\u002Fmecatequi.md` are illustrative and do NOT gate the release. The\n  release flow deliberately leaves them alone.\n- **If the release run fails on the version gate**, the tagged commit has inconsistent release\n  metadata. That should be impossible through the normal flow: `create-release-pr.yml` verifies\n  the bump before the PR can merge, and `create-release-tag.yml` tags only the merge commit. It\n  means someone tagged by hand or the chart metadata drifted from `VERSION`. Fix forward with a\n  patch.\n- **Rerunning is safe.** `create-release-tag.yml` makes one decision from the tag's state and\n  the commit's provenance, so it is quiet when there is nothing to do (the tag already points\n  here, or `VERSION` names an already-released tag this commit did not produce) and loud only\n  when a tag should have been created and something is wrong. `release.yml` re-signs\n  idempotently via its `workflow_dispatch` `tag` input.\n- **Setup, once.** Both workflows read the release GitHub App from a `release` GitHub\n  Environment (`vars.RELEASE_APP_CLIENT_ID`, `secrets.RELEASE_APP_PRIVATE_KEY`), whose\n  deployment-branch policy must be restricted to `main`. The App needs exactly two repository\n  permissions — Contents: write and Pull requests: write. It does NOT need Workflows: write,\n  because a release no longer edits anything under `.github\u002Fworkflows\u002F`. Repo-level secrets would let anyone\n  with push access dispatch a modified workflow from a branch and mint the App credential.\n- **One release at a time.** If any `release\u002Fv*` PR is open, the next dispatch refuses and names\n  it — merge or close it first. Once none is open, leftover `release\u002Fv*` branches from failed\n  runs are deleted automatically before the new PR is cut.\n- **The release gate checks synchronized values, not a fixed file list.** This lets the release\n  automation add another version projection or generated file without requiring a second gate\n  update. There is nothing to dry-run locally.\n","# Cut a mecatl release\n\nA release is a `vX.Y.Z` git tag. Pushing that tag triggers `.github\u002Fworkflows\u002Frelease.yml`,\nwhich publishes signed images (`mecated`, `mecatui`, `mecak8s`, execution provider\u002Fworkload,\nStudio, and the Slack-bot example), Helm charts, and versioned microVM and Brood Box\nartifacts. It also publishes a **GitHub Release** carrying `darwin`\u002F`linux` x\n`amd64`\u002F`arm64` CLI archives, a `checksums.txt`, cosign bundles, SBOMs, and build\nprovenance, and pushes a `mecatl` cask bump to the public `stacklok\u002Fhomebrew-tap`\nrepository. There is **no** version baked into the Go code — the tag IS the release.\n\nTwo consequences of the Homebrew half, before you start:\n\n- **A published tag is immutable in practice.** The cask records the release archives'\n  SHA256 checksums for a specific tag. Deleting and re-pushing a `vX.Y.Z` that already produced\n  a release and a tap commit leaves the tap pointing at checksums that no longer match, which\n  breaks `brew install` for everyone. If a release goes wrong after the tap commit lands,\n  **fix forward with the next patch version.** Re-tagging is only an option when the run failed\n  before publishing anything.\n- **Validate release-only inputs BEFORE tagging**, not by pushing a throwaway tag.\n  The release-PR workflow checks execution-image digest pins before it opens the PR;\n  CI checks the Brood Box build and microVM defaults handoff. A local\n  `task release:snapshot && task release:verify` only checks CLI archives and\n  Homebrew packaging; it does not prove the container publishing jobs work.\n\n**Nothing pushes a commit to `main`.** The release runs through an ordinary pull request:\nyou dispatch a workflow, a bot opens the PR, a human merges it, and a bot tags the merge\ncommit. You never run `git push origin main`, and you never create the tag by hand.\n\n`VERSION` (repo root, **bare** semver — `0.0.34`, not `v0.0.34`) is the source of truth\nfor the release version. The release PR propagates it to the `mecak8s` and\n`mecatl-studio` chart versions and `appVersion`s, and to the `mecak8s` default image\ntag. The workflows verify those values directly rather than maintaining a fixed list of\nfiles that a release PR may change.\n\nIt did not used to be. `mecatequi-reusable.yml` referenced its three sibling composite actions\nby a hardcoded `@vX.Y.Z` literal, so every release had to bump those pins in the same tagged\ncommit or ship version skew. That self-reference — a file naming a tag that does not exist yet —\nis why a release needed a commit on `main` at all. The pins are now `$\u002F` self-repository refs,\nwhich resolve to this repo at the exact ref the workflow is running from, so there is nothing\nleft to bump and skew is impossible rather than merely policed.\n\n## Steps\n\nRun from the repo root.\n\n1. **Confirm what you're shipping.** The release tags whatever is on `main` when the release PR\n   merges. Review what has landed since the last tag:\n   ```sh\n   git tag --sort=-v:refname --list 'v*' | head -1   # e.g. v0.0.33\n   git log \u003Clast-tag>..origin\u002Fmain --oneline\n   ```\n   Confirm the bump type with the operator before dispatch; `patch` is the established\n   release cadence even when the range includes additive changes. Do not infer a\n   `minor` bump from commit subjects alone.\n\n2. **Dispatch the release-PR workflow.** This is the only step that starts a release:\n   ```sh\n   gh workflow run create-release-pr.yml -f bump_type=patch\n   gh run watch \"$(gh run list --workflow=create-release-pr.yml --limit 1 --json databaseId --jq '.[0].databaseId')\"\n   ```\n   The workflow validates the tracked `ARG` defaults in\n   `build\u002Fexecution-provider\u002FDockerfile` and `build\u002Fexecution-workload\u002FDockerfile`\n   on `main`: the Go builder, static provider runtime, and Brood Box workload\n   runtime must have tagged digest pins available for Linux amd64 and arm64.\n   It does not build the images: `execution-images.yml` builds both for amd64\n   and arm64, without publishing, on every push to `main`. The tag workflow repeats the\n   pin and availability validation on the merged commit before tagging. Renovate\n   updates the pins through its native Dockerfile manager; no image repository\n   variables are required.\n   If validation fails, fix the tracked pins through review (or retry a transient\n   registry failure); do not bypass the check. The publishing job validates the\n   tagged checkout again. The workflow bumps `VERSION`, the `mecak8s` and\n   `mecatl-studio` chart versions and app versions, and the `mecak8s` chart's default\n   image tag. It opens `Release vX.Y.Z` from branch `release\u002FvX.Y.Z`, then asserts that\n   the required values are synchronized. **If that verification step fails, do not merge the PR**;\n   close it, delete the branch, and read the job log.\n\n3. **Review the release PR like any other PR** and confirm that all changes belong to the\n   release update:\n   ```sh\n   gh pr list --head \"release\u002FvX.Y.Z\" --json number,url,files\n   gh pr diff \u003Cnumber>\n   ```\n   Wait for CI to go green. The PR is opened by the release GitHub App, so it triggers checks\n   normally.\n\n4. **Squash-merge it.** A human does this — it is the approval gate, and it is the only way\n   `VERSION` changes on `main`:\n   ```sh\n   gh pr merge \u003Cnumber> --squash\n   ```\n   The tagging workflow does not read the commit subject — it asks GitHub which PR produced\n   the commit and requires a merged, bot-opened PR from branch `release\u002FvX.Y.Z` with matching\n   version values. The squash title and the number of updated files do not affect the tag gate.\n\n5. **Watch the tag get created.** Merging fires `create-release-tag.yml`, which re-verifies the\n   commit and pushes the annotated tag. That push fires `release.yml` on its own — the tag is\n   pushed by a GitHub App installation token precisely so the cascade happens, where a\n   `GITHUB_TOKEN`-pushed tag would trigger nothing:\n   ```sh\n   gh run watch \"$(gh run list --workflow=create-release-tag.yml --limit 1 --json databaseId --jq '.[0].databaseId')\"\n   git fetch --tags && git tag --sort=-v:refname --list 'v*' | head -1\n   ```\n\n6. **Confirm the release run started, then wait for every publishing job.** A\n   successful CLI archive or one green image is not a complete release. In\n   particular, check the `mecated`, execution provider\u002Fworkload, Brood Box\n   resolution, microVM, mecak8s\u002Fexecution\u002FStudio chart, and CLI\u002FHomebrew jobs\n   before reporting success:\n   ```sh\n   gh run list --workflow=release.yml --limit 3\n   gh run watch \"$(gh run list --workflow=release.yml --limit 1 --json databaseId --jq '.[0].databaseId')\"\n   ```\n   The Studio chart job skips with a notice when the tag has no chart, so confirm the\n   charts actually landed in GHCR (bare `X.Y.Z`, no `v`):\n   ```sh\n   helm show chart oci:\u002F\u002Fghcr.io\u002Fstacklok\u002Fmecatl\u002Fcharts\u002Fmecak8s --version X.Y.Z\n   helm show chart oci:\u002F\u002Fghcr.io\u002Fstacklok\u002Fmecatl\u002Fcharts\u002Fmecatl-studio --version X.Y.Z\n   ```\n\n7. **Verify the GitHub Release carries every artifact.** It must not be a draft, and it must\n   have four archives plus a checksum file, with a cosign bundle and an SBOM alongside each.\n   Also require `brood-base-index.json`, `brood-platforms.json`, and the\n   `microvm-default-*.json` completion assets for each platform. Missing assets\n   mean the release is incomplete even if the CLI archives are available:\n   ```sh\n   gh release view vX.Y.Z --json isDraft,assets --jq '{draft: .isDraft, assets: [.assets[].name]}'\n   ```\n   Then prove one archive is actually usable rather than trusting the asset list. Use the\n   repo-local `.scratch\u002F` dir, never `\u002Ftmp` (AGENTS.md):\n   ```sh\n   mkdir -p .scratch\u002Frelease-vX.Y.Z\n   gh release download vX.Y.Z -p 'checksums.txt' -p '*darwin_arm64*' -D .scratch\u002Frelease-vX.Y.Z\n   (cd .scratch\u002Frelease-vX.Y.Z \\\n     && shasum -a 256 -c checksums.txt --ignore-missing \\\n     && tar -xzf mecatl_*_darwin_arm64.tar.gz \\\n     && .\u002Fmecatui --version && .\u002Fmecated --version)\n   ```\n   Both `--version` lines must print the tag you just cut. A `dev+\u003Crevision>` output means the\n   release build lost its `BUILD_ID` linker stamp — a release bug, not a cosmetic one, because\n   the public install docs claim a released binary reports its tag.\n\n   **If the run died between \"release created\" and \"assets uploaded\"** it leaves a DRAFT, which\n   a lookup by tag does not return, so a naive re-run fails trying to create the release again.\n   Recover with `gh release delete vX.Y.Z --cleanup-tag=false --yes`, then re-dispatch.\n\n8. **Verify the Homebrew tap got the cask bump:**\n   ```sh\n   gh api repos\u002Fstacklok\u002Fhomebrew-tap\u002Fcommits --jq '.[0].commit.message'\n   gh api repos\u002Fstacklok\u002Fhomebrew-tap\u002Fcontents\u002FCasks\u002Fmecatl.rb --jq '.content' \\\n     | base64 -d | grep -E 'version|url|sha256' | head\n   ```\n   The top commit must name the version you just cut, and the cask's `url` and `sha256`\n   values must match the release assets from step 7.\n\n   **While `stacklok\u002Fmecatl` is private, `brew install stacklok\u002Ftap\u002Fmecatl` fails** even after a\n   correct tap commit: Homebrew's downloader does not authenticate, so it cannot fetch a release\n   archive from a private repository. The tap commit landing is the whole verification until the\n   repository goes public; this is known and accepted. Once it is public, run the real\n   end-to-end check once:\n   ```sh\n   brew update && brew install stacklok\u002Ftap\u002Fmecatl && mecatui --version\n   ```\n\n## The engine module is tagged separately\n\nThe `vX.Y.Z` release above is the **root repo \u002F `mecated` image** release. The importable\ncore, `github.com\u002Fstacklok\u002Fmecatl\u002Fengine`, is its **own Go module** with its own\ntag grammar `engine\u002FvX.Y.Z` (distinct from the root tags). It carries a public-API\ncompatibility contract (`engine\u002FCOMPATIBILITY.md`).\n\n- **Engine tags are cut on their own schedule.** Cutting one is a deliberate maintainer\n  decision — do NOT cut it as part of a routine root release unless asked. The `engine\u002FvX.Y.Z`\n  and root `vX.Y.Z` version lines are independent. Bumps follow `engine\u002FCOMPATIBILITY.md`\n  (pre-v1: minor = additive or breaking, patch = fixes only).\n\n### Cutting an engine tag (mirrors the root flow)\n\nRun from the repo root.\n\n1. **Pick the engine version.** Increment the latest engine tag per `engine\u002FCOMPATIBILITY.md`\n   (pre-v1: Added = minor, Changed\u002FRemoved = minor too; patch = fixes). The latest engine tag:\n   ```sh\n   git tag --sort=-v:refname --list 'engine\u002Fv*' | head -1\n   ```\n\n2. **Pre-flight.** Confirm `engine\u002FCHANGELOG.md` has an `[Unreleased]` entry covering everything\n   since the last engine tag. Then run the advisory `gorelease` check:\n   ```sh\n   task api:release-check\n   ```\n   It never fails: it previews the SemVer classification against the previous engine tag, so\n   compare its verdict with the version you picked. The authoritative guard is the `api-compat` gate\n   (`task api:check`), which already guarantees the committed `engine\u002Fapi\u002F*.txt` snapshots match the\n   surface being tagged.\n\n3. **Create the annotated tag** with a concise summary:\n   ```sh\n   git tag -a engine\u002FvX.Y.Z -m \"engine\u002FvX.Y.Z — \u003Cone-line summary>\"\n   ```\n\n4. **Push the tag:**\n   ```sh\n   git push origin engine\u002FvX.Y.Z\n   ```\n\n**This line is deliberately still manual.** An engine tag adds no commit to `main` and carries\nno pin bump, so it never needed the release-PR flow the root `vX.Y.Z` line uses — pushing the\ntag is the whole release.\n\n**IMPORTANT — an engine tag fires NO image build, NO GitHub Release, and NO Homebrew cask bump.** `release.yml` triggers on `v*` (the root tag\nglob), which does **not** match `engine\u002Fv*`, so cutting an engine tag runs none of the ko build \u002F\ncosign \u002F SBOM \u002F SLSA pipeline. It only publishes the module version, making it resolvable for\n`go get github.com\u002Fstacklok\u002Fmecatl\u002Fengine@engine\u002FvX.Y.Z` consumers. There is no pin\nbump and no `release.yml` run to confirm — the push of the tag is the whole release.\n\n## Notes\n\n- **Two publishing destinations, one tag.** A run can succeed on the GHCR images and still fail\n  on the release or the tap (or vice versa). GoReleaser's cask pipe continues on error and the\n  Release is created before the cask is pushed, so a bad tap token loses the cask but NOT\n  the Release. Steps 7 and 8 are not optional: a green `gh run list` line is not proof that\n  `brew install` works.\n- **Never hand-edit `stacklok\u002Fhomebrew-tap`.** The cask is generated from the tag by the\n  release workflow and carries a `DO NOT EDIT` header. A manual edit is overwritten by the next\n  release and desynchronizes the checksums in the meantime.\n- **Never push to `main`, and never create a root `vX.Y.Z` tag by hand.** Both are the\n  workflows' job. A hand-pushed version bump skips code review, and a hand-created tag can point\n  at a commit whose release metadata the gate rejects. If `VERSION` is edited on `main` outside a\n  release PR, `create-release-tag.yml` refuses to tag it rather than cutting a release from it.\n  `release.yml`'s `guard` job additionally refuses to publish anything from a tag that is\n  not an ancestor of `main`, so a tag cut on a branch builds nothing. (This applies to the ROOT\n  `v*` line only — the `engine\u002Fv*` tags below are still cut by hand, deliberately: they carry\n  no pin bump and add no commit to `main`.)\n- **Annotated tags only** (`git tag -a`), matching prior releases — they carry a tagger + message.\n  `create-release-tag.yml` does this; the tagger is `github-actions[bot]`.\n- **Don't bump illustrative documentation refs** unless asked — the `@vX.Y.Z` examples in\n  `user-docs\u002Fbuilding\u002Fdeployment\u002Fmecatequi.md` are illustrative and do NOT gate the release. The\n  release flow deliberately leaves them alone.\n- **If the release run fails on the version gate**, the tagged commit has inconsistent release\n  metadata. That should be impossible through the normal flow: `create-release-pr.yml` verifies\n  the bump before the PR can merge, and `create-release-tag.yml` tags only the merge commit. It\n  means someone tagged by hand or the chart metadata drifted from `VERSION`. Fix forward with a\n  patch.\n- **Rerunning is safe.** `create-release-tag.yml` makes one decision from the tag's state and\n  the commit's provenance, so it is quiet when there is nothing to do (the tag already points\n  here, or `VERSION` names an already-released tag this commit did not produce) and loud only\n  when a tag should have been created and something is wrong. `release.yml` re-signs\n  idempotently via its `workflow_dispatch` `tag` input.\n- **Setup, once.** Both workflows read the release GitHub App from a `release` GitHub\n  Environment (`vars.RELEASE_APP_CLIENT_ID`, `secrets.RELEASE_APP_PRIVATE_KEY`), whose\n  deployment-branch policy must be restricted to `main`. The App needs exactly two repository\n  permissions — Contents: write and Pull requests: write. It does NOT need Workflows: write,\n  because a release no longer edits anything under `.github\u002Fworkflows\u002F`. Repo-level secrets would let anyone\n  with push access dispatch a modified workflow from a branch and mint the App credential.\n- **One release at a time.** If any `release\u002Fv*` PR is open, the next dispatch refuses and names\n  it — merge or close it first. Once none is open, leftover `release\u002Fv*` branches from failed\n  runs are deleted automatically before the new PR is cut.\n- **The release gate checks synchronized values, not a fixed file list.** This lets the release\n  automation add another version projection or generated file without requiring a second gate\n  update. There is nothing to dry-run locally.\n",{"name":5,"metadata":24,"description":7},{"author":9},"main","c4ff786a7467eb2315de679ae78711d118775383",".claude\u002Fskills\u002Fcut-release",29,"https:\u002F\u002Fgithub.com\u002Fstacklok\u002Fmecatl","https:\u002F\u002Fmecatl.dev","https:\u002F\u002Fgithub.com\u002Fstacklok","https:\u002F\u002Fstacklok.com","Open Source AI Innovation, Grounded in Trust.","2026-11-15 04:19:54",{"source":8,"skillName":5,"commands":36,"prompt":46,"zipAvailable":47},[37,40,43],{"tool":38,"command":39},"npx","npx skills add stacklok\u002Fmecatl --skill cut-release",{"tool":41,"command":42},"bunx","bunx skills add stacklok\u002Fmecatl --skill cut-release",{"tool":44,"command":45},"pnpm","pnpm dlx skills add stacklok\u002Fmecatl --skill cut-release","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002Fstacklok\u002Fmecatl\u002Fblob\u002Fmain\u002F.claude\u002Fskills\u002Fcut-release\u002FSKILL.md",true,[],[50,57,64,71,78,85],{"skillNo":51,"name":52,"nameZh":6,"description":53,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":54,"categories":55,"keywords":56,"publishedAt":19},"01M754GX1DCZR950M0SJFD048A","repo-fact","Use when asked for this repository's registered fact or codeword. Loads the canonical answer format.","https:\u002F\u002Fgithub.com\u002Fstacklok\u002Fmecatl\u002Fblob\u002Fmain\u002Fe2e\u002Ffixtures\u002Fskills\u002Frepo-fact\u002FSKILL.md",[],[],{"skillNo":58,"name":59,"nameZh":6,"description":60,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":61,"categories":62,"keywords":63,"publishedAt":19},"01M754GSSV3ACG56M3XWT5F2PD","greet","Use when asked to greet someone with the ceremonial mecatl greeting. Loads the exact greeting format.","https:\u002F\u002Fgithub.com\u002Fstacklok\u002Fmecatl\u002Fblob\u002Fmain\u002Fe2e\u002Ffixtures\u002Fskills\u002Fgreet\u002FSKILL.md",[],[],{"skillNo":65,"name":66,"nameZh":6,"description":67,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":68,"categories":69,"keywords":70,"publishedAt":19},"01M754GPQN80V07DGCPCB7P2XX","go-table-tests","Write idiomatic table-driven Go tests that stay offline and follow this repo's conventions.","https:\u002F\u002Fgithub.com\u002Fstacklok\u002Fmecatl\u002Fblob\u002Fmain\u002Fdocs\u002Fexamples\u002Fskills\u002Fgo-table-tests\u002FSKILL.md",[],[],{"skillNo":72,"name":73,"nameZh":6,"description":74,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":75,"categories":76,"keywords":77,"publishedAt":19},"01M754GMQM60ARRZ0251WD6JN8","commit-style","Write Conventional-Commits messages for this repo, with the required Co-Authored-By trailer.","https:\u002F\u002Fgithub.com\u002Fstacklok\u002Fmecatl\u002Fblob\u002Fmain\u002Fdocs\u002Fexamples\u002Fskills\u002Fcommit-style\u002FSKILL.md",[],[],{"skillNo":79,"name":80,"nameZh":6,"description":81,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":82,"categories":83,"keywords":84,"publishedAt":19},"01M754GGFCQ3AWR3HHHSN3C91C","tech-writer","Use when writing or substantively editing user-facing documentation: drafting a new page, rewriting or restructuring an existing one, adding a major section, or turning engineering material (PR descriptions, specs, release notes, rough notes) into docs. Writes clear, focused technical documentation following the Diataxis framework and Mecatl's canonical style guide. Use it even when the request doesn't mention writing quality; it governs how documentation gets written. Not for editorial review of finished work.","https:\u002F\u002Fgithub.com\u002Fstacklok\u002Fmecatl\u002Fblob\u002Fmain\u002F.claude\u002Fskills\u002Ftech-writer\u002FSKILL.md",[],[],{"skillNo":86,"name":87,"nameZh":6,"description":88,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":89,"categories":90,"keywords":91,"publishedAt":19},"01M754G7VD9JBS4NVFNGREHP64","perf-optimization","Profile-driven performance optimization of mecatl using the offline benchmark + scenario harness. Use when optimizing allocations or latency, reducing allocs\u002Fop or memory, profiling a Go benchmark, pinpointing a hotspot with pprof, proving a win with benchstat, adding a regression benchmark, wiring profile-guided optimization (PGO), or investigating \"why is this slow \u002F allocating\" or a suspected perf regression. Covers task bench, task perf:scenarios, memprofile -> pprof, benchstat A\u002FB, allocs-first gating, PGO setup, and the discipline (follow the profile not the hypothesis; keep pure-perf changes byte-identical; mutation-test cache guards; skip the wrong abstraction). NOT for the live perf MCP server (use perf-mcp-interpretation) or non-mecatl Go profiling.","https:\u002F\u002Fgithub.com\u002Fstacklok\u002Fmecatl\u002Fblob\u002Fmain\u002F.claude\u002Fskills\u002Fperf-optimization\u002FSKILL.md",[],[]]