feat(version): derive display versions from release tags

Resolve user-facing versions from SemVer release tags and available local
history while retaining raw package metadata for API identity. Stamp Desktop
provenance at build time, and expose generic version details for every
Desktop install method.
This commit is contained in:
ethernet 2026-07-24 16:14:56 -04:00
parent ad6df5eb95
commit 1a4c4eca0d
35 changed files with 827 additions and 293 deletions

View file

@ -1,7 +1,7 @@
#!/usr/bin/env python3
"""Hermes Agent Release Script
Generates changelogs and creates GitHub releases with CalVer tags.
Generates changelogs and creates GitHub releases with SemVer tags.
Usage:
# Preview changelog (dry run)
@ -16,7 +16,7 @@ Usage:
# First release (no previous tag)
python scripts/release.py --bump minor --publish --first-release
# Override CalVer date (e.g. for a belated release)
# Override release-date metadata (e.g. for a belated release)
python scripts/release.py --bump minor --publish --date 2026.3.15
"""
@ -2116,26 +2116,30 @@ def git_result(*args, cwd=None):
)
_SEMVER_TAG_RE = re.compile(r"v(?:0|[1-9]\d*)\.\d+\.\d+$")
_LEGACY_CALVER_TAG_RE = re.compile(r"v20\d{2}\.\d+\.\d+(?:\.\d+)?$")
def release_tag_for_version(semver: str) -> str:
"""Return the canonical Git tag for a Hermes package version."""
return f"v{semver}"
def get_last_tag():
"""Get the most recent CalVer tag."""
tags = git("tag", "--list", "v20*", "--sort=-v:refname")
"""Get the latest SemVer tag, falling back to legacy CalVer history."""
tags = git("tag", "--list", "v[0-9]*", "--sort=-v:refname")
if tags:
return tags.split("\n")[0]
tag_list = tags.split("\n")
for tag in tag_list:
if _SEMVER_TAG_RE.fullmatch(tag) and not _LEGACY_CALVER_TAG_RE.fullmatch(tag):
return tag
legacy_tags = git("tag", "--list", "v20*", "--sort=-v:refname")
if legacy_tags:
return legacy_tags.split("\n")[0]
return None
def next_available_tag(base_tag: str) -> tuple[str, str]:
"""Return a tag/calver pair, suffixing same-day releases when needed."""
if not git("tag", "--list", base_tag):
return base_tag, base_tag.removeprefix("v")
suffix = 2
while git("tag", "--list", f"{base_tag}.{suffix}"):
suffix += 1
tag_name = f"{base_tag}.{suffix}"
return tag_name, tag_name.removeprefix("v")
def get_current_version():
"""Read current semver from __init__.py."""
content = VERSION_FILE.read_text(encoding="utf-8")
@ -2179,6 +2183,15 @@ def update_version_files(semver: str, calver_date: str):
f'__release_date__ = "{calver_date}"',
content,
)
# This function runs before the release-bump commit is created. Record the
# count that commit will have so Nix store builds can derive ``+N`` without
# a .git directory. The corresponding SemVer tag is made at that commit.
parent_count = int(git("rev-list", "--count", "HEAD") or "0")
content = re.sub(
r'__release_rev_count__\s*=\s*\d+',
f'__release_rev_count__ = {parent_count + 1}',
content,
)
VERSION_FILE.write_text(content, encoding="utf-8")
# Update pyproject.toml
@ -2467,31 +2480,27 @@ def main():
parser.add_argument("--publish", action="store_true",
help="Actually create the tag and GitHub release (otherwise dry run)")
parser.add_argument("--date", type=str,
help="Override CalVer date (format: YYYY.M.D)")
help="Override release date metadata (format: YYYY.M.D)")
parser.add_argument("--first-release", action="store_true",
help="Mark as first release (no previous tag expected)")
parser.add_argument("--output", type=str,
help="Write changelog to file instead of stdout")
args = parser.parse_args()
# Determine CalVer date
# Determine release-date metadata.
if args.date:
calver_date = args.date
else:
now = datetime.now()
calver_date = f"{now.year}.{now.month}.{now.day}"
base_tag = f"v{calver_date}"
tag_name, calver_date = next_available_tag(base_tag)
if tag_name != base_tag:
print(f"Note: Tag {base_tag} already exists, using {tag_name}")
# Determine semver
current_version = get_current_version()
if args.bump:
new_version = bump_version(current_version, args.bump)
else:
new_version = current_version
tag_name = release_tag_for_version(new_version)
# Get previous tag
prev_tag = get_last_tag()
@ -2511,7 +2520,7 @@ def main():
print(f"{'='*60}")
print(" Hermes Agent Release Preview")
print(f"{'='*60}")
print(f" CalVer tag: {tag_name}")
print(f" Release tag: {tag_name}")
print(f" SemVer: v{current_version} → v{new_version}")
print(f" Previous tag: {prev_tag or '(none — first release)'}")
print(f" Commits: {len(commits)}")