fix/acp-replay-reasoning-and-attachments
name: sigit-code-release
description: Prepare and verify siGit Code releases. Use when bumping versions, checking Cargo, npm, or PyPI package metadata, validating release notes, confirming CI readiness, or assembling a release checklist for a new siGit Code version.
siGit Code Release
Use this skill when preparing a release for this repository.
Focus areas
- Keep the branded product name as
siGit Codein prose andsigitfor commands, crate names, repo paths, and package identifiers. - Verify version consistency across release surfaces, but respect which files are real sources of truth versus generated or release-time rewritten artifacts.
- Check release-facing docs such as
README.md,CHANGELOG.md, workflow notes, and package metadata for stale version references or incorrect naming. - Validate the local release path pragmatically: formatting, linting, targeted tests, and any repo-specific release checks that matter for the requested version.
Working approach
- Read the files that define the release version and distribution metadata before proposing any changes.
- Treat
Cargo.tomlas the primary release version source for the Rust crate and the PyPI package in this repository. - Check how the release workflows rewrite or derive versions before editing checked-in npm or Python metadata.
- Prefer targeted verification commands that match CI or packaging workflows already present in the repository.
- Report blockers clearly: failed checks, version mismatches, missing changelog entries, or unpublished packaging changes.
Repo-specific release rules
- Bump the crate version in
Cargo.toml. - Update the root
sigitpackage entry inCargo.lockwhen the crate version changes. - Add or update the top changelog entry in
CHANGELOG.mdfor the release being cut. - Do not treat
npm/sigit/package.json0.0.0-devas a bug by default. The npm release workflow rewrites it at publish time usingnpm/scripts/render-main-package.cjsand the release tag. - Every npm package publishes twice, under
@getsigitand under@smbcloud.@smbcloudis the pre-1.5.5 name, mirrored so an install predating the rename keeps updating;@getsigitis canonical and is what the README, the Homebrew tap, and the ACP registry entry point at. All fourteen names (two base packages, twelve platform packages) publish over OIDC and each needs its own Trusted Publisher on npm pointing atrelease-npm.yml. - The scope is a render-time argument, not a second copy of the packaging:
render-platform-package.cjstakes it as a fifth argument, and the base package picks it up frompackage-main.json.tmplversuspackage-compat.json.tmpl.npm/sigit/src/index.tsis built once and shipped under both names — it reads its ownpackage.jsonat runtime to decide which scope to resolve the platform binary from, and prints the migration notice only when it finds itself running as@smbcloud/sigit. - Do not add a hardcoded version to
pypi/pyproject.tomlfor normal releases. PyPI usesmaturinwithdynamic = ["version"]and derives the published package version fromCargo.toml. - Release workflows are tag-driven.
release-github.yml,release-npm.yml,release-pypi.yml,release-crates.yml,release-homebrew.yml,release-nuget.yml, andrelease-mcp-registry.ymlall deriveRELEASE_VERSIONfrom av*.*.*tag or a manually supplied tag input. - The crate is published to crates.io (
release-crates.yml) and the Homebrew tap is updated (release-homebrew.yml) as part of the tag-driven flow. Per the siGit release flow, Homebrew is auto-triggered — do not dispatch it manually. release-github.ymlbuilds the binaries, attaches them (plus a.deb/.rpmper Linux target built with nfpm frompackaging/nfpm.yaml) to the GitHub release, then dispatchesrelease-homebrew,release-scoop,release-winget, andrelease-aur. A dispatch failure in any one of those four is logged as a warning, not a hard failure — an unconfigured channel doesn't block the rest of the release.release-nuget.ymlandrelease-mcp-registry.yml, by contrast, are tag-triggered directly like the other language-registry workflows, not dispatched fromrelease-github.yml.- Every release asset carries a
.sha256sidecar (not just the macOS Homebrew tarball). Scoop, winget, and the AUR PKGBUILD each consume the raw binaries plus their sidecar checksum, rather than the tarball. - The GitHub release body is generated, not hand-written:
release-github.yml's release job checks out the tag, awk-extracts the## <version>section fromCHANGELOG.md(a missing section is a hard failure), appends a full-changelog link and asigit.si/codefootnote, and publishes it viabody_path: release_notes.md. The only file a release-prep change needs to touch isCHANGELOG.md— never edit release notes in the GitHub UI, because the next tag push would overwrite them. - npm and NuGet publish via OIDC trusted publishing — neither has a stored token in the repo. On npm this is configured per package (each
@getsigit/*name has a trusted publisher pointing atrelease-npm.yml), so a new package needs the same setup before its first publish will work. OIDC requires npm >= 11.5.1 and Node >= 22.14.0. - The NuGet package (
SiGit.Code,dotnet tool install --global SiGit.Code) bundles all six platform binaries undernative/<os>-<arch>/behind a managed shim, rather than shipping one package per platform like npm/PyPI. Watch the nuget.org 250 MB package-size limit if it ever grows. - The winget channel (
release-winget.yml) needs aWINGET_TOKENsecret that is a classic PAT withpublic_reposcope — not a fine-grained PAT. wingetcreate forksmicrosoft/winget-pkgson first use and submits a PR from that fork; a fine-grained PAT scoped togetsigit/sigitcannot fork a repo outside its scope and every release fails with a 403/404 on the fork step. The token is passed asWINGET_CREATE_GITHUB_TOKEN(not--token) so it doesn't leak into logs.wingetcreate updateonly works on a package that already exists inmicrosoft/winget-pkgs, so the workflow checksmanifests/g/getSigit/siGitCodefirst and falls back towingetcreate submitwithpackaging/winget/*.yaml.infor the first release; every later release takes theupdatepath. If a winget release fails with a fork/permission error, check theWINGET_TOKENsecret type before re-dispatching. - The baked-in official MCP server is separately listed in the public MCP Registry as
si.sigit/sigit(release-mcp-registry.yml, driven byserver.jsonat the repo root). It's a remote Streamable-HTTP listing, so it's verified by a DNS TXT record on thesigit.sidomain rather than the GitHub-OIDC scheme used for package listings.
Git release flow
Releases are cut from development and shipped on main. The published tags (v1.2.0, v1.2.1) point at the main-side merge commit, never at a release branch or at development. Follow this order:
- Branch
release/v<version>offdevelopment. - Apply the version bump and changelog edits, then commit (e.g.
Release v<version>). - Merge
release/v<version>back intodevelopment. - Merge
developmentintomain(commit message:Merge development into main for v<version> release). - Tag
v<version>on thatmainmerge commit, then pushmainand the tag.
Step 4 is the only way commits are meant to reach main. A feature or fix PR targeting main breaks that: it puts work on the release branch that development does not have, so the next release is cut without it. Such PRs belong on development (see the pull request target rule in AGENTS.md).
Pushing the v*.*.* tag is what fires every release workflow, so create and push it only after the merge into main has landed. Do not commit, tag, or push until the user explicitly asks — confirm the version and that they want the release to go out first.
Scoped patch / hotfix flow (cherry-pick, not branch-off-development)
The flow above assumes the release ships everything on development. A scoped
patch is different: the user wants a new version that contains only one or a
few specific merged PRs on top of the last tag, deliberately leaving out
other work that has since landed on development. "A PR is merged into
development" does not mean it belongs in a patch — a patch inherits the
previous tag's tree, not development's.
Branching the release off development and only bumping the version is the
classic mistake here: it silently drags in every unrelated PR merged since the
last tag. For a scoped patch, never branch off development. Instead:
- Branch
release/v<version>off the previous tag (git checkout -b release/v<version> v<previous>), not offdevelopment. The tag already points at the rightmainmerge commit. git cherry-pickexactly the wanted commit(s). Prefer the feature/squash commit (the merge commit's second parent) over the merge commit itself. Resolve conflicts to keep the scope minimal.- Apply the version bump and changelog edits (promote the cherry-picked
## Unreleasedbullets into the## <version>section), then commitRelease v<version>. - Audit the scope before going further:
git log --oneline v<previous>..release/v<version>must show only the cherry-picks plus the release commit, andgit diff --stat v<previous>..release/v<version>must be the wanted source/test changes plus the three release-metadata files (Cargo.toml,Cargo.lock,CHANGELOG.md) — nothing else. - Merge
release/v<version>intodevelopment(so the version bump and the promoted changelog entry are not lost), then mergedevelopmentintomainand tagv<version>on thatmainmerge commit, exactly as in the normal flow. Thedevelopmentmerge may be a no-op for the cherry-picked source if it was already there; that is expected.
Decide up front which case you are in and confirm the exact PR list with the
user. If in doubt about whether a release is "everything on development" or a
scoped patch, ask — the two flows start from different base commits and are not
interchangeable.
Typical files to inspect
Cargo.tomlCargo.lockCHANGELOG.mdREADME.mdnpm/sigit/package.jsonnpm/package-main.json.tmpl,npm/package-compat.json.tmpl,npm/package.json.tmpl(publish-time package metadata for both scopes)npm/scripts/render-main-package.cjsnpm/pypi/nuget/sigit/(bundled-binary.NETtool packaging)packaging/(nfpm.yamlfor.deb/.rpm, plusaur/andwinget/templates)server.json(MCP Registry listing).github/workflows/(release-github.yml,release-npm.yml,release-pypi.yml,release-crates.yml,release-homebrew.yml,release-scoop.yml,release-winget.yml,release-aur.yml,release-nuget.yml,release-mcp-registry.yml)
Release checklist
- Version bump is applied everywhere it needs to be.
Cargo.tomland the root crate entry inCargo.lockmatch.CHANGELOG.mdhas a correct top entry for the new release.npm/sigit/package.jsonis left at0.0.0-devunless the packaging flow itself changed.pypi/pyproject.tomlstill uses dynamic versioning unless there is a deliberate packaging change.- Release workflows still derive their version from the tag as expected.
- Git flow followed: bump committed on
release/v<version>, merged back todevelopment, thendevelopmentmerged intomain, withv<version>tagged on themainmerge commit. - Scope matches intent: for a scoped patch/hotfix,
release/v<version>is branched off the previous tag and carries only the cherry-picked PR(s) plus the release-metadata bump — confirmed withgit log --oneline v<previous>..release/v<version>andgit diff --stat v<previous>..release/v<version>— not everything that landed ondevelopmentsince the last tag. - Release notes or changelog entries match the actual changes.
- CI-equivalent local checks pass for the relevant platform or target.
- Package names, install commands, and branding stay consistent across npm, PyPI, crates.io, Homebrew, NuGet, Scoop, winget, AUR, and the
.deb/.rpmpackages. - Every release asset (including the new
.deb/.rpm/raw binaries consumed by Scoop, winget, and AUR) has a matching.sha256sidecar. - Any known release limitations are called out explicitly, including a dispatch failure in
release-scoop/release-winget/release-aur(non-fatal — logged as a warning byrelease-github.yml) versus a hard failure in a directly tag-triggered workflow likerelease-nugetorrelease-mcp-registry. - The
WINGET_TOKENsecret is a classic PAT withpublic_reposcope (not fine-grained), so wingetcreate can forkmicrosoft/winget-pkgs. If a winget release fails on the fork step, verify the token type before re-dispatching.