Releases
How Gini Agent versions, ships, and documents releases. Read this before bumping a version, opening a release PR, or writing changelog entries.
Versioning
Gini follows Semantic Versioning . While pre-1.0:
0.x.0— anything user-visible: new CLI commands, gateway/api/*changes, config or state-file migrations, behavior changes that users would notice.0.x.y— bug fixes and internal-only changes.1.0.0— first release that commits to a stable public contract for the CLI, gateway API, and on-disk state shape.
The package is "private": true (no npm publish). Versions exist for human-readable identity and for what gini status / the web sidebar surface. The installer-managed runtime updates by git pull, so end users pick up new versions when they run gini update.
CHANGELOG conventions
CHANGELOG.md follows Keep a Changelog . The format is non-negotiable; tooling and humans both rely on it.
The CHANGELOG is curated at release time, not per PR. PRs land with clear titles (per AGENTS.md ) and the release author distills those titles into CHANGELOG entries when cutting the release. The [Unreleased] heading stays empty between releases; it exists as a placeholder for the next versioned section to anchor below.
Categories
When writing the section for a new release, group entries under these headings, in this order, and omit any that are empty:
- Added — new features users can use.
- Changed — behavior changes to existing features.
- Deprecated — features still present but marked for removal.
- Removed — features deleted.
- Fixed — bug fixes.
- Security — vulnerability fixes (cross-reference
SECURITY.md).
What to include
One line per user-visible change. User-visible means: changes a CLI command, the gateway API, the web UI, install/update behavior, config keys, on-disk state shape, or default behavior.
Omit: pure refactors, internal renames, test-only changes, doc fixes that don’t change product behavior, dependency bumps that don’t change behavior.
How to write an entry
Present tense, user-focused. Lead with the verb and the user-visible thing, not the file that was touched.
Good:
- Add `gini snapshots restore` for rolling an instance back to a named snapshot.
- Change `gini provider set` to read API keys from environment variables only (never from config).
- Fix `gini smoke` hanging when the runtime port is already bound.Bad:
- Refactor src/cli/snapshots.ts # not user-visible
- Update snapshot restore # vague, no subject
- Reviewer feedback round 2 # process meta, not the changeIf a change links to an issue, PR, or ADR that adds important context, append it: (#123), (see ADR provider-extra-body.md).
Release process
A release is a version bump, a tag, a GitHub release, and a CHANGELOG section — done together, from main.
1. Survey what changed
From a clean checkout of main:
PREV=$(git describe --tags --abbrev=0 --match 'v[0-9]*' 2>/dev/null)
if [ -n "$PREV" ]; then
git log "$PREV"..main --oneline
else
git log main --oneline # first release; show the full history
fiRead the PR titles. Decide:
- Any new features, behavior changes, removals, or deprecations → minor bump.
- Only fixes (including security) → patch bump.
If you can’t decide, default up.
2. Open a release PR
From the same checkout of main, branch off:
git checkout -b release/X.Y.ZEdit CHANGELOG.md:
- Add a new
## [X.Y.Z] - YYYY-MM-DDheading directly below## [Unreleased]. - Write entries by distilling the PR titles from step 1 into the categories above.
- Leave
## [Unreleased]empty (don’t delete the heading). - Update the link footnotes at the bottom of the file so
[X.Y.Z]points at the compare URL and[Unreleased]points atvX.Y.Z...HEAD.
Bump the version in package.json by editing the "version" field directly (don’t use bun pm version — it refuses on a dirty tree and would race with the CHANGELOG edit). Commit and push:
git add package.json CHANGELOG.md
git commit -m "vX.Y.Z"
git push -u origin release/X.Y.ZOpen the PR. Title: vX.Y.Z. Body: paste the new CHANGELOG section.
3. Merge, then tag from main
After the release PR is approved and merged (squash or merge — both work because the tag is created after the merge lands on main):
git checkout main
git pull
git tag -a vX.Y.Z -m "vX.Y.Z"
git push origin vX.Y.ZThe tag is created on the merge commit, so it’s always reachable from main.
4. Publish the GitHub release
The .github/workflows/release.yml workflow fires on tag push, extracts the matching [X.Y.Z] section from CHANGELOG.md, and publishes a GitHub release automatically. Watch it from the Actions tab and confirm the release page renders correctly.
If you need to publish by hand (workflow disabled, broken, etc.):
VERSION=X.Y.Z
NOTES=$(mktemp)
awk -v v="$VERSION" '
$0 ~ "^## \\[" v "\\]" { in_section=1; next }
in_section && /^## \[/ { exit }
in_section && /^\[.*\]:/ { exit }
in_section { print }
' CHANGELOG.md > "$NOTES"
gh release create "v$VERSION" --title "v$VERSION" --notes-file "$NOTES"
rm "$NOTES"Don’t use --generate-notes — it duplicates work and produces lower-quality notes than the curated CHANGELOG section.
5. Verify
gini update # on a test machine, picks up the new tag
gini status # confirm the sidebar/CLI report the new versionRelease notes vs. CHANGELOG
The CHANGELOG is the source of truth. The GitHub release body for vX.Y.Z is the same text as the [X.Y.Z] section of the CHANGELOG — copy it verbatim, don’t paraphrase.
If a release needs extra context (a migration note, a known issue, a breaking-change call-out), add it inside the CHANGELOG section under a ### Notes subsection so the CHANGELOG and release page stay identical.
Hotfixes
For an urgent fix to the latest release:
- Branch from the tag:
git checkout -b hotfix/X.Y.Z+1 vX.Y.Z. - Apply the minimal fix.
- Add a
Fixedentry to[Unreleased]and follow the normal release process forX.Y.Z+1. - Merge
maininto the hotfix branch (or rebase) somainincludes the fix before the tag.