agentleFS
Servant-Software-LLCServant-Software-LLC/Guardrails/.claude/skills/cut-release/SKILL.md

cut-release

Skill2 starsChanged 2 months agoMITView on GitHub

Cut a new NuGet release of the `ServantSoftware.Guardrails` dotnet tool. Verify the tree is fully pushed and green, pick the next version, tag it at master HEAD, and let the `release.yml` pipeline publish to NuGet.org via Trusted Publishing (OIDC — no API key to handle). Use when the maintainer says "cut a release", "publish a new version", "ship a NuGet package", or "cut v1.2.0".

  • Commits and pushes

36 other projects use a version of this.

Adopted by

Most recently taken up by

Used with

Projects with it also use quota-axi, iterate-pr and 6 more.

Other files people use alongside this one.

Files found in the same public projects as a version of this one, more often than they turn up anywhere else. Each project counts once, large collections count for less, and this file's own repository is left out.
  • layer5ioSkill

    quota-axi

    Report local Claude, Codex, Cursor, GitHub Copilot, and Grok quota windows via the quota-axi CLI - remaining percentages, reset times, and provider status read from local auth sources, with no routing, recommendation, or provider mutation. Use before deciding whether it is safe to keep spending a provider's quota, when the user asks about usage, rate limits, or remaining quota, or when comparing local provider headroom.

    Also in 4 projects that use this

  • SentrySkill

    iterate-pr

    Iterate on a PR until actionable CI passes and high/medium review feedback is addressed. Use for PR CI failures, review feedback, or green-check loops; do not wait for human approval, draft status, or merge gates.

    Also in 4 projects that use this

  • layer5ioSkill

    gh-axi

    Operate GitHub through the gh-axi CLI - issues, pull requests, workflow runs, workflows, releases, repositories, labels, Projects (v2), Actions secrets and variables, search, and raw API access. Use whenever a task touches GitHub: listing or filing issues, reviewing or merging PRs, checking CI runs, triggering workflows, cutting releases, managing Projects boards, or managing Actions secrets/variables.

    Also in 4 projects that use this

  • kunchenguidSkill

    chrome-devtools-axi

    Control a Chrome browser session through the chrome-devtools-axi CLI - navigate, snapshot, click, fill forms, run JavaScript, inspect console and network, take screenshots, audit performance. Use whenever a task needs a real browser: opening or testing a web page, clicking through a flow, extracting page content, or debugging a website.

    Also in 4 projects that use this

  • kunchenguidSkill

    lavish

    Turn complex or visual agent responses into rich, reviewable HTML artifacts (HTML files) the user can annotate and send feedback on, using the lavish-axi CLI. Use when about to give a plan, comparison, diagram, table, code diff, report, or anything easier to grasp visually than as prose.

    Also in 4 projects that use this

  • ThisIsSadeghiSkill

    verify-ui

    Verify UI implementation against Stitch design via a token audit (HTML ↔ Code, with X-component default-render checks) and an X-components compliance check.

    Also in 3 projects that use this

The file

We split this skill into the rules it gives an agent

7 rules. 0 are common, 3 are distinctive, and 4 are very specific to this project.

  • 0

    Common

    Most of the 36 edited versions we read keep these.

  • 3

    Distinctive

    This author's own choices. Worth a look.

  • 4

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  • could be automatic: a setting could enforce it
  1. Very specific:

    Use git -C <repo-root> for every git command (never cd … && git …).

    line 37
  2. Distinctive:

    No open PR you intend to include may still be unmerged.

    line 62
  3. Distinctive:

    The integrated HEAD is green: Every merged PR was CI-green individually, and the release pipeline re-runs the full 3-OS matrix on the …

    line 63
  4. Very specific:

    Do not run dotnet nuget push by hand or paste a NuGet API key anywhere — OIDC does the authenticated publish.

    line 273
  5. Very specific:

    Do not tag a commit that isn't on origin/master or isn't CI-green.

    line 276
  6. Distinctive:

    Do not reuse or hand-edit an already-published version to "patch" it.

    line 277
  7. Very specific:

    Do not re-open the 1.0.0-preview.N line, add a prerelease suffix to a release tag, or renumber down to 0.x — all three are SemVer …

    line 278

“Common” is measured: kept by at least 6 in 10 of the 36 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.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

Cut a release

Cutting a release of the ServantSoftware.Guardrails dotnet tool is one action: push a v* git tag. Everything else is the release.yml pipeline. The version is derived from the tag (leading v stripped): tag v1.2.0 publishes 1.2.0.

This is outward-facing and effectively irreversible — NuGet refuses to republish an existing version, and a published version can be unlisted but never truly deleted (it may already be cached/indexed). So the tree must be exactly what you intend to ship before you tag. Never tag a commit that isn't already on origin/master and CI-green.

You never handle a NuGet API key: publishing uses Trusted Publishing (OIDC). The publish job mints a short-lived key at push time via the nuget.org policy + the NUGET_USER repo secret. If publish ever fails on auth/login (not tests), that's a nuget.org Trusted-Publishing / NUGET_USER config issue for the maintainer — not a code problem.

Preconditions — verify ALL before tagging

Use git -C <repo-root> for every git command (never cd … && git …).

Run the mechanical checks first — they are scripted for a reason:

.github/scripts/release-preflight.sh preflight <sha-you-are-about-to-tag>

It resolves the tag target, compares it to origin/master, reports the working tree, and refuses to proceed inside the #747 timing window (see If it fails). Every check it performs is one that was done by hand — and got wrong — during a real cut. The prose below says what it checks and why; the script is what actually checks it.

  1. The commit you are about to TAG is origin/master. This is a check on the tag target, not on your working tree. You can be standing anywhere — a stale checkout, a worktree, a detached HEAD — and still tag correctly, provided the sha you name is origin/master. The failure that actually happened was tagging a specific sha from a checkout that was behind and assuming HEAD spoke for it. Resolve both sides through rev-parse before comparing, or a short sha compares unequal to a 40-character one and every check is a false result:
    git -C <repo> fetch origin --tags
    git -C <repo> rev-parse --verify "<sha>^{commit}"          # the tag target, fully resolved
    git -C <repo> rev-parse --verify "origin/master^{commit}"  # MUST be identical
    git -C <repo> log origin/master..HEAD --oneline            # nothing unpushed, if you are on master
    No open PR you intend to include may still be unmerged.
  2. The integrated HEAD is green. Every merged PR was CI-green individually, and the release pipeline re-runs the full 3-OS matrix on the tagged commit anyway — but run a final local gate on the actual release artifact for confidence:
    git -C <repo> checkout master && git -C <repo> pull --ff-only    # if not already there
    cd <repo> && dotnet build Guardrails.sln -c Release && dotnet test Guardrails.sln -c Release
    Expect 0 warnings/0 errors and a green suite (the two RealClaude* tests skip without a live key — that's normal).
  3. Working tree clean apart from any long-lived untracked plan-folder drafts under docs/plans/ that were never part of the release. This one is advisory: a tag names a commit, so uncommitted files cannot leak into the release. Check it to catch work you meant to ship and didn't — not as a gate on the tag.
  4. Nothing to edit before tagging. The shipped version comes from the TAG (-p:Version=${GITHUB_REF_NAME#v}), not from a file. src/Guardrails.Cli/Guardrails.Cli.csproj's <Version> is only the default a locally built tool reports (and stamps into installed skills); keeping it roughly current is hygiene, but bumping it is not a step of cutting a release — do not open a PR for it and do not block the tag on it.

Pick the version

git -C <repo> tag --sort=-creatordate | head -5

The scheme is vX.Y.0 — a STABLE version with NO prerelease suffix, monotonically increasing. Default: bump the MINOR by one and leave the patch at 0 (v1.1.0 → v1.2.0 → v1.3.0). Exactly one component moves per release:

ComponentWhen it moves
minor Yevery release — this is the default; no permission needed
major Xonly when the maintainer explicitly says "cut a major" in words
patch (third)never — it stays 0 under this scheme

A major bump is a deliberate maintainer call, stated out loud. Do NOT infer one from the changelog ("this change looks breaking"), and do NOT invent a patch release for a small fix — a small fix is simply the next minor. If the maintainer hasn't said otherwise, cut the next minor and state which version you're cutting before you tag.

The 1.0.0-preview.N line is CLOSED — do not continue it. git tag --sort=-creatordate still lists those preview tags (they remain the chronologically newest until v1.1.0 exists), so do not pattern-match them into a preview.50. If the newest tag you see is a preview.*, the version to cut is v1.1.0 — the first release under this scheme.

Why the line starts at 1.1.0, and why you must not "fix" it back to 0.x. The published history ends at 1.0.0-preview.49; 1.0.0 itself was never formally cut and never will be. The intuitive successor for a pre-1.0 product — restarting at 0.13.0 — is wrong, and unfixable once published: under SemVer 0.13.0 < 1.0.0-preview.49 (major 0 sorts below major 1), so NuGet would read the "new" release as a DOWNGRADE. Two concrete breakages: dotnet tool update would refuse to move an existing 1.0.0-preview.* install forward onto 0.13.0; and --prerelease would resolve 1.0.0-preview.49 — an OLDER build — while a plain install resolved the newer 0.13.0. Starting the stable line at 1.1.0 puts every future release strictly above every published preview, which is the whole point. A future maintainer who wants 0.x cannot have it without abandoning the package id — this paragraph is the answer, not a bug to file.

NuGet won't let you republish, so a typo'd or reused version wastes a number permanently.

Cut it

Annotate the tag with the headline changes since the previous tag. This annotation matters more than it looks. The create-release job publishes the GitHub Release with --generate-notes, which emits a raw list of merged commits and PR titles — accurate, and useless to someone deciding whether to upgrade. Your tag annotation and the notes you write over the generated ones are the only places a human-readable upgrade warning can live. If this release carries a breaking change, a migration step, or a reason to upgrade promptly, it has to be said here or it is not said at all.

# v1.2.0 below is a WORKED EXAMPLE — substitute the version you picked above.
# <PREV-TAG> is the previous tag verbatim, whatever its scheme — for the first stable cut
# that is still v1.0.0-preview.49.
git -C <repo> log <PREV-TAG>..origin/master --oneline    # source the summary
git -C <repo> tag -a v1.2.0 -m "v1.2.0 — <one-line theme>

<short bullet summary of the notable #issue fixes since the last tag>"
git -C <repo> push origin v1.2.0

The tag push is the trigger.

The pipeline creates the GitHub Release itself. The create-release job runs gh release create "$TAG" --title "$TAG" --generate-notes (adding --prerelease for any tag carrying a suffix, which is what keeps throwaway -ci. dry-run tags out of "latest"). It is idempotent — it reuses an existing Release rather than failing — and the five binaries jobs then upload their archives as assets to it.

So gh release list is not empty; every vX.Y.0 is there. Do not create the Release object by hand — the pipeline owns it, and a hand-made one races the job. Your job is the notes, not the object. Where the release warrants it, replace the generated commit list with something a consumer can act on:

gh release view v1.2.0 --repo Servant-Software-LLC/Guardrails          # read what --generate-notes produced
gh release edit v1.2.0 --repo Servant-Software-LLC/Guardrails --notes-file notes.md

A routine release can keep the generated notes. One with an upgrade note or a breaking change cannot — see the annotation guidance above.

Watch the pipeline to completion

.github/scripts/release-preflight.sh ci <sha>   # find the run, poll it, print every job's conclusion

That polls correctly and refuses to build a run URL from an empty id (a hand-written --jq filter that yielded nothing produced a malformed URL and a 404 during a real cut). By hand:

gh run list --repo Servant-Software-LLC/Guardrails --workflow release.yml --limit 3   # find the run id

Poll the top-level run status, then check the conclusion + every job:

# GOTCHA: poll `.status` of the RUN, not a nested job. `--json jobs` will show the
# packaged-tool-smoke job "completed" while the run is still in_progress — don't mistake
# that for the whole run finishing.
gh run view <run-id> --repo Servant-Software-LLC/Guardrails --json status --jq '.status'
# once "completed":
gh run view <run-id> --repo Servant-Software-LLC/Guardrails \
  --json conclusion,jobs --jq '{conclusion, jobs:[.jobs[]|{name,conclusion}]}'

Success = conclusion: success and all eleven jobs green:

JobWhat it is
test (windows-latest)the 3-OS matrix: restore, build, Core, Integration, the whole solution
test (ubuntu-latest)concurrently (#566), and every example's diagrams fresh (#636)
test (macos-latest)
packaged-tool-smoke (ubuntu)pack the tag version → install to an isolated tool-path → assert the skills/ payload shipped and is version-stamped (#171)
pack and publish to NuGet.orgneeds both of the above; the only job that touches NuGet
create GitHub Releaseneeds both of the above; creates the Release with --generate-notes
binary (osx-arm64)the five self-contained single-file binaries. All need create-release.
binary (osx-x64)The osx-* legs run on a macOS runner because codesign is macOS-only:
binary (linux-x64)each is ad-hoc signed (required to exec at all on Apple Silicon, #415) and
binary (linux-arm64)Developer-ID-signed + notarized when the signing secrets exist. Each
binary (win-x64)uploads its archive + .sha256 to the Release.

Two independent branches hang off the same gate: publish (NuGet) and create-release → binaries (the GitHub Release and its assets). A build-green-but-package-broken state (e.g. #169) fails the smoke before either branch can run. Note publish is skipped for -ci. dry-run tags, which still exercise the test, smoke, release and binary jobs — so on such a tag ten green jobs and one skipped is the expected result, not a failure.

Check the test counts, don't just read the tick. A green run that quietly stopped running some tests looks exactly like a green run:

.github/scripts/release-preflight.sh verify <run-id>   # counts vs the recorded baseline

Totals must be identical across all three OS; skips legitimately differ per platform, which is why the baseline records them. A changed total is expected when tests were added. A changed skipped under an unchanged total means a test stopped running somewhere — invisible in the tick, and the shape that cost v1.15.0. Re-record with baseline <run-id> once you have explained the difference, and commit the file.

Confirm live

The publish job succeeding means dotnet nuget push was accepted. NuGet indexing lags a couple of minutes, so an immediate dotnet tool install may not resolve yet. Confirm the version is actually on the feed before telling anyone it shipped:

.github/scripts/release-preflight.sh published 1.2.0

Then tell the consumer:

dotnet tool install --global ServantSoftware.Guardrails          # newest stable
dotnet tool update  --global ServantSoftware.Guardrails          # move an existing install forward
dotnet tool install --global ServantSoftware.Guardrails --version 1.2.0   # pin exactly

No --prerelease anywhere. Releases carry no prerelease suffix now, so a plain install/update resolves the newest release. If you find --prerelease in an instruction, a README, or an install script, it is stale — drop it (leaving it in is not fatal, but it teaches users a flag that no longer means anything for this package).

If it fails

  • A test or packaged-tool-smoke job fails → the code/package has a real problem on the tagged commit. The version is NOT published (the publish job needs: both). Fix on master via the normal PR flow, then cut a new tag — the next minor (v1.2.0 failed → cut v1.3.0). You cannot re-use a tagged version, and you do not "retry" it as a patch.
  • Only the publish job fails, on NuGet login/auth → a nuget.org Trusted-Publishing policy or NUGET_USER secret issue; the code is fine. Escalate to the maintainer (it's their nuget.org account config). Re-running just the failed job after they fix it can complete the same release without a new tag.
  • You tagged the wrong commit / wrong version and the pipeline hasn't published yet → you can delete the tag locally and on origin (git -C <repo> tag -d v…; git -C <repo> push origin :refs/tags/v…) to abort, then re-tag correctly. Once the publish job has pushed to NuGet, the version is permanent — do NOT try to "fix" it by republishing; cut the next minor instead.
  • Transient_PausesAndResumes_WithoutConsumingRetryBudget failed, and only that → check the clock before you believe it. While #747 is open, that test fails deterministically in the 10:50–11:20 window on the clock of whatever machine ran it (UTC on a CI runner). The fixture hardcodes resetHint: "11:20am" and asserts the wait equals the 30-minute probe interval + 4s; inside that window min(probeInterval, timeUntilReset) correctly picks the smaller remainder and the equality fails. It is arithmetic, not flake and not load — so do not "just re-run" it, and do not burn a version number on it. preflight refuses to start a release inside that window for exactly this reason. Wait until after 11:20, or fix #747.

Do not

  • Do not run dotnet nuget push by hand or paste a NuGet API key anywhere — OIDC does the authenticated publish. Handling the key is both unnecessary and a prohibited credential operation.
  • Do not tag a commit that isn't on origin/master or isn't CI-green.
  • Do not reuse or hand-edit an already-published version to "patch" it.
  • Do not re-open the 1.0.0-preview.N line, add a prerelease suffix to a release tag, or renumber down to 0.x — all three are SemVer downgrades against the published 1.0.0-preview.49 and are irreversible once pushed (see Pick the version).

Download now (ZIP)

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
What did you use it for? e.g. Reviewing pull requests in a Rails app

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.